From be3da624882bfd870aeff565247fca0a2c0ffcd9 Mon Sep 17 00:00:00 2001 From: Xavier Henner Date: Mon, 25 May 2015 00:33:47 +0200 Subject: [PATCH 1/3] Add more tags to mp4 1) correct a bug with the mdat atom. Sometimes, the metadata can be at the end, after the audio Demo : get any music file and strip the metadata convert it to m4a with ffmpeg analyse it with MusicBrainz Picard et voila, you have the metadata at the end. Example of structure of a m4a created that way; https://www.dropbox.com/s/602yivdstan7506/atom.txt?dl=0 2) add support for the "----" tag See http://atomicparsley.sourceforge.net/mpeg-4files.html It gives access to the tags created by MusicBrainz Picard --- mp4.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/mp4.go b/mp4.go index 4a4b142..641bfe7 100644 --- a/mp4.go +++ b/mp4.go @@ -72,6 +72,7 @@ func ReadAtoms(r io.ReadSeeker) (Metadata, error) { func (m metadataMP4) readAtoms(r io.ReadSeeker) error { for { var size uint32 + var subname string err := binary.Read(r, binary.BigEndian, &size) if err != nil { if err == io.EOF { @@ -101,8 +102,76 @@ func (m metadataMP4) readAtoms(r io.ReadSeeker) error { return err } continue - case "mdat": // stop when we get to the data - return nil + case "mdat": // skip the data, the metadata can be at the end + _, err := r.Seek(int64(size-8), os.SEEK_CUR) + if err != nil { + return err + } + continue + case "----": + /* Generic atom. + Should have 3 sub atoms : mean, name and data. + We check that mean=="com.apple.iTunes" and we use the subname as + the name, and move to the data atom if anything goes wrong, we jump + at the end of the "----" atom. */ + + // let's read the mean atom + var subsize uint32 + err := binary.Read(r, binary.BigEndian, &subsize) + if err != nil { + return err + } + sub, err := readString(r, 4) + if err != nil { + return err + } + + if sub != "mean" { + // Something's wrong. Remove 8 read bytes from the size counter + // since "----" is not a known atom name, the whole data will + // be skipped + size -= 8 + break + } + + mean, err := readBytes(r, int(subsize-8)) + if err != nil { + return err + } + // Remove the size of the mean atom from the size counter + size -= subsize + + if string(mean[4:]) != "com.apple.iTunes" { + // Something's wrong, skip this atom + break + } + + // Let's read the name atom + err = binary.Read(r, binary.BigEndian, &subsize) + if err != nil { + return err + } + sub, err = readString(r, 4) + if err != nil { + return err + } + + if sub != "name" { + // Something's wrong + size -= 8 + break + } + + b, err := readBytes(r, int(subsize-8)) + if err != nil { + return err + } + /* Remove the size of the name atom from the size counter. + We should now be at the start of the data subatom and size should + be equal to the size of the data atom and its header */ + size -= subsize + + subname = string(b[4:]) } b, err := readBytes(r, int(size-8)) @@ -110,8 +179,14 @@ func (m metadataMP4) readAtoms(r io.ReadSeeker) error { return err } + // Allow all known atoms and the valid "----" atoms _, ok := atoms[name] - if !ok { + switch { + case name == "----" && subname == "": + continue + case name == "----": + name = subname + case !ok: continue } From a931bf6e30082ccaa6ace8bf8cbd342ef4432917 Mon Sep 17 00:00:00 2001 From: Xavier Henner Date: Mon, 25 May 2015 08:45:31 +0200 Subject: [PATCH 2/3] Code refactorization Should even work if the subatoms of "----" are out of order --- mp4.go | 134 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/mp4.go b/mp4.go index 641bfe7..6acadc9 100644 --- a/mp4.go +++ b/mp4.go @@ -6,6 +6,7 @@ package tag import ( "encoding/binary" + "errors" "fmt" "io" "os" @@ -69,10 +70,62 @@ func ReadAtoms(r io.ReadSeeker) (Metadata, error) { return m, err } +func readCustomAtom(r io.ReadSeeker, size uint32) (string, uint32, error) { + var datasize uint32 + var datapos int64 + var name string + var mean string + + for size > 8 { + var subsize uint32 + err := binary.Read(r, binary.BigEndian, &subsize) + if err != nil { + return "----", size - 4, err + } + subname, err := readString(r, 4) + if err != nil { + return "----", size - 8, err + } + b, err := readBytes(r, int(subsize-8)) + if err != nil { + return "----", size - subsize, err + } + // Remove the size of the mean atom from the size counter + size -= subsize + + switch string(subname) { + case "mean": + mean = string(b[4:]) + case "name": + name = string(b[4:]) + case "data": + datapos, err = r.Seek(0, os.SEEK_CUR) + if err != nil { + return "----", size, err + } + datasize = subsize + datapos -= int64(subsize) + } + } + // there should remain only the header size + if size != 8 { + return "----", size, errors.New("---- atom out of bound") + } + if mean == "com.apple.iTunes" && datasize != 0 && name != "" { + // we jump just before the data subatom + _, err := r.Seek(datapos, os.SEEK_SET) + if err != nil { + return "----", size, err + } + return name, datasize + 8, nil + } + return "----", size, nil +} + func (m metadataMP4) readAtoms(r io.ReadSeeker) error { for { var size uint32 - var subname string + ok := false err := binary.Read(r, binary.BigEndian, &size) if err != nil { if err == io.EOF { @@ -109,69 +162,20 @@ func (m metadataMP4) readAtoms(r io.ReadSeeker) error { } continue case "----": - /* Generic atom. - Should have 3 sub atoms : mean, name and data. - We check that mean=="com.apple.iTunes" and we use the subname as - the name, and move to the data atom if anything goes wrong, we jump - at the end of the "----" atom. */ - - // let's read the mean atom - var subsize uint32 - err := binary.Read(r, binary.BigEndian, &subsize) + // Generic atom. + // Should have 3 sub atoms : mean, name and data. + // We check that mean=="com.apple.iTunes" and we use the subname as + // the name, and move to the data atom if anything goes wrong, + // we jump at the end of the "----" atom. + name, size, err = readCustomAtom(r, size) if err != nil { return err } - sub, err := readString(r, 4) - if err != nil { - return err + if name != "----" { + ok = true } - - if sub != "mean" { - // Something's wrong. Remove 8 read bytes from the size counter - // since "----" is not a known atom name, the whole data will - // be skipped - size -= 8 - break - } - - mean, err := readBytes(r, int(subsize-8)) - if err != nil { - return err - } - // Remove the size of the mean atom from the size counter - size -= subsize - - if string(mean[4:]) != "com.apple.iTunes" { - // Something's wrong, skip this atom - break - } - - // Let's read the name atom - err = binary.Read(r, binary.BigEndian, &subsize) - if err != nil { - return err - } - sub, err = readString(r, 4) - if err != nil { - return err - } - - if sub != "name" { - // Something's wrong - size -= 8 - break - } - - b, err := readBytes(r, int(subsize-8)) - if err != nil { - return err - } - /* Remove the size of the name atom from the size counter. - We should now be at the start of the data subatom and size should - be equal to the size of the data atom and its header */ - size -= subsize - - subname = string(b[4:]) + default: + _, ok = atoms[name] } b, err := readBytes(r, int(size-8)) @@ -179,14 +183,8 @@ func (m metadataMP4) readAtoms(r io.ReadSeeker) error { return err } - // Allow all known atoms and the valid "----" atoms - _, ok := atoms[name] - switch { - case name == "----" && subname == "": - continue - case name == "----": - name = subname - case !ok: + // At this point, we allow all known atoms and the valid "----" atoms + if !ok { continue } From 6c5d32240b1f19d1f6c83368d483f88b63349f95 Mon Sep 17 00:00:00 2001 From: Xavier Henner Date: Mon, 25 May 2015 08:57:37 +0200 Subject: [PATCH 3/3] typo in comment + small correction --- mp4.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mp4.go b/mp4.go index 6acadc9..3deed5d 100644 --- a/mp4.go +++ b/mp4.go @@ -90,7 +90,7 @@ func readCustomAtom(r io.ReadSeeker, size uint32) (string, uint32, error) { if err != nil { return "----", size - subsize, err } - // Remove the size of the mean atom from the size counter + // Remove the size of the atom from the size counter size -= subsize switch string(subname) { @@ -165,15 +165,13 @@ func (m metadataMP4) readAtoms(r io.ReadSeeker) error { // Generic atom. // Should have 3 sub atoms : mean, name and data. // We check that mean=="com.apple.iTunes" and we use the subname as - // the name, and move to the data atom if anything goes wrong, - // we jump at the end of the "----" atom. + // the name, and move to the data atom. + // If anything goes wrong, we jump at the end of the "----" atom. name, size, err = readCustomAtom(r, size) if err != nil { return err } - if name != "----" { - ok = true - } + ok = (name != "----") default: _, ok = atoms[name] }