From 3bbc27f4704fac50167eb93d489fdd1761aeae40 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 21 Nov 2015 14:09:02 +0800 Subject: [PATCH] rewrite genStruct.js --- atom/genStruct.js | 603 +++++++++++++++++++++++---------------- atom/reader.go | 39 ++- atom/struct.go | 712 +++++++++++++++++++++++++++++----------------- example/read.go | 15 + mp4.go | 35 ++- 5 files changed, 873 insertions(+), 531 deletions(-) create mode 100644 example/read.go diff --git a/atom/genStruct.js b/atom/genStruct.js index 94b2fa2..6c68689 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -4,227 +4,302 @@ Array.prototype.nonull = function () { return this.filter(x => x); }; -var Int = (name,len) => {return {name:uc(name),len,type:'int',fn:'Int'}}; -var Str = (name,len) => {return {name:uc(name),len,type:'string',fn:'String'}}; -var TimeStamp = (name,len) => {return {name:uc(name),len,type:'TimeStamp',fn:'TimeStamp'}}; -var Bytes = (name,len) => {return {name:uc(name),len,type:'[]byte',fn:'Bytes'}}; -var BytesLeft = (name) => {return {name:uc(name),type:'[]byte',fn:'BytesLeft'}}; -var Fixed32 = (name,len) => {return {name:uc(name),len,type:'Fixed32',fn:'Fixed32'}}; - -var Atom = (type,name) => {return {name:uc(name),type:uc(type)+'Atom',fn:uc(type)+'Atom'}}; -var AtomPtr = (type,name) => {return {name:uc(name),type:'*'+uc(type)+'Atom',fn:uc(type)+'Atom'}}; - -var Struct = (type,name) => {return {name:uc(name),type:uc(type),fn:uc(type)}}; -var StructPtr = (type,name) => {return {name:uc(name),type:'*'+uc(type),fn:uc(type)}}; - -var Arr = (name,elem,count) => {return {name:uc(name),elem,count,type:'[]'+elem.type}}; -var LenArr = (sizelen,name,elem) => {return {sizelen,name:uc(name),elem,type:'[]'+elem.type}}; - -var Size = (len) => {return {len,hide:true,fn:'Int'}}; -var _ = (len) => {return {len,hide:true,fn:'Dummy'}}; - var atoms = { - fileType: [ - 'ftyp', - AtomPtr('movie', 'movie'), - ], + fileType: { + cc4: 'ftyp', + fields: [ + ], + }, - movie: [ - 'moov', - AtomPtr('movieHeader', 'header'), - Arr('tracks', AtomPtr('track')), - ], + movie: { + cc4: 'moov', + atoms: [ + ['header', '*movieHeader'], + ['tracks', '[]*track'], + ], + }, - movieHeader: [ - 'mvhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('timeScale', 4), - Int('duration', 4), - Int('preferredRate', 4), - Int('preferredVolume', 2), - _(10), - Bytes('matrix', 36), - TimeStamp('previewTime', 4), - TimeStamp('previewDuration', 4), - TimeStamp('posterTime', 4), - TimeStamp('selectionTime', 4), - TimeStamp('selectionDuration', 4), - TimeStamp('currentTime', 4), - Int('nextTrackId', 4), - ], + movieHeader: { + cc4: 'mvhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['cTime', 'TimeStamp32'], + ['mTime', 'TimeStamp32'], + ['timeScale', 'TimeStamp32'], + ['duration', 'TimeStamp32'], + ['preferredRate', 'int32'], + ['preferredVolume', 'int16'], + ['_', '[10]byte'], + ['matrix', '[9]int32'], + ['previewTime', 'TimeStamp32'], + ['previewDuration', 'TimeStamp32'], + ['posterTime', 'TimeStamp32'], + ['selectionTime', 'TimeStamp32'], + ['selectionDuration', 'TimeStamp32'], + ['currentTime', 'TimeStamp32'], + ['nextTrackId', 'int32'], + ], + }, - track: [ - 'trak', - AtomPtr('trackHeader', 'header'), - AtomPtr('media', 'media'), - ], + track: { + cc4: 'trak', + atoms: [ + ['header', '*trackHeader'], + ['media', '*media'], + ], + }, - trackHeader: [ - 'tkhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('trackId', 4), - _(4), - Int('duration', 4), - _(8), - Int('layer', 2), - Int('alternateGroup', 2), - Int('volume', 2), - _(2), - Bytes('matrix', 36), - Fixed32('trackWidth', 4), - Fixed32('trackHeight', 4), - ], + trackHeader: { + cc4: 'tkhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['cTime', 'TimeStamp32'], + ['mTime', 'TimeStamp32'], + ['trackId', 'TimeStamp32'], + ['_', '[4]byte'], + ['duration', 'TimeStamp32'], + ['_', '[8]byte'], + ['layer', 'int16'], + ['alternateGroup', 'int16'], + ['volume', 'int16'], + ['_', '[2]byte'], + ['matrix', '[9]int32'], + ['trackWidth', 'int32'], + ['trackHeader', 'int32'], + ], + }, - media: [ - 'mdia', - AtomPtr('mediaHeader', 'header'), - AtomPtr('mediaInfo', 'info'), - ], + media: { + cc4: 'mdia', + atoms: [ + ['header', '*mediaHeader'], + ['info', '*mediaInfo'], + ], + }, - mediaHeader: [ - 'mdhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('timeScale', 4), - Int('duration', 4), - Int('language', 2), - Int('quality', 2), - ], + mediaHeader: { + cc4: 'mdhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['cTime', 'int32'], + ['mTime', 'int32'], + ['timeScale', 'int32'], + ['duration', 'int32'], + ['language', 'int16'], + ['quality', 'int16'], + ], + }, - mediaInfo: [ - 'minf', - AtomPtr('videoMediaInfo', 'video'), - AtomPtr('sampleTable', 'sample'), - ], + mediaInfo: { + cc4: 'minf', + atoms: [ + ['sound', '*soundMediaInfo'], + ['video', '*videoMediaInfo'], + ['sample', '*sampleTable'], + ], + }, - videoMediaInfo: [ - 'vmhd', - Int('version', 1), - Int('flags', 3), - Int('graphicsMode', 2), - Arr('opcolor', Int('', 2), 3), - ], + soundMediaInfo: { + cc4: 'smhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['balance', 'int16'], + ['_', 'int16'], + ], + }, - sampleTable: [ - 'stbl', - AtomPtr('sampleDesc', 'sampleDesc'), - AtomPtr('timeToSample', 'timeToSample'), - AtomPtr('compositionOffset', 'compositionOffset'), - AtomPtr('syncSample', 'syncSample'), - AtomPtr('sampleSize', 'sampleSize'), - AtomPtr('chunkOffset', 'chunkOffset'), - ], + videoMediaInfo: { + cc4: 'vmhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['graphicsMode', 'int16'], + ['opcolor', '[3]int16'], + ], + }, - sampleDesc: [ - 'stsd', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('sampleDescEntry')), - ], + sampleTable: { + cc4: 'stbl', + atoms: [ + ['sampleDesc', '*sampleDesc'], + ['timeToSample', '*timeToSample'], + ['compositionOffset', '*compositionOffset'], + ['sampleToChunk', '*sampleToChunk'], + ['syncSample', '*syncSample'], + ['chunkOffset', '*chunkOffset'], + ['sampleSize', '*sampleSize'], + ], + }, - timeToSample: [ - 'stts', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('timeToSampleEntry')), - ], + sampleDesc: { + cc4: 'stsd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]*sampleDescEntry'], + ], + }, - compositionOffset: [ - 'ctts', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('compositionOffsetEntry')), - ], + timeToSample: { + cc4: 'stts', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]timeToSampleEntry'], + ], + }, - syncSample: [ - 'stss', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], + timeToSampleEntry: { + fields: [ + ['count', 'int32'], + ['duration', 'int32'], + ], + }, - sampleSize: [ - 'stsz', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], + sampleToChunk: { + cc4: 'stsc', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]sampleToChunkEntry'], + ], + }, + + sampleToChunkEntry: { + fields: [ + ['firstChunk', 'int32'], + ['samplesPerChunk', 'int32'], + ['sampleDescId', 'int32'], + ], + }, + + compositionOffset: { + cc4: 'ctts', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, + + compositionOffsetEntry: { + fields: [ + ['count', 'int32'], + ['offset', 'int32'], + ], + }, + + syncSample: { + cc4: 'stss', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, + + sampleSize: { + cc4: 'stsz', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, + + chunkOffset: { + cc4: 'stco', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, - chunkOffset: [ - 'stco', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], }; -var structs = { - sampleDescEntry: [ - Size(4), - Str('format', 4), - _(6), - Int('dataRefIdx', 2), - BytesLeft('data'), - ], - - timeToSampleEntry: [ - Int('count', 4), - Int('duration', 4), - ], - - compositionOffsetEntry: [ - Int('count', 4), - Int('offset', 4), - ], -}; - -var genReadStmts = (opts) => { +var DeclReadFunc = (opts) => { var stmts = []; - if (opts.resIsPtr) - stmts = stmts.concat([StrStmt(`self := &${opts.atomType}{}`)]); + var DebugStmt = type => StrStmt(`// ${JSON.stringify(type)}`); - var readElemStmts = field => { - var arr = 'self.'+field.name; + var ReadArr = (name, type) => { return [ - DeclVar('item', field.elem.type), - CallCheckAssign('Read'+field.elem.fn, ['r', field.elem.len].nonull(), ['item']), - StrStmt(`${arr} = append(${arr}, item)`), + //StrStmt('// ReadArr'), + //DebugStmt(type), + type.varcount && [ + DeclVar('count', 'int'), + CallCheckAssign('ReadInt', ['r', type.varcount], ['count']), + StrStmt(`${name} = make(${typeStr(type)}, count)`), + ], + For(RangeN('i', type.varcount ? 'count' : type.count), [ + ReadCommnType(name+'[i]', type), + ]), + ]; + }; + + var elemTypeStr = type => typeStr(Object.assign({}, type, {arr: false})); + var ReadAtoms = () => [ + StrStmt(`// ReadAtoms`), + For(StrStmt(`r.N > 0`), [ + DeclVar('cc4', 'string'), + DeclVar('ar', '*io.LimitedReader'), + CallCheckAssign('ReadAtomHeader', ['r', '""'], ['ar', 'cc4']), + Switch('cc4', opts.fields.map(field => [ + `"${atoms[field.type.struct].cc4}"`, [ + field.type.arr ? [ + DeclVar('item', elemTypeStr(field.type)), + CallCheckAssign('Read'+field.type.Struct, ['ar'], ['item']), + StrStmt(`self.${field.name} = append(self.${field.name}, item)`), + ] : [ + CallCheckAssign('Read'+field.type.Struct, ['ar'], [`self.${field.name}`]), + ], + ] + ]), showlog && [StrStmt(`log.Println("skip", cc4)`)]), + CallCheckAssign('ReadDummy', ['ar', 'int(ar.N)'], ['_']), + ]) + ]; + + var ReadCommnType = (name, type) => { + if (type.struct) + return CallCheckAssign( + 'Read'+type.Struct, ['r'], [name]); + return [ + //DebugStmt(type), + CallCheckAssign( + 'Read'+type.fn, ['r', type.len].nonull(), [name]), ] }; - stmts = stmts.concat(opts.fields.map(field => { - if (field.sizelen) { - var arr = 'self.'+field.name; - return [ - DeclVar('count', 'int'), - CallCheckAssign('ReadInt', ['r', field.sizelen], ['count']), - For(RangeN('i', 'count'), readElemStmts(field)), - ]; - } else if (field.elem) { - var cond = field.count ? RangeN('i', field.count) : StrStmt('r.N > 0'); - return For(cond, readElemStmts(field)); - } else if (!field.hide) { - return CallCheckAssign('Read'+field.fn, ['r', field.len].nonull(), ['self.'+field.name]); - } - }).nonull()); + var ReadField = (name, type) => { + if (name == '_') + return CallCheckAssign('ReadDummy', ['r', type.len], ['_']); + if (type.arr && type.fn != 'Bytes') + return ReadArr('self.'+name, type); + return ReadCommnType('self.'+name, type); + }; - if (opts.resIsPtr) - stmts = stmts.concat([StrStmt(`res = self`)]); + var ReadFields = () => opts.fields.map(field => { + var name = field.name; + var type = field.type; + return ReadField(name, type); + }).nonull(); + + var ptr = opts.cc4; return Func( - 'Read'+opts.fnName, + 'Read'+opts.type, [['r', '*io.LimitedReader']], - [[opts.resIsPtr?'res':'self', (opts.resIsPtr?'*':'')+opts.atomType], ['err', 'error']], - stmts + [[ptr?'res':'self', (ptr?'*':'')+opts.type], ['err', 'error']], + [ + ptr && StrStmt(`self := &${opts.type}{}`), + !opts.atoms ? ReadFields() : ReadAtoms(), + ptr && StrStmt(`res = self`), + ] ); }; @@ -237,16 +312,21 @@ var D = (cls, ...fields) => { }; D('Func', 'name', 'args', 'rets', 'body'); -D('CallCheckAssign', 'fn', 'args', 'rets'); +D('CallCheckAssign', 'fn', 'args', 'rets', 'action'); D('DeclVar', 'name', 'type'); D('For', 'cond', 'body'); D('RangeN', 'i', 'n'); D('DeclStruct', 'name', 'body'); D('StrStmt', 'content'); +D('Switch', 'cond', 'cases', 'default'); + +var showlog = false; +var S = s => s && s || ''; var dumpFn = f => { var dumpArgs = x => x.map(x => x.join(' ')).join(','); return `func ${f.name}(${dumpArgs(f.args)}) (${dumpArgs(f.rets)}) { + ${S(showlog && 'log.Println("'+f.name+'")')} ${dumpStmts(f.body)} return }`; @@ -258,7 +338,7 @@ var dumpStmts = stmts => { return dumpStmts(stmt); } if (stmt.cls == 'CallCheckAssign') { return `if ${stmt.rets.concat(['err']).join(',')} = ${stmt.fn}(${stmt.args.join(',')}); err != nil { - return + ${stmt.action ? stmt.action : 'return'} }`; } else if (stmt.cls == 'DeclVar') { return `var ${stmt.name} ${stmt.type}`; @@ -276,68 +356,103 @@ var dumpStmts = stmts => { return dumpFn(stmt); } else if (stmt.cls == 'StrStmt') { return stmt.content; + } else if (stmt.cls == 'Switch') { + var dumpCase = c => `case ${c[0]}: { ${dumpStmts(c[1])} }`; + var dumpDefault = c => `default: { ${dumpStmts(c)} }`; + return `switch ${stmt.cond} { + ${stmt.cases.map(dumpCase).join('\n')} + ${stmt.default && dumpDefault(stmt.default) || ''} + }`; } }; - return stmts.map(dumpStmt).join('\n') + return stmts.nonull().map(dumpStmt).join('\n') }; -(() => { - var len = 3; - var f = Func('Readxx', [['f', '*io.LimitedReader']], [['res', '*xx'], ['err', 'error']], [ - CallCheckAssign('ReadInt', ['f', len], ['self.xx']), - CallCheckAssign('WriteInt', ['f', len], ['self.xx']), - DeclVar('n', 'int'), - For(RangeN('i', 'n'), [ - CallCheckAssign('WriteInt', ['f', len], ['self.xx']), - DeclStruct('hi', [['a', 'b'], ['c', 'd'], ['e', 'f']]), - ]), - ]); - console.log(dumpFn(f)); -}); +var parseType = s => { + var r = {}; + var bracket = /^\[(.*)\]/; + if (s.match(bracket)) { + var count = s.match(bracket)[1]; + if (count.substr(0,3) == 'int') { + r.varcount = +count.substr(3)/8; + } else { + r.count = +count; + } + r.arr = true; + s = s.replace(bracket, ''); + } + if (s.substr(0,1) == '*') { + r.ptr = true; + s = s.slice(1); + } + var types = /^(int|TimeStamp|byte|cc)/; + if (s.match(types)) { + r.type = s.match(types)[0]; + r.fn = uc(r.type); + s = s.replace(types, ''); + } + if (r.type == 'byte' && r.arr) { + r.len = r.count; + r.fn = 'Bytes'; + } + var lenDiv = 8; + if (r.type == 'cc') { + r.fn = 'String'; + r.type = 'string'; + lenDiv = 1; + } + var number = /[0-9]+/; + if (s.match(number)) { + r.len = +s.match(number)[0]/lenDiv; + s = s.replace(number, ''); + } + if (s != '') { + r.struct = s; + r.Struct = uc(s); + } + return r; +}; + +var typeStr = (t) => { + var s = ''; + if (t.arr) + s += '['+(t.count||'')+']'; + if (t.ptr) + s += '*'; + if (t.struct) + s += t.Struct; + if (t.type) + s += t.type; + return s; +}; + +var nameShouldHide = (name) => name == '_' var allStmts = () => { var stmts = []; - var convStructFields = fields => { - var typeStr = field => ( - field.cls == 'AtomPtr' || field.cls == 'StructPtr') ? '*'+field.type : field.type; - - return fields.filter(field => !field.hide) - .map(field => { - if (field.cls == 'Arr' || field.cls == 'LenArr') - return [field.name, '[]'+typeStr(field.elem)]; - return [field.name, typeStr(field)]; - }); - }; - for (var k in atoms) { - var list = atoms[k]; - var name = uc(k)+'Atom'; - var cc4 = list[0]; - var fields = list.slice(1); + var atom = atoms[k]; - stmts = stmts.concat([ - DeclStruct(name, convStructFields(fields)), - genReadStmts({ - cc4: cc4, - fields: fields, - fnName: name, - atomType: name, - resIsPtr: true, - }), - ]); - } - - for (var k in structs) { - var fields = structs[k]; var name = uc(k); + var fields = (atom.fields || atom.atoms).map(field => { + return { + name: uc(field[0]), + type: parseType(field[1]), + }; + }); stmts = stmts.concat([ - DeclStruct(name, convStructFields(fields)), - genReadStmts({ + DeclStruct(name, fields.map(field => !nameShouldHide(field.name) && [ + uc(field.name), + typeStr(field.type), + ]).nonull()), + + DeclReadFunc({ + type: name, fields: fields, - fnName: name, - atomType: name, + cc4: atom.cc4, + atoms: atom.atoms != null, }), ]); } @@ -345,10 +460,12 @@ var allStmts = () => { return stmts; }; -console.log(`// THIS FILE IS AUTO GENERATED +console.log(` +// THIS FILE IS AUTO GENERATED package atom import ( "io" + ${showlog && '"log"' || ''} ) `, dumpStmts(allStmts())); diff --git a/atom/reader.go b/atom/reader.go index 4af4a3a..ec6edcf 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -4,6 +4,7 @@ package atom import ( "io" "io/ioutil" + "log" ) type Fixed32 uint32 @@ -17,10 +18,6 @@ func ReadBytes(r io.Reader, n int) (res []byte, err error) { return } -func ReadBytesLeft(r *io.LimitedReader) (res []byte, err error) { - return ReadBytes(r, int(r.N)) -} - func ReadUInt(r io.Reader, n int) (res uint, err error) { var b []byte if b, err = ReadBytes(r, n); err != nil { @@ -74,42 +71,38 @@ func ReadDummy(r io.Reader, n int) (res int, err error) { return } -/* -func (self Reader) ReadAtom(atom Atom) (res Atom, err error) { +func ReadAtomHeader(r io.Reader, targetCC4 string) (res *io.LimitedReader, cc4 string, err error) { for { var size int - if size, err = self.ReadInt(4); err != nil { + if size, err = ReadInt(r, 4); err != nil { return } if size == 0 { continue } - var cc4 string - if cc4, err = self.ReadString(4); err != nil { + if cc4, err = ReadString(r, 4); err != nil { return } - if atom.CC4() != cc4 { - if err = self.Skip(size); err != nil { + size = size - 8 + + if false { + log.Println(cc4, targetCC4, size, cc4 == targetCC4) + } + + if targetCC4 != "" && cc4 != targetCC4 { + log.Println("ReadAtomHeader skip:", cc4) + if _, err = ReadDummy(r, size); err != nil { return } continue } - reader := &io.LimitedReader{ - R: self.Reader, - N: int64(size - 8), + res = &io.LimitedReader{ + R: r, + N: int64(size), } - if err = atom.Read(Reader{reader}); err != nil { - return - } - if err = self.Skip(int(reader.N)); err != nil { - return - } - - res = atom return } } -*/ diff --git a/atom/struct.go b/atom/struct.go index f0a240f..07b80a5 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -5,50 +5,67 @@ import ( "io" ) -type FileTypeAtom struct { - Movie *MovieAtom +type FileType struct { } -func ReadFileTypeAtom(r *io.LimitedReader) (res *FileTypeAtom, err error) { - self := &FileTypeAtom{} - if self.Movie, err = ReadMovieAtom(r); err != nil { - return - } +func ReadFileType(r *io.LimitedReader) (res *FileType, err error) { + + self := &FileType{} + res = self return } -type MovieAtom struct { - Header *MovieHeaderAtom - Tracks []*TrackAtom +type Movie struct { + Header *MovieHeader + Tracks []*Track } -func ReadMovieAtom(r *io.LimitedReader) (res *MovieAtom, err error) { - self := &MovieAtom{} - if self.Header, err = ReadMovieHeaderAtom(r); err != nil { - return - } +func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { + + self := &Movie{} + // ReadAtoms for r.N > 0 { - var item *TrackAtom - if item, err = ReadTrackAtom(r); err != nil { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "mvhd": + { + if self.Header, err = ReadMovieHeader(ar); err != nil { + return + } + } + case "trak": + { + var item *Track + if item, err = ReadTrack(ar); err != nil { + return + } + self.Tracks = append(self.Tracks, item) + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { return } - self.Tracks = append(self.Tracks, item) } res = self return } -type MovieHeaderAtom struct { +type MovieHeader struct { Version int Flags int CTime TimeStamp MTime TimeStamp - TimeScale int - Duration int + TimeScale TimeStamp + Duration TimeStamp PreferredRate int PreferredVolume int - Matrix []byte + Matrix [9]int PreviewTime TimeStamp PreviewDuration TimeStamp PosterTime TimeStamp @@ -58,8 +75,9 @@ type MovieHeaderAtom struct { NextTrackId int } -func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) { - self := &MovieHeaderAtom{} +func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { + + self := &MovieHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -72,10 +90,10 @@ func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) if self.MTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.TimeScale, err = ReadInt(r, 4); err != nil { + if self.TimeScale, err = ReadTimeStamp(r, 4); err != nil { return } - if self.Duration, err = ReadInt(r, 4); err != nil { + if self.Duration, err = ReadTimeStamp(r, 4); err != nil { return } if self.PreferredRate, err = ReadInt(r, 4); err != nil { @@ -84,9 +102,14 @@ func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) if self.PreferredVolume, err = ReadInt(r, 2); err != nil { return } - if self.Matrix, err = ReadBytes(r, 36); err != nil { + if _, err = ReadDummy(r, 10); err != nil { return } + for i := 0; i < 9; i++ { + if self.Matrix[i], err = ReadInt(r, 4); err != nil { + return + } + } if self.PreviewTime, err = ReadTimeStamp(r, 4); err != nil { return } @@ -112,40 +135,62 @@ func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) return } -type TrackAtom struct { - Header *TrackHeaderAtom - Media *MediaAtom +type Track struct { + Header *TrackHeader + Media *Media } -func ReadTrackAtom(r *io.LimitedReader) (res *TrackAtom, err error) { - self := &TrackAtom{} - if self.Header, err = ReadTrackHeaderAtom(r); err != nil { - return - } - if self.Media, err = ReadMediaAtom(r); err != nil { - return +func ReadTrack(r *io.LimitedReader) (res *Track, err error) { + + self := &Track{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "tkhd": + { + if self.Header, err = ReadTrackHeader(ar); err != nil { + return + } + } + case "mdia": + { + if self.Media, err = ReadMedia(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } } res = self return } -type TrackHeaderAtom struct { +type TrackHeader struct { Version int Flags int CTime TimeStamp MTime TimeStamp - TrackId int - Duration int + TrackId TimeStamp + Duration TimeStamp Layer int AlternateGroup int Volume int - Matrix []byte - TrackWidth Fixed32 - TrackHeight Fixed32 + Matrix [9]int + TrackWidth int + TrackHeader int } -func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) { - self := &TrackHeaderAtom{} +func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { + + self := &TrackHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -158,10 +203,16 @@ func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) if self.MTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.TrackId, err = ReadInt(r, 4); err != nil { + if self.TrackId, err = ReadTimeStamp(r, 4); err != nil { return } - if self.Duration, err = ReadInt(r, 4); err != nil { + if _, err = ReadDummy(r, 4); err != nil { + return + } + if self.Duration, err = ReadTimeStamp(r, 4); err != nil { + return + } + if _, err = ReadDummy(r, 8); err != nil { return } if self.Layer, err = ReadInt(r, 2); err != nil { @@ -173,59 +224,86 @@ func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) if self.Volume, err = ReadInt(r, 2); err != nil { return } - if self.Matrix, err = ReadBytes(r, 36); err != nil { + if _, err = ReadDummy(r, 2); err != nil { return } - if self.TrackWidth, err = ReadFixed32(r, 4); err != nil { + for i := 0; i < 9; i++ { + if self.Matrix[i], err = ReadInt(r, 4); err != nil { + return + } + } + if self.TrackWidth, err = ReadInt(r, 4); err != nil { return } - if self.TrackHeight, err = ReadFixed32(r, 4); err != nil { + if self.TrackHeader, err = ReadInt(r, 4); err != nil { return } res = self return } -type MediaAtom struct { - Header *MediaHeaderAtom - Info *MediaInfoAtom +type Media struct { + Header *MediaHeader + Info *MediaInfo } -func ReadMediaAtom(r *io.LimitedReader) (res *MediaAtom, err error) { - self := &MediaAtom{} - if self.Header, err = ReadMediaHeaderAtom(r); err != nil { - return - } - if self.Info, err = ReadMediaInfoAtom(r); err != nil { - return +func ReadMedia(r *io.LimitedReader) (res *Media, err error) { + + self := &Media{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "mdhd": + { + if self.Header, err = ReadMediaHeader(ar); err != nil { + return + } + } + case "minf": + { + if self.Info, err = ReadMediaInfo(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } } res = self return } -type MediaHeaderAtom struct { +type MediaHeader struct { Version int Flags int - CTime TimeStamp - MTime TimeStamp + CTime int + MTime int TimeScale int Duration int Language int Quality int } -func ReadMediaHeaderAtom(r *io.LimitedReader) (res *MediaHeaderAtom, err error) { - self := &MediaHeaderAtom{} +func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { + + self := &MediaHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return } if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + if self.CTime, err = ReadInt(r, 4); err != nil { return } - if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + if self.MTime, err = ReadInt(r, 4); err != nil { return } if self.TimeScale, err = ReadInt(r, 4); err != nil { @@ -244,32 +322,86 @@ func ReadMediaHeaderAtom(r *io.LimitedReader) (res *MediaHeaderAtom, err error) return } -type MediaInfoAtom struct { - Video *VideoMediaInfoAtom - Sample *SampleTableAtom +type MediaInfo struct { + Sound *SoundMediaInfo + Video *VideoMediaInfo + Sample *SampleTable } -func ReadMediaInfoAtom(r *io.LimitedReader) (res *MediaInfoAtom, err error) { - self := &MediaInfoAtom{} - if self.Video, err = ReadVideoMediaInfoAtom(r); err != nil { +func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { + + self := &MediaInfo{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "smhd": + { + if self.Sound, err = ReadSoundMediaInfo(ar); err != nil { + return + } + } + case "vmhd": + { + if self.Video, err = ReadVideoMediaInfo(ar); err != nil { + return + } + } + case "stbl": + { + if self.Sample, err = ReadSampleTable(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} + +type SoundMediaInfo struct { + Version int + Flags int + Balance int +} + +func ReadSoundMediaInfo(r *io.LimitedReader) (res *SoundMediaInfo, err error) { + + self := &SoundMediaInfo{} + if self.Version, err = ReadInt(r, 1); err != nil { return } - if self.Sample, err = ReadSampleTableAtom(r); err != nil { + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.Balance, err = ReadInt(r, 2); err != nil { + return + } + if _, err = ReadDummy(r, 2); err != nil { return } res = self return } -type VideoMediaInfoAtom struct { +type VideoMediaInfo struct { Version int Flags int GraphicsMode int - Opcolor []int + Opcolor [3]int } -func ReadVideoMediaInfoAtom(r *io.LimitedReader) (res *VideoMediaInfoAtom, err error) { - self := &VideoMediaInfoAtom{} +func ReadVideoMediaInfo(r *io.LimitedReader) (res *VideoMediaInfo, err error) { + + self := &VideoMediaInfo{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -280,57 +412,96 @@ func ReadVideoMediaInfoAtom(r *io.LimitedReader) (res *VideoMediaInfoAtom, err e return } for i := 0; i < 3; i++ { - var item int - if item, err = ReadInt(r, 2); err != nil { + if self.Opcolor[i], err = ReadInt(r, 2); err != nil { return } - self.Opcolor = append(self.Opcolor, item) } res = self return } -type SampleTableAtom struct { - SampleDesc *SampleDescAtom - TimeToSample *TimeToSampleAtom - CompositionOffset *CompositionOffsetAtom - SyncSample *SyncSampleAtom - SampleSize *SampleSizeAtom - ChunkOffset *ChunkOffsetAtom +type SampleTable struct { + SampleDesc *SampleDesc + TimeToSample *TimeToSample + CompositionOffset *CompositionOffset + SampleToChunk *SampleToChunk + SyncSample *SyncSample + ChunkOffset *ChunkOffset + SampleSize *SampleSize } -func ReadSampleTableAtom(r *io.LimitedReader) (res *SampleTableAtom, err error) { - self := &SampleTableAtom{} - if self.SampleDesc, err = ReadSampleDescAtom(r); err != nil { - return - } - if self.TimeToSample, err = ReadTimeToSampleAtom(r); err != nil { - return - } - if self.CompositionOffset, err = ReadCompositionOffsetAtom(r); err != nil { - return - } - if self.SyncSample, err = ReadSyncSampleAtom(r); err != nil { - return - } - if self.SampleSize, err = ReadSampleSizeAtom(r); err != nil { - return - } - if self.ChunkOffset, err = ReadChunkOffsetAtom(r); err != nil { - return +func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { + + self := &SampleTable{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "stsd": + { + if self.SampleDesc, err = ReadSampleDesc(ar); err != nil { + return + } + } + case "stts": + { + if self.TimeToSample, err = ReadTimeToSample(ar); err != nil { + return + } + } + case "ctts": + { + if self.CompositionOffset, err = ReadCompositionOffset(ar); err != nil { + return + } + } + case "stsc": + { + if self.SampleToChunk, err = ReadSampleToChunk(ar); err != nil { + return + } + } + case "stss": + { + if self.SyncSample, err = ReadSyncSample(ar); err != nil { + return + } + } + case "stco": + { + if self.ChunkOffset, err = ReadChunkOffset(ar); err != nil { + return + } + } + case "stsz": + { + if self.SampleSize, err = ReadSampleSize(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } } res = self return } -type SampleDescAtom struct { +type SampleDesc struct { Version int Flags int - Entries []SampleDescEntry + Entries []*SampleDescEntry } -func ReadSampleDescAtom(r *io.LimitedReader) (res *SampleDescAtom, err error) { - self := &SampleDescAtom{} +func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { + + self := &SampleDesc{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -341,25 +512,25 @@ func ReadSampleDescAtom(r *io.LimitedReader) (res *SampleDescAtom, err error) { if count, err = ReadInt(r, 4); err != nil { return } + self.Entries = make([]*SampleDescEntry, count) for i := 0; i < count; i++ { - var item SampleDescEntry - if item, err = ReadSampleDescEntry(r); err != nil { + if self.Entries[i], err = ReadSampleDescEntry(r); err != nil { return } - self.Entries = append(self.Entries, item) } res = self return } -type TimeToSampleAtom struct { +type TimeToSample struct { Version int Flags int Entries []TimeToSampleEntry } -func ReadTimeToSampleAtom(r *io.LimitedReader) (res *TimeToSampleAtom, err error) { - self := &TimeToSampleAtom{} +func ReadTimeToSample(r *io.LimitedReader) (res *TimeToSample, err error) { + + self := &TimeToSample{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -370,158 +541,23 @@ func ReadTimeToSampleAtom(r *io.LimitedReader) (res *TimeToSampleAtom, err error if count, err = ReadInt(r, 4); err != nil { return } + self.Entries = make([]TimeToSampleEntry, count) for i := 0; i < count; i++ { - var item TimeToSampleEntry - if item, err = ReadTimeToSampleEntry(r); err != nil { + if self.Entries[i], err = ReadTimeToSampleEntry(r); err != nil { return } - self.Entries = append(self.Entries, item) } res = self return } -type CompositionOffsetAtom struct { - Version int - Flags int - Entries []CompositionOffsetEntry -} - -func ReadCompositionOffsetAtom(r *io.LimitedReader) (res *CompositionOffsetAtom, err error) { - self := &CompositionOffsetAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item CompositionOffsetEntry - if item, err = ReadCompositionOffsetEntry(r); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type SyncSampleAtom struct { - Version int - Flags int - Entries []int -} - -func ReadSyncSampleAtom(r *io.LimitedReader) (res *SyncSampleAtom, err error) { - self := &SyncSampleAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item int - if item, err = ReadInt(r, 4); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type SampleSizeAtom struct { - Version int - Flags int - Entries []int -} - -func ReadSampleSizeAtom(r *io.LimitedReader) (res *SampleSizeAtom, err error) { - self := &SampleSizeAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item int - if item, err = ReadInt(r, 4); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type ChunkOffsetAtom struct { - Version int - Flags int - Entries []int -} - -func ReadChunkOffsetAtom(r *io.LimitedReader) (res *ChunkOffsetAtom, err error) { - self := &ChunkOffsetAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item int - if item, err = ReadInt(r, 4); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type SampleDescEntry struct { - Format string - DataRefIdx int - Data []byte -} - -func ReadSampleDescEntry(r *io.LimitedReader) (self SampleDescEntry, err error) { - if self.Format, err = ReadString(r, 4); err != nil { - return - } - if self.DataRefIdx, err = ReadInt(r, 2); err != nil { - return - } - if self.Data, err = ReadBytesLeft(r); err != nil { - return - } - return -} - type TimeToSampleEntry struct { Count int Duration int } func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err error) { + if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -531,12 +567,91 @@ func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err err return } +type SampleToChunk struct { + Version int + Flags int + Entries []SampleToChunkEntry +} + +func ReadSampleToChunk(r *io.LimitedReader) (res *SampleToChunk, err error) { + + self := &SampleToChunk{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]SampleToChunkEntry, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadSampleToChunkEntry(r); err != nil { + return + } + } + res = self + return +} + +type SampleToChunkEntry struct { + FirstChunk int + SamplesPerChunk int + SampleDescId int +} + +func ReadSampleToChunkEntry(r *io.LimitedReader) (self SampleToChunkEntry, err error) { + + if self.FirstChunk, err = ReadInt(r, 4); err != nil { + return + } + if self.SamplesPerChunk, err = ReadInt(r, 4); err != nil { + return + } + if self.SampleDescId, err = ReadInt(r, 4); err != nil { + return + } + return +} + +type CompositionOffset struct { + Version int + Flags int + Entries []int +} + +func ReadCompositionOffset(r *io.LimitedReader) (res *CompositionOffset, err error) { + + self := &CompositionOffset{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} + type CompositionOffsetEntry struct { Count int Offset int } func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntry, err error) { + if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -545,3 +660,90 @@ func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntr } return } + +type SyncSample struct { + Version int + Flags int + Entries []int +} + +func ReadSyncSample(r *io.LimitedReader) (res *SyncSample, err error) { + + self := &SyncSample{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} + +type SampleSize struct { + Version int + Flags int + Entries []int +} + +func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { + + self := &SampleSize{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} + +type ChunkOffset struct { + Version int + Flags int + Entries []int +} + +func ReadChunkOffset(r *io.LimitedReader) (res *ChunkOffset, err error) { + + self := &ChunkOffset{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} diff --git a/example/read.go b/example/read.go new file mode 100644 index 0000000..56d07c1 --- /dev/null +++ b/example/read.go @@ -0,0 +1,15 @@ + +package main + +import ( + mp4 "./.." + "log" +) + +func main() { + if _, err := mp4.Open("tiny2-avconv.mp4"); err != nil { + log.Println(err) + return + } +} + diff --git a/mp4.go b/mp4.go index 347c4ca..73bfb3d 100644 --- a/mp4.go +++ b/mp4.go @@ -4,11 +4,11 @@ package mp4 import ( "./atom" "os" + "io" + "log" ) type File struct { - moov *atom.Moov - ftyp *atom.Ftyp } func (self *File) AddAvcc(avcc *Avcc) { @@ -37,19 +37,34 @@ func Open(filename string) (file *File, err error) { return } - var entry atom.Atom - file = &File{} - r := atom.Reader{osfile} - - if entry, err = r.ReadAtom(&atom.Ftyp{}); err != nil { + var finfo os.FileInfo + if finfo, err = osfile.Stat(); err != nil { return } - file.ftyp = entry.(*atom.Ftyp) + log.Println("filesize", finfo.Size()) - if entry, err = r.ReadAtom(&atom.Moov{}); err != nil { + lr := &io.LimitedReader{R: osfile, N: finfo.Size()} + + for lr.N > 0 { + var r *io.LimitedReader + var cc4 string + if r, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + return + } + if cc4 == "moov" { + var moov *atom.Movie + if moov, err = atom.ReadMovie(r); err != nil { + return + } + log.Println("tracks nr", len(moov.Tracks)) + } + log.Println("atom", cc4, "left", lr.N) + atom.ReadDummy(r, int(r.N)) + } + + if _, err = os.Create(filename+".out.mp4"); err != nil { return } - file.moov = entry.(*atom.Moov) return }