Browse Source

ID3V1 and ID3v1.1 tag writing support

main
Unbewohnte 3 years ago
parent
commit
67a10faefe
  1. 22
      README.md
  2. 60
      id3v10.go
  3. 39
      id3v10_test.go
  4. 82
      id3v11.go
  5. 52
      id3v11_test.go
  6. 0
      testData/testread.mp3
  7. BIN
      testData/testwrite.mp3
  8. 41
      util.go

22
README.md

@ -45,6 +45,28 @@ func main() {
## Encoding ID3v1.1 ## Encoding ID3v1.1
``` ```
f, err := os.OpenFile("/path/to/file/myfile.mp3",os.O_WRONLY, os.ModePerm)
if err != nil {
panic(err)
}
defer f.Close()
// create your tags struct
tags := ID3v11Tags{
SongName: "mysong",
Artist: "me",
Album: "my album",
Year: 2021,
Comment: "Cool song",
Track: 1,
Genre: "Christian Gangsta Rap", // list of genres see "id3v1genres.go"
}
// write given tags in file
err = WriteID3v11Tags(f, tags)
if err != nil {
panic(err)
}
``` ```
# Testing # Testing

60
id3v10.go

@ -2,6 +2,7 @@ package id3ed
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
@ -71,8 +72,11 @@ func GetID3v1Tags(rs io.ReadSeeker) (*ID3v1Tags, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// genre is one byte by specification genreInt, err := binary.ReadVarint(bytes.NewBuffer(genreByte))
genre, exists := id3v1genres[int(genreByte[0])] if err != nil {
return nil, err
}
genre, exists := id3v1genres[int(genreInt)]
if !exists { if !exists {
genre = "" genre = ""
} }
@ -88,8 +92,58 @@ func GetID3v1Tags(rs io.ReadSeeker) (*ID3v1Tags, error) {
} }
// Writes given ID3v1.0 tags to dst // Writes given ID3v1.0 tags to dst
func SetID3v1Tags(dst io.WriteSeeker, tags ID3v11Tags) error { func WriteID3v1Tags(dst io.WriteSeeker, tags ID3v1Tags) error {
dst.Seek(0, io.SeekEnd) dst.Seek(0, io.SeekEnd)
// TAG
_, err := dst.Write([]byte("TAG"))
if err != nil {
return err
}
// Song name
err = writeToExtend(dst, []byte(tags.SongName), 30)
if err != nil {
return err
}
// Artist
err = writeToExtend(dst, []byte(tags.Artist), 30)
if err != nil {
return err
}
// Album
err = writeToExtend(dst, []byte(tags.Album), 30)
if err != nil {
return err
}
// Year
err = writeToExtend(dst, []byte(fmt.Sprint(tags.Year)), 4)
if err != nil {
return err
}
// Comment
err = writeToExtend(dst, []byte(tags.Comment), 30)
if err != nil {
return err
}
// Genre
genreCode := getKey(id3v1genres, tags.Genre)
if genreCode == -1 {
// if no genre found - encode genre code as 255
genreCode = 255
}
genrebyte := make([]byte, 1)
binary.PutVarint(genrebyte, int64(genreCode))
_, err = dst.Write(genrebyte)
if err != nil {
return err
}
return nil return nil
} }

39
id3v10_test.go

@ -6,7 +6,7 @@ import (
) )
func TestGetID3v1Tags(t *testing.T) { func TestGetID3v1Tags(t *testing.T) {
testfile, err := os.Open("./testData/testmp3id3v1.mp3") testfile, err := os.Open("./testData/testread.mp3")
if err != nil { if err != nil {
t.Errorf("could not open file for testing: %s", err) t.Errorf("could not open file for testing: %s", err)
} }
@ -19,3 +19,40 @@ func TestGetID3v1Tags(t *testing.T) {
t.Errorf("GetID3v1Tags failed: expected %s; got %s", "Comment here ", tags.Comment) t.Errorf("GetID3v1Tags failed: expected %s; got %s", "Comment here ", tags.Comment)
} }
} }
func TestWriteID3v1Tags(t *testing.T) {
os.Remove("./testData/testwrite.mp3")
f, err := os.Create("./testData/testwrite.mp3")
if err != nil {
t.Errorf("%s", err)
}
defer f.Close()
tags := ID3v1Tags{
SongName: "testsong",
Artist: "testartist",
Album: "testalbum",
Year: 727,
Comment: "testcomment",
Genre: "Blues",
}
err = WriteID3v1Tags(f, tags)
if err != nil {
t.Errorf("WriteID3v1Tags failed: %s", err)
}
readTags, err := GetID3v1Tags(f)
if err != nil {
t.Errorf("%s", err)
}
if readTags.Album != "testalbum" {
t.Errorf("WriteID3v1Tags failed: expected %s; got %s", "testalbum", readTags.Album)
}
if readTags.Year != 727 {
t.Errorf("WriteID3v1Tags failed: expected %d; got %d", 727, readTags.Year)
}
}

82
id3v11.go

@ -2,6 +2,7 @@ package id3ed
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
@ -75,15 +76,20 @@ func GetID3v11Tags(rs io.ReadSeeker) (*ID3v11Tags, error) {
return nil, err return nil, err
} }
// track is one byte by specification track, err := binary.ReadVarint(bytes.NewBuffer(trackByte))
track := int(trackByte[0]) if err != nil {
return nil, err
}
genreByte, err := read(rs, 1) genreByte, err := read(rs, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// genre is one byte by specification genreInt, err := binary.ReadVarint(bytes.NewBuffer(genreByte))
genre, exists := id3v1genres[int(genreByte[0])] if err != nil {
return nil, err
}
genre, exists := id3v1genres[int(genreInt)]
if !exists { if !exists {
genre = "" genre = ""
} }
@ -94,14 +100,78 @@ func GetID3v11Tags(rs io.ReadSeeker) (*ID3v11Tags, error) {
Album: album, Album: album,
Year: year, Year: year,
Comment: comment, Comment: comment,
Track: track, Track: int(track),
Genre: genre, Genre: genre,
}, nil }, nil
} }
// Writes given ID3v1.1 tags to dst // Writes given ID3v1.1 tags to dst
func SetID3v11Tags(dst io.WriteSeeker, tags ID3v11Tags) error { func WriteID3v11Tags(dst io.WriteSeeker, tags ID3v11Tags) error {
dst.Seek(0, io.SeekEnd) dst.Seek(0, io.SeekEnd)
// TAG
_, err := dst.Write([]byte("TAG"))
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
// Song name
err = writeToExtend(dst, []byte(tags.SongName), 30)
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
// Artist
err = writeToExtend(dst, []byte(tags.Artist), 30)
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
// Album
err = writeToExtend(dst, []byte(tags.Album), 30)
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
// Year
err = writeToExtend(dst, []byte(fmt.Sprint(tags.Year)), 4)
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
// Comment
err = writeToExtend(dst, []byte(tags.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(tags.Track))
// binary.BigEndian.PutUint16(trackBytes, uint16(tags.Track))
_, err = dst.Write(trackBytes)
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
//Genre
genreCode := getKey(id3v1genres, tags.Genre)
if genreCode == -1 {
// if no genre found - encode genre code as 255
genreCode = 255
}
genrebyte := make([]byte, 1)
binary.PutVarint(genrebyte, int64(genreCode))
err = writeToExtend(dst, genrebyte, 1)
if err != nil {
return fmt.Errorf("could not write to dst: %s", err)
}
return nil return nil
} }

52
id3v11_test.go

@ -7,21 +7,63 @@ import (
) )
func TestGetID3v11Tags(t *testing.T) { func TestGetID3v11Tags(t *testing.T) {
testfile, err := os.Open("./testData/testmp3id3v1.mp3") testfile, err := os.Open("./testData/testread.mp3")
if err != nil { if err != nil {
t.Errorf("could not open file for testing: %s", err) t.Errorf("could not open file for testing: %s", err)
} }
mp3tags, err := GetID3v11Tags(testfile) mp3tags, err := GetID3v11Tags(testfile)
if err != nil { if err != nil {
t.Errorf("GetID3v1Tags failed: %s", err) t.Errorf("GetID3v11Tags failed: %s", err)
} }
if mp3tags.Artist != "Artist" { if mp3tags.Artist != "Artist" {
fmt.Printf("%v", mp3tags.Artist) fmt.Printf("%v", mp3tags.Artist)
t.Errorf("GetID3v1Tags has failed: expected %v; got %v", "Artist", mp3tags.Artist) t.Errorf("GetID3v11Tags failed: expected %s; got %s", "Artist", mp3tags.Artist)
} }
if mp3tags.Track != 8 { if mp3tags.Track != 4 {
t.Errorf("GetID3v1Tags has failed: expected %v; got %v", 8, mp3tags.Track) t.Errorf("GetID3v11Tags failed: expected %d; got %d", 4, mp3tags.Track)
}
}
func TestWriteID3v11Tags(t *testing.T) {
os.Remove("./testData/testwrite.mp3")
f, err := os.Create("./testData/testwrite.mp3")
if err != nil {
t.Errorf("%s", err)
}
defer f.Close()
tags := ID3v11Tags{
SongName: "testsong",
Artist: "testartist",
Album: "testalbum",
Year: 727,
Comment: "testcomment",
Track: 5,
Genre: "Blues",
}
err = WriteID3v11Tags(f, tags)
if err != nil {
t.Errorf("WriteID3v1Tags failed: %s", err)
}
readTags, err := GetID3v11Tags(f)
if err != nil {
t.Errorf("%s", err)
}
if readTags.Album != "testalbum" {
t.Errorf("WriteID3v11Tags failed: expected %s; got %s", "testalbum", readTags.Album)
}
if readTags.Year != 727 {
t.Errorf("WriteID3v11Tags failed: expected %d; got %d", 727, readTags.Year)
}
if readTags.Track != 5 {
t.Errorf("WriteID3v11Tags failed: expected %d; got %d", 5, readTags.Track)
} }
} }

0
testData/testmp3id3v1.mp3 → testData/testread.mp3

BIN
testData/testwrite.mp3

Binary file not shown.

41
util.go

@ -1,6 +1,7 @@
package id3ed package id3ed
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
) )
@ -35,3 +36,43 @@ func readToString(rs io.Reader, n int) (string, error) {
return readString, nil return readString, nil
} }
// Writes data to wr, if len(data) is less than lenNeeded - adds null bytes until written lenNeeded bytes
func writeToExtend(wr io.Writer, data []byte, lenNeeded int) error {
if len(data) > lenNeeded {
return fmt.Errorf("length of given data bytes is bigger than length needed")
}
buff := new(bytes.Buffer)
for i := 0; i < lenNeeded; i++ {
if i < len(data) {
err := buff.WriteByte(data[i])
if err != nil {
return err
}
} else {
err := buff.WriteByte(0)
if err != nil {
return err
}
}
}
_, err := wr.Write(buff.Bytes())
if err != nil {
return err
}
return nil
}
// Returns found key (int) in provided map by value (string);
// If key does not exist in map - returns -1
func getKey(mp map[int]string, givenValue string) int {
for key, value := range mp {
if value == givenValue {
return key
}
}
return -1
}

Loading…
Cancel
Save