wp/wp-includes/ID3/module.audio-video.riff.php
changeset 5 5e2f62d02dcd
parent 0 d970ebf37754
child 7 cf61fcea0001
--- a/wp/wp-includes/ID3/module.audio-video.riff.php	Mon Jun 08 16:11:51 2015 +0000
+++ b/wp/wp-includes/ID3/module.audio-video.riff.php	Tue Jun 09 03:35:32 2015 +0200
@@ -3,6 +3,7 @@
 /// getID3() by James Heinrich <info@getid3.org>               //
 //  available at http://getid3.sourceforge.net                 //
 //            or http://www.getid3.org                         //
+//          also https://github.com/JamesHeinrich/getID3       //
 /////////////////////////////////////////////////////////////////
 // See readme.txt for more details                             //
 /////////////////////////////////////////////////////////////////
@@ -26,8 +27,9 @@
 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true);
 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true);
 
-class getid3_riff extends getid3_handler
-{
+class getid3_riff extends getid3_handler {
+
+	protected $container = 'riff'; // default
 
 	public function Analyze() {
 		$info = &$this->getid3->info;
@@ -58,7 +60,8 @@
 		switch ($RIFFtype) {
 
 			case 'FORM':  // AIFF, AIFC
-				$info['fileformat']   = 'aiff';
+				//$info['fileformat']   = 'aiff';
+				$this->container = 'aiff';
 				$thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize);
 				$thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
 				break;
@@ -66,13 +69,18 @@
 			case 'RIFF':  // AVI, WAV, etc
 			case 'SDSS':  // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com)
 			case 'RMP3':  // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s
-				$info['fileformat']   = 'riff';
+				//$info['fileformat']   = 'riff';
+				$this->container = 'riff';
 				$thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize);
 				if ($RIFFsubtype == 'RMP3') {
 					// RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s
 					$RIFFsubtype = 'WAVE';
 				}
-				$thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
+				if ($RIFFsubtype != 'AMV ') {
+					// 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
+					// Handled separately in ParseRIFFAMV()
+					$thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
+				}
 				if (($info['avdataend'] - $info['filesize']) == 1) {
 					// LiteWave appears to incorrectly *not* pad actual output file
 					// to nearest WORD boundary so may appear to be short by one
@@ -110,16 +118,19 @@
 					$nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size'];
 
 					switch ($nextRIFFheaderID) {
-
 						case 'RIFF':
 							$chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset);
-
 							if (!isset($thisfile_riff[$nextRIFFtype])) {
 								$thisfile_riff[$nextRIFFtype] = array();
 							}
 							$thisfile_riff[$nextRIFFtype][] = $chunkdata;
 							break;
 
+						case 'AMV ':
+							unset($info['riff']);
+							$info['amv'] = $this->ParseRIFFAMV($chunkdata['offset'] + 4, $nextRIFFoffset);
+							break;
+
 						case 'JUNK':
 							// ignore
 							$thisfile_riff[$nextRIFFheaderID][] = $chunkdata;
@@ -152,13 +163,17 @@
 
 			default:
 				$this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead');
-				unset($info['fileformat']);
+				//unset($info['fileformat']);
 				return false;
 		}
 
 		$streamindex = 0;
 		switch ($RIFFsubtype) {
+
+			// http://en.wikipedia.org/wiki/Wav
 			case 'WAVE':
+				$info['fileformat'] = 'wav';
+
 				if (empty($thisfile_audio['bitrate_mode'])) {
 					$thisfile_audio['bitrate_mode'] = 'cbr';
 				}
@@ -588,10 +603,13 @@
 				}
 				break;
 
+			// http://en.wikipedia.org/wiki/Audio_Video_Interleave
 			case 'AVI ':
+				$info['fileformat'] = 'avi';
+				$info['mime_type']  = 'video/avi';
+
 				$thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably
 				$thisfile_video['dataformat']   = 'avi';
-				$info['mime_type']      = 'video/avi';
 
 				if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) {
 					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8;
@@ -825,7 +843,7 @@
 
 											switch ($strhfccType) {
 												case 'vids':
-													$thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff'));
+													$thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($this->container == 'riff'));
 													$thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'];
 
 													if ($thisfile_riff_video_current['codec'] == 'DV') {
@@ -875,11 +893,28 @@
 				}
 				break;
 
+
+			case 'AMV ':
+				$info['fileformat'] = 'amv';
+				$info['mime_type']  = 'video/amv';
+
+				$thisfile_video['bitrate_mode']    = 'vbr'; // it's MJPEG, presumably contant-quality encoding, thereby VBR
+				$thisfile_video['dataformat']      = 'mjpeg';
+				$thisfile_video['codec']           = 'mjpeg';
+				$thisfile_video['lossless']        = false;
+				$thisfile_video['bits_per_sample'] = 24;
+
+				$thisfile_audio['dataformat']   = 'adpcm';
+				$thisfile_audio['lossless']     = false;
+				break;
+
+
+			// http://en.wikipedia.org/wiki/CD-DA
 			case 'CDDA':
-				$thisfile_audio['bitrate_mode'] = 'cbr';
+				$info['fileformat'] = 'cda';
+			    unset($info['mime_type']);
+
 				$thisfile_audio_dataformat      = 'cda';
-				$thisfile_audio['lossless']     = true;
-				unset($info['mime_type']);
 
 				$info['avdataoffset'] = 44;
 
@@ -901,6 +936,7 @@
 					$info['playtime_seconds']                 = $thisfile_riff_CDDA_fmt_0['playtime_seconds'];
 
 					// hardcoded data for CD-audio
+					$thisfile_audio['lossless']        = true;
 					$thisfile_audio['sample_rate']     = 44100;
 					$thisfile_audio['channels']        = 2;
 					$thisfile_audio['bits_per_sample'] = 16;
@@ -909,13 +945,15 @@
 				}
 				break;
 
-
+            // http://en.wikipedia.org/wiki/AIFF
 			case 'AIFF':
 			case 'AIFC':
+				$info['fileformat'] = 'aiff';
+				$info['mime_type']  = 'audio/x-aiff';
+
 				$thisfile_audio['bitrate_mode'] = 'cbr';
 				$thisfile_audio_dataformat      = 'aiff';
 				$thisfile_audio['lossless']     = true;
-				$info['mime_type']      = 'audio/x-aiff';
 
 				if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) {
 					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8;
@@ -1028,12 +1066,15 @@
 */
 				break;
 
+			// http://en.wikipedia.org/wiki/8SVX
 			case '8SVX':
+				$info['fileformat'] = '8svx';
+				$info['mime_type']  = 'audio/8svx';
+
 				$thisfile_audio['bitrate_mode']    = 'cbr';
 				$thisfile_audio_dataformat         = '8svx';
 				$thisfile_audio['bits_per_sample'] = 8;
 				$thisfile_audio['channels']        = 1; // overridden below, if need be
-				$info['mime_type']                = 'audio/x-aiff';
 
 				if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) {
 					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8;
@@ -1108,31 +1149,31 @@
 				}
 				break;
 
+			case 'CDXA':
+				$info['fileformat'] = 'vcd'; // Asume Video CD
+				$info['mime_type']  = 'video/mpeg';
 
-			case 'CDXA':
-				$info['mime_type'] = 'video/mpeg';
 				if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) {
-					if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) {
-						$getid3_temp = new getID3();
-						$getid3_temp->openfile($this->getid3->filename);
-						$getid3_mpeg = new getid3_mpeg($getid3_temp);
-						$getid3_mpeg->Analyze();
-						if (empty($getid3_temp->info['error'])) {
-							$info['audio']   = $getid3_temp->info['audio'];
-							$info['video']   = $getid3_temp->info['video'];
-							$info['mpeg']    = $getid3_temp->info['mpeg'];
-							$info['warning'] = $getid3_temp->info['warning'];
-						}
-						unset($getid3_temp, $getid3_mpeg);
+					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true);
+
+					$getid3_temp = new getID3();
+					$getid3_temp->openfile($this->getid3->filename);
+					$getid3_mpeg = new getid3_mpeg($getid3_temp);
+					$getid3_mpeg->Analyze();
+					if (empty($getid3_temp->info['error'])) {
+						$info['audio']   = $getid3_temp->info['audio'];
+						$info['video']   = $getid3_temp->info['video'];
+						$info['mpeg']    = $getid3_temp->info['mpeg'];
+						$info['warning'] = $getid3_temp->info['warning'];
 					}
+					unset($getid3_temp, $getid3_mpeg);
 				}
 				break;
 
 
 			default:
 				$info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead';
-				unset($info['fileformat']);
-				break;
+				//unset($info['fileformat']);
 		}
 
 		switch ($RIFFsubtype) {
@@ -1150,6 +1191,7 @@
 
 				if (isset($thisfile_riff[$RIFFsubtype]['id3 '])) {
 					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
+
 					$getid3_temp = new getID3();
 					$getid3_temp->openfile($this->getid3->filename);
 					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
@@ -1278,6 +1320,115 @@
 		return true;
 	}
 
+	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;
+	}
+
+
 	public function ParseRIFF($startoffset, $maxoffset) {
 		$info = &$this->getid3->info;
 
@@ -1329,7 +1480,7 @@
 											$getid3_temp->openfile($this->getid3->filename);
 											$getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
 											$getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
-											$getid3_mp3 = new getid3_mp3($getid3_temp);
+											$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
 											$getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
 											if (isset($getid3_temp->info['mpeg']['audio'])) {
 												$info['mpeg']['audio']         = $getid3_temp->info['mpeg']['audio'];
@@ -1412,7 +1563,7 @@
 										$getid3_temp->openfile($this->getid3->filename);
 										$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
 										$getid3_temp->info['avdataend']    = $info['avdataend'];
-										$getid3_mp3 = new getid3_mp3($getid3_temp);
+										$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
 										$getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false);
 										if (empty($getid3_temp->info['error'])) {
 											$info['audio'] = $getid3_temp->info['audio'];
@@ -2426,10 +2577,10 @@
 	}
 
 	private function EitherEndian2Int($byteword, $signed=false) {
-		if ($this->getid3->info['fileformat'] == 'riff') {
+		if ($this->container == 'riff') {
 			return getid3_lib::LittleEndian2Int($byteword, $signed);
 		}
 		return getid3_lib::BigEndian2Int($byteword, false, $signed);
 	}
 
-}
+}
\ No newline at end of file