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 { func (f *File) WriteID3v1(tag *v1.ID3v1Tag) error {
fhandler, err := os.OpenFile(f.path, os.O_RDWR, os.ModePerm) fhandler, err := os.OpenFile(f.path, os.O_RDWR, os.ModePerm)
if err != nil { 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() defer fhandler.Close()
@ -70,7 +70,18 @@ func (f *File) WriteID3v1(tag *v1.ID3v1Tag) error {
return nil return nil
} }
// still not implemented // Writes given ID3v2 tag to file
// func (f *File) WriteID3v2(tag *v2.ID3v2Tag) error { func (f *File) WriteID3v2(tag *v2.ID3v2Tag) error {
// return nil 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) 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 return false
} }
if !bytes.Equal(identifier, []byte(ENHANCEDIDENTIFIER)) { if !bytes.Equal(identifier, []byte(ENHANCEDIDENTIFIER)) {
fmt.Printf("UWAH: %s ---- %s\n", identifier, ENHANCEDIDENTIFIER)
return false return false
} }

8
v1/write.go

@ -163,8 +163,6 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error {
switch { switch {
case containsEnhancedTAG(f) && containsTAG(f): case containsEnhancedTAG(f) && containsTAG(f):
fmt.Println("HEA1")
// remove both // remove both
err = f.Truncate(filesize - int64(TAGSIZE+ENHANCEDSIZE)) err = f.Truncate(filesize - int64(TAGSIZE+ENHANCEDSIZE))
if err != nil { if err != nil {
@ -177,8 +175,6 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error {
} }
case containsEnhancedTAG(f) && !containsTAG(f): case containsEnhancedTAG(f) && !containsTAG(f):
fmt.Println("HEA2")
// remove enhanced tag, replace with new // remove enhanced tag, replace with new
err = f.Truncate(filesize - int64(ENHANCEDSIZE)) err = f.Truncate(filesize - int64(ENHANCEDSIZE))
if err != nil { if err != nil {
@ -191,8 +187,6 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error {
} }
case !containsEnhancedTAG(f) && containsTAG(f): case !containsEnhancedTAG(f) && containsTAG(f):
fmt.Println("HEA3")
// remove regular one, replace with new // remove regular one, replace with new
err = f.Truncate(filesize - int64(TAGSIZE)) err = f.Truncate(filesize - int64(TAGSIZE))
if err != nil { if err != nil {
@ -205,8 +199,6 @@ func (tag *ID3v1Tag) WriteToFile(f *os.File) error {
} }
case !containsEnhancedTAG(f) && !containsTAG(f): case !containsEnhancedTAG(f) && !containsTAG(f):
fmt.Println("HEA4")
// no existing TAGs, simply write what we have // no existing TAGs, simply write what we have
err := tag.write(f) err := tag.write(f)
if err != nil { if err != nil {

22
v2/read.go

@ -17,26 +17,27 @@ func ReadV2Tag(rs io.ReadSeeker) (*ID3v2Tag, error) {
var read uint64 = 0 var read uint64 = 0
var frames []Frame var frames []Frame
var padding uint32 = 0
for { for {
if read == uint64(header.Size()) { if read == uint64(header.Size()) {
break 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()) frame, err := readNextFrame(rs, header.Version())
switch err { switch err {
case nil: case nil:
case ErrGotPadding: 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{ return &ID3v2Tag{
Header: header, Header: header,
Frames: frames, Frames: frames,
Padding: padding,
}, nil }, nil
case ErrInvalidID: case ErrInvalidID:
// expected error, just return what we`ve collected // return what has been collected
return &ID3v2Tag{ return &ID3v2Tag{
Header: header, Header: header,
Frames: frames, Frames: frames,
@ -57,7 +58,8 @@ func ReadV2Tag(rs io.ReadSeeker) (*ID3v2Tag, error) {
} }
return &ID3v2Tag{ return &ID3v2Tag{
Header: header, Header: header,
Frames: frames, Frames: frames,
Padding: padding,
}, nil }, nil
} }

9
v2/read_test.go

@ -17,6 +17,10 @@ func TestReadV2Tag(t *testing.T) {
t.Errorf("GetV2Tag failed: %s", err) 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") titleFrame := tag.GetFrame("TIT2")
if titleFrame.Text() != "title" { if titleFrame.Text() != "title" {
@ -34,4 +38,9 @@ func TestReadV2Tag(t *testing.T) {
if picture != nil { if picture != nil {
t.Errorf("ReadV2Tag failed: expected file not to have a picture") 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" import "strings"
type ID3v2Tag struct { type ID3v2Tag struct {
Header Header Header Header
Frames []Frame Frames []Frame
Padding uint32
} }
// Creates a new v2 tag from given created frames // Creates a new v2 tag from given created frames

330
v2/write.go

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