You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
4.5 KiB
221 lines
4.5 KiB
package v2 |
|
|
|
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 |
|
}
|
|
|