mirror of
https://github.com/zhaarey/go-mp4tag.git
synced 2025-10-23 15:11:07 +00:00
1071 lines
21 KiB
Go
1071 lines
21 KiB
Go
package mp4tag
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const BufSize = 4096*1024
|
|
|
|
func overwriteTags(mergedTags, tags *MP4Tags, delStrings []string) *MP4Tags{
|
|
if containsStr(delStrings, "alltags") {
|
|
mergedPics := mergedTags.Pictures
|
|
mergedTags = &MP4Tags{}
|
|
mergedTags.Pictures = mergedPics
|
|
} else if containsStr(delStrings, "allcustom") {
|
|
mergedTags.Custom = map[string]string{}
|
|
}
|
|
|
|
if containsStr(delStrings, "album") {
|
|
mergedTags.Album = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "albumartist") {
|
|
mergedTags.AlbumArtist = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "albumartistsort") {
|
|
mergedTags.AlbumArtistSort = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "albumsort") {
|
|
mergedTags.AlbumSort = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "artist") {
|
|
mergedTags.Artist = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "artistsort") {
|
|
mergedTags.ArtistSort = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "bpm") {
|
|
mergedTags.BPM = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "comment") {
|
|
mergedTags.Comment = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "composer") {
|
|
mergedTags.Composer = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "composersort") {
|
|
mergedTags.ComposerSort = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "conductor") {
|
|
mergedTags.Conductor = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "copyright") {
|
|
mergedTags.Copyright = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "customgenre") {
|
|
mergedTags.CustomGenre = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "date") {
|
|
mergedTags.Date = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "description") {
|
|
mergedTags.Description = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "director") {
|
|
mergedTags.Director = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "discnumber") || containsStr(delStrings, "disknumber") {
|
|
mergedTags.DiscNumber = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "disctotal") || containsStr(delStrings, "disktotal") {
|
|
mergedTags.DiscTotal = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "genre") {
|
|
mergedTags.Genre = GenreNone
|
|
}
|
|
|
|
if containsStr(delStrings, "itunesadvisory") {
|
|
mergedTags.ItunesAdvisory = ItunesAdvisoryNone
|
|
}
|
|
|
|
if containsStr(delStrings, "itunesalbumid") {
|
|
mergedTags.ItunesAlbumID = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "itunesartistid") {
|
|
mergedTags.ItunesArtistID = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "lyrics") {
|
|
mergedTags.Lyrics = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "narrator") {
|
|
mergedTags.Narrator = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "publisher") {
|
|
mergedTags.Publisher = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "title") {
|
|
mergedTags.Title = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "titlesort") {
|
|
mergedTags.TitleSort = ""
|
|
}
|
|
|
|
if containsStr(delStrings, "tracknumber") {
|
|
mergedTags.TrackNumber = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "tracktotal") {
|
|
mergedTags.TrackTotal = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "year") {
|
|
mergedTags.Year = 0
|
|
}
|
|
|
|
if containsStr(delStrings, "allpictures") {
|
|
mergedTags.Pictures = []*MP4Picture{}
|
|
}
|
|
|
|
if tags.Album != "" {
|
|
mergedTags.Album = tags.Album
|
|
}
|
|
|
|
if tags.AlbumSort != "" {
|
|
mergedTags.AlbumSort = tags.AlbumSort
|
|
}
|
|
|
|
if tags.AlbumArtist != "" {
|
|
mergedTags.AlbumArtist = tags.AlbumArtist
|
|
}
|
|
|
|
if tags.AlbumArtistSort != "" {
|
|
mergedTags.AlbumArtistSort = tags.AlbumArtistSort
|
|
}
|
|
|
|
if tags.Artist != "" {
|
|
mergedTags.Artist = tags.Artist
|
|
}
|
|
|
|
if tags.ArtistSort != "" {
|
|
mergedTags.ArtistSort = tags.ArtistSort
|
|
}
|
|
|
|
if tags.BPM > 0 {
|
|
mergedTags.BPM = tags.BPM
|
|
}
|
|
|
|
if tags.Comment != "" {
|
|
mergedTags.Comment = tags.Comment
|
|
}
|
|
|
|
if tags.Composer != "" {
|
|
mergedTags.Composer = tags.Composer
|
|
}
|
|
|
|
if tags.ComposerSort != "" {
|
|
mergedTags.ComposerSort = tags.ComposerSort
|
|
}
|
|
if tags.Conductor != "" {
|
|
mergedTags.Conductor = tags.Conductor
|
|
}
|
|
|
|
if tags.Copyright != "" {
|
|
mergedTags.Copyright = tags.Copyright
|
|
}
|
|
|
|
if tags.CustomGenre != "" {
|
|
mergedTags.CustomGenre = tags.CustomGenre
|
|
}
|
|
|
|
if tags.Date != "" {
|
|
mergedTags.Date = tags.Date
|
|
}
|
|
|
|
if tags.Description != "" {
|
|
mergedTags.Description = tags.Description
|
|
}
|
|
|
|
if tags.Director != "" {
|
|
mergedTags.Director = tags.Director
|
|
}
|
|
|
|
if tags.DiscNumber > 0 {
|
|
mergedTags.DiscNumber = tags.DiscNumber
|
|
}
|
|
|
|
if tags.DiscTotal > 0 {
|
|
mergedTags.DiscTotal = tags.DiscTotal
|
|
}
|
|
|
|
if tags.ItunesAdvisory != ItunesAdvisoryNone {
|
|
mergedTags.ItunesAdvisory = tags.ItunesAdvisory
|
|
}
|
|
|
|
if tags.ItunesAlbumID > 0 {
|
|
mergedTags.ItunesAlbumID = tags.ItunesAlbumID
|
|
}
|
|
|
|
if tags.ItunesArtistID > 0 {
|
|
mergedTags.ItunesArtistID = tags.ItunesArtistID
|
|
}
|
|
|
|
if tags.Lyrics != "" {
|
|
mergedTags.Lyrics = tags.Lyrics
|
|
}
|
|
|
|
if tags.Narrator != "" {
|
|
mergedTags.Narrator = tags.Narrator
|
|
}
|
|
|
|
if tags.Publisher != "" {
|
|
mergedTags.Publisher = tags.Publisher
|
|
}
|
|
|
|
if tags.Title != "" {
|
|
mergedTags.Title = tags.Title
|
|
}
|
|
|
|
if tags.TitleSort != "" {
|
|
mergedTags.TitleSort = tags.TitleSort
|
|
}
|
|
|
|
if tags.TrackNumber > 0 {
|
|
mergedTags.TrackNumber = tags.TrackNumber
|
|
}
|
|
|
|
if tags.TrackTotal > 0 {
|
|
mergedTags.TrackTotal = tags.TrackTotal
|
|
}
|
|
|
|
if tags.Year > 0 {
|
|
mergedTags.Year = tags.Year
|
|
}
|
|
|
|
if tags.Genre != GenreNone {
|
|
mergedTags.Genre = tags.Genre
|
|
}
|
|
|
|
for k, v := range tags.Custom {
|
|
if v != "" {
|
|
mergedTags.Custom[k] = v
|
|
}
|
|
}
|
|
|
|
var filteredPics []*MP4Picture
|
|
|
|
for idx, p := range mergedTags.Pictures {
|
|
if !containsStr(delStrings, fmt.Sprintf("picture:%d", idx+1)) {
|
|
filteredPics = append(filteredPics, p)
|
|
}
|
|
}
|
|
|
|
for _, p := range tags.Pictures {
|
|
filteredPics = append(filteredPics, p)
|
|
}
|
|
|
|
mergedTags.Pictures = filteredPics
|
|
return mergedTags
|
|
}
|
|
|
|
func putI16BE(n int16) []byte {
|
|
buf := make([]byte, 2)
|
|
|
|
binary.BigEndian.PutUint16(buf, uint16(n))
|
|
return buf
|
|
}
|
|
|
|
func putI32BE(n int32) []byte {
|
|
buf := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(buf, uint32(n))
|
|
return buf
|
|
}
|
|
|
|
func (mp4 MP4) updateChunkOffsets(outF *os.File, boxes MP4Boxes, oldIlistSize, newIlistSize int64) error {
|
|
stco := boxes.getBoxByPath("moov.trak.mdia.minf.stbl.stco")
|
|
_, err := mp4.f.Seek(stco.StartOffset+12, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = outF.Seek(stco.StartOffset+16, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
count, err := mp4.readI32BE()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if stco.BoxSize != int64(count) * 4 + 16 {
|
|
return &ErrInvalidStcoSize{}
|
|
}
|
|
|
|
for i := int32(1); i<=count; i++ {
|
|
offset, err := mp4.readI32BE()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
offsetBytes := putI32BE(offset-int32(oldIlistSize)+int32(newIlistSize))
|
|
_, err = outF.Write(offsetBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mp4 MP4) readToOffset(f *os.File, startOffset int64) error {
|
|
_, err := mp4.f.Seek(0, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := make([]byte, BufSize)
|
|
var totalRead int64
|
|
|
|
for {
|
|
read, err := mp4.f.Read(buf)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
readI64 := int64(read)
|
|
totalRead += readI64
|
|
if totalRead > startOffset {
|
|
_, err = f.Write(buf[:readI64+startOffset-totalRead])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
_, err = f.Write(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|
|
func writeRegular(f *os.File, boxName, val string, prefix bool) error {
|
|
// boxSize := utf8.RuneCountInString(val) + 24
|
|
valBytes := []byte(val)
|
|
boxSize := len(valBytes) + 24
|
|
boxSizeI32 := int32(boxSize)
|
|
boxSizeBytes := putI32BE(boxSizeI32)
|
|
_, err := f.Write(boxSizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if prefix {
|
|
_, err = f.Write([]byte{0xA9})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = f.WriteString(boxName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
boxSizeBytes = putI32BE(boxSizeI32-8)
|
|
_, err = f.Write(boxSizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Write(
|
|
[]byte{0x0, 0x0, 0x0, 0x01, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Write(valBytes)
|
|
return err
|
|
}
|
|
|
|
func writeGenre(f *os.File, genre Genre) error {
|
|
_, err := f.Write([]byte{0x0, 0x0, 0x0, 0x1A})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("gnre")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x12})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(bytes.Repeat([]byte{0x0}, 9))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{byte(genre)})
|
|
return err
|
|
}
|
|
|
|
func writeTrknDisc(f *os.File, n, total int16, isTrkn bool) error {
|
|
var boxSize int32 = 30
|
|
if n < 0 {
|
|
n = 0
|
|
}
|
|
if total < 0 {
|
|
total = 0
|
|
}
|
|
if isTrkn {
|
|
boxSize += 2
|
|
}
|
|
boxSizeBytes := putI32BE(boxSize)
|
|
_, err := f.Write(boxSizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isTrkn {
|
|
_, err = f.WriteString("trkn")
|
|
} else {
|
|
_, err = f.WriteString("disk")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
boxSizeBytes = putI32BE(boxSize-8)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(boxSizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(bytes.Repeat([]byte{0x0}, 10))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nBytes := putI16BE(n)
|
|
_, err = f.Write(nBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
totalBytes := putI16BE(total)
|
|
_, err = f.Write(totalBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isTrkn {
|
|
_, err = f.Write([]byte{0x0, 0x0})
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeBPM(f *os.File, bpm int16) error {
|
|
_, err := f.Write([]byte{0x0, 0x0, 0x0, 0x1A})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("tmpo")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x12})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(
|
|
[]byte{0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bpmBytes := putI16BE(bpm)
|
|
_, err = f.Write(bpmBytes)
|
|
return err
|
|
}
|
|
|
|
func writeAdvisory(f *os.File, advisory ItunesAdvisory) error {
|
|
_, err := f.Write([]byte{0x0, 0x0, 0x0, 0x19})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("rtng")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x11})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{byte(advisory)})
|
|
return err
|
|
}
|
|
|
|
func writeItunesAlbumID(f *os.File, albumID int32) error {
|
|
_, err := f.Write([]byte{0x0, 0x0, 0x0, 0x20})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("plID")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x18})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(
|
|
[]byte{0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
albumIDBytes := putI32BE(albumID)
|
|
_, err = f.Write(albumIDBytes)
|
|
return err
|
|
}
|
|
|
|
func writeItunesArtistID(f *os.File, artistID int32) error {
|
|
_, err := f.Write([]byte{0x0, 0x0, 0x0, 0x1C})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("atID")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x14})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(
|
|
[]byte{0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
artistIDBytes := putI32BE(artistID)
|
|
_, err = f.Write(artistIDBytes)
|
|
return err
|
|
}
|
|
|
|
func writeCustom(f *os.File, name, value string) error {
|
|
nameUpperBytes := []byte(strings.ToUpper(name))
|
|
valueBytes := []byte(value)
|
|
nameSize := len(nameUpperBytes)
|
|
valueSize := len(valueBytes)
|
|
|
|
sizeBytes := putI32BE(int32(nameSize+valueSize)+64)
|
|
_, err := f.Write(sizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("----")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, 0x1C})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("mean")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(bytes.Repeat([]byte{0x0}, 4))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("com.apple.iTunes")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sizeBytes = putI32BE(int32(nameSize)+12)
|
|
_, err = f.Write(sizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("name")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(bytes.Repeat([]byte{0x0}, 4))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(nameUpperBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sizeBytes = putI32BE(int32(valueSize)+16)
|
|
_, err = f.Write(sizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(
|
|
[]byte{0x0, 0x0, 0x0, 0x01, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(valueBytes)
|
|
return err
|
|
}
|
|
|
|
func getPicFormat(imageType ImageType, magic []byte) uint8 {
|
|
if imageType == ImageTypeAuto {
|
|
if bytes.Equal(magic, []byte{0x89, 0x50, 0x4E, 0x47}) {
|
|
return 0xE
|
|
}
|
|
}
|
|
if imageType == ImageTypePNG {
|
|
return 0xE
|
|
}
|
|
return 0x0D
|
|
}
|
|
|
|
func writePics(f *os.File, pics []*MP4Picture) error {
|
|
var boxSize int32 = 8
|
|
for _, pic := range pics {
|
|
dataSize := len(pic.Data)
|
|
if dataSize < 1 {
|
|
continue
|
|
}
|
|
boxSize += int32(dataSize + 16)
|
|
}
|
|
|
|
boxSizeBytes := putI32BE(boxSize)
|
|
_, err := f.Write(boxSizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("covr")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, pic := range pics {
|
|
dataSize := len(pic.Data)
|
|
if dataSize < 1 {
|
|
continue
|
|
}
|
|
boxSizeBytes = putI32BE(int32(dataSize+16))
|
|
_, err = f.Write(boxSizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("data")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
format := getPicFormat(pic.Format, pic.Data[:4])
|
|
_, err = f.Write([]byte{0x0, 0x0, 0x0, format, 0x0, 0x0, 0x0, 0x0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(pic.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resizeBoxes(f *os.File, boxes MP4Boxes, oldIlstSize, newIlistSize int64) error {
|
|
moov := boxes.getBoxByPath("moov")
|
|
udta := boxes.getBoxByPath("moov.udta")
|
|
meta := boxes.getBoxByPath("moov.udta.meta")
|
|
|
|
sizeBytes := putI32BE(int32(newIlistSize))
|
|
_, err := f.Write(sizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Seek(moov.StartOffset, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newMoovSize := moov.BoxSize - oldIlstSize + newIlistSize
|
|
sizeBytes = putI32BE(int32(newMoovSize))
|
|
_, err = f.Write(sizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Seek(udta.StartOffset, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newUdtaSize := udta.BoxSize - oldIlstSize + newIlistSize
|
|
sizeBytes = putI32BE(int32(newUdtaSize))
|
|
_, err = f.Write(sizeBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Seek(meta.StartOffset, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newMetaSize := meta.BoxSize - oldIlstSize + newIlistSize
|
|
sizeBytes = putI32BE(int32(newMetaSize))
|
|
_, err = f.Write(sizeBytes)
|
|
return err
|
|
}
|
|
|
|
func (mp4 MP4) writeRemaining(f *os.File) error {
|
|
buf := make([]byte, BufSize)
|
|
for {
|
|
read, err := mp4.f.Read(buf)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
if read < BufSize {
|
|
_, err := f.Write(buf[:read])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err = f.Write(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mp4 MP4) writeTags(boxes MP4Boxes, tags *MP4Tags, tempPath string) error {
|
|
ilst := boxes.getBoxByPath("moov.udta.meta.ilst")
|
|
oldIlstSize := ilst.BoxSize
|
|
f, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
err = mp4.readToOffset(f, ilst.StartOffset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ilstStartOffset, err := getPos(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = f.Write(bytes.Repeat([]byte{0x0}, 4))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.WriteString("ilst")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tags.Title != "" {
|
|
err = writeRegular(f, "nam", tags.Title, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if tags.TitleSort != "" {
|
|
err = writeRegular(f, "sonm", tags.TitleSort, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if tags.Album != "" {
|
|
err = writeRegular(f, "alb", tags.Album, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if tags.AlbumSort != "" {
|
|
err = writeRegular(f, "soal", tags.AlbumSort, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.AlbumArtist != "" {
|
|
err = writeRegular(f, "aART", tags.AlbumArtist, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.AlbumArtistSort != "" {
|
|
err = writeRegular(f, "soaa", tags.AlbumArtistSort, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Artist != "" {
|
|
err = writeRegular(f, "ART", tags.Artist, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.ArtistSort != "" {
|
|
err = writeRegular(f, "soar", tags.ArtistSort, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Comment != "" {
|
|
err = writeRegular(f, "cmt", tags.Comment, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Composer != "" {
|
|
err = writeRegular(f, "wrt", tags.Composer, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.ComposerSort != "" {
|
|
err = writeRegular(f, "soco", tags.ComposerSort, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Copyright != "" {
|
|
err = writeRegular(f, "cprt", tags.Copyright, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Lyrics != "" {
|
|
err = writeRegular(f, "lyr", tags.Lyrics, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.CustomGenre != "" {
|
|
err = writeRegular(f, "gen", tags.CustomGenre, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Description != "" {
|
|
err = writeRegular(f, "desc", tags.Description, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Publisher != "" {
|
|
err = writeRegular(f, "pub", tags.Publisher, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Conductor != "" {
|
|
err = writeRegular(f, "con", tags.Conductor, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.ItunesAdvisory != ItunesAdvisoryNone {
|
|
err = writeAdvisory(f, tags.ItunesAdvisory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.ItunesAlbumID > 0 {
|
|
err = writeItunesAlbumID(f, tags.ItunesAlbumID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.ItunesArtistID > 0 {
|
|
err = writeItunesArtistID(f, tags.ItunesArtistID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.TrackNumber > 0 || tags.TrackTotal > 0 {
|
|
err = writeTrknDisc(f, tags.TrackNumber, tags.TrackTotal, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.DiscNumber > 0 || tags.DiscTotal > 0 {
|
|
err = writeTrknDisc(f, tags.DiscNumber, tags.DiscTotal, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.BPM > 0 {
|
|
err = writeBPM(f, tags.BPM)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Year > 0 {
|
|
err = writeRegular(f, "day", strconv.Itoa(int(tags.Year)), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if tags.Date == "" {
|
|
err = writeRegular(f, "day", tags.Date, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tags.Genre != GenreNone {
|
|
err = writeGenre(f, tags.Genre)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for k, v := range tags.Custom {
|
|
err = writeCustom(f, k, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = writePics(f, tags.Pictures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newIlstEndOffset, err := getPos(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newIlstSize := newIlstEndOffset - ilstStartOffset
|
|
_, err = f.Seek(ilstStartOffset, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = resizeBoxes(f, boxes, oldIlstSize, newIlstSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mdat := boxes.getBoxByPath("mdat")
|
|
if mdat.StartOffset > ilstStartOffset && oldIlstSize != newIlstSize {
|
|
err = mp4.updateChunkOffsets(f, boxes, oldIlstSize, newIlstSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, err = f.Seek(newIlstEndOffset, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = mp4.f.Seek(ilst.EndOffset, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = mp4.writeRemaining(f)
|
|
return err
|
|
}
|
|
|
|
func (mp4 *MP4) actualWrite(tags *MP4Tags, _delStrings []string) error {
|
|
delStrings := strArrToLower(_delStrings)
|
|
|
|
mergedTags, boxes, err := mp4.actualRead()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if boxes.getBoxByPath("moov.udta.meta.ilst") == nil {
|
|
return &ErrBoxNotPresent{Msg: "ilst box not present, implement me"}
|
|
}
|
|
mergedTags = overwriteTags(mergedTags, tags, delStrings)
|
|
tempPath := getTempPath(mp4.path)
|
|
err = mp4.writeTags(boxes, mergedTags, tempPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mp4.Close()
|
|
err = moveMP4(tempPath, mp4.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m, err := Open(mp4.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mp4.f = m.f
|
|
mp4.size = m.size
|
|
return nil
|
|
} |