Browse Source

Removed my debugging leftovers), implemented ID3v2 writing !

main
Unbewohnte 3 years ago
parent
commit
6753c53635
  1. 21
      id3ed.go
  2. 17
      id3ed_test.go
  3. BIN
      testData/testreadv2.mp3
  4. BIN
      testData/testwritev1.mp3
  5. BIN
      testData/testwritev2.mp3
  6. 1
      v1/read.go
  7. 8
      v1/write.go
  8. 22
      v2/read.go
  9. 9
      v2/read_test.go
  10. 5
      v2/v2tag.go
  11. 330
      v2/write.go
  12. 50
      v2/write_test.go

21
id3ed.go

@ -58,7 +58,7 @@ func Open(path string) (*File, error) {
func (f *File) WriteID3v1(tag *v1.ID3v1Tag) error {
fhandler, err := os.OpenFile(f.path, os.O_RDWR, os.ModePerm)
if err != nil {
return fmt.Errorf("could not read a file: %s", err)
return fmt.Errorf("could not open a file: %s", err)
}
defer fhandler.Close()
@ -70,7 +70,18 @@ func (f *File) WriteID3v1(tag *v1.ID3v1Tag) error {
return nil
}
// still not implemented
// func (f *File) WriteID3v2(tag *v2.ID3v2Tag) error {
// return nil
// }
// Writes given ID3v2 tag to file
func (f *File) WriteID3v2(tag *v2.ID3v2Tag) error {
fhandler, err := os.OpenFile(f.path, os.O_RDWR, os.ModePerm)
if err != nil {
return fmt.Errorf("could not open a file: %s", err)
}
defer fhandler.Close()
err = tag.WriteToFile(fhandler)
if err != nil {
return fmt.Errorf("could not write ID3v2 to file: %s", err)
}
return nil
}

17
id3ed_test.go

@ -51,3 +51,20 @@ func TestWriteID3v1(t *testing.T) {
t.Errorf("WriteID3v1 failed: %s", err)
}
}
func TestWriteID3v2(t *testing.T) {
file, err := Open(filepath.Join(TESTDATAPATH, "testwritev2.mp3"))
if err != nil {
t.Errorf("Open failed: %s", err)
}
frame1, _ := v2.NewFrame("COMM", []byte("Very Cool Song"), true)
frame2, _ := v2.NewFrame("TXXX", []byte("\n\n\n\n\n\n\nF\n\n\n\n"), true)
v2tag := v2.NewTAG([]v2.Frame{*frame1, *frame2})
err = file.WriteID3v2(v2tag)
if err != nil {
t.Errorf("WriteID3v2 failed: %s", err)
}
}

BIN
testData/testreadv2.mp3

Binary file not shown.

BIN
testData/testwritev1.mp3

Binary file not shown.

BIN
testData/testwritev2.mp3

Binary file not shown.

1
v1/read.go

@ -41,7 +41,6 @@ func containsEnhancedTAG(rs io.ReadSeeker) bool {
return false
}
if !bytes.Equal(identifier, []byte(ENHANCEDIDENTIFIER)) {
fmt.Printf("UWAH: %s ---- %s\n", identifier, ENHANCEDIDENTIFIER)
return false
}

8
v1/write.go

@ -163,8 +163,6 @@ 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+ENHANCEDSIZE))
if err != nil {
@ -177,8 +175,6 @@ 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 {
@ -191,8 +187,6 @@ 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 {
@ -205,8 +199,6 @@ 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 {

22
v2/read.go

@ -17,26 +17,27 @@ func ReadV2Tag(rs io.ReadSeeker) (*ID3v2Tag, error) {
var read uint64 = 0
var frames []Frame
var padding uint32 = 0
for {
if read == uint64(header.Size()) {
break
} else if read > uint64(header.Size()) {
// read more than required, but did not
// encouter padding, something is wrong here
return nil, ErrReadMoreThanSize
}
frame, err := readNextFrame(rs, header.Version())
switch err {
case nil:
case ErrGotPadding:
// expected error, just return what we`ve collected
// take a note how many padding bytes are left and
// return collected frames
padding += header.Size() - uint32(read)
return &ID3v2Tag{
Header: header,
Frames: frames,
Header: header,
Frames: frames,
Padding: padding,
}, nil
case ErrInvalidID:
// expected error, just return what we`ve collected
// return what has been collected
return &ID3v2Tag{
Header: header,
Frames: frames,
@ -57,7 +58,8 @@ func ReadV2Tag(rs io.ReadSeeker) (*ID3v2Tag, error) {
}
return &ID3v2Tag{
Header: header,
Frames: frames,
Header: header,
Frames: frames,
Padding: padding,
}, nil
}

9
v2/read_test.go

@ -17,6 +17,10 @@ func TestReadV2Tag(t *testing.T) {
t.Errorf("GetV2Tag failed: %s", err)
}
if tag.Padding != 1024 {
t.Errorf("GetV2Tag failed: expected to have %d padding bytes: got %d", 1024, tag.Padding)
}
titleFrame := tag.GetFrame("TIT2")
if titleFrame.Text() != "title" {
@ -34,4 +38,9 @@ func TestReadV2Tag(t *testing.T) {
if picture != nil {
t.Errorf("ReadV2Tag failed: expected file not to have a picture")
}
genre := tag.GetFrame("TCON")
if genre == nil {
t.Errorf("ReadV2Tag failed: expected genre to be %s; got %v", "anime", genre)
}
}

5
v2/v2tag.go

@ -3,8 +3,9 @@ package v2
import "strings"
type ID3v2Tag struct {
Header Header
Frames []Frame
Header Header
Frames []Frame
Padding uint32
}
// Creates a new v2 tag from given created frames

330
v2/write.go

@ -1,113 +1,221 @@
package v2
// // Writes ID3v2Tag to ws
// func (tag *ID3v2Tag) write(ws io.WriteSeeker) error {
// _, err := ws.Seek(0, io.SeekStart)
// if err != nil {
// return fmt.Errorf("could not seek: %s", err)
// }
// // write header
// _, 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 {
// _, err = ws.Write(frame.toBytes())
// if err != nil {
// return fmt.Errorf("could not write to writer: %s", err)
// }
// }
// return nil
// }
// // Writes ID3v2Tag to file, removing already existing tag if found
// func (tag *ID3v2Tag) WriteToFile(f *os.File) error {
// defer f.Close()
// _, err := f.Seek(0, io.SeekStart)
// if err != nil {
// return fmt.Errorf("could not seek: %s", err)
// }
// // check for existing tag
// possibleHeaderID, err := util.ReadToString(f, 3)
// if err != nil {
// return err
// }
// if possibleHeaderID != HEADERIDENTIFIER {
// // No existing tag, just write what we have
// // and exit
// tag.write(f)
// return nil
// }
// // there is an existing tag, remove it
// // and write a new one
// // get size of the existing tag
// existingHeader, err := readHeader(f)
// if err != nil {
// return err
// }
// existingHSize := existingHeader.Size()
// // cannot truncate just the existing tag with f.Truncate(),
// // so we need to improvise and have a temporary copy of the mp3,
// // wipe the original file, write our tag and place the actual
// // music without the old tag from the temporary copy.
// // create a temporary file
// temporaryDir := os.TempDir()
// tmpF, err := os.CreateTemp(temporaryDir, fmt.Sprintf("%s_TEMP", filepath.Base(f.Name())))
// if err != nil {
// return err
// }
// defer tmpF.Close()
// // remove it afterwards
// defer os.Remove(filepath.Join(temporaryDir, tmpF.Name()))
// tmpFStats, err := tmpF.Stat()
// if err != nil {
// return err
// }
// // copy contents from the original mp3 to a temporary one
// _, err = io.Copy(tmpF, f)
// if err != nil {
// return err
// }
// // fully remove contents from the original file
// err = f.Truncate(0)
// if err != nil {
// return err
// }
// // write our tag
// tag.write(f)
// // read all contents of the temporary file, except the existing tag
// tmpF.Seek(int64(existingHSize), io.SeekStart)
// musicDataSize := uint64(tmpFStats.Size() - int64(existingHSize))
// musicData, err := util.Read(tmpF, musicDataSize)
// if err != nil {
// return err
// }
// // and write them into the original file, which
// // contains only the new tag
// _, err = f.Write(musicData)
// if err != nil {
// return err
// }
// return nil
// }
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/Unbewohnte/id3ed/util"
)
// Writes ID3v2Tag to ws
func (tag *ID3v2Tag) write(ws io.WriteSeeker) error {
_, err := ws.Seek(0, io.SeekStart)
if err != nil {
return fmt.Errorf("could not seek: %s", err)
}
// write header
_, 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 {
_, err = ws.Write(frame.toBytes())
if err != nil {
return fmt.Errorf("could not write to writer: %s", err)
}
}
// write padding if has any
if tag.Padding != 0 {
util.WriteToExtent(ws, []byte{0}, int(tag.Padding))
}
return nil
}
// Writes ID3v2Tag to file, removing already existing tag if found
func (tag *ID3v2Tag) WriteToFile(f *os.File) error {
defer f.Close()
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return fmt.Errorf("could not seek: %s", err)
}
// check if there`s content at all
fStats, err := f.Stat()
if err != nil {
return err
}
if fStats.Size() < 3 {
// there`s no way that the file can contain TAG,
// just write and exit
// `write` for some reason removes all contents if there`s no tag, so
// we need forcefully store already existing data and
// write it again afterwards
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return fmt.Errorf("could not seek: %s", err)
}
contents, err := util.Read(f, uint64(fStats.Size()))
if err != nil {
return err
}
err = tag.write(f)
if err != nil {
return err
}
_, err = f.Write(contents)
if err != nil {
return err
}
// apparently, there are 3 zerobytes
// that appear after writing the contents for some
// alien-like reason so we need to remove them.
fStats, err = f.Stat()
if err != nil {
return err
}
err = f.Truncate(fStats.Size() - 3)
if err != nil {
return err
}
return nil
}
// check for an existing tag
possibleHeaderID, err := util.ReadToString(f, 3)
if err != nil {
return err
}
if possibleHeaderID != HEADERIDENTIFIER {
// No existing tag, just write what we have
// and exit
// `write` for some reason removes all contents if there`s no tag, so
// we need forcefully store already existing data and
// write it again afterwards
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return fmt.Errorf("could not seek: %s", err)
}
contents, err := util.Read(f, uint64(fStats.Size()))
if err != nil {
return err
}
err = tag.write(f)
if err != nil {
return err
}
_, err = f.Write(contents)
if err != nil {
return err
}
// apparently, there are 3 zerobytes
// that appear after writing the contents for some
// alien-like reason so we need to remove them.
fStats, err = f.Stat()
if err != nil {
return err
}
err = f.Truncate(fStats.Size() - 3)
if err != nil {
return err
}
return nil
}
// there is an existing tag, remove it
// and write a new one
// get size of the existing tag
existingHeader, err := readHeader(f)
if err != nil {
return err
}
existingHeaderSize := existingHeader.Size()
// cannot truncate just the existing tag with f.Truncate(),
// so we need to improvise and have a temporary copy of the mp3,
// wipe the original file, write our tag and place the actual
// music without the old tag from the temporary copy.
// create a temporary file
temporaryDir := os.TempDir()
tmpF, err := os.CreateTemp(temporaryDir, fmt.Sprintf("%s_TEMP", filepath.Base(f.Name())))
if err != nil {
return err
}
defer tmpF.Close()
// remove it afterwards
defer os.Remove(filepath.Join(temporaryDir, tmpF.Name()))
// copy contents of the original mp3 to a temporary one
_, err = io.Copy(tmpF, f)
if err != nil {
return err
}
// fully remove contents of the original file
err = f.Truncate(0)
if err != nil {
return err
}
// write our tag to the original file, which is at that moment is
// empty
err = tag.write(f)
if err != nil {
return err
}
tmpFStats, err := tmpF.Stat()
if err != nil {
return err
}
// read all contents of the temporary file, except the existing tag
musicDataSize := int64(tmpFStats.Size() - int64(existingHeaderSize))
_, err = tmpF.Seek(int64(existingHeaderSize), io.SeekStart)
if err != nil {
return fmt.Errorf("could not seek: %s", err)
}
musicData, err := util.Read(tmpF, uint64(musicDataSize))
if err != nil {
return err
}
// and write them into the original file, which
// contains only the new tag
_, err = f.Write(musicData)
if err != nil {
return err
}
return nil
}

50
v2/write_test.go

@ -1,27 +1,33 @@
package v2
// func TestWrite(t *testing.T) {
// f, err := os.Open(filepath.Join(TESTDATAPATH, "testreadv2.mp3"))
// if err != nil {
// t.Errorf("%s", err)
// }
// defer f.Close()
import (
"os"
"path/filepath"
"testing"
)
// testTag, err := ReadV2Tag(f)
// if err != nil {
// t.Errorf("%s", err)
// }
func TestWrite(t *testing.T) {
f, err := os.Open(filepath.Join(TESTDATAPATH, "testreadv2.mp3"))
if err != nil {
t.Errorf("%s", err)
}
defer f.Close()
// ff, err := os.OpenFile(filepath.Join(TESTDATAPATH, "testwritev2.mp3"),
// os.O_CREATE|os.O_RDWR, os.ModePerm)
// if err != nil {
// t.Errorf("%s", err)
// }
// defer ff.Close()
testTag, err := ReadV2Tag(f)
if err != nil {
t.Errorf("%s", err)
}
// // WRITING
// err = testTag.WriteToFile(ff)
// if err != nil {
// t.Errorf("WriteToFile failed: %s", err)
// }
// }
ff, err := os.OpenFile(filepath.Join(TESTDATAPATH, "testwritev2.mp3"),
os.O_CREATE|os.O_RDWR, os.ModePerm)
if err != nil {
t.Errorf("%s", err)
}
defer ff.Close()
// write testTag to the ff
err = testTag.WriteToFile(ff)
if err != nil {
t.Errorf("WriteToFile failed: %s", err)
}
}

Loading…
Cancel
Save