diff --git a/testData/testwritev1.mp3 b/testData/testwritev1.mp3 index 477ef56..2831d26 100644 Binary files a/testData/testwritev1.mp3 and b/testData/testwritev1.mp3 differ diff --git a/v1/read.go b/v1/read.go index c4748f1..0d3bfdb 100644 --- a/v1/read.go +++ b/v1/read.go @@ -41,6 +41,7 @@ func containsEnhancedTAG(rs io.ReadSeeker) bool { return false } if !bytes.Equal(identifier, []byte(ENHANCEDIDENTIFIER)) { + fmt.Printf("UWAH: %s ---- %s\n", identifier, ENHANCEDIDENTIFIER) return false } @@ -88,19 +89,7 @@ func readEnhancedTag(rs io.ReadSeeker) (EnhancedID3v1Tag, error) { return enhanced, err } - var speed string - switch speedByte[0] { - case 0: - speed = "Unset" - case 1: - speed = "Slow" - case 2: - speed = "Medium" - case 3: - speed = "Fast" - case 4: - speed = "Hardcore" - } + var speed string = EnhancedSpeed[int(speedByte[0])] enhanced.Speed = speed // genre diff --git a/v1/read_test.go b/v1/read_test.go index 5aa361d..267e2e0 100644 --- a/v1/read_test.go +++ b/v1/read_test.go @@ -18,6 +18,7 @@ var TESTv1TAG = &ID3v1Tag{ Artist: "ARRRTIST", Album: "ALLLLBUUUM", SongName: "NAME", + Speed: EnhancedSpeed[2], }, } diff --git a/v1/tag.go b/v1/tag.go index 39ec676..773c0b1 100644 --- a/v1/tag.go +++ b/v1/tag.go @@ -26,3 +26,11 @@ type EnhancedID3v1Tag struct { StartTime string EndTime string } + +var EnhancedSpeed = map[int]string{ + 0: "Unset", + 1: "Slow", + 2: "Medium", + 3: "Fast", + 4: "Hardcore", +} diff --git a/v1/write.go b/v1/write.go index a6d0e44..9de8fad 100644 --- a/v1/write.go +++ b/v1/write.go @@ -45,7 +45,8 @@ func (tag *ID3v1Tag) write(dst io.WriteSeeker) error { } // Speed - _, err = dst.Write([]byte(tag.EnhancedTag.Speed)) + speed := util.GetKey(EnhancedSpeed, tag.EnhancedTag.Speed) + _, err = dst.Write([]byte{byte(speed)}) if err != nil { return err } @@ -162,8 +163,10 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error { switch { case containsEnhancedTAG(f) && containsTAG(f): + fmt.Println("HEA1") + // remove both - err = f.Truncate(filesize - int64(TAGSIZE) - int64(ENHANCEDSIZE)) + err = f.Truncate(filesize - int64(TAGSIZE+ENHANCEDSIZE)) if err != nil { return fmt.Errorf("could not truncate file %s", err) } @@ -174,6 +177,8 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error { } case containsEnhancedTAG(f) && !containsTAG(f): + fmt.Println("HEA2") + // remove enhanced tag, replace with new err = f.Truncate(filesize - int64(ENHANCEDSIZE)) if err != nil { @@ -186,6 +191,8 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error { } case !containsEnhancedTAG(f) && containsTAG(f): + fmt.Println("HEA3") + // remove regular one, replace with new err = f.Truncate(filesize - int64(TAGSIZE)) if err != nil { @@ -198,6 +205,8 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error { } case !containsEnhancedTAG(f) && !containsTAG(f): + fmt.Println("HEA4") + // no existing TAGs, simply write what we have err := tag.write(f) if err != nil { diff --git a/v1/write_test.go b/v1/write_test.go index 8e6c901..955bd62 100644 --- a/v1/write_test.go +++ b/v1/write_test.go @@ -20,5 +20,4 @@ func TestWriteID3v1ToFile(t *testing.T) { if err != nil { t.Errorf("WriteID3v1ToFile failed: %s", err) } - } diff --git a/v2/frame.go b/v2/frame.go index 88bd8f9..c550fd8 100644 --- a/v2/frame.go +++ b/v2/frame.go @@ -2,6 +2,7 @@ package v2 import ( "bytes" + "fmt" "io" "unicode" @@ -20,9 +21,9 @@ type FrameFlags struct { } type FrameHeader struct { - ID string - Size uint32 - Flags FrameFlags + id string + size uint32 + flags FrameFlags } type Frame struct { @@ -30,6 +31,20 @@ type Frame struct { Contents []byte } +// getters + +func (fh *FrameHeader) ID() string { + return fh.id +} + +func (fh *FrameHeader) Size() uint32 { + return fh.size +} + +func (fh *FrameHeader) Flags() FrameFlags { + return fh.flags +} + // Checks if given identifier is valid by specification func isValidID(frameID string) bool { // check if id is in ASCII table @@ -50,10 +65,10 @@ func isValidID(frameID string) bool { func getV22FrameHeader(fHeaderbytes []byte) FrameHeader { var header FrameHeader - header.ID = string(fHeaderbytes[0:3]) + header.id = string(fHeaderbytes[0:3]) framesizeBytes := util.BytesToIntSynchsafe(fHeaderbytes[3:6]) - header.Size = framesizeBytes + header.size = framesizeBytes return header } @@ -62,14 +77,14 @@ func getV23FrameHeader(fHeaderbytes []byte) FrameHeader { var header FrameHeader // ID - header.ID = string(fHeaderbytes[0:4]) + header.id = string(fHeaderbytes[0:4]) // Size framesizeBytes := fHeaderbytes[4:8] framesize := util.BytesToIntSynchsafe(framesizeBytes) - header.Size = framesize + header.size = framesize // Flags frameFlags1 := fHeaderbytes[8] @@ -108,7 +123,7 @@ func getV23FrameHeader(fHeaderbytes []byte) FrameHeader { flags.InGroup = false } - header.Flags = flags + header.flags = flags return header } @@ -116,14 +131,14 @@ func getV23FrameHeader(fHeaderbytes []byte) FrameHeader { func getV24FrameHeader(fHeaderbytes []byte) FrameHeader { var header FrameHeader - header.ID = string(fHeaderbytes[0:4]) + header.id = string(fHeaderbytes[0:4]) // Size framesizeBytes := fHeaderbytes[4:8] framesize := util.BytesToIntSynchsafe(framesizeBytes) - header.Size = framesize + header.size = framesize // Flags frameFlags1 := fHeaderbytes[8] @@ -172,7 +187,7 @@ func getV24FrameHeader(fHeaderbytes []byte) FrameHeader { flags.HasDataLengthIndicator = false } - header.Flags = flags + header.flags = flags return header } @@ -229,7 +244,7 @@ func readNextFrame(r io.Reader, version string) (Frame, error) { frame.Header = frameHeader // Contents - contents, err := util.Read(r, uint64(frameHeader.Size)) + contents, err := util.Read(r, uint64(frameHeader.size)) if err != nil { return Frame{}, err } @@ -309,17 +324,25 @@ func frameFlagsToBytes(ff FrameFlags, version string) []byte { } // Converts frame to ready-to-write bytes -func (f *Frame) toBytes(version string) []byte { +func (f *Frame) toBytes() []byte { buff := new(bytes.Buffer) // identifier - buff.Write([]byte(f.Header.ID)) + buff.Write([]byte(f.Header.id)) // size - buff.Write(util.IntToBytesSynchsafe(f.Header.Size)) + buff.Write(util.IntToBytesSynchsafe(f.Header.size)) // flags - flagBytes := frameFlagsToBytes(f.Header.Flags, version) + + var version string + if len(f.Header.id) == 4 { + version = V2_4 + } else { + version = V2_2 + } + + flagBytes := frameFlagsToBytes(f.Header.flags, version) if flagBytes != nil { buff.Write(flagBytes) } @@ -329,3 +352,38 @@ func (f *Frame) toBytes(version string) []byte { return buff.Bytes() } + +// Constructs a new frame from provided information. +// isTextFrame must be set to true if gContents are not a binary data. +// Returns an error if provided id`s len is neither 3 or 4 +func NewFrame(id string, gContents []byte, isTextframe bool) (*Frame, error) { + if len(id) != 3 && len(id) != 4 { + return nil, fmt.Errorf("frame identifier`s length must be 3 or 4") + } + + var fheader FrameHeader + + // contents + var contents []byte + if isTextframe { + // add UTF-8 encoding byte + contents = []byte{util.EncodingUTF8} + contents = append(contents, gContents...) + } else { + contents = gContents + } + + // id + fheader.id = id + + // size + fheader.size = uint32(len(contents)) + + // flags (all false) + fheader.flags = FrameFlags{} + + return &Frame{ + Header: fheader, + Contents: contents, + }, nil +} diff --git a/v2/frame_test.go b/v2/frame_test.go index 6ba9fd2..27a1770 100644 --- a/v2/frame_test.go +++ b/v2/frame_test.go @@ -24,14 +24,14 @@ func TestReadNextFrame(t *testing.T) { t.Errorf("ReadFrame failed: %s", err) } - if firstFrame.Header.ID != "TRCK" { + if firstFrame.Header.ID() != "TRCK" { t.Errorf("GetFrame failed: expected ID %s; got %s", - "TRCK", firstFrame.Header.ID) + "TRCK", firstFrame.Header.ID()) } - if firstFrame.Header.Flags.Encrypted != false { + if firstFrame.Header.Flags().Encrypted != false { t.Errorf("ReadFrame failed: expected compressed flag to be %v; got %v", - false, firstFrame.Header.Flags.Encrypted) + false, firstFrame.Header.Flags().Encrypted) } secondFrame, err := readNextFrame(f, header.Version()) @@ -39,9 +39,9 @@ func TestReadNextFrame(t *testing.T) { t.Errorf("ReadFrame failed: %s", err) } - if secondFrame.Header.ID != "TDRC" { + if secondFrame.Header.ID() != "TDRC" { t.Errorf("ReadFrame failed: expected ID %s; got %s", - "TDRC", secondFrame.Header.ID) + "TDRC", secondFrame.Header.ID()) } if util.ToStringLossy(secondFrame.Contents) != "2006" { @@ -72,14 +72,14 @@ func TestFrameFlagsToBytes(t *testing.T) { func TestFrameToBytes(t *testing.T) { testframe := Frame{ Header: FrameHeader{ - ID: "TEST", - Flags: FrameFlags{}, // all false - Size: 4, + id: "TEST", + flags: FrameFlags{}, // all false + size: 4, }, Contents: []byte{util.EncodingUTF8, 60, 60, 60}, // 60 == < } - frameBytes := testframe.toBytes(V2_4) + frameBytes := testframe.toBytes() // t.Errorf("%+v", frameBytes) // 84 69 83 84 0 0 0 4 0 0 3 60 60 60 @@ -90,9 +90,9 @@ func TestFrameToBytes(t *testing.T) { // header - 4 + 4 + 2 = 10 bytes (success) // 3 60 60 60 - contents - if len(frameBytes)-int(testframe.Header.Size) != HEADERSIZE { + if len(frameBytes)-int(testframe.Header.Size()) != HEADERSIZE { t.Errorf("FrameToBytes failed: expected header size to be %d; got %d", - HEADERSIZE, len(frameBytes)-int(testframe.Header.Size)) + HEADERSIZE, len(frameBytes)-int(testframe.Header.Size())) } if util.DecodeText(frameBytes[10:]) != "<<<" { @@ -100,3 +100,22 @@ func TestFrameToBytes(t *testing.T) { testframe.Contents, frameBytes[10:]) } } + +func TestNewFrame(t *testing.T) { + gotFrame, err := NewFrame("TMRK", []byte("Very cool"), true) + if err != nil { + t.Errorf("CreateFrame failed: %s", err) + } + + // check for encoding byte + if gotFrame.Contents[0] != util.EncodingUTF8 { + t.Errorf("CreateFrame failed: contents are expected to have an encoding byte %v; got %v", + util.EncodingUTF8, gotFrame.Contents[0]) + } + + if gotFrame.Header.Size() != uint32(len(gotFrame.Contents)) { + t.Errorf("CreateFrame failed: expected size to be %d; got %d", + len(gotFrame.Contents), gotFrame.Header.Size()) + } + +} diff --git a/v2/header.go b/v2/header.go index 9c19171..fdaab6d 100644 --- a/v2/header.go +++ b/v2/header.go @@ -452,3 +452,32 @@ func (h *Header) toBytes() []byte { return buff.Bytes() } + +// Creates an appropriate header based on +// provided frames +func newHeader(frames []Frame) *Header { + var header Header + + // flags (all false) + header.flags = HeaderFlags{} + + // version + if len(frames[0].Header.ID()) == 4 { + header.version = V2_4 + } else { + header.version = V2_2 + } + + // size + var framesSize uint32 = 0 + for _, frame := range frames { + framesSize += uint32(len(frame.toBytes())) + } + + header.size = framesSize + + // extended header (not used) + header.extendedHeader = ExtendedHeader{} + + return &header +} diff --git a/v2/read.go b/v2/read.go index 7df9465..82f95f2 100644 --- a/v2/read.go +++ b/v2/read.go @@ -50,9 +50,9 @@ func ReadV2Tag(rs io.ReadSeeker) (*ID3v2Tag, error) { // counting how many bytes read if header.Version() == V2_2 { - read += uint64(V2_2FrameHeaderSize) + uint64(frame.Header.Size) + read += uint64(V2_2FrameHeaderSize) + uint64(frame.Header.Size()) } else { - read += uint64(V2_3FrameHeaderSize) + uint64(frame.Header.Size) + read += uint64(V2_3FrameHeaderSize) + uint64(frame.Header.Size()) } } diff --git a/v2/v2tag.go b/v2/v2tag.go index 3bae1ee..300ca11 100644 --- a/v2/v2tag.go +++ b/v2/v2tag.go @@ -7,11 +7,23 @@ type ID3v2Tag struct { Frames []Frame } +// Creates a new v2 tag from given created frames +func NewTAG(frames []Frame) *ID3v2Tag { + var newtag ID3v2Tag + + header := newHeader(frames) + + newtag.Header = *header + newtag.Frames = frames + + return &newtag +} + // Searches for frame with the same identifier as id in tag, // returns &it if found func (tag *ID3v2Tag) GetFrame(id string) *Frame { for _, frame := range tag.Frames { - if strings.EqualFold(frame.Header.ID, id) { + if strings.EqualFold(frame.Header.ID(), id) { return &frame } } @@ -21,7 +33,7 @@ func (tag *ID3v2Tag) GetFrame(id string) *Frame { // Checks if a frame with given id exists func (tag *ID3v2Tag) FrameExists(id string) bool { for _, frame := range tag.Frames { - if strings.EqualFold(frame.Header.ID, id) { + if strings.EqualFold(frame.Header.ID(), id) { return true } } diff --git a/v2/v2tag_test.go b/v2/v2tag_test.go new file mode 100644 index 0000000..cbc3461 --- /dev/null +++ b/v2/v2tag_test.go @@ -0,0 +1,34 @@ +package v2 + +import "testing" + +func TestNewTAG(t *testing.T) { + frame1, err := NewFrame("TTST", []byte("TEST text FRAME (ᗜˬᗜ)"), true) + if err != nil { + t.Errorf("%s", err) + } + frame2, err := NewFrame("BNRY", []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 255}, false) + if err != nil { + t.Errorf("%s", err) + } + + newtag := NewTAG([]Frame{*frame1, *frame2}) + + if newtag.Header.Version() != V2_4 { + t.Errorf("NewTAG failed: expected version to be %s; got %s", + V2_4, newtag.Header.Version()) + } + + var size uint32 = 0 + for _, frame := range newtag.Frames { + size += uint32(len(frame.toBytes())) + } + + if newtag.Header.Size() != size { + t.Errorf("NewTAG failed: expected size to be %d; got %d", + size, newtag.Header.Size()) + } + + // t.Errorf("%+v", newtag) + +} diff --git a/v2/write.go b/v2/write.go index 4934680..652c5de 100644 --- a/v2/write.go +++ b/v2/write.go @@ -1,6 +1,6 @@ package v2 -// Writes ID3v2Tag to ws +// // Writes ID3v2Tag to ws // func (tag *ID3v2Tag) write(ws io.WriteSeeker) error { // _, err := ws.Seek(0, io.SeekStart) // if err != nil { @@ -8,11 +8,17 @@ package v2 // } // // write header -// ws.Write(tag.Header.toBytes()) +// _, err = ws.Write(tag.Header.toBytes()) +// if err != nil { +// return fmt.Errorf("could not write to writer: %s", err) +// } // // write frames // for _, frame := range tag.Frames { -// ws.Write(frame.toBytes(tag.Header.Version())) +// _, err = ws.Write(frame.toBytes()) +// if err != nil { +// return fmt.Errorf("could not write to writer: %s", err) +// } // } // return nil