+ public function ParseRIFFAMV($startoffset, $maxoffset) {
+ // AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size
+
+ // https://code.google.com/p/amv-codec-tools/wiki/AmvDocumentation
+ //typedef struct _amvmainheader {
+ //FOURCC fcc; // 'amvh'
+ //DWORD cb;
+ //DWORD dwMicroSecPerFrame;
+ //BYTE reserve[28];
+ //DWORD dwWidth;
+ //DWORD dwHeight;
+ //DWORD dwSpeed;
+ //DWORD reserve0;
+ //DWORD reserve1;
+ //BYTE bTimeSec;
+ //BYTE bTimeMin;
+ //WORD wTimeHour;
+ //} AMVMAINHEADER;
+
+ $info = &$this->getid3->info;
+ $RIFFchunk = false;
+
+ try {
+
+ $this->fseek($startoffset);
+ $maxoffset = min($maxoffset, $info['avdataend']);
+ $AMVheader = $this->fread(284);
+ if (substr($AMVheader, 0, 8) != 'hdrlamvh') {
+ throw new Exception('expecting "hdrlamv" at offset '.($startoffset + 0).', found "'.substr($AMVheader, 0, 8).'"');
+ }
+ if (substr($AMVheader, 8, 4) != "\x38\x00\x00\x00") { // "amvh" chunk size, hardcoded to 0x38 = 56 bytes
+ throw new Exception('expecting "0x38000000" at offset '.($startoffset + 8).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 8, 4)).'"');
+ }
+ $RIFFchunk = array();
+ $RIFFchunk['amvh']['us_per_frame'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 12, 4));
+ $RIFFchunk['amvh']['reserved28'] = substr($AMVheader, 16, 28); // null? reserved?
+ $RIFFchunk['amvh']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 44, 4));
+ $RIFFchunk['amvh']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 48, 4));
+ $RIFFchunk['amvh']['frame_rate_int'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 52, 4));
+ $RIFFchunk['amvh']['reserved0'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 56, 4)); // 1? reserved?
+ $RIFFchunk['amvh']['reserved1'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 60, 4)); // 0? reserved?
+ $RIFFchunk['amvh']['runtime_sec'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 64, 1));
+ $RIFFchunk['amvh']['runtime_min'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 65, 1));
+ $RIFFchunk['amvh']['runtime_hrs'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 66, 2));
+
+ $info['video']['frame_rate'] = 1000000 / $RIFFchunk['amvh']['us_per_frame'];
+ $info['video']['resolution_x'] = $RIFFchunk['amvh']['resolution_x'];
+ $info['video']['resolution_y'] = $RIFFchunk['amvh']['resolution_y'];
+ $info['playtime_seconds'] = ($RIFFchunk['amvh']['runtime_hrs'] * 3600) + ($RIFFchunk['amvh']['runtime_min'] * 60) + $RIFFchunk['amvh']['runtime_sec'];
+
+ // the rest is all hardcoded(?) and does not appear to be useful until you get to audio info at offset 256, even then everything is probably hardcoded
+
+ if (substr($AMVheader, 68, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x38\x00\x00\x00") {
+ throw new Exception('expecting "LIST<0x00000000>strlstrh<0x38000000>" at offset '.($startoffset + 68).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 68, 20)).'"');
+ }
+ // followed by 56 bytes of null: substr($AMVheader, 88, 56) -> 144
+ if (substr($AMVheader, 144, 8) != 'strf'."\x24\x00\x00\x00") {
+ throw new Exception('expecting "strf<0x24000000>" at offset '.($startoffset + 144).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 144, 8)).'"');
+ }
+ // followed by 36 bytes of null: substr($AMVheader, 144, 36) -> 180
+
+ if (substr($AMVheader, 188, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x30\x00\x00\x00") {
+ throw new Exception('expecting "LIST<0x00000000>strlstrh<0x30000000>" at offset '.($startoffset + 188).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 188, 20)).'"');
+ }
+ // followed by 48 bytes of null: substr($AMVheader, 208, 48) -> 256
+ if (substr($AMVheader, 256, 8) != 'strf'."\x14\x00\x00\x00") {
+ throw new Exception('expecting "strf<0x14000000>" at offset '.($startoffset + 256).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 256, 8)).'"');
+ }
+ // followed by 20 bytes of a modified WAVEFORMATEX:
+ // typedef struct {
+ // WORD wFormatTag; //(Fixme: this is equal to PCM's 0x01 format code)
+ // WORD nChannels; //(Fixme: this is always 1)
+ // DWORD nSamplesPerSec; //(Fixme: for all known sample files this is equal to 22050)
+ // DWORD nAvgBytesPerSec; //(Fixme: for all known sample files this is equal to 44100)
+ // WORD nBlockAlign; //(Fixme: this seems to be 2 in AMV files, is this correct ?)
+ // WORD wBitsPerSample; //(Fixme: this seems to be 16 in AMV files instead of the expected 4)
+ // WORD cbSize; //(Fixme: this seems to be 0 in AMV files)
+ // WORD reserved;
+ // } WAVEFORMATEX;
+ $RIFFchunk['strf']['wformattag'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 264, 2));
+ $RIFFchunk['strf']['nchannels'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 266, 2));
+ $RIFFchunk['strf']['nsamplespersec'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 268, 4));
+ $RIFFchunk['strf']['navgbytespersec'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 272, 4));
+ $RIFFchunk['strf']['nblockalign'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 276, 2));
+ $RIFFchunk['strf']['wbitspersample'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 278, 2));
+ $RIFFchunk['strf']['cbsize'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 280, 2));
+ $RIFFchunk['strf']['reserved'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 282, 2));
+
+
+ $info['audio']['lossless'] = false;
+ $info['audio']['sample_rate'] = $RIFFchunk['strf']['nsamplespersec'];
+ $info['audio']['channels'] = $RIFFchunk['strf']['nchannels'];
+ $info['audio']['bits_per_sample'] = $RIFFchunk['strf']['wbitspersample'];
+ $info['audio']['bitrate'] = $info['audio']['sample_rate'] * $info['audio']['channels'] * $info['audio']['bits_per_sample'];
+ $info['audio']['bitrate_mode'] = 'cbr';
+
+
+ } catch (getid3_exception $e) {
+ if ($e->getCode() == 10) {
+ $this->warning('RIFFAMV parser: '.$e->getMessage());
+ } else {
+ throw $e;
+ }
+ }
+
+ return $RIFFchunk;
+ }
+
+