public function Analyze() { $info =& $this->getid3->info; // parse container try { $this->parseEBML($info); } catch (Exception $e) { $info['error'][] = 'EBML parser: ' . $e->getMessage(); } // calculate playtime if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } // extract tags if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } // process tracks if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']); $track_info['default'] = isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true; if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } switch ($trackarray['TrackType']) { case 1: // Video $track_info['resolution_x'] = $trackarray['PixelWidth']; $track_info['resolution_y'] = $trackarray['PixelHeight']; $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); $track_info['display_x'] = isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']; $track_info['display_y'] = isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']; if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'V_MS/VFW/FOURCC': getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, true); $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; /*case 'V_MPEG4/ISO/AVC': $h264['profile'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1)); $h264['level'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1)); $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1)); $h264['NALUlength'] = ($rn & 3) + 1; $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1)); $nsps = ($rn & 31); $offset = 6; for ($i = 0; $i < $nsps; $i ++) { $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); $offset += 2 + $length; } $npps = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1)); $offset += 1; for ($i = 0; $i < $npps; $i ++) { $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); $offset += 2 + $length; } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264; break;*/ } $info['video']['streams'][] = $track_info; break; case 2: // Audio $track_info['sample_rate'] = isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0; $track_info['channels'] = isset($trackarray['Channels']) ? $trackarray['Channels'] : 1; $track_info['language'] = isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'; if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': case 'A_DTS': case 'A_MPEG/L3': case 'A_MPEG/L2': case 'A_FLAC': getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.' . ($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']) . '.php', __FILE__, true); if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '] not set'); break; } // create temp instance $getid3_temp = new getID3(); if ($track_info['dataformat'] != 'flac') { $getid3_temp->openfile($this->getid3->filename); } $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') { $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; } // analyze $class = 'getid3_' . ($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']); $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat']; $getid3_audio = new $class($getid3_temp, __CLASS__); if ($track_info['dataformat'] == 'flac') { $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); } else { $getid3_audio->Analyze(); } if (!empty($getid3_temp->info[$header_data_key])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because ' . $class . '::Analyze() failed at offset ' . $getid3_temp->info['avdataoffset']); } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warning($class . '() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warning($class . '() says: [' . $newerror . ']'); } } unset($getid3_temp, $getid3_audio); break; case 'A_AAC': case 'A_AAC/MPEG2/LC': case 'A_AAC/MPEG2/LC/SBR': case 'A_AAC/MPEG4/LC': case 'A_AAC/MPEG4/LC/SBR': $this->warning($trackarray['CodecID'] . ' audio data contains no header, audio/video bitrates can\'t be calculated'); break; case 'A_VORBIS': if (!isset($trackarray['CodecPrivate'])) { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data not set'); break; } $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); if ($vorbis_offset === false) { $this->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data does not contain "vorbis" keyword'); break; } $vorbis_offset -= 1; getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, true); // create temp instance $getid3_temp = new getID3(); // analyze $getid3_ogg = new getid3_ogg($getid3_temp); $oggpageinfo['page_seqno'] = 0; $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); if (!empty($getid3_temp->info['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; } unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); break; case 'A_MS/ACM': getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, true); $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); foreach ($parsed as $key => $value) { if ($key != 'raw') { $track_info[$key] = $value; } } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; default: $this->warning('Unhandled audio type "' . (isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '') . '"'); break; } $info['audio']['streams'][] = $track_info; break; } } if (!empty($info['video']['streams'])) { $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); } if (!empty($info['audio']['streams'])) { $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); } } // process attachments if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) { foreach ($info['matroska']['attachments'] as $i => $entry) { if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) { $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']); } } } // determine mime type if (!empty($info['video']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } return true; }
function getid3_riff(&$fd, &$ThisFileInfo) { // initialize these values to an empty array, otherwise they default to NULL // and you can't append array values to a NULL value $ThisFileInfo['riff'] = array('raw' => array()); // Shortcuts $thisfile_riff =& $ThisFileInfo['riff']; $thisfile_riff_raw =& $thisfile_riff['raw']; $thisfile_audio =& $ThisFileInfo['audio']; $thisfile_video =& $ThisFileInfo['video']; $thisfile_avdataoffset =& $ThisFileInfo['avdataoffset']; $thisfile_avdataend =& $ThisFileInfo['avdataend']; $thisfile_audio_dataformat =& $thisfile_audio['dataformat']; $thisfile_riff_audio =& $thisfile_riff['audio']; $thisfile_riff_video =& $thisfile_riff['video']; $Original['avdataoffset'] = $thisfile_avdataoffset; $Original['avdataend'] = $thisfile_avdataend; fseek($fd, $thisfile_avdataoffset, SEEK_SET); $RIFFheader = fread($fd, 12); $RIFFsubtype = substr($RIFFheader, 8, 4); switch (substr($RIFFheader, 0, 4)) { case 'FORM': $ThisFileInfo['fileformat'] = 'aiff'; $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); $thisfile_riff['header_size'] = $RIFFheaderSize; break; case 'RIFF': case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) // 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 if ($RIFFsubtype == 'RMP3') { // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s $RIFFsubtype = 'WAVE'; } $ThisFileInfo['fileformat'] = 'riff'; $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); $thisfile_riff['header_size'] = $RIFFheaderSize; $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); fseek($fd, $thisfile_avdataoffset + $RIFFheaderSize); $nextRIFFheader = fread($fd, 20); if (substr($nextRIFFheader, 8, 4) == 'RIFF') { $nextRIFFsize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($nextRIFFheader, 12, 4)); $nextRIFFtype = substr($nextRIFFheader, 16, 4) . '<br>'; $thisfile_riff[$nextRIFFtype]['offset'] = ftell($fd) - 4; $thisfile_riff[$nextRIFFtype]['size'] = $nextRIFFsize; $ThisFileInfo['avdataend'] = $thisfile_riff[$nextRIFFtype]['offset'] + $thisfile_riff[$nextRIFFtype]['size']; $ThisFileInfo['error'][] = 'AVI extends beyond 2GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; $ThisFileInfo['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; $thisfile_riff[$nextRIFFtype] = getid3_riff::ParseRIFF($fd, $thisfile_riff[$nextRIFFtype]['offset'] + 4, $thisfile_riff[$nextRIFFtype]['offset'] + $thisfile_riff[$nextRIFFtype]['size'], $ThisFileInfo); } if ($RIFFsubtype == 'WAVE') { $thisfile_riff_WAVE =& $thisfile_riff['WAVE']; } break; default: $ThisFileInfo['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "' . $RIFFsubtype . '" instead'; unset($ThisFileInfo['fileformat']); return false; break; } $streamindex = 0; switch ($RIFFsubtype) { case 'WAVE': if (empty($thisfile_audio['bitrate_mode'])) { $thisfile_audio['bitrate_mode'] = 'cbr'; } if (empty($thisfile_audio_dataformat)) { $thisfile_audio_dataformat = 'wav'; } if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { $thisfile_avdataoffset = $thisfile_riff_WAVE['data'][0]['offset'] + 8; $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff_WAVE['data'][0]['size']; } if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; if (@$thisfile_riff_audio[$streamindex]['bitrate'] == 0) { $ThisFileInfo['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; return false; } $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; unset($thisfile_riff_audio[$streamindex]['raw']); $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { $ThisFileInfo['warning'][] = 'Audio codec = ' . $thisfile_audio['codec']; } $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; $ThisFileInfo['playtime_seconds'] = (double) (($thisfile_avdataend - $thisfile_avdataoffset) * 8 / $thisfile_audio['bitrate']); $thisfile_audio['lossless'] = false; if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { case 0x1: // PCM $thisfile_audio['lossless'] = true; break; case 0x2000: // AC-3 $thisfile_audio_dataformat = 'ac3'; break; default: // do nothing break; } } $thisfile_audio['streams'][$streamindex]['wformattag'] = $thisfile_audio['wformattag']; $thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; $thisfile_audio['streams'][$streamindex]['lossless'] = $thisfile_audio['lossless']; $thisfile_audio['streams'][$streamindex]['dataformat'] = $thisfile_audio_dataformat; } if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) { // shortcuts $rgadData =& $thisfile_riff_WAVE['rgad'][0]['data']; $thisfile_riff_raw['rgad'] = array('track' => array(), 'album' => array()); $thisfile_riff_raw_rgad =& $thisfile_riff_raw['rgad']; $thisfile_riff_raw_rgad_track =& $thisfile_riff_raw_rgad['track']; $thisfile_riff_raw_rgad_album =& $thisfile_riff_raw_rgad['album']; $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); $thisfile_riff_raw_rgad['nRadioRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 4, 2)); $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 6, 2)); $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); $thisfile_riff_raw_rgad_track['name'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3)); $thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3)); $thisfile_riff_raw_rgad_track['signbit'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1)); $thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9)); $thisfile_riff_raw_rgad_album['name'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3)); $thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3)); $thisfile_riff_raw_rgad_album['signbit'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1)); $thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9)); $thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude']; if ($thisfile_riff_raw_rgad_track['name'] != 0 && $thisfile_riff_raw_rgad_track['originator'] != 0) { $thisfile_riff['rgad']['track']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']); $thisfile_riff['rgad']['track']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']); $thisfile_riff['rgad']['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']); } if ($thisfile_riff_raw_rgad_album['name'] != 0 && $thisfile_riff_raw_rgad_album['originator'] != 0) { $thisfile_riff['rgad']['album']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']); $thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']); $thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']); } } if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { $thisfile_riff_raw['fact']['NumberOfSamples'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); // This should be a good way of calculating exact playtime, // but some sample files have had incorrect number of samples, // so cannot use this method // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { // $ThisFileInfo['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; // } } if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { $thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8); } if (isset($thisfile_riff_WAVE['bext'][0]['data'])) { // shortcut $thisfile_riff_WAVE_bext_0 =& $thisfile_riff_WAVE['bext'][0]; $thisfile_riff_WAVE_bext_0['title'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 0, 256)); $thisfile_riff_WAVE_bext_0['author'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 256, 32)); $thisfile_riff_WAVE_bext_0['reference'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 288, 32)); $thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10); $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); $thisfile_riff_WAVE_bext_0['reserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 347, 254)); $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime((int) substr($thisfile_riff_WAVE_bext_0['origin_time'], 0, 2), (int) substr($thisfile_riff_WAVE_bext_0['origin_time'], 3, 2), (int) substr($thisfile_riff_WAVE_bext_0['origin_time'], 6, 2), (int) substr($thisfile_riff_WAVE_bext_0['origin_date'], 5, 2), (int) substr($thisfile_riff_WAVE_bext_0['origin_date'], 8, 2), (int) substr($thisfile_riff_WAVE_bext_0['origin_date'], 0, 4)); $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; } if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) { // shortcut $thisfile_riff_WAVE_MEXT_0 =& $thisfile_riff_WAVE['MEXT'][0]; $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2)); $thisfile_riff_WAVE_MEXT_0['flags']['homogenous'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x1); if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) { $thisfile_riff_WAVE_MEXT_0['flags']['padding'] = $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x2 ? false : true; $thisfile_riff_WAVE_MEXT_0['flags']['22_or_44'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x4); $thisfile_riff_WAVE_MEXT_0['flags']['free_format'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x8); $thisfile_riff_WAVE_MEXT_0['nominal_frame_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2)); } $thisfile_riff_WAVE_MEXT_0['anciliary_data_length'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2)); $thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2)); $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x1); $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x2); $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x4); } if (isset($thisfile_riff_WAVE['cart'][0]['data'])) { // shortcut $thisfile_riff_WAVE_cart_0 =& $thisfile_riff_WAVE['cart'][0]; $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); for ($i = 0; $i < 8; $i++) { $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] = substr($thisfile_riff_WAVE_cart_0['data'], 684 + $i * 8, 4); $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + $i * 8 + 4, 4)); } $thisfile_riff_WAVE_cart_0['url'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 748, 1024)); $thisfile_riff_WAVE_cart_0['tag_text'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772))); $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; } if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; $ThisFileInfo['playtime_seconds'] = (double) (($thisfile_avdataend - $thisfile_avdataoffset) * 8 / $thisfile_audio['bitrate']); } if (!empty($ThisFileInfo['wavpack'])) { $thisfile_audio_dataformat = 'wavpack'; $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_audio['encoder'] = 'WavPack v' . $ThisFileInfo['wavpack']['version']; // Reset to the way it was - RIFF parsing will have messed this up $thisfile_avdataend = $Original['avdataend']; $thisfile_audio['bitrate'] = ($thisfile_avdataend - $thisfile_avdataoffset) * 8 / $ThisFileInfo['playtime_seconds']; fseek($fd, $thisfile_avdataoffset - 44, SEEK_SET); $RIFFdata = fread($fd, 44); $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { $thisfile_avdataend -= $OrignalRIFFheaderSize - $OrignalRIFFdataSize; fseek($fd, $thisfile_avdataend, SEEK_SET); $RIFFdata .= fread($fd, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); } // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36) . substr($RIFFdata, 44) . substr($RIFFdata, 36, 8); getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); } if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { case 0x8ae: // ClearJump LiteWave $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_audio_dataformat = 'litewave'; //typedef struct tagSLwFormat { // WORD m_wCompFormat; // low byte defines compression method, high byte is compression flags // DWORD m_dwScale; // scale factor for lossy compression // DWORD m_dwBlockSize; // number of samples in encoded blocks // WORD m_wQuality; // alias for the scale factor // WORD m_wMarkDistance; // distance between marks in bytes // WORD m_wReserved; // // //following paramters are ignored if CF_FILESRC is not set // DWORD m_dwOrgSize; // original file size in bytes // WORD m_bFactExists; // indicates if 'fact' chunk exists in the original file // DWORD m_dwRiffChunkSize; // riff chunk size in the original file // // PCMWAVEFORMAT m_OrgWf; // original wave format // }SLwFormat, *PSLwFormat; // shortcut $thisfile_riff['litewave']['raw'] = array(); $thisfile_riff_litewave =& $thisfile_riff['litewave']; $thisfile_riff_litewave_raw =& $thisfile_riff_litewave['raw']; $thisfile_riff_litewave_raw['compression_method'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 18, 1)); $thisfile_riff_litewave_raw['compression_flags'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 19, 1)); $thisfile_riff_litewave_raw['m_dwScale'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 20, 4)); $thisfile_riff_litewave_raw['m_dwBlockSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 24, 4)); $thisfile_riff_litewave_raw['m_wQuality'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 28, 2)); $thisfile_riff_litewave_raw['m_wMarkDistance'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 30, 2)); $thisfile_riff_litewave_raw['m_wReserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 32, 2)); $thisfile_riff_litewave_raw['m_dwOrgSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 34, 4)); $thisfile_riff_litewave_raw['m_bFactExists'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 38, 2)); $thisfile_riff_litewave_raw['m_dwRiffChunkSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 40, 4)); //$thisfile_riff_litewave['quality_factor'] = intval(round((2000 - $thisfile_riff_litewave_raw['m_dwScale']) / 20)); $thisfile_riff_litewave['quality_factor'] = $thisfile_riff_litewave_raw['m_wQuality']; $thisfile_riff_litewave['flags']['raw_source'] = $thisfile_riff_litewave_raw['compression_flags'] & 0x1 ? false : true; $thisfile_riff_litewave['flags']['vbr_blocksize'] = $thisfile_riff_litewave_raw['compression_flags'] & 0x2 ? false : true; $thisfile_riff_litewave['flags']['seekpoints'] = (bool) ($thisfile_riff_litewave_raw['compression_flags'] & 0x4); $thisfile_audio['lossless'] = $thisfile_riff_litewave_raw['m_wQuality'] == 100 ? true : false; $thisfile_audio['encoder_options'] = '-q' . $thisfile_riff_litewave['quality_factor']; break; default: break; } } if ($thisfile_avdataend > $ThisFileInfo['filesize']) { switch (@$thisfile_audio_dataformat) { case 'wavpack': // WavPack // WavPack case 'lpac': // LPAC // LPAC case 'ofr': // OptimFROG // OptimFROG case 'ofs': // OptimFROG DualStream // lossless compressed audio formats that keep original RIFF headers - skip warning break; case 'litewave': if ($thisfile_avdataend - $ThisFileInfo['filesize'] == 1) { // LiteWave appears to incorrectly *not* pad actual output file // to nearest WORD boundary so may appear to be short by one // byte, in which case - skip warning } else { // Short by more than one byte, throw warning $ThisFileInfo['warning'][] = 'Probably truncated file - expecting ' . $thisfile_riff[$RIFFsubtype]['data'][0]['size'] . ' bytes of data, only found ' . ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) . ' (short by ' . ($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)) . ' bytes)'; $thisfile_avdataend = $ThisFileInfo['filesize']; } break; default: if ($thisfile_avdataend - $ThisFileInfo['filesize'] == 1 && $thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2 == 0 && ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) % 2 == 1) { // output file appears to be incorrectly *not* padded to nearest WORD boundary // Output less severe warning $ThisFileInfo['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting ' . $thisfile_riff[$RIFFsubtype]['data'][0]['size'] . ' bytes of data, only found ' . ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) . ' therefore short by ' . ($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)) . ' bytes)'; $thisfile_avdataend = $ThisFileInfo['filesize']; break; } // Short by more than one byte, throw warning $ThisFileInfo['warning'][] = 'Probably truncated file - expecting ' . $thisfile_riff[$RIFFsubtype]['data'][0]['size'] . ' bytes of data, only found ' . ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) . ' (short by ' . ($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)) . ' bytes)'; $thisfile_avdataend = $ThisFileInfo['filesize']; break; } } if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'])) { if ($thisfile_avdataend - $thisfile_avdataoffset - $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'] == 1) { $thisfile_avdataend--; $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; } } if (@$thisfile_audio_dataformat == 'ac3') { unset($thisfile_audio['bits_per_sample']); if (!empty($ThisFileInfo['ac3']['bitrate']) && $ThisFileInfo['ac3']['bitrate'] != $thisfile_audio['bitrate']) { $thisfile_audio['bitrate'] = $ThisFileInfo['ac3']['bitrate']; } } break; case 'AVI ': $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably $thisfile_video['dataformat'] = 'avi'; $ThisFileInfo['mime_type'] = 'video/avi'; if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['movi']['size']; if ($thisfile_avdataend > $ThisFileInfo['filesize']) { $ThisFileInfo['warning'][] = 'Probably truncated file - expecting ' . $thisfile_riff[$RIFFsubtype]['movi']['size'] . ' bytes of data, only found ' . ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) . ' (short by ' . ($thisfile_riff[$RIFFsubtype]['movi']['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)) . ' bytes)'; $thisfile_avdataend = $ThisFileInfo['filesize']; } } if (isset($thisfile_riff['AVI ']['hdrl']['strl']['indx'])) { //$bIndexType = array( // 0x00 => 'AVI_INDEX_OF_INDEXES', // 0x01 => 'AVI_INDEX_OF_CHUNKS', // 0x80 => 'AVI_INDEX_IS_DATA', //); //$bIndexSubtype = array( // 0x01 => array( // 0x01 => 'AVI_INDEX_2FIELD', // ), //); foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { $thisfile_riff_avi_hdrl_strl_indx_stream_data =& $thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = @$bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = @$bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; unset($thisfile_riff_avi_hdrl_strl_indx_stream_data); } } if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) { $avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data']; // shortcut $thisfile_riff_raw['avih'] = array(); $thisfile_riff_raw_avih =& $thisfile_riff_raw['avih']; $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 0, 4)); // frame display rate (or 0L) if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { $ThisFileInfo['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; return false; } $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 4, 4)); // max. transfer rate $thisfile_riff_raw_avih['dwPaddingGranularity'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. $thisfile_riff_raw_avih['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 12, 4)); // the ever-present flags $thisfile_riff_raw_avih['dwTotalFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 16, 4)); // # frames in file $thisfile_riff_raw_avih['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 20, 4)); $thisfile_riff_raw_avih['dwStreams'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 24, 4)); $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 28, 4)); $thisfile_riff_raw_avih['dwWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 32, 4)); $thisfile_riff_raw_avih['dwHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 36, 4)); $thisfile_riff_raw_avih['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 40, 4)); $thisfile_riff_raw_avih['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 44, 4)); $thisfile_riff_raw_avih['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 48, 4)); $thisfile_riff_raw_avih['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 52, 4)); $thisfile_riff_raw_avih['flags']['hasindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x10); $thisfile_riff_raw_avih['flags']['mustuseindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x20); $thisfile_riff_raw_avih['flags']['interleaved'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x100); $thisfile_riff_raw_avih['flags']['trustcktype'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x800); $thisfile_riff_raw_avih['flags']['capturedfile'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x10000); $thisfile_riff_raw_avih['flags']['copyrighted'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x20010); // shortcut $thisfile_riff_video[$streamindex] = array(); $thisfile_riff_video_current =& $thisfile_riff_video[$streamindex]; if ($thisfile_riff_raw_avih['dwWidth'] > 0) { $thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth']; $thisfile_video['resolution_x'] = $thisfile_riff_video_current['frame_width']; } if ($thisfile_riff_raw_avih['dwHeight'] > 0) { $thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight']; $thisfile_video['resolution_y'] = $thisfile_riff_video_current['frame_height']; } if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { $thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames']; $thisfile_video['total_frames'] = $thisfile_riff_video_current['total_frames']; } $thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3); $thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate']; } if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) { if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) { for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) { if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) { $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data']; $strhfccType = substr($strhData, 0, 4); if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) { $strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data']; // shortcut $thisfile_riff_raw_strf_strhfccType_streamindex =& $thisfile_riff_raw['strf'][$strhfccType][$streamindex]; switch ($strhfccType) { case 'auds': $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'wav'; if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) { $streamindex = count($thisfile_riff_audio); } $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($strfData); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; // shortcut $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; $thisfile_audio_streams_currentstream =& $thisfile_audio['streams'][$streamindex]; if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) { unset($thisfile_audio_streams_currentstream['bits_per_sample']); } $thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag']; unset($thisfile_audio_streams_currentstream['raw']); // shortcut $thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw']; unset($thisfile_riff_audio[$streamindex]['raw']); $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); $thisfile_audio['lossless'] = false; switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) { case 0x1: // PCM $thisfile_audio_dataformat = 'wav'; $thisfile_audio['lossless'] = true; break; case 0x50: // MPEG Layer 2 or Layer 1 $thisfile_audio_dataformat = 'mp2'; // Assume Layer-2 break; case 0x55: // MPEG Layer 3 $thisfile_audio_dataformat = 'mp3'; break; case 0xff: // AAC $thisfile_audio_dataformat = 'aac'; break; case 0x161: // Windows Media v7 / v8 / v9 // Windows Media v7 / v8 / v9 case 0x162: // Windows Media Professional v9 // Windows Media Professional v9 case 0x163: // Windows Media Lossess v9 $thisfile_audio_dataformat = 'wma'; break; case 0x2000: // AC-3 $thisfile_audio_dataformat = 'ac3'; break; case 0x2001: // DTS $thisfile_audio_dataformat = 'dts'; break; default: $thisfile_audio_dataformat = 'wav'; break; } $thisfile_audio_streams_currentstream['dataformat'] = $thisfile_audio_dataformat; $thisfile_audio_streams_currentstream['lossless'] = $thisfile_audio['lossless']; $thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode']; break; case 'iavs': case 'vids': // shortcut $thisfile_riff_raw['strh'][$i] = array(); $thisfile_riff_raw_strh_current =& $thisfile_riff_raw['strh'][$i]; $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); $thisfile_riff_raw_strh_current['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 8, 4)); // Contains AVITF_* flags $thisfile_riff_raw_strh_current['wPriority'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 12, 2)); $thisfile_riff_raw_strh_current['wLanguage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 14, 2)); $thisfile_riff_raw_strh_current['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 16, 4)); $thisfile_riff_raw_strh_current['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 20, 4)); $thisfile_riff_raw_strh_current['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 24, 4)); $thisfile_riff_raw_strh_current['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 28, 4)); $thisfile_riff_raw_strh_current['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 32, 4)); $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 36, 4)); $thisfile_riff_raw_strh_current['dwQuality'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 40, 4)); $thisfile_riff_raw_strh_current['dwSampleSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 44, 4)); $thisfile_riff_raw_strh_current['rcFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 48, 4)); $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strh_current['fccHandler']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; } $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; $thisfile_video['pixel_aspect_ratio'] = (double) 1; switch ($thisfile_riff_raw_strh_current['fccHandler']) { case 'HFYU': // Huffman Lossless Codec // Huffman Lossless Codec case 'IRAW': // Intel YUV Uncompressed // Intel YUV Uncompressed case 'YUY2': // Uncompressed YUV 4:2:2 $thisfile_video['lossless'] = true; break; default: $thisfile_video['lossless'] = false; break; } switch ($strhfccType) { case 'vids': $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), $ThisFileInfo['fileformat'] == 'riff'); //$thisfile_riff_raw_strf_strhfccType_streamindex['biSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure //$thisfile_riff_raw_strf_strhfccType_streamindex['biWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 4, 4)); // width of the bitmap in pixels //$thisfile_riff_raw_strf_strhfccType_streamindex['biHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner //$thisfile_riff_raw_strf_strhfccType_streamindex['biPlanes'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 //$thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 14, 2)); // Specifies the number of bits per pixels //$thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'] = substr($strfData, 16, 4); // //$thisfile_riff_raw_strf_strhfccType_streamindex['biSizeImage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) //$thisfile_riff_raw_strf_strhfccType_streamindex['biXPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 24, 4)); // horizontal resolution, in pixels per metre, of the target device //$thisfile_riff_raw_strf_strhfccType_streamindex['biYPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 28, 4)); // vertical resolution, in pixels per metre, of the target device //$thisfile_riff_raw_strf_strhfccType_streamindex['biClrUsed'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression //$thisfile_riff_raw_strf_strhfccType_streamindex['biClrImportant'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; if ($thisfile_riff_video_current['codec'] == 'DV') { $thisfile_riff_video_current['dv_type'] = 2; } break; case 'iavs': $thisfile_riff_video_current['dv_type'] = 1; break; } break; default: $ThisFileInfo['warning'][] = 'Unhandled fccType for stream (' . $i . '): "' . $strhfccType . '"'; break; } } } if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { case 'HFYU': // Huffman Lossless Codec // Huffman Lossless Codec case 'IRAW': // Intel YUV Uncompressed // Intel YUV Uncompressed case 'YUY2': // Uncompressed YUV 4:2:2 $thisfile_video['lossless'] = true; $thisfile_video['bits_per_sample'] = 24; break; default: $thisfile_video['lossless'] = false; $thisfile_video['bits_per_sample'] = 24; break; } } } } } break; case 'CDDA': $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'cda'; $thisfile_audio['lossless'] = true; unset($ThisFileInfo['mime_type']); $thisfile_avdataoffset = 44; if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { // shortcut $thisfile_riff_CDDA_fmt_0 =& $thisfile_riff['CDDA']['fmt '][0]; $thisfile_riff_CDDA_fmt_0['unknown1'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); $thisfile_riff_CDDA_fmt_0['track_num'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); $thisfile_riff_CDDA_fmt_0['disc_id'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); $thisfile_riff_CDDA_fmt_0['playtime_frames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); $thisfile_riff_CDDA_fmt_0['unknown6'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); $thisfile_riff_CDDA_fmt_0['unknown7'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (double) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (double) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; $ThisFileInfo['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; $ThisFileInfo['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; // hardcoded data for CD-audio $thisfile_audio['sample_rate'] = 44100; $thisfile_audio['channels'] = 2; $thisfile_audio['bits_per_sample'] = 16; $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample']; $thisfile_audio['bitrate_mode'] = 'cbr'; } break; case 'AIFF': case 'AIFC': $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'aiff'; $thisfile_audio['lossless'] = true; $ThisFileInfo['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; if ($thisfile_avdataend > $ThisFileInfo['filesize']) { if ($thisfile_avdataend == $ThisFileInfo['filesize'] + 1 && $ThisFileInfo['filesize'] % 2 == 1) { // structures rounded to 2-byte boundary, but dumb encoders // forget to pad end of file to make this actually work } else { $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting ' . $thisfile_riff[$RIFFsubtype]['SSND'][0]['size'] . ' bytes of audio data, only ' . ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) . ' bytes found'; } $thisfile_avdataend = $ThisFileInfo['filesize']; } } if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) { // shortcut $thisfile_riff_RIFFsubtype_COMM_0_data =& $thisfile_riff[$RIFFsubtype]['COMM'][0]['data']; $thisfile_riff_audio['channels'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 0, 2), true); $thisfile_riff_audio['total_samples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 2, 4), false); $thisfile_riff_audio['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 6, 2), true); $thisfile_riff_audio['sample_rate'] = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 8, 10)); if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) { $thisfile_riff_audio['codec_fourcc'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18, 4); $CodecNameSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22, 1), false); $thisfile_riff_audio['codec_name'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23, $CodecNameSize); switch ($thisfile_riff_audio['codec_name']) { case 'NONE': $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; $thisfile_audio['lossless'] = true; break; case '': switch ($thisfile_riff_audio['codec_fourcc']) { // http://developer.apple.com/qa/snd/snd07.html case 'sowt': $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM'; $thisfile_audio['lossless'] = true; break; case 'twos': $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM'; $thisfile_audio['lossless'] = true; break; default: break; } break; default: $thisfile_audio['codec'] = $thisfile_riff_audio['codec_name']; $thisfile_audio['lossless'] = false; break; } } $thisfile_audio['channels'] = $thisfile_riff_audio['channels']; if ($thisfile_riff_audio['bits_per_sample'] > 0) { $thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample']; } $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; if ($thisfile_audio['sample_rate'] == 0) { $ThisFileInfo['error'][] = 'Corrupted AIFF file: sample_rate == zero'; return false; } $ThisFileInfo['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; } if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { $offset = 0; $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; for ($i = 0; $i < $CommentCount; $i++) { $ThisFileInfo['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); $offset += 4; $ThisFileInfo['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); $offset += 2; $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; $ThisFileInfo['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); $offset += $CommentLength; $ThisFileInfo['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($ThisFileInfo['comments_raw'][$i]['timestamp']); $thisfile_riff['comments']['comment'][] = $ThisFileInfo['comments_raw'][$i]['comment']; } } $CommentsChunkNames = array('NAME' => 'title', 'author' => 'artist', '(c) ' => 'copyright', 'ANNO' => 'comment'); foreach ($CommentsChunkNames as $key => $value) { if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } break; case '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 $ThisFileInfo['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; if ($thisfile_avdataend > $ThisFileInfo['filesize']) { $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting ' . $thisfile_riff[$RIFFsubtype]['BODY'][0]['size'] . ' bytes of audio data, only ' . ($ThisFileInfo['filesize'] - $thisfile_avdataoffset) . ' bytes found'; } } if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) { // shortcut $thisfile_riff_RIFFsubtype_VHDR_0 =& $thisfile_riff[$RIFFsubtype]['VHDR'][0]; $thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 0, 4)); $thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 4, 4)); $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 8, 4)); $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2)); $thisfile_riff_RIFFsubtype_VHDR_0['ctOctave'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1)); $thisfile_riff_RIFFsubtype_VHDR_0['sCompression'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1)); $thisfile_riff_RIFFsubtype_VHDR_0['Volume'] = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4)); $thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']; switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) { case 0: $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; $thisfile_audio['lossless'] = true; $ActualBitsPerSample = 8; break; case 1: $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; $thisfile_audio['lossless'] = false; $ActualBitsPerSample = 4; break; default: $ThisFileInfo['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "' . sCompression . '"'; break; } } if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) { $ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4)); switch ($ChannelsIndex) { case 6: // Stereo $thisfile_audio['channels'] = 2; break; case 2: // Left channel only // Left channel only case 4: // Right channel only $thisfile_audio['channels'] = 1; break; default: $ThisFileInfo['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "' . $ChannelsIndex . '"'; break; } } $CommentsChunkNames = array('NAME' => 'title', 'author' => 'artist', '(c) ' => 'copyright', 'ANNO' => 'comment'); foreach ($CommentsChunkNames as $key => $value) { if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; if (!empty($thisfile_audio['bitrate'])) { $ThisFileInfo['playtime_seconds'] = ($thisfile_avdataend - $thisfile_avdataoffset) / ($thisfile_audio['bitrate'] / 8); } break; case 'CDXA': $ThisFileInfo['mime_type'] = 'video/mpeg'; if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { $GETID3_ERRORARRAY =& $ThisFileInfo['warning']; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.mpeg.php', __FILE__, false)) { $dummy = $ThisFileInfo; $dummy['error'] = array(); $mpeg_scanner = new getid3_mpeg($fd, $dummy); if (empty($dummy['error'])) { $ThisFileInfo['audio'] = $dummy['audio']; $ThisFileInfo['video'] = $dummy['video']; $ThisFileInfo['mpeg'] = $dummy['mpeg']; $ThisFileInfo['warning'] = $dummy['warning']; } unset($mpeg_scanner); } } break; default: $ThisFileInfo['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "' . $RIFFsubtype . '" instead'; unset($ThisFileInfo['fileformat']); break; } if (@$thisfile_riff_raw['fmt ']['wFormatTag'] == 1) { // http://www.mega-nerd.com/erikd/Blog/Windiots/dts.html fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); $FirstFourBytes = fread($fd, 4); if (preg_match('/^\\xFF\\x1F\\x00\\xE8/s', $FirstFourBytes)) { // DTSWAV $thisfile_audio_dataformat = 'dts'; } elseif (preg_match('/^\\x7F\\xFF\\x80\\x01/s', $FirstFourBytes)) { // DTS, but this probably shouldn't happen $thisfile_audio_dataformat = 'dts'; } } if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); } if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { $this->RIFFcommentsParse($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); } if (empty($thisfile_audio['encoder']) && !empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { $thisfile_audio['encoder'] = $ThisFileInfo['mpeg']['audio']['LAME']['short_version']; } if (!isset($ThisFileInfo['playtime_seconds'])) { $ThisFileInfo['playtime_seconds'] = 0; } if (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { $ThisFileInfo['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); } if ($ThisFileInfo['playtime_seconds'] > 0) { if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { if (!isset($ThisFileInfo['bitrate'])) { $ThisFileInfo['bitrate'] = ($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds'] * 8; } } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { if (!isset($thisfile_audio['bitrate'])) { $thisfile_audio['bitrate'] = ($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds'] * 8; } } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { if (!isset($thisfile_video['bitrate'])) { $thisfile_video['bitrate'] = ($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds'] * 8; } } } if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && $thisfile_audio['bitrate'] > 0 && $ThisFileInfo['playtime_seconds'] > 0) { $ThisFileInfo['bitrate'] = ($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds'] * 8; $thisfile_audio['bitrate'] = 0; $thisfile_video['bitrate'] = $ThisFileInfo['bitrate']; foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; } if ($thisfile_video['bitrate'] <= 0) { unset($thisfile_video['bitrate']); } if ($thisfile_audio['bitrate'] <= 0) { unset($thisfile_audio['bitrate']); } } if (isset($ThisFileInfo['mpeg']['audio'])) { $thisfile_audio_dataformat = 'mp' . $ThisFileInfo['mpeg']['audio']['layer']; $thisfile_audio['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; $thisfile_audio['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; $thisfile_audio['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; $thisfile_audio['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); if (!empty($ThisFileInfo['mpeg']['audio']['codec'])) { $thisfile_audio['codec'] = $ThisFileInfo['mpeg']['audio']['codec'] . ' ' . $thisfile_audio['codec']; } if (!empty($thisfile_audio['streams'])) { foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { if ($streamdata['dataformat'] == $thisfile_audio_dataformat) { $thisfile_audio['streams'][$streamnumber]['sample_rate'] = $thisfile_audio['sample_rate']; $thisfile_audio['streams'][$streamnumber]['channels'] = $thisfile_audio['channels']; $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; $thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; $thisfile_audio['streams'][$streamnumber]['codec'] = $thisfile_audio['codec']; } } } $thisfile_audio['encoder_options'] = getid3_mp3::GuessEncoderOptions($ThisFileInfo); } if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && $thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0) { switch ($thisfile_audio_dataformat) { case 'ac3': // ignore bits_per_sample break; default: $thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample']; break; } } if (empty($thisfile_riff_raw)) { unset($thisfile_riff['raw']); } if (empty($thisfile_riff_audio)) { unset($thisfile_riff['audio']); } if (empty($thisfile_riff_video)) { unset($thisfile_riff['video']); } return true; }
function Analyze() { $info =& $this->getid3->info; // http://www.matroska.org/technical/specs/index.html#EBMLBasics $offset = $info['avdataoffset']; $EBMLdata = ''; $EBMLdata_offset = $offset; if (!getid3_lib::intValueSupported($info['avdataend'])) { $this->warnings[] = 'This version of getID3() [' . $this->getid3->version() . '] may or may not correctly handle Matroska files larger than ' . round(PHP_INT_MAX / 1073741824) . 'GB'; } while ($offset < $info['avdataend']) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $top_element_offset = $offset; $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($top_element_length === false) { $this->warnings[] = 'invalid chunk length at ' . $top_element_offset; $offset = PHP_INT_MAX + 1; break; } $top_element_endoffset = $offset + $top_element_length; switch ($top_element_id) { case EBML_ID_EBML: $info['fileformat'] = 'matroska'; $info['matroska']['header']['offset'] = $top_element_offset; $info['matroska']['header']['length'] = $top_element_length; while ($offset < $top_element_endoffset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data_offset = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $end_offset = $offset + $element_data['length']; switch ($element_data['id']) { case EBML_ID_VOID: // padding, ignore break; case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); break; case EBML_ID_DOCTYPE: $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), ""); break; case EBML_ID_CRC32: // probably not useful, ignore unset($element_data); break; default: $this->warnings[] = 'Unhandled track.video element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data_offset; break; } $offset = $end_offset; if (!empty($element_data)) { $info['matroska']['header']['elements'][] = $element_data; } } break; case EBML_ID_SEGMENT: $info['matroska']['segment'][0]['offset'] = $top_element_offset; $info['matroska']['segment'][0]['length'] = $top_element_length; $segment_key = -1; while ($offset < $info['avdataend']) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data['offset'] = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($element_data['length'] === false) { $this->warnings[] = 'invalid chunk length at ' . $element_data['offset']; //$offset = PHP_INT_MAX + 1; $offset = $info['avdataend']; break; } $element_end = $offset + $element_data['length']; switch ($element_data['id']) { //case EBML_ID_CLUSTER: // // too many cluster entries, probably not useful // break; case false: $this->warnings[] = 'invalid ID at ' . $element_data['offset']; $offset = $element_end; continue 3; default: $info['matroska']['segments'][] = $element_data; break; } $segment_key++; switch ($element_data['id']) { case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $seek_entry = array(); $seek_entry['offset'] = $offset; $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_end_offset = $offset + $seek_entry['length']; switch ($seek_entry['id']) { case EBML_ID_SEEK: // Contains a single seek entry to an EBML element while ($offset < $seek_end_offset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); $offset += $length; switch ($id) { case EBML_ID_SEEKID: $dummy = 0; $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); break; case EBML_ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); break; case EBML_ID_CRC32: // probably not useful, ignore //$seek_entry['crc32'] = getid3_lib::PrintHexBytes($value, true, false, false); unset($seek_entry); break; default: $info['error'][] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $id . ') at ' . $offset; break; } } if (!empty($seek_entry)) { $info['matroska']['seek'][] = $seek_entry; } //switch ($seek_entry['target_id']) { // case EBML_ID_CLUSTER: // // too many cluster seek points, probably not useful // break; // default: // $info['matroska']['seek'][] = $seek_entry; // break; //} break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled seekhead element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $seek_entry['id'] . '::' . $seek_entry['id_name'] . ') at ' . $offset; break; } $offset = $seek_end_offset; } break; case EBML_ID_TRACKS: // information about all tracks in segment $info['matroska']['tracks'] = $element_data; while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $track_entry = array(); $track_entry['offset'] = $offset; $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry_endoffset = $offset + $track_entry['length']; // $track_entry['offset'] is not the same as $offset, even though they were set equal a few lines up: $offset has been automagically incremented by readEMLint() switch ($track_entry['id']) { case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($offset < $track_entry_endoffset) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: case EBML_ID_MINCACHE: case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_TRACKTIMECODESCALE: $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: case EBML_ID_CODECNAME: case EBML_ID_CODECPRIVATE: $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; // thought maybe it was a nice wFormatTag entry, but it's not :( //case EBML_ID_CODECPRIVATE: //$track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); //if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { // $track_entry[$subelement_idname.'_decoded'] = getid3_riff::RIFFparseWAVEFORMATex($track_entry[$subelement_idname]); // if (isset($track_entry[$subelement_idname.'_decoded']['raw']['wFormatTag'])) { // } //} else { // $this->warnings[] = 'failed to include "module.audio-video.riff.php" for parsing codec private data'; //} //break; // thought maybe it was a nice wFormatTag entry, but it's not :( //case EBML_ID_CODECPRIVATE: //$track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); //if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { // $track_entry[$subelement_idname.'_decoded'] = getid3_riff::RIFFparseWAVEFORMATex($track_entry[$subelement_idname]); // if (isset($track_entry[$subelement_idname.'_decoded']['raw']['wFormatTag'])) { // } //} else { // $this->warnings[] = 'failed to include "module.audio-video.riff.php" for parsing codec private data'; //} //break; case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_VIDEO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: case EBML_ID_PIXELCROPRIGHT: case EBML_ID_DISPLAYWIDTH: case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_FLAGINTERLACED: $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_GAMMAVALUE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_COLOURSPACE: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.video element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (isset($track_entry[$this->EBMLidName(EBML_ID_CODECID)]) && $track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC' && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); } else { $this->warnings[] = 'Unable to parse codec private data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'; } } break; case EBML_ID_AUDIO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHANNELPOSITIONS: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.audio element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CONTENTENCODINGS: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CONTENTENCODING: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPRESSION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTCOMPALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPSETTINGS: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; case EBML_ID_CONTENTENCRYPTION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled track element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CRC32: // probably not useful, ignore $offset = $track_entry_endoffset; break; default: $this->warnings[] = 'Unhandled track element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $track_entry['id'] . '::' . $track_entry['id_name'] . ') at ' . $track_entry['offset']; $offset = $track_entry_endoffset; break; } $info['matroska']['tracks']['tracks'][] = $track_entry; } break; case EBML_ID_INFO: // Contains the position of other level 1 elements $info_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DURATION: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DATEUTC: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); $info_entry[$subelement_idname . '_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); break; case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); $info['matroska']['comments'][strtolower($subelement_idname)][] = $info_entry[$subelement_idname]; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled info element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['info'][] = $info_entry; break; case EBML_ID_CUES: $cues_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CUEPOINT: $cuepoint_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CUETRACKPOSITIONS: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CUETRACK: $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CUETIME: $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled cues element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['cues'] = $cues_entry; break; case EBML_ID_TAGS: $tags_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; $tag_entry = array(); switch ($subelement_id) { case EBML_ID_WRITINGAPP: $tag_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); break; case EBML_ID_TAG: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_TARGETS: $targets_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TARGETTYPEVALUE: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); $targets_entry[strtolower($sub_sub_subelement_idname) . '_long'] = $this->MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement_idname]); break; case EBML_ID_EDITIONUID: case EBML_ID_CHAPTERUID: case EBML_ID_ATTACHMENTUID: case EBML_ID_TAGTRACKUID: case EBML_ID_TAGCHAPTERUID: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.targets element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $targets_entry; break; case EBML_ID_SIMPLETAG: //$tag_entry[$sub_subelement_idname][] = $simpletag_entry; $tag_entry[$sub_subelement_idname][] = $this->Handle_EMBL_ID_SIMPLETAG($offset, $sub_subelement_end); break; case EBML_ID_TARGETTYPE: $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_TRACKUID: $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tags.tag element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled tags element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $tags_entry['tags'][] = $tag_entry; $offset = $subelement_end; } $info['matroska']['tags'] = $tags_entry['tags']; break; case EBML_ID_ATTACHMENTS: while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_FILEDATA: $attachedfile_entry['data_offset'] = $offset; $attachedfile_entry['data_length'] = $sub_subelement_length; do { if ($this->inline_attachments === false) { // skip entirely break; } if ($this->inline_attachments === true) { // great } elseif (is_int($this->inline_attachments)) { if ($this->inline_attachments < $sub_subelement_length) { // too big, skip $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' is too large to process inline (' . number_format($sub_subelement_length) . ' bytes)'; break; } } elseif (is_string($this->inline_attachments)) { $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { // cannot write, skip $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' cannot be saved to "' . $this->inline_attachments . '" (not writable)'; break; } } // if we get this far, must be OK $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset, $sub_subelement_length); $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); if (is_string($this->inline_attachments)) { $destination_filename = $this->inline_attachments . DIRECTORY_SEPARATOR . md5($info['filenamepath']) . '_' . $attachedfile_entry['data_offset']; if (!file_exists($destination_filename) || is_writable($destination_filename)) { file_put_contents($destination_filename, $attachedfile_entry[$sub_subelement_idname]); } else { $this->warnings[] = 'attachment at ' . $sub_subelement_offset . ' cannot be saved to "' . $destination_filename . '" (not writable)'; } $attachedfile_entry[$sub_subelement_idname . '_filename'] = $destination_filename; unset($attachedfile_entry[$sub_subelement_idname]); } } while (false); break; case EBML_ID_FILEUID: $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled attachment.attachedfile element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (!empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]) && !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]) && preg_match('#^image/#i', $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)])) { if ($this->inline_attachments === true || is_int($this->inline_attachments) && $this->inline_attachments >= strlen($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)])) { $attachedfile_entry['data'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]; $attachedfile_entry['image_mime'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]; $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)]) ? $attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)] : ''); unset($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)], $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]); } } if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] } else { $info['matroska']['attachments'][] = $attachedfile_entry; } $offset = $sub_subelement_end; break; case EBML_ID_CRC32: // probably not useful, ignore break; default: $this->warnings[] = 'Unhandled tags element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_EDITIONUID: $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; break; case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $info['matroska']['chapters'][] = $editionentry_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled chapters element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_VOID: // padding, ignore $void_entry = array(); $void_entry['offset'] = $offset; $info['matroska']['void'][] = $void_entry; break; case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore $cluster_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERSILENTTRACKNUMBER: $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled clusters.silenttracks element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERBLOCKGROUP: $cluster_block_group = array('offset' => $offset); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERBLOCK: $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x8) >> 3); $cluster_block_data['flags']['lacing'] = ($cluster_block_data['flags_raw'] & 0x6) >> 1; //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); if ($cluster_block_data['flags']['lacing'] != 0) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) $offset += 1; if ($cluster_block_data['flags']['lacing'] != 2) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $offset += 1; } } if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $offset; $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $subelement_length; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); break; default: $this->warnings[] = 'Unhandled clusters.blockgroup element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_block_group; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERSIMPLEBLOCK: // http://www.matroska.org/technical/specs/index.html#simpleblock_structure $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; $cluster_block_data['flags']['keyframe'] = ($cluster_block_data['flags_raw'] & 0x80) >> 7; $cluster_block_data['flags']['reserved1'] = ($cluster_block_data['flags_raw'] & 0x70) >> 4; $cluster_block_data['flags']['invisible'] = ($cluster_block_data['flags_raw'] & 0x8) >> 3; $cluster_block_data['flags']['lacing'] = ($cluster_block_data['flags_raw'] & 0x6) >> 1; // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing $cluster_block_data['flags']['discardable'] = $cluster_block_data['flags_raw'] & 0x1; if ($cluster_block_data['flags']['lacing'] > 0) { $cluster_block_data['lace_frames'] = 1 + getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; if ($cluster_block_data['flags']['lacing'] != 0x2) { // *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $cluster_block_data['lace_frame_size'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; } } if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $offset; $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $subelement_length; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; default: $this->warnings[] = 'Unhandled cluster element [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ' [' . $subelement_length . ' bytes]) at ' . $subelement_offset; break; } $offset = $subelement_end; } $info['matroska']['cluster'][] = $cluster_entry; // check to see if all the data we need exists already, if so, break out of the loop if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { break 2; } } break; default: if ($element_data['id_name'] == dechex($element_data['id'])) { $info['error'][] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . ') at ' . $element_data_offset; } else { $this->warnings[] = 'Unhandled segment [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data['offset']; } break; } $offset = $element_end; } break; default: $info['error'][] = 'Unhandled chunk [' . basename(__FILE__) . ':' . __LINE__ . '] (' . $top_element_id . ') at ' . $offset; break; } $offset = $top_element_endoffset; } if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); if (isset($trackarray['FlagDefault'])) { $track_info['default'] = $trackarray['FlagDefault']; } switch (isset($trackarray['TrackType']) ? $trackarray['TrackType'] : '') { case 1: // Video if (!empty($trackarray['PixelWidth'])) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } if (!empty($trackarray['PixelHeight'])) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } if (!empty($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (!empty($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['codec_private_parsed']['fourcc'])) { $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; } $info['video']['streams'][] = $track_info; if (isset($track_info['resolution_x']) && empty($info['video']['resolution_x'])) { foreach ($track_info as $key => $value) { $info['video'][$key] = $value; } } break; case 2: // Audio if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['SamplingFrequency'])) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } if (!empty($trackarray['Channels'])) { $track_info['channels'] = $trackarray['Channels']; } if (!empty($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } if (!empty($trackarray['Language'])) { $track_info['language'] = $trackarray['Language']; } switch (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ac3.php', __FILE__, false)) { if (isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'])) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_ac3 = new getid3_ac3($getid3_temp); $getid3_ac3->Analyze(); unset($getid3_temp->info['ac3']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ac3']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($getid3_temp, $getid3_ac3); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '][offset] not set'; } } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ac3.php"'; } break; case 'A_DTS': $dts_offset = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better fseek($this->getid3->fp, $dts_offset, SEEK_SET); $magic_test = fread($this->getid3->fp, 8); for ($i = 0; $i < 4; $i++) { // look to see if DTS "magic" is here, if so adjust offset by that many bytes if (substr($magic_test, $i, 4) == "þ€") { $dts_offset += $i; break; } } if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.dts.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $dts_offset; $getid3_dts = new getid3_dts($getid3_temp); $getid3_dts->Analyze(); unset($getid3_temp->info['dts']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['dts']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_dts() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_dts() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($getid3_temp, $getid3_dts); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.dts.php"'; } break; case 'A_AAC': $this->warnings[] = 'This version of getID3() [v' . $this->getid3->version() . '] has problems parsing AAC audio in Matroska containers [' . basename(__FILE__) . ':' . __LINE__ . ']'; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.aac.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_aac = new getid3_aac($getid3_temp); $getid3_aac->Analyze(); unset($getid3_temp->info['aac']['GETID3_VERSION']); if (!empty($getid3_temp->info['audio']['dataformat'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['aac']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warnings[] = 'Failed to parse ' . $trackarray[$this->EBMLidName(EBML_ID_CODECID)] . ' audio data [' . basename(__FILE__) . ':' . __LINE__ . ']'; } unset($getid3_temp, $getid3_aac); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.aac.php"'; } break; case 'A_MPEG/L3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.mp3.php', __FILE__, false)) { $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; $getid3_mp3 = new getid3_mp3($getid3_temp); $getid3_mp3->allow_bruteforce = true; $getid3_mp3->Analyze(); if (!empty($getid3_temp->info['mpeg'])) { unset($getid3_temp->info['mpeg']['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['mpeg']; if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: [' . $newerror . ']'; } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: [' . $newerror . ']'; } } if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because getid3_mp3::Analyze failed at offset ' . $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; } unset($getid3_temp, $getid3_mp3); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.mp3.php"'; } break; case 'A_VORBIS': if (isset($trackarray['CodecPrivate'])) { // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out $found_vorbis = false; for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { $vorbis_offset--; $found_vorbis = true; break; } } if ($found_vorbis) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, false)) { $oggpageinfo['page_seqno'] = 0; $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_ogg = new getid3_ogg($getid3_temp); $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); $vorbis_fileinfo = $getid3_temp->info; unset($getid3_temp, $getid3_ogg); if (isset($vorbis_fileinfo['audio'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']]['audio'] = $vorbis_fileinfo['audio']; } if (isset($vorbis_fileinfo['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']]['ogg'] = $vorbis_fileinfo['ogg']; } if (!empty($vorbis_fileinfo['error'])) { foreach ($vorbis_fileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: [' . $newerror . ']'; } } if (!empty($vorbis_fileinfo['warning'])) { foreach ($vorbis_fileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: [' . $newerror . ']'; } } if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { foreach ($vorbis_fileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } if (!empty($vorbis_fileinfo['ogg']['bitrate_average'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; } elseif (!empty($vorbis_fileinfo['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; } unset($vorbis_fileinfo); unset($oggpageinfo); } else { $this->warnings[] = 'Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ogg.php"'; } } else { } } else { } break; default: $this->warnings[] = 'Unhandled audio type "' . (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') . '"'; break; } $info['audio']['streams'][] = $track_info; if (isset($track_info['dataformat']) && empty($info['audio']['dataformat'])) { foreach ($track_info as $key => $value) { $info['audio'][$key] = $value; } } break; default: // ignore, do nothing break; } } } if ($this->hide_clusters) { // too much data returned that is usually not useful if (isset($info['matroska']['segments']) && is_array($info['matroska']['segments'])) { foreach ($info['matroska']['segments'] as $key => $segmentsarray) { if ($segmentsarray['id'] == EBML_ID_CLUSTER) { unset($info['matroska']['segments'][$key]); } } } if (isset($info['matroska']['seek']) && is_array($info['matroska']['seek'])) { foreach ($info['matroska']['seek'] as $key => $seekarray) { if ($seekarray['target_id'] == EBML_ID_CLUSTER) { unset($info['matroska']['seek'][$key]); } } } //unset($info['matroska']['cluster']); //unset($info['matroska']['track_data_offsets']); } if (!empty($info['video']['streams'])) { $info['mime_type'] = 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } foreach ($this->warnings as $key => $value) { $info['warning'][] = $value; } return true; }
public function Analyze() { $info =& $this->getid3->info; // parse container try { $this->parseEBML($info); } catch (Exception $e) { $info['error'][] = 'EBML parser: ' . $e->getMessage(); } // calculate playtime if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { foreach ($info['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } // extract tags if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { foreach ($info['matroska']['tags'] as $key => $infoarray) { $this->ExtractCommentsSimpleTag($infoarray); } } // process tracks if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); $track_info['dataformat'] = self::MatroskaCodecIDtoCommonName($trackarray['CodecID']); $track_info['default'] = isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true; if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } switch ($trackarray['TrackType']) { case 1: // Video $track_info['resolution_x'] = $trackarray['PixelWidth']; $track_info['resolution_y'] = $trackarray['PixelHeight']; if (isset($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (isset($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'V_MS/VFW/FOURCC': if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $this->getid3->warning('Unable to parse codec private data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'); break; } $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); $track_info['codec'] = getid3_riff::RIFFfourccLookup($parsed['fourcc']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; } $info['video']['streams'][] = $track_info; break; case 2: // Audio $track_info['sample_rate'] = isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0; $track_info['channels'] = isset($trackarray['Channels']) ? $trackarray['Channels'] : 1; $track_info['language'] = isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'; if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } switch ($trackarray['CodecID']) { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': case 'A_DTS': case 'A_MPEG/L3': //case 'A_FLAC': if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.' . $track_info['dataformat'] . '.php', __FILE__, false)) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.' . $track_info['dataformat'] . '.php"'); break; } if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because $info[matroska][track_data_offsets][' . $trackarray['TrackNumber'] . '] not set'); break; } // create temp instance $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; if ($track_info['dataformat'] == 'mp3' || $track_info['dataformat'] == 'flac') { $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; } // analyze $class = 'getid3_' . $track_info['dataformat']; $header_data_key = $track_info['dataformat'] == 'mp3' ? 'mpeg' : $track_info['dataformat']; $getid3_audio = new $class($getid3_temp); if ($track_info['dataformat'] == 'mp3') { $getid3_audio->allow_bruteforce = true; } if ($track_info['dataformat'] == 'flac') { $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); } else { $getid3_audio->Analyze(); } if (!empty($getid3_temp->info[$header_data_key])) { unset($getid3_temp->info[$header_data_key]['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } else { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because ' . $class . '::Analyze() failed at offset ' . $getid3_temp->info['avdataoffset']); } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->getid3->warning($class . '() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \\d+ bytes of audio data, only found \\d+ \\(short by \\d+ bytes\\)$/', $newerror)) { // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning continue; } $this->getid3->warning($class . '() says: [' . $newerror . ']'); } } unset($getid3_temp, $getid3_audio); break; case 'A_AAC': case 'A_AAC/MPEG2/LC': case 'A_AAC/MPEG4/LC': case 'A_AAC/MPEG4/LC/SBR': $this->getid3->warning($trackarray['CodecID'] . ' audio data contains no header, audio/video bitrates can\'t be calculated'); break; case 'A_VORBIS': if (!isset($trackarray['CodecPrivate'])) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data not set'); break; } $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); if ($vorbis_offset === false) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because CodecPrivate data does not contain "vorbis" keyword'); break; } $vorbis_offset -= 1; if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio.ogg.php', __FILE__, false)) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio.ogg.php"'); } // create temp instance $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); // analyze $getid3_ogg = new getid3_ogg($getid3_temp); $oggpageinfo['page_seqno'] = 0; $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); if (!empty($getid3_temp->info['ogg'])) { $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { $track_info[$key] = $value; } } } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { $this->getid3->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { $this->getid3->warning('getid3_ogg() says: [' . $newerror . ']'); } } if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; } unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); break; case 'A_MS/ACM': if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $this->getid3->warning('Unable to parse audio data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'); break; } $parsed = getid3_riff::RIFFparseWAVEFORMATex($trackarray['CodecPrivate']); foreach ($parsed as $key => $value) { if ($key != 'raw') { $track_info[$key] = $value; } } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; default: $this->getid3->warning('Unhandled audio type "' . (isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '') . '"'); } $info['audio']['streams'][] = $track_info; break; } } if (!empty($info['video']['streams'])) { $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); } if (!empty($info['audio']['streams'])) { $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); } } // determine mime type if (!empty($info['video']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'; } elseif (!empty($info['audio']['streams'])) { $info['mime_type'] = $info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'; } elseif (isset($info['mime_type'])) { unset($info['mime_type']); } return true; }
function Analyze() { $info =& $this->getid3->info; // initialize these values to an empty array, otherwise they default to NULL // and you can't append array values to a NULL value $info['riff'] = array('raw' => array()); // Shortcuts $thisfile_riff =& $info['riff']; $thisfile_riff_raw =& $thisfile_riff['raw']; $thisfile_audio =& $info['audio']; $thisfile_video =& $info['video']; $thisfile_audio_dataformat =& $thisfile_audio['dataformat']; $thisfile_riff_audio =& $thisfile_riff['audio']; $thisfile_riff_video =& $thisfile_riff['video']; $Original['avdataoffset'] = $info['avdataoffset']; $Original['avdataend'] = $info['avdataend']; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $RIFFheader = fread($this->getid3->fp, 12); $RIFFsubtype = substr($RIFFheader, 8, 4); switch (substr($RIFFheader, 0, 4)) { case 'FORM': $info['fileformat'] = 'aiff'; $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); break; case 'RIFF': // AVI, WAV, etc // AVI, WAV, etc case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) // 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'; $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); 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($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); 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 // byte, in which case - skip warning $info['avdataend'] = $info['filesize']; } $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { if (!getid3_lib::intValueSupported($nextRIFFoffset + 1024)) { $info['error'][] = 'AVI extends beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; break; } else { fseek($this->getid3->fp, $nextRIFFoffset, SEEK_SET); $nextRIFFheader = fread($this->getid3->fp, 12); if ($nextRIFFoffset == $info['avdataend'] - 1) { if (substr($nextRIFFheader, 0, 1) == "") { // RIFF padded to WORD boundary, we're actually already at the end break; } } $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); $nextRIFFtype = substr($nextRIFFheader, 8, 4); $chunkdata = array(); $chunkdata['offset'] = $nextRIFFoffset + 8; $chunkdata['size'] = $nextRIFFsize; $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; switch ($nextRIFFheaderID) { case 'RIFF': $info['avdataend'] = $nextRIFFoffset; if (!getid3_lib::intValueSupported($info['avdataend'])) { $info['error'][] = 'AVI extends beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; } $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $chunkdata['offset'] + $chunkdata['size']); if (!isset($thisfile_riff[$nextRIFFtype])) { $thisfile_riff[$nextRIFFtype] = array(); } $thisfile_riff[$nextRIFFtype][] = $chunkdata; break; case 'JUNK': // ignore $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; break; default: if ($info['filesize'] == $chunkdata['offset'] - 8 + 128) { $DIVXTAG = $nextRIFFheader . fread($this->getid3->fp, 128 - 12); if (substr($DIVXTAG, -7) == 'DIVXTAG') { // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file $info['warning'][] = 'Found wrongly-structured DIVXTAG at offset ' . (ftell($this->getid3->fp) - 128 + 12) . ', parsing anyway'; $thisfile_riff['DIVXTAG'] = $this->ParseDIVXTAG($DIVXTAG); foreach ($thisfile_riff['DIVXTAG'] as $key => $value) { if ($value && !preg_match('#_id$#', $key)) { $thisfile_riff['comments'][$key][] = $value; } } break 2; } } $info['warning'][] = 'expecting "RIFF" or "JUNK" at ' . $nextRIFFoffset . ', found ' . getid3_lib::PrintHexBytes(substr($nextRIFFheader, 0, 4)) . ' - skipping rest of file'; break 2; } } } if ($RIFFsubtype == 'WAVE') { $thisfile_riff_WAVE =& $thisfile_riff['WAVE']; } break; default: $info['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "' . $RIFFsubtype . '" instead'; unset($info['fileformat']); return false; break; } $streamindex = 0; switch ($RIFFsubtype) { case 'WAVE': if (empty($thisfile_audio['bitrate_mode'])) { $thisfile_audio['bitrate_mode'] = 'cbr'; } if (empty($thisfile_audio_dataformat)) { $thisfile_audio_dataformat = 'wav'; } if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { $info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8; $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size']; } if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || $thisfile_riff_audio[$streamindex]['bitrate'] == 0) { $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; return false; } $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; unset($thisfile_riff_audio[$streamindex]['raw']); $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { $info['warning'][] = 'Audio codec = ' . $thisfile_audio['codec']; } $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; $info['playtime_seconds'] = (double) (($info['avdataend'] - $info['avdataoffset']) * 8 / $thisfile_audio['bitrate']); $thisfile_audio['lossless'] = false; if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { case 0x1: // PCM $thisfile_audio['lossless'] = true; break; case 0x2000: // AC-3 $thisfile_audio_dataformat = 'ac3'; break; default: // do nothing break; } } $thisfile_audio['streams'][$streamindex]['wformattag'] = $thisfile_audio['wformattag']; $thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; $thisfile_audio['streams'][$streamindex]['lossless'] = $thisfile_audio['lossless']; $thisfile_audio['streams'][$streamindex]['dataformat'] = $thisfile_audio_dataformat; } if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) { // shortcuts $rgadData =& $thisfile_riff_WAVE['rgad'][0]['data']; $thisfile_riff_raw['rgad'] = array('track' => array(), 'album' => array()); $thisfile_riff_raw_rgad =& $thisfile_riff_raw['rgad']; $thisfile_riff_raw_rgad_track =& $thisfile_riff_raw_rgad['track']; $thisfile_riff_raw_rgad_album =& $thisfile_riff_raw_rgad['album']; $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); $thisfile_riff_raw_rgad['nRadioRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 4, 2)); $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 6, 2)); $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); $thisfile_riff_raw_rgad_track['name'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3)); $thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3)); $thisfile_riff_raw_rgad_track['signbit'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1)); $thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9)); $thisfile_riff_raw_rgad_album['name'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3)); $thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3)); $thisfile_riff_raw_rgad_album['signbit'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1)); $thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9)); $thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude']; if ($thisfile_riff_raw_rgad_track['name'] != 0 && $thisfile_riff_raw_rgad_track['originator'] != 0) { $thisfile_riff['rgad']['track']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']); $thisfile_riff['rgad']['track']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']); $thisfile_riff['rgad']['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']); } if ($thisfile_riff_raw_rgad_album['name'] != 0 && $thisfile_riff_raw_rgad_album['originator'] != 0) { $thisfile_riff['rgad']['album']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']); $thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']); $thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']); } } if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { $thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); // This should be a good way of calculating exact playtime, // but some sample files have had incorrect number of samples, // so cannot use this method // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { // $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; // } } if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { $thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8); } if (isset($thisfile_riff_WAVE['bext'][0]['data'])) { // shortcut $thisfile_riff_WAVE_bext_0 =& $thisfile_riff_WAVE['bext'][0]; $thisfile_riff_WAVE_bext_0['title'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 0, 256)); $thisfile_riff_WAVE_bext_0['author'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 256, 32)); $thisfile_riff_WAVE_bext_0['reference'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 288, 32)); $thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10); $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); $thisfile_riff_WAVE_bext_0['reserved'] = substr($thisfile_riff_WAVE_bext_0['data'], 347, 254); $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) { if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) { list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date; list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time; $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']); } else { $info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid'; } } else { $info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid'; } $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; } if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) { // shortcut $thisfile_riff_WAVE_MEXT_0 =& $thisfile_riff_WAVE['MEXT'][0]; $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2)); $thisfile_riff_WAVE_MEXT_0['flags']['homogenous'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x1); if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) { $thisfile_riff_WAVE_MEXT_0['flags']['padding'] = $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x2 ? false : true; $thisfile_riff_WAVE_MEXT_0['flags']['22_or_44'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x4); $thisfile_riff_WAVE_MEXT_0['flags']['free_format'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x8); $thisfile_riff_WAVE_MEXT_0['nominal_frame_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2)); } $thisfile_riff_WAVE_MEXT_0['anciliary_data_length'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2)); $thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2)); $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x1); $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x2); $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x4); } if (isset($thisfile_riff_WAVE['cart'][0]['data'])) { // shortcut $thisfile_riff_WAVE_cart_0 =& $thisfile_riff_WAVE['cart'][0]; $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); for ($i = 0; $i < 8; $i++) { $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] = substr($thisfile_riff_WAVE_cart_0['data'], 684 + $i * 8, 4); $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + $i * 8 + 4, 4)); } $thisfile_riff_WAVE_cart_0['url'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 748, 1024)); $thisfile_riff_WAVE_cart_0['tag_text'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772))); $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist']; $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; } if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) { // SoundMiner metadata // shortcuts $thisfile_riff_WAVE_SNDM_0 =& $thisfile_riff_WAVE['SNDM'][0]; $thisfile_riff_WAVE_SNDM_0_data =& $thisfile_riff_WAVE_SNDM_0['data']; $SNDM_startoffset = 0; $SNDM_endoffset = $thisfile_riff_WAVE_SNDM_0['size']; while ($SNDM_startoffset < $SNDM_endoffset) { $SNDM_thisTagOffset = 0; $SNDM_thisTagSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4)); $SNDM_thisTagOffset += 4; $SNDM_thisTagKey = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4); $SNDM_thisTagOffset += 4; $SNDM_thisTagDataSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); $SNDM_thisTagOffset += 2; $SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); $SNDM_thisTagOffset += 2; $SNDM_thisTagDataText = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize); $SNDM_thisTagOffset += $SNDM_thisTagDataSize; if ($SNDM_thisTagSize != 4 + 4 + 2 + 2 + $SNDM_thisTagDataSize) { $info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: ' . $SNDM_thisTagSize . ', found: ' . (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize) . ') at offset ' . $SNDM_startoffset . ' (file offset ' . ($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset) . ')'; break; } elseif ($SNDM_thisTagSize <= 0) { $info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset ' . $SNDM_startoffset . ' (file offset ' . ($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset) . ')'; break; } $SNDM_startoffset += $SNDM_thisTagSize; $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText; if ($parsedkey = $this->RIFFwaveSNDMtagLookup($SNDM_thisTagKey)) { $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText; } else { $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "' . $SNDM_thisTagKey . '" at offset ' . $SNDM_startoffset . ' (file offset ' . ($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset) . ')'; } } $tagmapping = array('tracktitle' => 'title', 'category' => 'genre', 'cdtitle' => 'album', 'tracktitle' => 'title'); foreach ($tagmapping as $fromkey => $tokey) { if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) { $thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey]; } } } if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) { // requires functions simplexml_load_string and get_object_vars if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) { $thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML; if (isset($parsedXML['SPEED']['MASTER_SPEED'])) { @(list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED'])); $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000); } if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) { @(list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE'])); $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000); } if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) { $samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'] . $parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0')); $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']; $h = floor($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] / 3600); $m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - $h * 3600) / 60); $s = floor($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - $h * 3600 - $m * 60); $f = ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - $h * 3600 - $m * 60 - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate']; $thisfile_riff_WAVE['iXML'][0]['timecode_string'] = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s, $f); $thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d', $h, $m, $s, round($f)); } unset($parsedXML); } } if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; $info['playtime_seconds'] = (double) (($info['avdataend'] - $info['avdataoffset']) * 8 / $thisfile_audio['bitrate']); } if (!empty($info['wavpack'])) { $thisfile_audio_dataformat = 'wavpack'; $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_audio['encoder'] = 'WavPack v' . $info['wavpack']['version']; // Reset to the way it was - RIFF parsing will have messed this up $info['avdataend'] = $Original['avdataend']; $thisfile_audio['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; fseek($this->getid3->fp, $info['avdataoffset'] - 44, SEEK_SET); $RIFFdata = fread($this->getid3->fp, 44); $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { $info['avdataend'] -= $OrignalRIFFheaderSize - $OrignalRIFFdataSize; fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); $RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); } // move the data chunk after all other chunks (if any) // so that the RIFF parser doesn't see EOF when trying // to skip over the data chunk $RIFFdata = substr($RIFFdata, 0, 36) . substr($RIFFdata, 44) . substr($RIFFdata, 36, 8); $getid3_riff = new getid3_riff($this->getid3); $getid3_riff->ParseRIFFdata($RIFFdata); unset($getid3_riff); } if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { case 0x1: // PCM if (!empty($info['ac3'])) { // Dolby Digital WAV files masquerade as PCM-WAV, but they're not $thisfile_audio['wformattag'] = 0x2000; $thisfile_audio['codec'] = $this->RIFFwFormatTagLookup($thisfile_audio['wformattag']); $thisfile_audio['lossless'] = false; $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; } break; case 0x8ae: // ClearJump LiteWave $thisfile_audio['bitrate_mode'] = 'vbr'; $thisfile_audio_dataformat = 'litewave'; //typedef struct tagSLwFormat { // WORD m_wCompFormat; // low byte defines compression method, high byte is compression flags // DWORD m_dwScale; // scale factor for lossy compression // DWORD m_dwBlockSize; // number of samples in encoded blocks // WORD m_wQuality; // alias for the scale factor // WORD m_wMarkDistance; // distance between marks in bytes // WORD m_wReserved; // // //following paramters are ignored if CF_FILESRC is not set // DWORD m_dwOrgSize; // original file size in bytes // WORD m_bFactExists; // indicates if 'fact' chunk exists in the original file // DWORD m_dwRiffChunkSize; // riff chunk size in the original file // // PCMWAVEFORMAT m_OrgWf; // original wave format // }SLwFormat, *PSLwFormat; // shortcut $thisfile_riff['litewave']['raw'] = array(); $thisfile_riff_litewave =& $thisfile_riff['litewave']; $thisfile_riff_litewave_raw =& $thisfile_riff_litewave['raw']; $thisfile_riff_litewave_raw['compression_method'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 18, 1)); $thisfile_riff_litewave_raw['compression_flags'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 19, 1)); $thisfile_riff_litewave_raw['m_dwScale'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 20, 4)); $thisfile_riff_litewave_raw['m_dwBlockSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 24, 4)); $thisfile_riff_litewave_raw['m_wQuality'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 28, 2)); $thisfile_riff_litewave_raw['m_wMarkDistance'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 30, 2)); $thisfile_riff_litewave_raw['m_wReserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 32, 2)); $thisfile_riff_litewave_raw['m_dwOrgSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 34, 4)); $thisfile_riff_litewave_raw['m_bFactExists'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 38, 2)); $thisfile_riff_litewave_raw['m_dwRiffChunkSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 40, 4)); //$thisfile_riff_litewave['quality_factor'] = intval(round((2000 - $thisfile_riff_litewave_raw['m_dwScale']) / 20)); $thisfile_riff_litewave['quality_factor'] = $thisfile_riff_litewave_raw['m_wQuality']; $thisfile_riff_litewave['flags']['raw_source'] = $thisfile_riff_litewave_raw['compression_flags'] & 0x1 ? false : true; $thisfile_riff_litewave['flags']['vbr_blocksize'] = $thisfile_riff_litewave_raw['compression_flags'] & 0x2 ? false : true; $thisfile_riff_litewave['flags']['seekpoints'] = (bool) ($thisfile_riff_litewave_raw['compression_flags'] & 0x4); $thisfile_audio['lossless'] = $thisfile_riff_litewave_raw['m_wQuality'] == 100 ? true : false; $thisfile_audio['encoder_options'] = '-q' . $thisfile_riff_litewave['quality_factor']; break; default: break; } } if ($info['avdataend'] > $info['filesize']) { switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') { case 'wavpack': // WavPack // WavPack case 'lpac': // LPAC // LPAC case 'ofr': // OptimFROG // OptimFROG case 'ofs': // OptimFROG DualStream // lossless compressed audio formats that keep original RIFF headers - skip warning break; case 'litewave': 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 // byte, in which case - skip warning } else { // Short by more than one byte, throw warning $info['warning'][] = 'Probably truncated file - expecting ' . $thisfile_riff[$RIFFsubtype]['data'][0]['size'] . ' bytes of data, only found ' . ($info['filesize'] - $info['avdataoffset']) . ' (short by ' . ($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])) . ' bytes)'; $info['avdataend'] = $info['filesize']; } break; default: if ($info['avdataend'] - $info['filesize'] == 1 && $thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2 == 0 && ($info['filesize'] - $info['avdataoffset']) % 2 == 1) { // output file appears to be incorrectly *not* padded to nearest WORD boundary // Output less severe warning $info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting ' . $thisfile_riff[$RIFFsubtype]['data'][0]['size'] . ' bytes of data, only found ' . ($info['filesize'] - $info['avdataoffset']) . ' therefore short by ' . ($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])) . ' bytes)'; $info['avdataend'] = $info['filesize']; } else { // Short by more than one byte, throw warning $info['warning'][] = 'Probably truncated file - expecting ' . $thisfile_riff[$RIFFsubtype]['data'][0]['size'] . ' bytes of data, only found ' . ($info['filesize'] - $info['avdataoffset']) . ' (short by ' . ($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])) . ' bytes)'; $info['avdataend'] = $info['filesize']; } break; } } if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) { if ($info['avdataend'] - $info['avdataoffset'] - $info['mpeg']['audio']['LAME']['audio_bytes'] == 1) { $info['avdataend']--; $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; } } if (isset($thisfile_audio_dataformat) && $thisfile_audio_dataformat == 'ac3') { unset($thisfile_audio['bits_per_sample']); if (!empty($info['ac3']['bitrate']) && $info['ac3']['bitrate'] != $thisfile_audio['bitrate']) { $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; } } break; case '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; if (isset($thisfile_riff['AVIX'])) { $info['avdataend'] = $thisfile_riff['AVIX'][count($thisfile_riff['AVIX']) - 1]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][count($thisfile_riff['AVIX']) - 1]['chunks']['movi']['size']; } else { $info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size']; } if ($info['avdataend'] > $info['filesize']) { $info['warning'][] = 'Probably truncated file - expecting ' . ($info['avdataend'] - $info['avdataoffset']) . ' bytes of data, only found ' . ($info['filesize'] - $info['avdataoffset']) . ' (short by ' . ($info['avdataend'] - $info['filesize']) . ' bytes)'; $info['avdataend'] = $info['filesize']; } } if (isset($thisfile_riff['AVI ']['hdrl']['strl']['indx'])) { //$bIndexType = array( // 0x00 => 'AVI_INDEX_OF_INDEXES', // 0x01 => 'AVI_INDEX_OF_CHUNKS', // 0x80 => 'AVI_INDEX_IS_DATA', //); //$bIndexSubtype = array( // 0x01 => array( // 0x01 => 'AVI_INDEX_2FIELD', // ), //); foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { $thisfile_riff_avi_hdrl_strl_indx_stream_data =& $thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; unset($thisfile_riff_avi_hdrl_strl_indx_stream_data); } } if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) { $avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data']; // shortcut $thisfile_riff_raw['avih'] = array(); $thisfile_riff_raw_avih =& $thisfile_riff_raw['avih']; $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = $this->EitherEndian2Int(substr($avihData, 0, 4)); // frame display rate (or 0L) if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; return false; } $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = $this->EitherEndian2Int(substr($avihData, 4, 4)); // max. transfer rate $thisfile_riff_raw_avih['dwPaddingGranularity'] = $this->EitherEndian2Int(substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. $thisfile_riff_raw_avih['dwFlags'] = $this->EitherEndian2Int(substr($avihData, 12, 4)); // the ever-present flags $thisfile_riff_raw_avih['dwTotalFrames'] = $this->EitherEndian2Int(substr($avihData, 16, 4)); // # frames in file $thisfile_riff_raw_avih['dwInitialFrames'] = $this->EitherEndian2Int(substr($avihData, 20, 4)); $thisfile_riff_raw_avih['dwStreams'] = $this->EitherEndian2Int(substr($avihData, 24, 4)); $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($avihData, 28, 4)); $thisfile_riff_raw_avih['dwWidth'] = $this->EitherEndian2Int(substr($avihData, 32, 4)); $thisfile_riff_raw_avih['dwHeight'] = $this->EitherEndian2Int(substr($avihData, 36, 4)); $thisfile_riff_raw_avih['dwScale'] = $this->EitherEndian2Int(substr($avihData, 40, 4)); $thisfile_riff_raw_avih['dwRate'] = $this->EitherEndian2Int(substr($avihData, 44, 4)); $thisfile_riff_raw_avih['dwStart'] = $this->EitherEndian2Int(substr($avihData, 48, 4)); $thisfile_riff_raw_avih['dwLength'] = $this->EitherEndian2Int(substr($avihData, 52, 4)); $thisfile_riff_raw_avih['flags']['hasindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x10); $thisfile_riff_raw_avih['flags']['mustuseindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x20); $thisfile_riff_raw_avih['flags']['interleaved'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x100); $thisfile_riff_raw_avih['flags']['trustcktype'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x800); $thisfile_riff_raw_avih['flags']['capturedfile'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x10000); $thisfile_riff_raw_avih['flags']['copyrighted'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x20010); // shortcut $thisfile_riff_video[$streamindex] = array(); $thisfile_riff_video_current =& $thisfile_riff_video[$streamindex]; if ($thisfile_riff_raw_avih['dwWidth'] > 0) { $thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth']; $thisfile_video['resolution_x'] = $thisfile_riff_video_current['frame_width']; } if ($thisfile_riff_raw_avih['dwHeight'] > 0) { $thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight']; $thisfile_video['resolution_y'] = $thisfile_riff_video_current['frame_height']; } if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { $thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames']; $thisfile_video['total_frames'] = $thisfile_riff_video_current['total_frames']; } $thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3); $thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate']; } if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) { if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) { for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) { if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) { $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data']; $strhfccType = substr($strhData, 0, 4); if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) { $strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data']; // shortcut $thisfile_riff_raw_strf_strhfccType_streamindex =& $thisfile_riff_raw['strf'][$strhfccType][$streamindex]; switch ($strhfccType) { case 'auds': $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'wav'; if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) { $streamindex = count($thisfile_riff_audio); } $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($strfData); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; // shortcut $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; $thisfile_audio_streams_currentstream =& $thisfile_audio['streams'][$streamindex]; if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) { unset($thisfile_audio_streams_currentstream['bits_per_sample']); } $thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag']; unset($thisfile_audio_streams_currentstream['raw']); // shortcut $thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw']; unset($thisfile_riff_audio[$streamindex]['raw']); $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); $thisfile_audio['lossless'] = false; switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) { case 0x1: // PCM $thisfile_audio_dataformat = 'wav'; $thisfile_audio['lossless'] = true; break; case 0x50: // MPEG Layer 2 or Layer 1 $thisfile_audio_dataformat = 'mp2'; // Assume Layer-2 break; case 0x55: // MPEG Layer 3 $thisfile_audio_dataformat = 'mp3'; break; case 0xff: // AAC $thisfile_audio_dataformat = 'aac'; break; case 0x161: // Windows Media v7 / v8 / v9 // Windows Media v7 / v8 / v9 case 0x162: // Windows Media Professional v9 // Windows Media Professional v9 case 0x163: // Windows Media Lossess v9 $thisfile_audio_dataformat = 'wma'; break; case 0x2000: // AC-3 $thisfile_audio_dataformat = 'ac3'; break; case 0x2001: // DTS $thisfile_audio_dataformat = 'dts'; break; default: $thisfile_audio_dataformat = 'wav'; break; } $thisfile_audio_streams_currentstream['dataformat'] = $thisfile_audio_dataformat; $thisfile_audio_streams_currentstream['lossless'] = $thisfile_audio['lossless']; $thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode']; break; case 'iavs': case 'vids': // shortcut $thisfile_riff_raw['strh'][$i] = array(); $thisfile_riff_raw_strh_current =& $thisfile_riff_raw['strh'][$i]; $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); $thisfile_riff_raw_strh_current['dwFlags'] = $this->EitherEndian2Int(substr($strhData, 8, 4)); // Contains AVITF_* flags $thisfile_riff_raw_strh_current['wPriority'] = $this->EitherEndian2Int(substr($strhData, 12, 2)); $thisfile_riff_raw_strh_current['wLanguage'] = $this->EitherEndian2Int(substr($strhData, 14, 2)); $thisfile_riff_raw_strh_current['dwInitialFrames'] = $this->EitherEndian2Int(substr($strhData, 16, 4)); $thisfile_riff_raw_strh_current['dwScale'] = $this->EitherEndian2Int(substr($strhData, 20, 4)); $thisfile_riff_raw_strh_current['dwRate'] = $this->EitherEndian2Int(substr($strhData, 24, 4)); $thisfile_riff_raw_strh_current['dwStart'] = $this->EitherEndian2Int(substr($strhData, 28, 4)); $thisfile_riff_raw_strh_current['dwLength'] = $this->EitherEndian2Int(substr($strhData, 32, 4)); $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4)); $thisfile_riff_raw_strh_current['dwQuality'] = $this->EitherEndian2Int(substr($strhData, 40, 4)); $thisfile_riff_raw_strh_current['dwSampleSize'] = $this->EitherEndian2Int(substr($strhData, 44, 4)); $thisfile_riff_raw_strh_current['rcFrame'] = $this->EitherEndian2Int(substr($strhData, 48, 4)); $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strh_current['fccHandler']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; } $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; $thisfile_video['pixel_aspect_ratio'] = (double) 1; switch ($thisfile_riff_raw_strh_current['fccHandler']) { case 'HFYU': // Huffman Lossless Codec // Huffman Lossless Codec case 'IRAW': // Intel YUV Uncompressed // Intel YUV Uncompressed case 'YUY2': // Uncompressed YUV 4:2:2 $thisfile_video['lossless'] = true; break; default: $thisfile_video['lossless'] = false; break; } switch ($strhfccType) { case 'vids': $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), $info['fileformat'] == 'riff'); //echo '<pre>'.print_r($thisfile_riff_raw_strf_strhfccType_streamindex, true).'</pre>'; $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; if ($thisfile_riff_video_current['codec'] == 'DV') { $thisfile_riff_video_current['dv_type'] = 2; } break; case 'iavs': $thisfile_riff_video_current['dv_type'] = 1; break; } break; default: $info['warning'][] = 'Unhandled fccType for stream (' . $i . '): "' . $strhfccType . '"'; break; } } } if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; if (getid3_riff::RIFFfourccLookup($thisfile_video['fourcc'])) { $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_video['fourcc']); $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; } switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { case 'HFYU': // Huffman Lossless Codec // Huffman Lossless Codec case 'IRAW': // Intel YUV Uncompressed // Intel YUV Uncompressed case 'YUY2': // Uncompressed YUV 4:2:2 $thisfile_video['lossless'] = true; //$thisfile_video['bits_per_sample'] = 24; break; default: $thisfile_video['lossless'] = false; //$thisfile_video['bits_per_sample'] = 24; break; } } } } } break; case 'CDDA': $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'cda'; $thisfile_audio['lossless'] = true; unset($info['mime_type']); $info['avdataoffset'] = 44; if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { // shortcut $thisfile_riff_CDDA_fmt_0 =& $thisfile_riff['CDDA']['fmt '][0]; $thisfile_riff_CDDA_fmt_0['unknown1'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); $thisfile_riff_CDDA_fmt_0['track_num'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); $thisfile_riff_CDDA_fmt_0['disc_id'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); $thisfile_riff_CDDA_fmt_0['playtime_frames'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); $thisfile_riff_CDDA_fmt_0['unknown6'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); $thisfile_riff_CDDA_fmt_0['unknown7'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (double) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (double) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; $info['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; // hardcoded data for CD-audio $thisfile_audio['sample_rate'] = 44100; $thisfile_audio['channels'] = 2; $thisfile_audio['bits_per_sample'] = 16; $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample']; $thisfile_audio['bitrate_mode'] = 'cbr'; } break; case 'AIFF': case 'AIFC': $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; $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; if ($info['avdataend'] > $info['filesize']) { if ($info['avdataend'] == $info['filesize'] + 1 && $info['filesize'] % 2 == 1) { // structures rounded to 2-byte boundary, but dumb encoders // forget to pad end of file to make this actually work } else { $info['warning'][] = 'Probable truncated AIFF file: expecting ' . $thisfile_riff[$RIFFsubtype]['SSND'][0]['size'] . ' bytes of audio data, only ' . ($info['filesize'] - $info['avdataoffset']) . ' bytes found'; } $info['avdataend'] = $info['filesize']; } } if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) { // shortcut $thisfile_riff_RIFFsubtype_COMM_0_data =& $thisfile_riff[$RIFFsubtype]['COMM'][0]['data']; $thisfile_riff_audio['channels'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 0, 2), true); $thisfile_riff_audio['total_samples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 2, 4), false); $thisfile_riff_audio['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 6, 2), true); $thisfile_riff_audio['sample_rate'] = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 8, 10)); if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) { $thisfile_riff_audio['codec_fourcc'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18, 4); $CodecNameSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22, 1), false); $thisfile_riff_audio['codec_name'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23, $CodecNameSize); switch ($thisfile_riff_audio['codec_name']) { case 'NONE': $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; $thisfile_audio['lossless'] = true; break; case '': switch ($thisfile_riff_audio['codec_fourcc']) { // http://developer.apple.com/qa/snd/snd07.html case 'sowt': $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM'; $thisfile_audio['lossless'] = true; break; case 'twos': $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM'; $thisfile_audio['lossless'] = true; break; default: break; } break; default: $thisfile_audio['codec'] = $thisfile_riff_audio['codec_name']; $thisfile_audio['lossless'] = false; break; } } $thisfile_audio['channels'] = $thisfile_riff_audio['channels']; if ($thisfile_riff_audio['bits_per_sample'] > 0) { $thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample']; } $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; if ($thisfile_audio['sample_rate'] == 0) { $info['error'][] = 'Corrupted AIFF file: sample_rate == zero'; return false; } $info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; } if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { $offset = 0; $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; for ($i = 0; $i < $CommentCount; $i++) { $info['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); $offset += 4; $info['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); $offset += 2; $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); $offset += 2; $info['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); $offset += $CommentLength; $info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']); $thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment']; } } $CommentsChunkNames = array('NAME' => 'title', 'author' => 'artist', '(c) ' => 'copyright', 'ANNO' => 'comment'); foreach ($CommentsChunkNames as $key => $value) { if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } 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); $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { $info['id3v2'] = $getid3_temp->info['id3v2']; } unset($getid3_temp, $getid3_id3v2); } break; case '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; $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; if ($info['avdataend'] > $info['filesize']) { $info['warning'][] = 'Probable truncated AIFF file: expecting ' . $thisfile_riff[$RIFFsubtype]['BODY'][0]['size'] . ' bytes of audio data, only ' . ($info['filesize'] - $info['avdataoffset']) . ' bytes found'; } } if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) { // shortcut $thisfile_riff_RIFFsubtype_VHDR_0 =& $thisfile_riff[$RIFFsubtype]['VHDR'][0]; $thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 0, 4)); $thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 4, 4)); $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 8, 4)); $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2)); $thisfile_riff_RIFFsubtype_VHDR_0['ctOctave'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1)); $thisfile_riff_RIFFsubtype_VHDR_0['sCompression'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1)); $thisfile_riff_RIFFsubtype_VHDR_0['Volume'] = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4)); $thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']; switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) { case 0: $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; $thisfile_audio['lossless'] = true; $ActualBitsPerSample = 8; break; case 1: $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; $thisfile_audio['lossless'] = false; $ActualBitsPerSample = 4; break; default: $info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "' . sCompression . '"'; break; } } if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) { $ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4)); switch ($ChannelsIndex) { case 6: // Stereo $thisfile_audio['channels'] = 2; break; case 2: // Left channel only // Left channel only case 4: // Right channel only $thisfile_audio['channels'] = 1; break; default: $info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "' . $ChannelsIndex . '"'; break; } } $CommentsChunkNames = array('NAME' => 'title', 'author' => 'artist', '(c) ' => 'copyright', 'ANNO' => 'comment'); foreach ($CommentsChunkNames as $key => $value) { if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; if (!empty($thisfile_audio['bitrate'])) { $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8); } break; 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); } } 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; } if (isset($thisfile_riff_raw['fmt ']['wFormatTag']) && $thisfile_riff_raw['fmt ']['wFormatTag'] == 1) { // http://www.mega-nerd.com/erikd/Blog/Windiots/dts.html fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $FirstFourBytes = fread($this->getid3->fp, 4); if (preg_match('/^\\xFF\\x1F\\x00\\xE8/s', $FirstFourBytes)) { // DTSWAV $thisfile_audio_dataformat = 'dts'; } elseif (preg_match('/^\\x7F\\xFF\\x80\\x01/s', $FirstFourBytes)) { // DTS, but this probably shouldn't happen $thisfile_audio_dataformat = 'dts'; } } if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); } if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { $this->RIFFcommentsParse($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); } if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) { $this->RIFFcommentsParse($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); } if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) { $thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version']; } if (!isset($info['playtime_seconds'])) { $info['playtime_seconds'] = 0; } if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { // needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie $info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); } elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { $info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); } if ($info['playtime_seconds'] > 0) { if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { if (!isset($info['bitrate'])) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds'] * 8; } } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { if (!isset($thisfile_audio['bitrate'])) { $thisfile_audio['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds'] * 8; } } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { if (!isset($thisfile_video['bitrate'])) { $thisfile_video['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds'] * 8; } } } if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && $thisfile_audio['bitrate'] > 0 && $info['playtime_seconds'] > 0) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds'] * 8; $thisfile_audio['bitrate'] = 0; $thisfile_video['bitrate'] = $info['bitrate']; foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; } if ($thisfile_video['bitrate'] <= 0) { unset($thisfile_video['bitrate']); } if ($thisfile_audio['bitrate'] <= 0) { unset($thisfile_audio['bitrate']); } } if (isset($info['mpeg']['audio'])) { $thisfile_audio_dataformat = 'mp' . $info['mpeg']['audio']['layer']; $thisfile_audio['sample_rate'] = $info['mpeg']['audio']['sample_rate']; $thisfile_audio['channels'] = $info['mpeg']['audio']['channels']; $thisfile_audio['bitrate'] = $info['mpeg']['audio']['bitrate']; $thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); if (!empty($info['mpeg']['audio']['codec'])) { $thisfile_audio['codec'] = $info['mpeg']['audio']['codec'] . ' ' . $thisfile_audio['codec']; } if (!empty($thisfile_audio['streams'])) { foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { if ($streamdata['dataformat'] == $thisfile_audio_dataformat) { $thisfile_audio['streams'][$streamnumber]['sample_rate'] = $thisfile_audio['sample_rate']; $thisfile_audio['streams'][$streamnumber]['channels'] = $thisfile_audio['channels']; $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; $thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; $thisfile_audio['streams'][$streamnumber]['codec'] = $thisfile_audio['codec']; } } } $getid3_mp3 = new getid3_mp3($this->getid3); $thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions(); unset($getid3_mp3); } if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && $thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0) { switch ($thisfile_audio_dataformat) { case 'ac3': // ignore bits_per_sample break; default: $thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample']; break; } } if (empty($thisfile_riff_raw)) { unset($thisfile_riff['raw']); } if (empty($thisfile_riff_audio)) { unset($thisfile_riff['audio']); } if (empty($thisfile_riff_video)) { unset($thisfile_riff['video']); } return true; }
function getid3_matroska(&$fd, &$ThisFileInfo) { // http://www.matroska.org/technical/specs/index.html#EBMLBasics $offset = $ThisFileInfo['avdataoffset']; $EBMLdata = ''; $EBMLdata_offset = $offset; if ($ThisFileInfo['avdataend'] > 2147483648) { $this->warnings[] = 'This version of getID3() may or may not correctly handle Matroska files larger than 2GB'; } while ($offset < $ThisFileInfo['avdataend']) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $top_element_offset = $offset; $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($top_element_length === false) { $this->warnings[] = 'invalid chunk length at ' . $top_element_offset; $offset = pow(2, 63); break; } $top_element_endoffset = $offset + $top_element_length; switch ($top_element_id) { case EBML_ID_EBML: $ThisFileInfo['fileformat'] = 'matroska'; $ThisFileInfo['matroska']['header']['offset'] = $top_element_offset; $ThisFileInfo['matroska']['header']['length'] = $top_element_length; while ($offset < $top_element_endoffset) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data_offset = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $end_offset = $offset + $element_data_length; switch ($element_data['id']) { case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data_length)); break; case EBML_ID_DOCTYPE: $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data_length), ""); break; default: $this->warnings[] = 'Unhandled track.video element[' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data_offset; break; } $offset = $end_offset; $ThisFileInfo['matroska']['header']['elements'][] = $element_data; } break; case EBML_ID_SEGMENT: $ThisFileInfo['matroska']['segment'][0]['offset'] = $top_element_offset; $ThisFileInfo['matroska']['segment'][0]['length'] = $top_element_length; $segment_key = -1; while ($offset < $ThisFileInfo['avdataend']) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data['offset'] = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($element_data_length === false) { $this->warnings[] = 'invalid chunk length at ' . $element_data['offset']; $offset = pow(2, 63); break; } $element_end = $offset + $element_data_length; switch ($element_data['id']) { //case EBML_ID_CLUSTER: // // too many cluster entries, probably not useful // break; case false: $this->warnings[] = 'invalid ID at ' . $element_data['offset']; $offset = $element_end; continue 3; default: $ThisFileInfo['matroska']['segments'][] = $element_data; break; } $segment_key++; switch ($element_data['id']) { case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $seek_entry = array(); $seek_entry['offset'] = $offset; $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_end_offset = $offset + $seek_entry['length']; switch ($seek_entry['id']) { case EBML_ID_SEEK: // Contains a single seek entry to an EBML element while ($offset < $seek_end_offset) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); $offset += $length; switch ($id) { case EBML_ID_SEEKID: $dummy = 0; $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); break; case EBML_ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); break; default: $ThisFileInfo['error'][] = 'Unhandled segment[' . __LINE__ . '] (' . $id . ') at ' . $offset; break; } } $ThisFileInfo['matroska']['seek'][] = $seek_entry; //switch ($seek_entry['target_id']) { // case EBML_ID_CLUSTER: // // too many cluster seek points, probably not useful // break; // default: // $ThisFileInfo['matroska']['seek'][] = $seek_entry; // break; //} break; default: $this->warnings[] = 'Unhandled seekhead element[' . __LINE__ . '] (' . $seek_entry['id'] . ') at ' . $offset; break; } $offset = $seek_end_offset; } break; case EBML_ID_TRACKS: // information about all tracks in segment $ThisFileInfo['matroska']['tracks'] = $element_data; while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $track_entry = array(); $track_entry['offset'] = $offset; $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry_endoffset = $offset + $track_entry['length']; switch ($track_entry['id']) { case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($offset < $track_entry_endoffset) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: case EBML_ID_MINCACHE: case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_TRACKTIMECODESCALE: $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: case EBML_ID_CODECPRIVATE: $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_VIDEO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: case EBML_ID_PIXELCROPRIGHT: case EBML_ID_DISPLAYWIDTH: case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_FLAGINTERLACED: $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_GAMMAVALUE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_COLOURSPACE: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.video element[' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (@$track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC' && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); } else { $this->warnings[] = 'Unable to parse codec private data[' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'; } } break; case EBML_ID_AUDIO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHANNELPOSITIONS: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), ""); break; default: $this->warnings[] = 'Unhandled track.video element[' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track element[' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ') at ' . $subelement_offset; break; } $offset = $subelement_end; } break; default: $this->warnings[] = 'Unhandled track element[' . __LINE__ . '] (' . $track_entry['id'] . '::' . $track_entry['id_name'] . ') at ' . $track_entry['offset']; $offset = $track_entry_endoffset; break; } $ThisFileInfo['matroska']['tracks']['tracks'][] = $track_entry; } break; case EBML_ID_INFO: // Contains the position of other level 1 elements $info_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DURATION: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DATEUTC: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); $info_entry[$subelement_idname . '_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); break; case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), ""); break; default: $this->warnings[] = 'Unhandled info element[' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ') at ' . $subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['info'][] = $info_entry; break; case EBML_ID_CUES: $cues_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CUEPOINT: $cuepoint_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CUETRACKPOSITIONS: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CUETRACK: $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element[' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CUETIME: $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint element[' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled cues element[' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ') at ' . $subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['cues'] = $cues_entry; break; case EBML_ID_TAGS: $tags_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_WRITINGAPP: $tags_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); break; case EBML_ID_TAG: $tag_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_TARGETS: $targets_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TARGETTYPEVALUE: case EBML_ID_EDITIONUID: case EBML_ID_CHAPTERUID: case EBML_ID_ATTACHMENTUID: case EBML_ID_TAGTRACKUID: case EBML_ID_TAGCHAPTERUID: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.targets element[' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $targets_entry; break; case EBML_ID_SIMPLETAG: $simpletag_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TAGNAME: case EBML_ID_TAGLANGUAGE: case EBML_ID_TAGSTRING: case EBML_ID_TAGBINARY: $simpletag_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_TAGDEFAULT: $simpletag_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.simpletag element[' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $simpletag_entry; break; case EBML_ID_TARGETTYPE: $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_TRACKUID: $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tags.tag element[' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $tags_entry['tags'][] = $tag_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled tags element[' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ') at ' . $subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['tags'] = $tags_entry; break; case EBML_ID_ATTACHMENTS: while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_FILEDATA: $attachedfile_entry['data_offset'] = $offset; $attachedfile_entry['data_length'] = $sub_subelement_length; if ($sub_subelement_length < 1024) { $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); } break; case EBML_ID_FILEUID: $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled attachment.attachedfile element[' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $ThisFileInfo['matroska']['attachments'][] = $attachedfile_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled tags element[' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ') at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_EDITIONUID: $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element[' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; break; case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element[' . __LINE__ . '] (' . $sub_sub_sub_subelement_id . '::' . $sub_sub_sub_subelement_idname . ') at ' . $sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element[' . __LINE__ . '] (' . $sub_sub_subelement_id . '::' . $sub_sub_subelement_idname . ') at ' . $sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry element[' . __LINE__ . '] (' . $sub_subelement_id . '::' . $sub_subelement_idname . ') at ' . $sub_subelement_offset; break; } $offset = $sub_subelement_end; } $ThisFileInfo['matroska']['chapters'][] = $editionentry_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled chapters element[' . __LINE__ . '] (' . $subelement_id . '::' . $subelement_idname . ') at ' . $subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_VOID: // padding, ignore // padding, ignore case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore break; default: if ($element_data['id_name'] == dechex($element_data['id'])) { $ThisFileInfo['error'][] = 'Unhandled segment[' . __LINE__ . '] (' . $element_data['id'] . ') at ' . $element_data_offset; } else { $this->warnings[] = 'Unhandled segment[' . __LINE__ . '] (' . $element_data['id'] . '::' . $element_data['id_name'] . ') at ' . $element_data['offset']; } break; } $offset = $element_end; } break; default: $ThisFileInfo['error'][] = 'Unhandled chunk[' . __LINE__ . '] (' . $top_element_id . ') at ' . $offset; break; } $offset = $top_element_endoffset; } if ($this->hide_clusters) { // too much data returned that is usually not useful if (isset($ThisFileInfo['matroska']['segments']) && is_array($ThisFileInfo['matroska']['segments'])) { foreach ($ThisFileInfo['matroska']['segments'] as $key => $segmentsarray) { if ($segmentsarray['id'] == EBML_ID_CLUSTER) { unset($ThisFileInfo['matroska']['segments'][$key]); } } } if (isset($ThisFileInfo['matroska']['seek']) && is_array($ThisFileInfo['matroska']['seek'])) { foreach ($ThisFileInfo['matroska']['seek'] as $key => $seekarray) { if ($seekarray['target_id'] == EBML_ID_CLUSTER) { unset($ThisFileInfo['matroska']['seek'][$key]); } } } } if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { foreach ($ThisFileInfo['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $ThisFileInfo['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { foreach ($ThisFileInfo['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); switch (@$trackarray['TrackType']) { case 1: // Video if (@$trackarray['PixelWidth']) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } if (@$trackarray['PixelHeight']) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } if (@$trackarray['DisplayWidth']) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (@$trackarray['DisplayHeight']) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (@$trackarray['DefaultDuration']) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (@$trackarray['CodecID']) { $track_info['codec'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['codec_private_parsed']['fourcc'])) { $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; } $ThisFileInfo['video']['streams'][] = $track_info; if (isset($track_info['resolution_x']) && empty($ThisFileInfo['video']['resolution_x'])) { foreach ($track_info as $key => $value) { $ThisFileInfo['video'][$key] = $value; } } break; case 2: // Audio if (@$trackarray['CodecID']) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (@$trackarray['SamplingFrequency']) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } if (@$trackarray['Channels']) { $track_info['channels'] = $trackarray['Channels']; } if (@$trackarray['BitDepth']) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } switch (@$trackarray[$this->EBMLidName(EBML_ID_CODECID)]) { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; default: break; } $ThisFileInfo['audio']['streams'][] = $track_info; if (isset($track_info['dataformat']) && empty($ThisFileInfo['audio']['dataformat'])) { foreach ($track_info as $key => $value) { $ThisFileInfo['audio'][$key] = $value; } } break; default: // ignore, do nothing break; } } } if (!empty($ThisFileInfo['video']['streams'])) { $ThisFileInfo['mime_type'] = 'video/x-matroska'; } elseif (!empty($ThisFileInfo['video']['streams'])) { $ThisFileInfo['mime_type'] = 'audio/x-matroska'; } elseif (isset($ThisFileInfo['mime_type'])) { unset($ThisFileInfo['mime_type']); } foreach ($this->warnings as $key => $value) { $ThisFileInfo['warning'][] = $value; } return true; }
function getid3_matroska(&$fd, &$ThisFileInfo) { // http://www.matroska.org/technical/specs/index.html#EBMLBasics $offset = $ThisFileInfo['avdataoffset']; $EBMLdata = ''; $EBMLdata_offset = $offset; if ($ThisFileInfo['avdataend'] > 2147483648) { $this->warnings[] = 'This version of getID3() may or may not correctly handle Matroska files larger than 2GB'; } while ($offset < $ThisFileInfo['avdataend']) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $top_element_offset = $offset; $top_element_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $top_element_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($top_element_length === false) { $this->warnings[] = 'invalid chunk length at '.$top_element_offset; $offset = pow(2, 63); break; } $top_element_endoffset = $offset + $top_element_length; switch ($top_element_id) { case EBML_ID_EBML: $ThisFileInfo['fileformat'] = 'matroska'; $ThisFileInfo['matroska']['header']['offset'] = $top_element_offset; $ThisFileInfo['matroska']['header']['length'] = $top_element_length; while ($offset < $top_element_endoffset) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data_offset = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $end_offset = $offset + $element_data['length']; switch ($element_data['id']) { case EBML_ID_VOID: // padding, ignore break; case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: $element_data['data'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length'])); break; case EBML_ID_DOCTYPE: $element_data['data'] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $element_data['length']), "\x00"); break; default: $this->warnings[] = 'Unhandled track.video element ['.basename(__FILE__).':'.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data_offset; break; } $offset = $end_offset; $ThisFileInfo['matroska']['header']['elements'][] = $element_data; } break; case EBML_ID_SEGMENT: $ThisFileInfo['matroska']['segment'][0]['offset'] = $top_element_offset; $ThisFileInfo['matroska']['segment'][0]['length'] = $top_element_length; $segment_key = -1; while ($offset < $ThisFileInfo['avdataend']) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $element_data = array(); $element_data['offset'] = $offset; $element_data['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $element_data['id_name'] = $this->EBMLidName($element_data['id']); $element_data['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); if ($element_data['length'] === false) { $this->warnings[] = 'invalid chunk length at '.$element_data['offset']; //$offset = pow(2, 63); $offset = $ThisFileInfo['avdataend']; break; } $element_end = $offset + $element_data['length']; switch ($element_data['id']) { //case EBML_ID_CLUSTER: // // too many cluster entries, probably not useful // break; case false: $this->warnings[] = 'invalid ID at '.$element_data['offset']; $offset = $element_end; continue 3; default: $ThisFileInfo['matroska']['segments'][] = $element_data; break; } $segment_key++; switch ($element_data['id']) { case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $seek_entry = array(); $seek_entry['offset'] = $offset; $seek_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_entry['id_name'] = $this->EBMLidName($seek_entry['id']); $seek_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $seek_end_offset = $offset + $seek_entry['length']; switch ($seek_entry['id']) { case EBML_ID_SEEK: // Contains a single seek entry to an EBML element while ($offset < $seek_end_offset) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $value = substr($EBMLdata, $offset - $EBMLdata_offset, $length); $offset += $length; switch ($id) { case EBML_ID_SEEKID: $dummy = 0; $seek_entry['target_id'] = $this->readEBMLint($value, $dummy); $seek_entry['target_name'] = $this->EBMLidName($seek_entry['target_id']); break; case EBML_ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($value); break; default: $ThisFileInfo['error'][] = 'Unhandled segment ['.basename(__FILE__).':'.__LINE__.'] ('.$id.') at '.$offset; break; } } $ThisFileInfo['matroska']['seek'][] = $seek_entry; //switch ($seek_entry['target_id']) { // case EBML_ID_CLUSTER: // // too many cluster seek points, probably not useful // break; // default: // $ThisFileInfo['matroska']['seek'][] = $seek_entry; // break; //} break; default: $this->warnings[] = 'Unhandled seekhead element ['.basename(__FILE__).':'.__LINE__.'] ('.$seek_entry['id'].') at '.$offset; break; } $offset = $seek_end_offset; } break; case EBML_ID_TRACKS: // information about all tracks in segment $ThisFileInfo['matroska']['tracks'] = $element_data; while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $track_entry = array(); $track_entry['offset'] = $offset; $track_entry['id'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry['id_name'] = $this->EBMLidName($track_entry['id']); $track_entry['length'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $track_entry_endoffset = $offset + $track_entry['length']; switch ($track_entry['id']) { case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($offset < $track_entry_endoffset) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: case EBML_ID_MINCACHE: case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_TRACKTIMECODESCALE: $track_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: case EBML_ID_CODECNAME: case EBML_ID_CODECPRIVATE: $track_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); break; case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: $track_entry[$subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_VIDEO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: case EBML_ID_PIXELCROPRIGHT: case EBML_ID_DISPLAYWIDTH: case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_FLAGINTERLACED: $track_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_GAMMAVALUE: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_COLOURSPACE: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); break; default: $this->warnings[] = 'Unhandled track.video element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } if (isset($track_entry[$this->EBMLidName(EBML_ID_CODECID)]) && ($track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC') && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); } else { $this->warnings[] = 'Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'; } } break; case EBML_ID_AUDIO: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHANNELPOSITIONS: $track_entry[$sub_subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), "\x00"); break; default: $this->warnings[] = 'Unhandled track.video element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CONTENTENCODINGS: while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CONTENTENCODING: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPRESSION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTCOMPALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTCOMPSETTINGS: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; case EBML_ID_CONTENTENCRYPTION: while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: $track_entry[$sub_subelement_idname][$sub_sub_subelement_idname][$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding.contentcompression element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings.contentencoding element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $sub_sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track.contentencodings element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $sub_subelement_end; } break; default: $this->warnings[] = 'Unhandled track element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } break; default: $this->warnings[] = 'Unhandled track element ['.basename(__FILE__).':'.__LINE__.'] ('.$track_entry['id'].'::'.$track_entry['id_name'].') at '.$track_entry['offset']; $offset = $track_entry_endoffset; break; } $ThisFileInfo['matroska']['tracks']['tracks'][] = $track_entry; } break; case EBML_ID_INFO: // Contains the position of other level 1 elements $info_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DURATION: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Float(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_DATEUTC: $info_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); $info_entry[$subelement_idname.'_unix'] = $this->EBMLdate2unix($info_entry[$subelement_idname]); break; case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); break; case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: $info_entry[$subelement_idname] = trim(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length), "\x00"); break; default: $this->warnings[] = 'Unhandled info element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['info'][] = $info_entry; break; case EBML_ID_CUES: $cues_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CUEPOINT: $cuepoint_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CUETRACKPOSITIONS: while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CUETRACK: $cuepoint_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint.cuetrackpositions element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; break; } $offset = $sub_subelement_end; } break; case EBML_ID_CUETIME: $cuepoint_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled cues.cuepoint element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cues_entry[] = $cuepoint_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled cues element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['cues'] = $cues_entry; break; case EBML_ID_TAGS: $tags_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_WRITINGAPP: $tags_entry[$subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length); break; case EBML_ID_TAG: $tag_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_TARGETS: $targets_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TARGETTYPEVALUE: case EBML_ID_EDITIONUID: case EBML_ID_CHAPTERUID: case EBML_ID_ATTACHMENTUID: case EBML_ID_TAGTRACKUID: case EBML_ID_TAGCHAPTERUID: $targets_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.targets element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $targets_entry; break; case EBML_ID_SIMPLETAG: $simpletag_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_TAGNAME: case EBML_ID_TAGLANGUAGE: case EBML_ID_TAGSTRING: case EBML_ID_TAGBINARY: $simpletag_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_TAGDEFAULT: $simpletag_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tag.simpletag element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $tag_entry[$sub_subelement_idname][] = $simpletag_entry; break; case EBML_ID_TARGETTYPE: $tag_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_TRACKUID: $tag_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled tags.tag element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } $tags_entry['tags'][] = $tag_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled tags element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['tags'] = $tags_entry; break; case EBML_ID_ATTACHMENTS: while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); break; case EBML_ID_FILEDATA: $attachedfile_entry['data_offset'] = $offset; $attachedfile_entry['data_length'] = $sub_subelement_length; if ($sub_subelement_length < 1024) { $attachedfile_entry[$sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length); } break; case EBML_ID_FILEUID: $attachedfile_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled attachment.attachedfile element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } $ThisFileInfo['matroska']['attachments'][] = $attachedfile_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled tags element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_CHAPTERS: // not important to us, contains mostly actual audio/video data, ignore while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_EDITIONUID: $editionentry_entry[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); while ($offset < $sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_offset = $offset; $sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_subelement_end = $offset + $sub_sub_subelement_length; switch ($sub_sub_subelement_id) { case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length); break; case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement_idname] = (bool) getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_subelement_length)); break; case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chaptertrack element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chaptertrack_entry; break; case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); while ($offset < $sub_sub_subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_offset = $offset; $sub_sub_sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_idname = $this->EBMLidName($sub_sub_sub_subelement_id); $sub_sub_sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_sub_sub_subelement_end = $offset + $sub_sub_sub_subelement_length; switch ($sub_sub_sub_subelement_id) { case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement_idname] = substr($EBMLdata, $offset - $EBMLdata_offset, $sub_sub_sub_subelement_length); break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom.chapterdisplay element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_sub_subelement_id.'::'.$sub_sub_sub_subelement_idname.') at '.$sub_sub_sub_subelement_offset; break; } $offset = $sub_sub_sub_subelement_end; } $chapteratom_entry[$sub_sub_subelement_idname][] = $chapterdisplay_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry.chapteratom element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_sub_subelement_id.'::'.$sub_sub_subelement_idname.') at '.$sub_sub_subelement_offset; break; } $offset = $sub_sub_subelement_end; } $editionentry_entry[$sub_subelement_idname][] = $chapteratom_entry; break; default: $this->warnings[] = 'Unhandled chapters.editionentry element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } $ThisFileInfo['matroska']['chapters'][] = $editionentry_entry; $offset = $sub_subelement_end; break; default: $this->warnings[] = 'Unhandled chapters element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } break; case EBML_ID_VOID: // padding, ignore $void_entry = array(); $void_entry['offset'] = $offset; $ThisFileInfo['matroska']['void'][] = $void_entry; break; case EBML_ID_CLUSTER: // not important to us, contains mostly actual audio/video data, ignore $cluster_entry = array(); while ($offset < $element_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $subelement_offset = $offset; $subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_idname = $this->EBMLidName($subelement_id); $subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $subelement_end = $offset + $subelement_length; switch ($subelement_id) { case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: $cluster_entry[$subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $subelement_length)); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERSILENTTRACKNUMBER: $cluster_silent_tracks[] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; default: $this->warnings[] = 'Unhandled clusters.silenttracks element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_silent_tracks; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERBLOCKGROUP: $cluster_block_group = array('offset'=>$offset); while ($offset < $subelement_end) { $this->EnsureBufferHasEnoughData($fd, $EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_offset = $offset; $sub_subelement_id = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_idname = $this->EBMLidName($sub_subelement_id); $sub_subelement_length = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $sub_subelement_end = $offset + $sub_subelement_length; switch ($sub_subelement_id) { case EBML_ID_CLUSTERBLOCK: $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; // unsure whether this is 1 octect or 2 octets? (http://matroska.org/technical/specs/index.html#block_structure) $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); $cluster_block_data['flags']['invisible'] = (bool) (($cluster_block_data['flags_raw'] & 0x08) >> 3); $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); $cluster_block_data['flags']['lacing_type'] = $this->MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); if ($cluster_block_data['flags']['lacing'] != 0) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Number of frames in the lace-1 (uint8) $offset += 1; if ($cluster_block_data['flags']['lacing'] != 2) { $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $offset += 1; } } if (!isset($ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']] = $offset; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length)); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement_idname] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset - $EBMLdata_offset, $sub_subelement_length), false, true); break; default: $this->warnings[] = 'Unhandled clusters.blockgroup element ['.basename(__FILE__).':'.__LINE__.'] ('.$sub_subelement_id.'::'.$sub_subelement_idname.') at '.$sub_subelement_offset; break; } $offset = $sub_subelement_end; } $cluster_entry[$subelement_idname][] = $cluster_block_group; $offset = $sub_subelement_end; break; case EBML_ID_CLUSTERSIMPLEBLOCK: // http://www.matroska.org/technical/specs/index.html#simpleblock_structure $cluster_block_data = array(); $cluster_block_data['tracknumber'] = $this->readEBMLint($EBMLdata, $offset, $EBMLdata_offset); $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 2)); $offset += 2; $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; $cluster_block_data['flags']['keyframe'] = (($cluster_block_data['flags_raw'] & 0x80) >> 7); $cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0x70) >> 4); $cluster_block_data['flags']['invisible'] = (($cluster_block_data['flags_raw'] & 0x08) >> 3); $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing $cluster_block_data['flags']['discardable'] = (($cluster_block_data['flags_raw'] & 0x01)); if ($cluster_block_data['flags']['lacing'] > 0) { $cluster_block_data['lace_frames'] = 1 + getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; if ($cluster_block_data['flags']['lacing'] != 0x02) { // *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). $cluster_block_data['lace_frame_size'] = getid3_lib::BigEndian2Int(substr($EBMLdata, $offset, 1)); $offset += 1; } } if (!isset($ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { $ThisFileInfo['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']] = $offset; } $cluster_block_group[$sub_subelement_idname] = $cluster_block_data; break; default: $this->warnings[] = 'Unhandled cluster element ['.basename(__FILE__).':'.__LINE__.'] ('.$subelement_id.'::'.$subelement_idname.' ['.$subelement_length.' bytes]) at '.$subelement_offset; break; } $offset = $subelement_end; } $ThisFileInfo['matroska']['cluster'][] = $cluster_entry; // check to see if all the data we need exists already, if so, break out of the loop if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { break 2; } } break; default: if ($element_data['id_name'] == dechex($element_data['id'])) { $ThisFileInfo['error'][] = 'Unhandled segment ['.basename(__FILE__).':'.__LINE__.'] ('.$element_data['id'].') at '.$element_data_offset; } else { $this->warnings[] = 'Unhandled segment ['.basename(__FILE__).':'.__LINE__.'] ('.$element_data['id'].'::'.$element_data['id_name'].') at '.$element_data['offset']; } break; } $offset = $element_end; } break; default: $ThisFileInfo['error'][] = 'Unhandled chunk ['.basename(__FILE__).':'.__LINE__.'] ('.$top_element_id.') at '.$offset; break; } $offset = $top_element_endoffset; } if (isset($ThisFileInfo['matroska']['info']) && is_array($ThisFileInfo['matroska']['info'])) { foreach ($ThisFileInfo['matroska']['info'] as $key => $infoarray) { if (isset($infoarray['Duration'])) { // TimecodeScale is how many nanoseconds each Duration unit is $ThisFileInfo['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); break; } } } if (isset($ThisFileInfo['matroska']['tracks']['tracks']) && is_array($ThisFileInfo['matroska']['tracks']['tracks'])) { foreach ($ThisFileInfo['matroska']['tracks']['tracks'] as $key => $trackarray) { $track_info = array(); switch (isset($trackarray['TrackType']) ? $trackarray['TrackType'] : '') { case 1: // Video if (!empty($trackarray['PixelWidth'])) { $track_info['resolution_x'] = $trackarray['PixelWidth']; } if (!empty($trackarray['PixelHeight'])) { $track_info['resolution_y'] = $trackarray['PixelHeight']; } if (!empty($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } if (!empty($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['codec_private_parsed']['fourcc'])) { $track_info['fourcc'] = $trackarray['codec_private_parsed']['fourcc']; } $ThisFileInfo['video']['streams'][] = $track_info; if (isset($track_info['resolution_x']) && empty($ThisFileInfo['video']['resolution_x'])) { foreach ($track_info as $key => $value) { $ThisFileInfo['video'][$key] = $value; } } break; case 2: // Audio if (!empty($trackarray['CodecID'])) { $track_info['dataformat'] = $this->MatroskaCodecIDtoCommonName($trackarray['CodecID']); } if (!empty($trackarray['SamplingFrequency'])) { $track_info['sample_rate'] = $trackarray['SamplingFrequency']; } if (!empty($trackarray['Channels'])) { $track_info['channels'] = $trackarray['Channels']; } if (!empty($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } switch (isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '') { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; break; case 'A_AC3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { if (isset($ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { $ac3_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); $getid3_ac3 = new getid3_ac3($fd, $ac3_thisfileinfo); $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $ac3_thisfileinfo; if (!empty($ac3_thisfileinfo['error'])) { foreach ($ac3_thisfileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; } } if (!empty($ac3_thisfileinfo['warning'])) { foreach ($ac3_thisfileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_ac3() says: ['.$newerror.']'; } } if (isset($ac3_thisfileinfo['audio']) && is_array($ac3_thisfileinfo['audio'])) { foreach ($ac3_thisfileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($ac3_thisfileinfo); unset($getid3_ac3); } else { $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $ThisFileInfo[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'; } } else { $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ac3.php"'; } break; case 'A_DTS': $dts_offset = $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]; // this is a NASTY hack, but sometimes audio data is off by a byte or two and not sure why, email info@getid3.org if you can explain better fseek($fd, $dts_offset, SEEK_SET); $magic_test = fread($fd, 8); for ($i = 0; $i < 4; $i++) { // look to see if DTS "magic" is here, if so adjust offset by that many bytes if (substr($magic_test, $i, 4) == "\x7F\xFE\x80\x01") { $dts_offset += $i; break; } } if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, false)) { $dts_thisfileinfo = array('avdataoffset'=>$dts_offset); $getid3_dts = new getid3_dts($fd, $dts_thisfileinfo); $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $dts_thisfileinfo; if (!empty($dts_thisfileinfo['error'])) { foreach ($dts_thisfileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; } } if (!empty($dts_thisfileinfo['warning'])) { foreach ($dts_thisfileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_dts() says: ['.$newerror.']'; } } if (isset($dts_thisfileinfo['audio']) && is_array($dts_thisfileinfo['audio'])) { foreach ($dts_thisfileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($dts_thisfileinfo); unset($getid3_dts); } else { $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.dts.php"'; } break; case 'A_AAC': $this->warnings[] = 'This version of getID3() [v'.GETID3_VERSION.'] has problems parsing AAC audio in Matroska containers ['.basename(__FILE__).':'.__LINE__.']'; if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.aac.php', __FILE__, false)) { // $aac_thisfileinfo = array('avdataoffset'=>$ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']]); // $getid3_aac = new getid3_aac($fd, $aac_thisfileinfo); // $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $aac_thisfileinfo; // if (isset($aac_thisfileinfo['audio']) && is_array($aac_thisfileinfo['audio'])) { // foreach ($aac_thisfileinfo['audio'] as $key => $value) { // $track_info[$key] = $value; // } // } // unset($aac_thisfileinfo); // unset($getid3_aac); } else { $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.aac.php"'; } break; case 'A_MPEG/L3': if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, false)) { $mp3_thisfileinfo = array( 'avdataoffset' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']], 'avdataend' => $ThisFileInfo['matroska']['track_data_offsets'][$trackarray['TrackNumber']] + 1024, ); $getid3_mp3 = new getid3_mp3($fd, $mp3_thisfileinfo); $getid3_mp3->allow_bruteforce = true; //getid3_mp3::getOnlyMPEGaudioInfo($fd, $mp3_thisfileinfo, $offset, false); $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $mp3_thisfileinfo; if (!empty($mp3_thisfileinfo['error'])) { foreach ($mp3_thisfileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; } } if (!empty($mp3_thisfileinfo['warning'])) { foreach ($mp3_thisfileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_mp3() says: ['.$newerror.']'; } } if (isset($mp3_thisfileinfo['audio']) && is_array($mp3_thisfileinfo['audio'])) { foreach ($mp3_thisfileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } unset($mp3_thisfileinfo); unset($getid3_mp3); } else { $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.mp3.php"'; } break; case 'A_VORBIS': if (isset($trackarray['CodecPrivate'])) { // this is a NASTY hack, email info@getid3.org if you have a better idea how to get this info out $found_vorbis = false; for ($vorbis_offset = 1; $vorbis_offset < 16; $vorbis_offset++) { if (substr($trackarray['CodecPrivate'], $vorbis_offset, 6) == 'vorbis') { $vorbis_offset--; $found_vorbis = true; break; } } if ($found_vorbis) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { $vorbis_fileinfo = array(); $oggpageinfo['page_seqno'] = 0; getid3_ogg::ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $vorbis_fileinfo, $oggpageinfo); $ThisFileInfo['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $vorbis_fileinfo; if (!empty($vorbis_fileinfo['error'])) { foreach ($vorbis_fileinfo['error'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; } } if (!empty($vorbis_fileinfo['warning'])) { foreach ($vorbis_fileinfo['warning'] as $newerror) { $this->warnings[] = 'getid3_ogg() says: ['.$newerror.']'; } } if (isset($vorbis_fileinfo['audio']) && is_array($vorbis_fileinfo['audio'])) { foreach ($vorbis_fileinfo['audio'] as $key => $value) { $track_info[$key] = $value; } } if (!empty($vorbis_fileinfo['ogg']['bitrate_average'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_average']; } elseif (!empty($vorbis_fileinfo['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $vorbis_fileinfo['ogg']['bitrate_nominal']; } unset($vorbis_fileinfo); unset($oggpageinfo); } else { $this->warnings[] = 'Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'; } } else { } } else { } break; default: $this->warnings[] = 'Unhandled audio type "'.(isset($trackarray[$this->EBMLidName(EBML_ID_CODECID)]) ? $trackarray[$this->EBMLidName(EBML_ID_CODECID)] : '').'"'; break; } $ThisFileInfo['audio']['streams'][] = $track_info; if (isset($track_info['dataformat']) && empty($ThisFileInfo['audio']['dataformat'])) { foreach ($track_info as $key => $value) { $ThisFileInfo['audio'][$key] = $value; } } break; default: // ignore, do nothing break; } } } if ($this->hide_clusters) { // too much data returned that is usually not useful if (isset($ThisFileInfo['matroska']['segments']) && is_array($ThisFileInfo['matroska']['segments'])) { foreach ($ThisFileInfo['matroska']['segments'] as $key => $segmentsarray) { if ($segmentsarray['id'] == EBML_ID_CLUSTER) { unset($ThisFileInfo['matroska']['segments'][$key]); } } } if (isset($ThisFileInfo['matroska']['seek']) && is_array($ThisFileInfo['matroska']['seek'])) { foreach ($ThisFileInfo['matroska']['seek'] as $key => $seekarray) { if ($seekarray['target_id'] == EBML_ID_CLUSTER) { unset($ThisFileInfo['matroska']['seek'][$key]); } } } //unset($ThisFileInfo['matroska']['cluster']); //unset($ThisFileInfo['matroska']['track_data_offsets']); } if (!empty($ThisFileInfo['video']['streams'])) { $ThisFileInfo['mime_type'] = 'video/x-matroska'; } elseif (!empty($ThisFileInfo['video']['streams'])) { $ThisFileInfo['mime_type'] = 'audio/x-matroska'; } elseif (isset($ThisFileInfo['mime_type'])) { unset($ThisFileInfo['mime_type']); } foreach ($this->warnings as $key => $value) { $ThisFileInfo['warning'][] = $value; } return true; }
private function parseEBML(&$info) { // http://www.matroska.org/technical/specs/index.html#EBMLBasics $this->current_offset = $info['avdataoffset']; while ($this->getEBMLelement($top_element, $info['avdataend'])) { switch ($top_element['id']) { case EBML_ID_EBML: $info['fileformat'] = 'matroska'; $info['matroska']['header']['offset'] = $top_element['offset']; $info['matroska']['header']['length'] = $top_element['length']; while ($this->getEBMLelement($element_data, $top_element['end'], true)) { switch ($element_data['id']) { case EBML_ID_EBMLVERSION: case EBML_ID_EBMLREADVERSION: case EBML_ID_EBMLMAXIDLENGTH: case EBML_ID_EBMLMAXSIZELENGTH: case EBML_ID_DOCTYPEVERSION: case EBML_ID_DOCTYPEREADVERSION: $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']); break; case EBML_ID_DOCTYPE: $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); break; case EBML_ID_CRC32: // not useful, ignore $this->current_offset = $element_data['end']; unset($element_data); break; default: $this->unhandledElement('header', __LINE__, $element_data); } if (!empty($element_data)) { unset($element_data['offset'], $element_data['end']); $info['matroska']['header']['elements'][] = $element_data; } } break; case EBML_ID_SEGMENT: $info['matroska']['segment'][0]['offset'] = $top_element['offset']; $info['matroska']['segment'][0]['length'] = $top_element['length']; while ($this->getEBMLelement($element_data, $top_element['end'])) { if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required $info['matroska']['segments'][] = $element_data; } switch ($element_data['id']) { case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements. while ($this->getEBMLelement($seek_entry, $element_data['end'])) { switch ($seek_entry['id']) { case EBML_ID_SEEK: // Contains a single seek entry to an EBML element while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { switch ($sub_seek_entry['id']) { case EBML_ID_SEEKID: $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); break; case EBML_ID_SEEKPOSITION: $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']); break; default: $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } } if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required $info['matroska']['seek'][] = $seek_entry; } break; default: $this->unhandledElement('seekhead', __LINE__, $seek_entry); } } break; case EBML_ID_TRACKS: // A top-level block of information with many tracks described. $info['matroska']['tracks'] = $element_data; while ($this->getEBMLelement($track_entry, $element_data['end'])) { switch ($track_entry['id']) { case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS))) { switch ($subelement['id']) { case EBML_ID_TRACKNUMBER: case EBML_ID_TRACKUID: case EBML_ID_TRACKTYPE: case EBML_ID_MINCACHE: case EBML_ID_MAXCACHE: case EBML_ID_MAXBLOCKADDITIONID: case EBML_ID_DEFAULTDURATION: // nanoseconds per frame $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; case EBML_ID_TRACKTIMECODESCALE: $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); break; case EBML_ID_CODECID: case EBML_ID_LANGUAGE: case EBML_ID_NAME: case EBML_ID_CODECNAME: case EBML_ID_CODECPRIVATE: $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; case EBML_ID_FLAGENABLED: case EBML_ID_FLAGDEFAULT: case EBML_ID_FLAGFORCED: case EBML_ID_FLAGLACING: case EBML_ID_CODECDECODEALL: $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']); break; case EBML_ID_VIDEO: while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: case EBML_ID_PIXELCROPRIGHT: case EBML_ID_DISPLAYWIDTH: case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_FLAGINTERLACED: $track_entry[$sub_subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_GAMMAVALUE: $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); break; case EBML_ID_COLOURSPACE: $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; default: $this->unhandledElement('track.video', __LINE__, $sub_subelement); } } if (isset($track_entry[$this->EBMLidName(EBML_ID_CODECID)]) && $track_entry[$this->EBMLidName(EBML_ID_CODECID)] == 'V_MS/VFW/FOURCC' && isset($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)])) { if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'module.audio-video.riff.php', __FILE__, false)) { $track_entry['codec_private_parsed'] = getid3_riff::ParseBITMAPINFOHEADER($track_entry[$this->EBMLidName(EBML_ID_CODECPRIVATE)]); } else { $this->getid3->warning('Unable to parse codec private data [' . basename(__FILE__) . ':' . __LINE__ . '] because cannot include "module.audio-video.riff.php"'); } } break; case EBML_ID_AUDIO: while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case EBML_ID_CHANNELS: case EBML_ID_BITDEPTH: $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_SAMPLINGFREQUENCY: case EBML_ID_OUTPUTSAMPLINGFREQUENCY: $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); break; case EBML_ID_CHANNELPOSITIONS: $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; default: $this->unhandledElement('track.audio', __LINE__, $sub_subelement); } } break; case EBML_ID_CONTENTENCODINGS: while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { switch ($sub_subelement['id']) { case EBML_ID_CONTENTENCODING: while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) { switch ($sub_sub_subelement['id']) { case EBML_ID_CONTENTENCODINGORDER: case EBML_ID_CONTENTENCODINGSCOPE: case EBML_ID_CONTENTENCODINGTYPE: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; case EBML_ID_CONTENTCOMPRESSION: while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case EBML_ID_CONTENTCOMPALGO: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; case EBML_ID_CONTENTCOMPSETTINGS: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; default: $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } } break; case EBML_ID_CONTENTENCRYPTION: while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case EBML_ID_CONTENTENCALGO: case EBML_ID_CONTENTSIGALGO: case EBML_ID_CONTENTSIGHASHALGO: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; case EBML_ID_CONTENTENCKEYID: case EBML_ID_CONTENTSIGNATURE: case EBML_ID_CONTENTSIGKEYID: $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; default: $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); } } break; default: $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); } } break; default: $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); } } break; default: $this->unhandledElement('track', __LINE__, $subelement); } } $info['matroska']['tracks']['tracks'][] = $track_entry; break; default: $this->unhandledElement('tracks', __LINE__, $track_entry); } } break; case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file. $info_entry = array(); while ($this->getEBMLelement($subelement, $element_data['end'], true)) { switch ($subelement['id']) { case EBML_ID_CHAPTERTRANSLATEEDITIONUID: case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; case EBML_ID_DURATION: $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); break; case EBML_ID_DATEUTC: $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); $info_entry[$subelement['id_name'] . '_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); break; case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: case EBML_ID_SEGMENTFAMILY: case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: case EBML_ID_TITLE: case EBML_ID_MUXINGAPP: case EBML_ID_WRITINGAPP: $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; break; default: $this->unhandledElement('info', __LINE__, $subelement); } } $info['matroska']['info'][] = $info_entry; break; case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway $this->current_offset = $element_data['end']; break; } $cues_entry = array(); while ($this->getEBMLelement($subelement, $element_data['end'])) { switch ($subelement['id']) { case EBML_ID_CUEPOINT: $cuepoint_entry = array(); while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) { switch ($sub_subelement['id']) { case EBML_ID_CUETRACKPOSITIONS: while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { switch ($sub_sub_subelement['id']) { case EBML_ID_CUETRACK: case EBML_ID_CUECLUSTERPOSITION: $cuepoint_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; default: $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); } } break; case EBML_ID_CUETIME: $cuepoint_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); } } $cues_entry[] = $cuepoint_entry; break; default: $this->unhandledElement('cues', __LINE__, $subelement); } } $info['matroska']['cues'] = $cues_entry; break; case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. $tags_entry = array(); while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_TAG))) { $tag_entry = array(); switch ($subelement['id']) { case EBML_ID_WRITINGAPP: $tag_entry[$subelement['id_name']] = $subelement['data']; break; case EBML_ID_TAG: while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_TARGETS, EBML_ID_SIMPLETAG))) { switch ($sub_subelement['id']) { case EBML_ID_TARGETS: $targets_entry = array(); while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { switch ($sub_sub_subelement['id']) { case EBML_ID_TARGETTYPEVALUE: $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); $targets_entry[strtolower($sub_sub_subelement['id_name']) . '_long'] = $this->MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); break; case EBML_ID_EDITIONUID: case EBML_ID_CHAPTERUID: case EBML_ID_ATTACHMENTUID: case EBML_ID_TAGTRACKUID: case EBML_ID_TAGCHAPTERUID: $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; default: $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); } } $tag_entry[$sub_subelement['id_name']][] = $targets_entry; break; case EBML_ID_SIMPLETAG: $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); break; case EBML_ID_TARGETTYPE: $tag_entry[$sub_subelement['id_name']] = $sub_subelement['data']; break; case EBML_ID_TRACKUID: $tag_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); } } break; default: $this->unhandledElement('tags', __LINE__, $subelement); } $tags_entry['tags'][] = $tag_entry; } $info['matroska']['tags'] = $tags_entry['tags']; break; case EBML_ID_ATTACHMENTS: // Contain attached files. while ($this->getEBMLelement($subelement, $element_data['end'])) { switch ($subelement['id']) { case EBML_ID_ATTACHEDFILE: $attachedfile_entry = array(); while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) { switch ($sub_subelement['id']) { case EBML_ID_FILEDESCRIPTION: case EBML_ID_FILENAME: case EBML_ID_FILEMIMETYPE: $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; break; case EBML_ID_FILEDATA: $attachedfile_entry['data_offset'] = $this->current_offset; $attachedfile_entry['data_length'] = $sub_subelement['length']; $this->getid3->saveAttachment($attachedfile_entry[$sub_subelement['id_name']], $attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)], $attachedfile_entry['data_offset'], $attachedfile_entry['data_length']); if (@$attachedfile_entry[$sub_subelement['id_name']] && is_file($attachedfile_entry[$sub_subelement['id_name']])) { $attachedfile_entry[$sub_subelement['id_name'] . '_filename'] = $attachedfile_entry[$sub_subelement['id_name']]; unset($attachedfile_entry[$sub_subelement['id_name']]); } $this->current_offset = $sub_subelement['end']; break; case EBML_ID_FILEUID: $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); } } if (!empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]) && !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]) && preg_match('#^image/#i', $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)])) { if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { $attachedfile_entry['data'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)]; $attachedfile_entry['image_mime'] = $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]; $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => !empty($attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)]) ? $attachedfile_entry[$this->EBMLidName(EBML_ID_FILENAME)] : ''); unset($attachedfile_entry[$this->EBMLidName(EBML_ID_FILEDATA)], $attachedfile_entry[$this->EBMLidName(EBML_ID_FILEMIMETYPE)]); } } if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] } else { $info['matroska']['attachments'][] = $attachedfile_entry; } break; default: $this->unhandledElement('attachments', __LINE__, $subelement); } } break; case EBML_ID_CHAPTERS: while ($this->getEBMLelement($subelement, $element_data['end'])) { switch ($subelement['id']) { case EBML_ID_EDITIONENTRY: $editionentry_entry = array(); while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) { switch ($sub_subelement['id']) { case EBML_ID_EDITIONUID: $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_EDITIONFLAGHIDDEN: case EBML_ID_EDITIONFLAGDEFAULT: case EBML_ID_EDITIONFLAGORDERED: $editionentry_entry[$sub_subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_CHAPTERATOM: $chapteratom_entry = array(); while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) { switch ($sub_sub_subelement['id']) { case EBML_ID_CHAPTERSEGMENTUID: case EBML_ID_CHAPTERSEGMENTEDITIONUID: $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; break; case EBML_ID_CHAPTERFLAGENABLED: case EBML_ID_CHAPTERFLAGHIDDEN: $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; case EBML_ID_CHAPTERUID: case EBML_ID_CHAPTERTIMESTART: case EBML_ID_CHAPTERTIMEEND: $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); break; case EBML_ID_CHAPTERTRACK: $chaptertrack_entry = array(); while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case EBML_ID_CHAPTERTRACKNUMBER: $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); break; default: $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); } } $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; break; case EBML_ID_CHAPTERDISPLAY: $chapterdisplay_entry = array(); while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { switch ($sub_sub_sub_subelement['id']) { case EBML_ID_CHAPSTRING: case EBML_ID_CHAPLANGUAGE: case EBML_ID_CHAPCOUNTRY: $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; break; default: $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); } } $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; break; default: $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); } } $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; break; default: $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); } } $info['matroska']['chapters'][] = $editionentry_entry; break; default: $this->unhandledElement('chapters', __LINE__, $subelement); } } break; case EBML_ID_VOID: // padding, ignore $void_entry = array(); // TODO: do we really need to collect void's? $void_entry['offset'] = $this->current_offset; $info['matroska']['void'][] = $void_entry; $this->current_offset = $element_data['end']; break; case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. $cluster_entry = array(); while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) { switch ($subelement['id']) { case EBML_ID_CLUSTERTIMECODE: case EBML_ID_CLUSTERPOSITION: case EBML_ID_CLUSTERPREVSIZE: $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; case EBML_ID_CLUSTERSILENTTRACKS: $cluster_silent_tracks = array(); while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { switch ($sub_subelement['id']) { case EBML_ID_CLUSTERSILENTTRACKNUMBER: $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; default: $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); } } $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; break; case EBML_ID_CLUSTERBLOCKGROUP: $cluster_block_group = array('offset' => $this->current_offset); while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) { switch ($sub_subelement['id']) { case EBML_ID_CLUSTERBLOCK: $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info); break; case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int // unsigned-int case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); break; default: $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); } } $cluster_entry[$subelement['id_name']][] = $cluster_block_group; break; case EBML_ID_CLUSTERSIMPLEBLOCK: $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info); break; default: $this->unhandledElement('cluster', __LINE__, $subelement); } $this->current_offset = $subelement['end']; } if (!self::$hide_clusters) { $info['matroska']['cluster'][] = $cluster_entry; } // check to see if all the data we need exists already, if so, break out of the loop if (!self::$parse_whole_file) { if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { return; } } } break; default: $this->unhandledElement('segment', __LINE__, $element_data); } } break; default: $this->unhandledElement('root', __LINE__, $top_element); } } }