diff --git a/testData/testwritev1.mp3 b/testData/testwritev1.mp3 index a8c312d..0f1adcb 100644 Binary files a/testData/testwritev1.mp3 and b/testData/testwritev1.mp3 differ diff --git a/util/conversion.go b/util/conversion.go index 1901e26..a047bc9 100644 --- a/util/conversion.go +++ b/util/conversion.go @@ -7,8 +7,8 @@ import ( ) // Decodes given byte into integer -func ByteToInt(gByte byte) (int64, error) { - integer, err := strconv.ParseInt(fmt.Sprintf("%d", gByte), 10, 64) +func ByteToInt(gByte byte) (int, error) { + integer, err := strconv.Atoi(fmt.Sprintf("%d", gByte)) if err != nil { return 0, err } diff --git a/v1/constants.go b/v1/constants.go index 3fdcf0f..5281e2e 100644 --- a/v1/constants.go +++ b/v1/constants.go @@ -3,3 +3,6 @@ package v1 const ID3v1IDENTIFIER string = "TAG" const ID3v1SIZE int = 128 // bytes const ID3v1INVALIDGENRE int = 255 + +const V1_0 string = "ID3v1.0" +const V1_1 string = "ID3v1.1" diff --git a/v1/id3v11_test.go b/v1/id3v11_test.go deleted file mode 100644 index 0ba6637..0000000 --- a/v1/id3v11_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package v1 - -import ( - "fmt" - "os" - "path/filepath" - "testing" -) - -var TESTV11TAG = &ID3v11Tag{ - SongName: "testsong", - Artist: "testartist", - Album: "testalbum", - Year: 727, - Comment: "testcomment", - Track: 5, - Genre: "Blues", -} - -func TestGetID3v11Tags(t *testing.T) { - testfile, err := os.Open(filepath.Join(TESTDATAPATH, "testreadv1.mp3")) - if err != nil { - t.Errorf("could not open file for testing: %s", err) - } - mp3tags, err := Getv11Tag(testfile) - if err != nil { - t.Errorf("GetID3v11Tag failed: %s", err) - } - - if mp3tags.Artist != "Artist" { - fmt.Printf("%v", mp3tags.Artist) - t.Errorf("GetID3v11Tag failed: expected artist %s; got %s", "Artist", mp3tags.Artist) - } - - if mp3tags.Track != 8 { - t.Errorf("GetID3v11Tag failed: expected track %d; got %d", 8, mp3tags.Track) - } -} - -// WILL ADD NEW "TAG" WITHOUT REMOVING THE OLD ONE !!! -func TestWriteID3v11Tags(t *testing.T) { - f, err := os.OpenFile(filepath.Join(TESTDATAPATH, "testwritev1.mp3"), os.O_CREATE|os.O_RDWR, os.ModePerm) - if err != nil { - t.Errorf("%s", err) - } - defer f.Close() - - tag := TESTV11TAG - - err = tag.Write(f) - if err != nil { - t.Errorf("WriteID3v1Tags failed: %s", err) - } - - readTags, err := Getv11Tag(f) - if err != nil { - t.Errorf("%s", err) - } - - if readTags.Album != "testalbum" { - t.Errorf("WriteID3v11Tag failed: expected album %s; got %s", "testalbum", readTags.Album) - } - - if readTags.Year != 727 { - t.Errorf("WriteID3v11Tag failed: expected year %d; got %d", 727, readTags.Year) - } - - if readTags.Track != 10 { - t.Errorf("WriteID3v11Tag failed: expected track %d; got %d", 10, readTags.Track) - } -} - -func TestWriteID3v11ToFile(t *testing.T) { - f, err := os.OpenFile(filepath.Join(TESTDATAPATH, "testwritev1.mp3"), os.O_CREATE|os.O_RDWR, os.ModePerm) - if err != nil { - t.Errorf("%s", err) - } - - tag := TESTV11TAG - - err = tag.WriteToFile(f) - if err != nil { - t.Errorf("WriteID3v1ToFile failed: %s", err) - } -} diff --git a/v1/v11read.go b/v1/read.go similarity index 60% rename from v1/v11read.go rename to v1/read.go index d60d9a6..03efe41 100644 --- a/v1/v11read.go +++ b/v1/read.go @@ -9,14 +9,17 @@ import ( "github.com/Unbewohnte/id3ed/util" ) -// Retrieves ID3v1.1 field values of provided io.ReadSeeker -func Getv11Tag(rs io.ReadSeeker) (*ID3v11Tag, error) { +// Retrieves ID3v1 field values of provided io.ReadSeeker (usually a file) +func Getv1Tag(rs io.ReadSeeker) (*ID3v1Tag, error) { + var tag ID3v1Tag + // set reader to the last 128 bytes _, err := rs.Seek(-int64(ID3v1SIZE), io.SeekEnd) if err != nil { return nil, fmt.Errorf("could not seek: %s", err) } + // ID identifier, err := util.Read(rs, 3) if err != nil { return nil, err @@ -27,21 +30,28 @@ func Getv11Tag(rs io.ReadSeeker) (*ID3v11Tag, error) { return nil, fmt.Errorf("does not use ID3v1: expected %s; got %s", ID3v1IDENTIFIER, identifier) } + // Songname songname, err := util.ReadToString(rs, 30) if err != nil { return nil, err } + tag.SongName = songname + // Artist artist, err := util.ReadToString(rs, 30) if err != nil { return nil, err } + tag.Artist = artist + // Album name album, err := util.ReadToString(rs, 30) if err != nil { return nil, err } + tag.Album = album + // Year yearStr, err := util.ReadToString(rs, 4) if err != nil { return nil, err @@ -50,28 +60,31 @@ func Getv11Tag(rs io.ReadSeeker) (*ID3v11Tag, error) { if err != nil { return nil, fmt.Errorf("could not convert yearbytes into int: %s", err) } + tag.Year = year - comment, err := util.ReadToString(rs, 28) - if err != nil { - return nil, err - } - - // skip 1 null byte - _, err = util.Read(rs, 1) + // Comment and Track + comment, err := util.Read(rs, 30) if err != nil { return nil, err } + tag.Comment = util.ToString(comment) + tag.Track = 0 - trackByte, err := util.Read(rs, 1) - if err != nil { - return nil, err - } + var track int = 0 + // check if 29th byte is null byte (v1.0 or v1.1) + if comment[28] == 0 { + // it is v1.1, track number exists + track, err = util.ByteToInt(comment[29]) + if err != nil { + return nil, fmt.Errorf("could not get int from byte: %s", err) + } + tag.Track = uint8(track) - track, err := util.ByteToInt(trackByte[0]) - if err != nil { - return nil, fmt.Errorf("cannot convert bytes to int: %s", err) + comment = comment[0:28] + tag.Comment = util.ToString(comment) } + // Genre genreByte, err := util.Read(rs, 1) if err != nil { return nil, err @@ -84,14 +97,13 @@ func Getv11Tag(rs io.ReadSeeker) (*ID3v11Tag, error) { if !exists { genre = "" } + tag.Genre = genre + + if track == 0 { + tag.Version = V1_0 + } else { + tag.Version = V1_1 + } - return &ID3v11Tag{ - SongName: songname, - Artist: artist, - Album: album, - Year: year, - Comment: comment, - Track: int(track), - Genre: genre, - }, nil + return &tag, nil } diff --git a/v1/v1tag.go b/v1/tag.go similarity index 66% rename from v1/v1tag.go rename to v1/tag.go index 311eed0..df12b5f 100644 --- a/v1/v1tag.go +++ b/v1/tag.go @@ -3,14 +3,12 @@ package v1 // https://id3.org/ID3v1 - documentation type ID3v1Tag struct { + Version string SongName string Artist string Album string Year int Comment string + Track uint8 // basically a byte, but converted to int for convenience Genre string } - -func (*ID3v1Tag) Version() int { - return 10 -} diff --git a/v1/v11tag.go b/v1/v11tag.go deleted file mode 100644 index 7b97135..0000000 --- a/v1/v11tag.go +++ /dev/null @@ -1,15 +0,0 @@ -package v1 - -type ID3v11Tag struct { - SongName string - Artist string - Album string - Year int - Comment string - Track int - Genre string -} - -func (*ID3v11Tag) Version() int { - return 11 -} diff --git a/v1/v11write.go b/v1/v11write.go deleted file mode 100644 index b5719c6..0000000 --- a/v1/v11write.go +++ /dev/null @@ -1,129 +0,0 @@ -package v1 - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "os" - - "github.com/Unbewohnte/id3ed/util" -) - -// Writes given ID3v1.1 tag to dst -// NOTE: will not remove already existing ID3v1.1 tag if it`s present, -// use ⁕WriteToFile⁕ method if you`re working with REAL mp3 files !!! -func (tag *ID3v11Tag) Write(dst io.WriteSeeker) error { - dst.Seek(0, io.SeekEnd) - - // TAG - _, err := dst.Write([]byte(ID3v1IDENTIFIER)) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - // Song name - err = util.WriteToExtent(dst, []byte(tag.SongName), 30) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - // Artist - err = util.WriteToExtent(dst, []byte(tag.Artist), 30) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - // Album - err = util.WriteToExtent(dst, []byte(tag.Album), 30) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - // Year - err = util.WriteToExtent(dst, []byte(fmt.Sprint(tag.Year)), 4) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - // Comment - err = util.WriteToExtent(dst, []byte(tag.Comment), 28) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - _, err = dst.Write([]byte{0}) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - // Track - trackBytes := make([]byte, 1) - binary.PutVarint(trackBytes, int64(tag.Track)) - _, err = dst.Write(trackBytes) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - //Genre - genreCode := util.GetKey(id3v1genres, tag.Genre) - if genreCode == -1 { - // if no genre found - encode genre code as 255 - genreCode = ID3v1INVALIDGENRE - } - genrebyte := make([]byte, 1) - binary.PutVarint(genrebyte, int64(genreCode)) - - err = util.WriteToExtent(dst, genrebyte, 1) - if err != nil { - return fmt.Errorf("could not write to dst: %s", err) - } - - return nil -} - -// Checks for existing ID3v1.1 tag in file, if present - removes it and replaces with provided tag -func (tag *ID3v11Tag) WriteToFile(f *os.File) error { - defer f.Close() - - // check for existing ID3v1.1 tag - _, err := f.Seek(-int64(ID3v1SIZE), io.SeekEnd) - if err != nil { - return fmt.Errorf("could not seek: %s", err) - } - - identifier, err := util.Read(f, 3) - if err != nil { - // return err - return err - - } - - if !bytes.Equal(identifier, []byte(ID3v1IDENTIFIER)) { - // no existing identifier, just write given tag - err = tag.Write(f) - if err != nil { - return fmt.Errorf("could not write to writer: %s", err) - } - return nil - } - - // does contain ID3v1.1 tag. Removing it - fStats, err := f.Stat() - if err != nil { - return fmt.Errorf("cannot get file stats: %s", err) - } - - err = f.Truncate(fStats.Size() - int64(ID3v1SIZE)) - if err != nil { - return fmt.Errorf("could not truncate file %s", err) - } - - // writing new tag - err = tag.Write(f) - if err != nil { - return fmt.Errorf("could not write to writer: %s", err) - } - - return nil -} diff --git a/v1/id3v10_test.go b/v1/v1_test.go similarity index 70% rename from v1/id3v10_test.go rename to v1/v1_test.go index fe40494..e8beee9 100644 --- a/v1/id3v10_test.go +++ b/v1/v1_test.go @@ -27,8 +27,20 @@ func TestGetv1Tags(t *testing.T) { t.Errorf("GetID3v1Tag failed: %s", err) } + if tag.Version != V1_1 { + t.Errorf("GetID3v1Tag failed: expected version to be %s; got %s", V1_1, tag.Version) + } + if tag.Comment != "Comment here " { - t.Errorf("GetID3v1Tag failed: expected %s; got %s", "Comment here ", tag.Comment) + t.Errorf("GetID3v1Tag failed: expected comment to be %s; got %s", "Comment here ", tag.Comment) + } + + if tag.Genre != "Soundtrack" { + t.Errorf("GetID3v1Tag failed: expected genre to be %s; got %s", "Soundtrack", tag.Genre) + } + + if tag.Track != 8 { + t.Errorf("GetID3v1Tag failed: expected track number to be %d; got %d", 8, tag.Track) } } @@ -42,23 +54,23 @@ func TestWritev1Tags(t *testing.T) { tag := TESTv1TAG // writing a tag - err = tag.Write(f) + err = tag.write(f) if err != nil { t.Errorf("WriteID3v1Tag failed: %s", err) } // reading a tag - readTags, err := Getv1Tag(f) + readTag, err := Getv1Tag(f) if err != nil { t.Errorf("%s", err) } - if readTags.Album != "testalbum" { - t.Errorf("WriteID3v1Tag failed: expected %s; got %s", "testalbum", readTags.Album) + if readTag.Album != "testalbum" { + t.Errorf("WriteID3v1Tag failed: expected %s; got %s", "testalbum", readTag.Album) } - if readTags.Year != 727 { - t.Errorf("WriteID3v1Tag failed: expected %d; got %d", 727, readTags.Year) + if readTag.Year != 727 { + t.Errorf("WriteID3v1Tag failed: expected %d; got %d", 727, readTag.Year) } } diff --git a/v1/v1read.go b/v1/v1read.go deleted file mode 100644 index 638ae25..0000000 --- a/v1/v1read.go +++ /dev/null @@ -1,80 +0,0 @@ -package v1 - -import ( - "bytes" - "fmt" - "io" - "strconv" - - "github.com/Unbewohnte/id3ed/util" -) - -// Retrieves ID3v1 field values of provided io.ReadSeeker (usually a file) -func Getv1Tag(rs io.ReadSeeker) (*ID3v1Tag, error) { - // set reader to the last 128 bytes - _, err := rs.Seek(-int64(ID3v1SIZE), io.SeekEnd) - if err != nil { - return nil, fmt.Errorf("could not seek: %s", err) - } - - tag, err := util.Read(rs, 3) - if err != nil { - return nil, err - } - - if !bytes.Equal(tag, []byte(ID3v1IDENTIFIER)) { - // no TAG, given file does not use ID3v1 - return nil, fmt.Errorf("does not use ID3v1: expected %s; got %s", ID3v1IDENTIFIER, tag) - } - - songname, err := util.ReadToString(rs, 30) - if err != nil { - return nil, err - } - - artist, err := util.ReadToString(rs, 30) - if err != nil { - return nil, err - } - - album, err := util.ReadToString(rs, 30) - if err != nil { - return nil, err - } - - yearStr, err := util.ReadToString(rs, 4) - if err != nil { - return nil, err - } - year, err := strconv.Atoi(yearStr) - if err != nil { - return nil, fmt.Errorf("could not convert yearbytes into int: %s", err) - } - - comment, err := util.ReadToString(rs, 30) - if err != nil { - return nil, err - } - - genreByte, err := util.Read(rs, 1) - if err != nil { - return nil, err - } - genreInt, err := util.ByteToInt(genreByte[0]) - if err != nil { - return nil, fmt.Errorf("cannot convert bytes to int: %s", err) - } - genre, exists := id3v1genres[int(genreInt)] - if !exists { - genre = "" - } - - return &ID3v1Tag{ - SongName: songname, - Artist: artist, - Album: album, - Year: year, - Comment: comment, - Genre: genre, - }, nil -} diff --git a/v1/v1write.go b/v1/write.go similarity index 64% rename from v1/v1write.go rename to v1/write.go index b9e7c9e..6c1fcc7 100644 --- a/v1/v1write.go +++ b/v1/write.go @@ -10,10 +10,10 @@ import ( "github.com/Unbewohnte/id3ed/util" ) -// Writes given ID3v1.0 tag to given io.WriteSeeker. +// Writes given ID3v1.0 or ID3v1.1 tag to given io.WriteSeeker. // NOTE: will not remove already existing ID3v1 tag if it`s present, // use ⁕WriteToFile⁕ method if you`re working with REAL mp3 files !!! -func (tag *ID3v1Tag) Write(dst io.WriteSeeker) error { +func (tag *ID3v1Tag) write(dst io.WriteSeeker) error { dst.Seek(0, io.SeekEnd) // ID @@ -46,10 +46,34 @@ func (tag *ID3v1Tag) Write(dst io.WriteSeeker) error { return fmt.Errorf("could not write to writer: %s", err) } - // Comment - err = util.WriteToExtent(dst, []byte(tag.Comment), 30) - if err != nil { - return fmt.Errorf("could not write to writer: %s", err) + // Comment and Track + + // check for track number, if specified and valid - comment must be shrinked to 28 bytes and 29th + // byte must be 0 byte (use ID3v1.1 instead of v1.0) + if tag.Track == 0 { + // write only 30 bytes long comment without track + err = util.WriteToExtent(dst, []byte(tag.Comment), 30) + if err != nil { + return fmt.Errorf("could not write to writer: %s", err) + } + } else { + // write 28 bytes long shrinked comment + err = util.WriteToExtent(dst, []byte(tag.Comment), 28) + if err != nil { + return fmt.Errorf("could not write to writer: %s", err) + } + + // write 0 byte as padding + _, err = dst.Write([]byte{0}) + if err != nil { + return fmt.Errorf("could not write to writer: %s", err) + } + + // write track byte + _, err = dst.Write([]byte{byte(tag.Track)}) + if err != nil { + return fmt.Errorf("could not write to writer: %s", err) + } } // Genre @@ -69,7 +93,7 @@ func (tag *ID3v1Tag) Write(dst io.WriteSeeker) error { return nil } -// Checks for existing ID3v1 tag in file, if present - removes it and replaces with provided tag +// Checks for existing ID3v1 or ID3v1.1 tag in file, if present - removes it and replaces with provided tag func (tag *ID3v1Tag) WriteToFile(f *os.File) error { defer f.Close() @@ -83,7 +107,7 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error { if !bytes.Equal(identifier, []byte(ID3v1IDENTIFIER)) { // no existing identifier, just write given tag - err = tag.Write(f) + err = tag.write(f) if err != nil { return err } @@ -101,8 +125,8 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error { return fmt.Errorf("could not truncate file %s", err) } - // writing new tag - err = tag.Write(f) + // writing a new tag + err = tag.write(f) if err != nil { return fmt.Errorf("could not write to writer: %s", err) } diff --git a/v2/constants.go b/v2/constants.go index 2aa3f6c..60963f4 100644 --- a/v2/constants.go +++ b/v2/constants.go @@ -7,6 +7,6 @@ const V2_2 string = "ID3v2.2" const V2_3 string = "ID3v2.3" const V2_4 string = "ID3v2.4" -const V2_2FrameNameSize uint = 3 -const V2_3FrameNameSize uint = 4 -const V2_4FrameNameSize uint = 4 +const V2_2FrameIDSize uint = 3 +const V2_3FrameIDSize uint = 4 +const V2_4FrameIDSize uint = 4 diff --git a/v2/v2tag.go b/v2/v2tag.go index 8870f37..de8fc43 100644 --- a/v2/v2tag.go +++ b/v2/v2tag.go @@ -1,34 +1,6 @@ package v2 -// type ID3v2Tag struct { -// Header Header -// Frames []Frame -// } - -// type V2TagReader interface { -// ReadFrames(io.ReadSeeker) ([]*Frame, error) -// GetHeader(io.ReadSeeker) (*Header, error) -// HasPadding(io.ReadSeeker) (bool, error) -// } - -// type V2TagWriter interface { -// Write(*os.File) error -// } - -// func Get(f *os.File) (*ID3v2Tag, error) { -// var tag ID3v2Tag - -// header, err := GetHeader(f) -// if err != nil { -// return nil, err -// } -// frames, err := GetFrames(f) -// if err != nil { -// return nil, err -// } - -// tag.Header = header -// tag.Frames = frames - -// return &tag, nil -// } +type ID3v2Tag struct { + Header Header + Frames []Frame +}