Browse Source

❖ Simplified V1 ❖

main
Unbewohnte 3 years ago
parent
commit
7bcbb733d3
  1. BIN
      testData/testwritev1.mp3
  2. 4
      util/conversion.go
  3. 3
      v1/constants.go
  4. 85
      v1/id3v11_test.go
  5. 62
      v1/read.go
  6. 6
      v1/tag.go
  7. 15
      v1/v11tag.go
  8. 129
      v1/v11write.go
  9. 26
      v1/v1_test.go
  10. 80
      v1/v1read.go
  11. 44
      v1/write.go
  12. 6
      v2/constants.go
  13. 36
      v2/v2tag.go

BIN
testData/testwritev1.mp3

Binary file not shown.

4
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
}

3
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"

85
v1/id3v11_test.go

@ -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)
}
}

62
v1/v11read.go → 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
}

6
v1/v1tag.go → 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
}

15
v1/v11tag.go

@ -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
}

129
v1/v11write.go

@ -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
}

26
v1/id3v10_test.go → 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)
}
}

80
v1/v1read.go

@ -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
}

44
v1/v1write.go → 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)
}

6
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

36
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
}

Loading…
Cancel
Save