/** * @return bool */ public function analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $AAheader = fread($this->getid3->fp, 8); $magic = "W�u6"; if (substr($AAheader, 4, 4) != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes(substr($AAheader, 4, 4)) . '"'; return false; } // shortcut $info['aa'] = array(); $thisfile_au =& $info['aa']; $info['fileformat'] = 'aa'; $info['audio']['dataformat'] = 'aa'; $info['error'][] = 'Audible Audiobook (.aa) parsing not enabled in this version of GetId3Core() [' . $this->getid3->version() . ']'; return false; $info['audio']['bitrate_mode'] = 'cbr'; // is it? $thisfile_au['encoding'] = 'ISO-8859-1'; $thisfile_au['filesize'] = Helper::BigEndian2Int(substr($AUheader, 0, 4)); if ($thisfile_au['filesize'] > $info['avdataend'] - $info['avdataoffset']) { $info['warning'][] = 'Possible truncated file - expecting "' . $thisfile_au['filesize'] . '" bytes of data, only found ' . ($info['avdataend'] - $info['avdataoffset']) . ' bytes"'; } $info['audio']['bits_per_sample'] = 16; // is it? $info['audio']['sample_rate'] = $thisfile_au['sample_rate']; $info['audio']['channels'] = $thisfile_au['channels']; //$info['playtime_seconds'] = 0; //$info['audio']['bitrate'] = 0; return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $SZIPHeader = fread($this->getid3->fp, 6); if (substr($SZIPHeader, 0, 4) != "SZ\n") { $info['error'][] = 'Expecting "53 5A 0A 04" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes(substr($SZIPHeader, 0, 4)) . '"'; return false; } $info['fileformat'] = 'szip'; $info['szip']['major_version'] = Helper::BigEndian2Int(substr($SZIPHeader, 4, 1)); $info['szip']['minor_version'] = Helper::BigEndian2Int(substr($SZIPHeader, 5, 1)); while (!feof($this->getid3->fp)) { $NextBlockID = fread($this->getid3->fp, 2); switch ($NextBlockID) { case 'SZ': // Note that szip files can be concatenated, this has the same effect as // concatenating the files. this also means that global header blocks // might be present between directory/data blocks. fseek($this->getid3->fp, 4, SEEK_CUR); break; case 'BH': $BHheaderbytes = Helper::BigEndian2Int(fread($this->getid3->fp, 3)); $BHheaderdata = fread($this->getid3->fp, $BHheaderbytes); $BHheaderoffset = 0; while (strpos($BHheaderdata, "", $BHheaderoffset) > 0) { //filename as \0 terminated string (empty string indicates end) //owner as \0 terminated string (empty is same as last file) //group as \0 terminated string (empty is same as last file) //3 byte filelength in this block //2 byte access flags //4 byte creation time (like in unix) //4 byte modification time (like in unix) //4 byte access time (like in unix) $BHdataArray['filename'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "")); $BHheaderoffset += strlen($BHdataArray['filename']) + 1; $BHdataArray['owner'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "")); $BHheaderoffset += strlen($BHdataArray['owner']) + 1; $BHdataArray['group'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "")); $BHheaderoffset += strlen($BHdataArray['group']) + 1; $BHdataArray['filelength'] = Helper::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 3)); $BHheaderoffset += 3; $BHdataArray['access_flags'] = Helper::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 2)); $BHheaderoffset += 2; $BHdataArray['creation_time'] = Helper::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); $BHheaderoffset += 4; $BHdataArray['modification_time'] = Helper::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); $BHheaderoffset += 4; $BHdataArray['access_time'] = Helper::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); $BHheaderoffset += 4; $info['szip']['BH'][] = $BHdataArray; } break; default: break 2; } } return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $AUheader = fread($this->getid3->fp, 8); $magic = '.snd'; if (substr($AUheader, 0, 4) != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" (".snd") at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes(substr($AUheader, 0, 4)) . '"'; return false; } // shortcut $info['au'] = array(); $thisfile_au =& $info['au']; $info['fileformat'] = 'au'; $info['audio']['dataformat'] = 'au'; $info['audio']['bitrate_mode'] = 'cbr'; $thisfile_au['encoding'] = 'ISO-8859-1'; $thisfile_au['header_length'] = Helper::BigEndian2Int(substr($AUheader, 4, 4)); $AUheader .= fread($this->getid3->fp, $thisfile_au['header_length'] - 8); $info['avdataoffset'] += $thisfile_au['header_length']; $thisfile_au['data_size'] = Helper::BigEndian2Int(substr($AUheader, 8, 4)); $thisfile_au['data_format_id'] = Helper::BigEndian2Int(substr($AUheader, 12, 4)); $thisfile_au['sample_rate'] = Helper::BigEndian2Int(substr($AUheader, 16, 4)); $thisfile_au['channels'] = Helper::BigEndian2Int(substr($AUheader, 20, 4)); $thisfile_au['comments']['comment'][] = trim(substr($AUheader, 24)); $thisfile_au['data_format'] = $this->AUdataFormatNameLookup($thisfile_au['data_format_id']); $thisfile_au['used_bits_per_sample'] = $this->AUdataFormatUsedBitsPerSampleLookup($thisfile_au['data_format_id']); if ($thisfile_au['bits_per_sample'] = $this->AUdataFormatBitsPerSampleLookup($thisfile_au['data_format_id'])) { $info['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample']; } else { unset($thisfile_au['bits_per_sample']); } $info['audio']['sample_rate'] = $thisfile_au['sample_rate']; $info['audio']['channels'] = $thisfile_au['channels']; if ($info['avdataoffset'] + $thisfile_au['data_size'] > $info['avdataend']) { $info['warning'][] = 'Possible truncated file - expecting "' . $thisfile_au['data_size'] . '" bytes of audio data, only found ' . ($info['avdataend'] - $info['avdataoffset']) . ' bytes"'; } $info['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8)); $info['audio']['bitrate'] = $thisfile_au['data_size'] * 8 / $info['playtime_seconds']; return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $LPACheader = fread($this->getid3->fp, 14); if (substr($LPACheader, 0, 4) != 'LPAC') { $info['error'][] = 'Expected "LPAC" at offset ' . $info['avdataoffset'] . ', found "' . $StreamMarker . '"'; return false; } $info['avdataoffset'] += 14; $info['fileformat'] = 'lpac'; $info['audio']['dataformat'] = 'lpac'; $info['audio']['lossless'] = true; $info['audio']['bitrate_mode'] = 'vbr'; $info['lpac']['file_version'] = Helper::BigEndian2Int(substr($LPACheader, 4, 1)); $flags['audio_type'] = Helper::BigEndian2Int(substr($LPACheader, 5, 1)); $info['lpac']['total_samples'] = Helper::BigEndian2Int(substr($LPACheader, 6, 4)); $flags['parameters'] = Helper::BigEndian2Int(substr($LPACheader, 10, 4)); $info['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40); $info['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x4); $info['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x2); $info['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x1); if ($info['lpac']['flags']['24_bit'] && $info['lpac']['flags']['16_bit']) { $info['warning'][] = '24-bit and 16-bit flags cannot both be set'; } $info['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000); $info['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x8000000); $info['lpac']['block_length'] = pow(2, ($flags['parameters'] & 0x7000000) >> 24) * 256; $info['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x800000); $info['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x400000); $info['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x40000); $info['lpac']['quantization'] = ($flags['parameters'] & 0x1f00) >> 8; $info['lpac']['max_prediction_order'] = $flags['parameters'] & 0x3f; if ($info['lpac']['flags']['fast_compress'] && $info['lpac']['max_prediction_order'] != 3) { $info['warning'][] = 'max_prediction_order expected to be "3" if fast_compress is true, actual value is "' . $info['lpac']['max_prediction_order'] . '"'; } switch ($info['lpac']['file_version']) { case 6: if ($info['lpac']['flags']['adaptive_quantization']) { $info['warning'][] = 'adaptive_quantization expected to be false in LPAC file stucture v6, actually true'; } if ($info['lpac']['quantization'] != 20) { $info['warning'][] = 'Quantization expected to be 20 in LPAC file stucture v6, actually ' . $info['lpac']['flags']['Q']; } break; default: //$info['warning'][] = 'This version of GetId3Core() ['.$this->getid3->version().'] only supports LPAC file format version 6, this file is version '.$info['lpac']['file_version'].' - please report to info@getid3.org'; break; } $getid3_temp = new GetId3Core(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info = $info; $getid3_riff = new Riff($getid3_temp); $getid3_riff->analyze(); $info['avdataoffset'] = $getid3_temp->info['avdataoffset']; $info['riff'] = $getid3_temp->info['riff']; $info['error'] = $getid3_temp->info['error']; $info['warning'] = $getid3_temp->info['warning']; $info['lpac']['comments']['comment'] = $getid3_temp->info['comments']; $info['audio']['sample_rate'] = $getid3_temp->info['audio']['sample_rate']; unset($getid3_temp, $getid3_riff); $info['audio']['channels'] = $info['lpac']['flags']['stereo'] ? 2 : 1; if ($info['lpac']['flags']['24_bit']) { $info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample']; } elseif ($info['lpac']['flags']['16_bit']) { $info['audio']['bits_per_sample'] = 16; } else { $info['audio']['bits_per_sample'] = 8; } if ($info['lpac']['flags']['fast_compress']) { // fast $info['audio']['encoder_options'] = '-1'; } else { switch ($info['lpac']['max_prediction_order']) { case 20: // simple $info['audio']['encoder_options'] = '-2'; break; case 30: // medium $info['audio']['encoder_options'] = '-3'; break; case 40: // high $info['audio']['encoder_options'] = '-4'; break; case 60: // extrahigh $info['audio']['encoder_options'] = '-5'; break; } } $info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate']; $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; if ($info['avdataend'] <= $info['avdataoffset']) { $info['error'][] = '"avdataend" (' . $info['avdataend'] . ') is unexpectedly less-than-or-equal-to "avdataoffset" (' . $info['avdataoffset'] . ')'; return false; } $info['fileformat'] = 'mpeg'; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset'])); $MPEGstreamDataLength = strlen($MPEGstreamData); $foundVideo = true; $VideoChunkOffset = 0; while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== self::GETID3_MPEG_VIDEO_SEQUENCE_HEADER) { if ($VideoChunkOffset >= $MPEGstreamDataLength) { $foundVideo = false; break; } } if ($foundVideo) { // Start code 32 bits // horizontal frame size 12 bits // vertical frame size 12 bits // pixel aspect ratio 4 bits // frame rate 4 bits // bitrate 18 bits // marker bit 1 bit // VBV buffer size 10 bits // constrained parameter flag 1 bit // intra quant. matrix flag 1 bit // intra quant. matrix values 512 bits (present if matrix flag == 1) // non-intra quant. matrix flag 1 bit // non-intra quant. matrix values 512 bits (present if matrix flag == 1) $info['video']['dataformat'] = 'mpeg'; $VideoChunkOffset += strlen(self::GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1; $FrameSizeDWORD = Helper::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3)); $VideoChunkOffset += 3; $AspectRatioFrameRateDWORD = Helper::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1)); $VideoChunkOffset += 1; $assortedinformation = Helper::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); $VideoChunkOffset += 4; $info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xfff000) >> 12; // 12 bits for horizontal frame size $info['mpeg']['video']['raw']['framesize_vertical'] = $FrameSizeDWORD & 0xfff; // 12 bits for vertical frame size $info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xf0) >> 4; $info['mpeg']['video']['raw']['frame_rate'] = $AspectRatioFrameRateDWORD & 0xf; $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal']; $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical']; $info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); $info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); $info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']); $info['mpeg']['video']['raw']['bitrate'] = Helper::Bin2Dec(substr($assortedinformation, 0, 18)); $info['mpeg']['video']['raw']['marker_bit'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 18, 1)); $info['mpeg']['video']['raw']['vbv_buffer_size'] = Helper::Bin2Dec(substr($assortedinformation, 19, 10)); $info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 29, 1)); $info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 30, 1)); if ($info['mpeg']['video']['raw']['intra_quant_flag']) { // read 512 bits $info['mpeg']['video']['raw']['intra_quant'] = Helper::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); $VideoChunkOffset += 64; $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) Helper::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1)); $info['mpeg']['video']['raw']['intra_quant'] = Helper::Bin2Dec(substr($assortedinformation, 31, 1)) . substr(Helper::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } else { $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) Helper::Bin2Dec(substr($assortedinformation, 31, 1)); if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); $VideoChunkOffset += 64; } } if ($info['mpeg']['video']['raw']['bitrate'] == 0x3ffff) { // 18 set bits $info['warning'][] = 'This version of GetId3Core() [' . $this->getid3->version() . '] cannot determine average bitrate of VBR MPEG video files'; $info['mpeg']['video']['bitrate_mode'] = 'vbr'; } else { $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; $info['mpeg']['video']['bitrate_mode'] = 'cbr'; $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; } $info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal']; $info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical']; $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; $info['video']['lossless'] = false; $info['video']['bits_per_sample'] = 24; } else { $info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; } //0x000001B3 begins the sequence_header of every MPEG video stream. //But in MPEG-2, this header must immediately be followed by an //extension_start_code (0x000001B5) with a sequence_extension ID (1). //(This extension contains all the additional MPEG-2 stuff.) //MPEG-1 doesn't have this extension, so that's a sure way to tell the //difference between MPEG-1 and MPEG-2 video streams. if (substr($MPEGstreamData, $VideoChunkOffset, 4) == self::GETID3_MPEG_VIDEO_EXTENSION_START) { $info['video']['codec'] = 'MPEG-2'; } else { $info['video']['codec'] = 'MPEG-1'; } $AudioChunkOffset = 0; while (true) { while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== self::GETID3_MPEG_AUDIO_START) { if ($AudioChunkOffset >= $MPEGstreamDataLength) { break 2; } } $getid3_temp = new GetId3Core(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info = $info; $getid3_mp3 = new Mp3($getid3_temp); for ($i = 0; $i <= 7; ++$i) { // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after // I have no idea why or what the difference is, so this is a stupid hack. // If anybody has any better idea of what's going on, please let me know - info@getid3.org fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET); $getid3_temp->info = $info; // only overwrite real data if valid header found if ($getid3_mp3->decodeMPEGaudioHeader($AudioChunkOffset + 3 + 8 + $i, $getid3_temp->info, false)) { $info = $getid3_temp->info; $info['audio']['bitrate_mode'] = 'cbr'; $info['audio']['lossless'] = false; unset($getid3_temp, $getid3_mp3); break 2; } } unset($getid3_temp, $getid3_mp3); } // Temporary hack to account for interleaving overhead: if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / ($info['video']['bitrate'] + $info['audio']['bitrate']); // Interleaved MPEG audio/video files have a certain amount of overhead that varies // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter // Use interpolated lookup tables to approximately guess how much is overhead, because // playtime is calculated as filesize / total-bitrate $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); //switch ($info['video']['bitrate']) { // case('5000000'): // $multiplier = 0.93292642112380355828048824319889; // break; // case('5500000'): // $multiplier = 0.93582895375200989965359777343219; // break; // case('6000000'): // $multiplier = 0.93796247714820932532911373859139; // break; // case('7000000'): // $multiplier = 0.9413264083635103463010117778776; // break; // default: // $multiplier = 1; // break; //} //$info['playtime_seconds'] *= $multiplier; //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; if ($info['video']['bitrate'] < 50000) { $info['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'; } } return true; }
/** * @return type */ public function readByte() { return Helper::BigEndian2Int(substr($this->bytes, $this->pos++, 1)); }
/** * @param type $atom_data * * @return type * * @link http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG */ public function QuicktimeParseNikonNCTG($atom_data) { // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 // Data is stored as records of: // * 4 bytes record type // * 2 bytes size of data field type: // 0x0001 = flag (size field *= 1-byte) // 0x0002 = char (size field *= 1-byte) // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? // * 2 bytes data size field // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15") // all integers are stored BigEndian $NCTGtagName = array(0x1 => 'Make', 0x2 => 'Model', 0x3 => 'Software', 0x11 => 'CreateDate', 0x12 => 'DateTimeOriginal', 0x13 => 'FrameCount', 0x16 => 'FrameRate', 0x22 => 'FrameWidth', 0x23 => 'FrameHeight', 0x32 => 'AudioChannels', 0x33 => 'AudioBitsPerSample', 0x34 => 'AudioSampleRate', 0x2000001 => 'MakerNoteVersion', 0x2000005 => 'WhiteBalance', 0x200000b => 'WhiteBalanceFineTune', 0x200001e => 'ColorSpace', 0x2000023 => 'PictureControlData', 0x2000024 => 'WorldTime', 0x2000032 => 'UnknownInfo', 0x2000083 => 'LensType', 0x2000084 => 'Lens'); $offset = 0; $datalength = strlen($atom_data); $parsed = array(); while ($offset < $datalength) { //echo GetId3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'<br>'; $record_type = Helper::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; $data_size_type = Helper::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; $data_size = Helper::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; switch ($data_size_type) { case 0x1: // 0x0001 = flag (size field *= 1-byte) $data = Helper::BigEndian2Int(substr($atom_data, $offset, $data_size * 1)); $offset += $data_size * 1; break; case 0x2: // 0x0002 = char (size field *= 1-byte) $data = substr($atom_data, $offset, $data_size * 1); $offset += $data_size * 1; $data = rtrim($data, ""); break; case 0x3: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB $data = ''; for ($i = $data_size - 1; $i >= 0; --$i) { $data .= substr($atom_data, $offset + $i * 2, 2); } $data = Helper::BigEndian2Int($data); $offset += $data_size * 2; break; case 0x4: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD $data = ''; for ($i = $data_size - 1; $i >= 0; --$i) { $data .= substr($atom_data, $offset + $i * 4, 4); } $data = Helper::BigEndian2Int($data); $offset += $data_size * 4; break; case 0x5: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together $data = array(); for ($i = 0; $i < $data_size; ++$i) { $numerator = Helper::BigEndian2Int(substr($atom_data, $offset + $i * 8 + 0, 4)); $denomninator = Helper::BigEndian2Int(substr($atom_data, $offset + $i * 8 + 4, 4)); if ($denomninator == 0) { $data[$i] = false; } else { $data[$i] = (double) $numerator / $denomninator; } } $offset += 8 * $data_size; if (count($data) == 1) { $data = $data[0]; } break; case 0x7: // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? $data = substr($atom_data, $offset, $data_size * 1); $offset += $data_size * 1; break; case 0x8: // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? $data = substr($atom_data, $offset, $data_size * 2); $offset += $data_size * 2; break; default: echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: ' . $data_size_type . '<br>'; break 2; } switch ($record_type) { case 0x11: // CreateDate // CreateDate case 0x12: // DateTimeOriginal $data = strtotime($data); break; case 0x200001e: // ColorSpace switch ($data) { case 1: $data = 'sRGB'; break; case 2: $data = 'Adobe RGB'; break; } break; case 0x2000023: // PictureControlData $PictureControlAdjust = array(0 => 'default', 1 => 'quick', 2 => 'full'); $FilterEffect = array(0x80 => 'off', 0x81 => 'yellow', 0x82 => 'orange', 0x83 => 'red', 0x84 => 'green', 0xff => 'n/a'); $ToningEffect = array(0x80 => 'b&w', 0x81 => 'sepia', 0x82 => 'cyanotype', 0x83 => 'red', 0x84 => 'yellow', 0x85 => 'green', 0x86 => 'blue-green', 0x87 => 'blue', 0x88 => 'purple-blue', 0x89 => 'red-purple', 0xff => 'n/a'); $data = array('PictureControlVersion' => substr($data, 0, 4), 'PictureControlName' => rtrim(substr($data, 4, 20), ""), 'PictureControlBase' => rtrim(substr($data, 24, 20), ""), 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))], 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)), 'Sharpness' => ord(substr($data, 50, 1)), 'Contrast' => ord(substr($data, 51, 1)), 'Brightness' => ord(substr($data, 52, 1)), 'Saturation' => ord(substr($data, 53, 1)), 'HueAdjustment' => ord(substr($data, 54, 1)), 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))], 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))], 'ToningSaturation' => ord(substr($data, 57, 1))); break; case 0x2000024: // WorldTime // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime // timezone is stored as offset from GMT in minutes $timezone = Helper::BigEndian2Int(substr($data, 0, 2)); if ($timezone & 0x8000) { $timezone = 0 - (0x10000 - $timezone); } $timezone /= 60; $dst = (bool) Helper::BigEndian2Int(substr($data, 2, 1)); switch (Helper::BigEndian2Int(substr($data, 3, 1))) { case 2: $datedisplayformat = 'D/M/Y'; break; case 1: $datedisplayformat = 'M/D/Y'; break; case 0: default: $datedisplayformat = 'Y/M/D'; break; } $data = array('timezone' => floatval($timezone), 'dst' => $dst, 'display' => $datedisplayformat); break; case 0x2000083: // LensType $data = array('mf' => (bool) ($data & 0x1), 'd' => (bool) ($data & 0x2), 'g' => (bool) ($data & 0x4), 'vr' => (bool) ($data & 0x8)); break; } $tag_name = isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x' . str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT); $parsed[$tag_name] = $data; } return $parsed; }
/** * @param type $EBMLstring * * @return type * * @link http://matroska.org/specs/ */ private static function EBML2Int($EBMLstring) { // Element ID coded with an UTF-8 like system: // 1xxx xxxx - Class A IDs (2^7 -2 possible values) (base 0x8X) // 01xx xxxx xxxx xxxx - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX) // 001x xxxx xxxx xxxx xxxx xxxx - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX) // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX) // Values with all x at 0 and 1 are reserved (hence the -2). // Data size, in octets, is also coded with an UTF-8 like system : // 1xxx xxxx - value 0 to 2^7-2 // 01xx xxxx xxxx xxxx - value 0 to 2^14-2 // 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 // 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 // 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 $first_byte_int = ord($EBMLstring[0]); if (0x80 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x7f); } elseif (0x40 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x3f); } elseif (0x20 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x1f); } elseif (0x10 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0xf); } elseif (0x8 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x7); } elseif (0x4 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x3); } elseif (0x2 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x1); } elseif (0x1 & $first_byte_int) { $EBMLstring[0] = chr($first_byte_int & 0x0); } return Helper::BigEndian2Int($EBMLstring); }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $FLVdataLength = $info['avdataend'] - $info['avdataoffset']; $FLVheader = fread($this->getid3->fp, 5); $info['fileformat'] = 'flv'; $info['flv']['header']['signature'] = substr($FLVheader, 0, 3); $info['flv']['header']['version'] = Helper::BigEndian2Int(substr($FLVheader, 3, 1)); $TypeFlags = Helper::BigEndian2Int(substr($FLVheader, 4, 1)); $magic = 'FLV'; if ($info['flv']['header']['signature'] != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes($info['flv']['header']['signature']) . '"'; unset($info['flv']); unset($info['fileformat']); return false; } $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x4); $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x1); $FrameSizeDataLength = Helper::BigEndian2Int(fread($this->getid3->fp, 4)); $FLVheaderFrameLength = 9; if ($FrameSizeDataLength > $FLVheaderFrameLength) { fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR); } $Duration = 0; $found_video = false; $found_audio = false; $found_meta = false; $found_valid_meta_playtime = false; $tagParseCount = 0; $info['flv']['framecount'] = array('total' => 0, 'audio' => 0, 'video' => 0); $flv_framecount =& $info['flv']['framecount']; while (ftell($this->getid3->fp) + 16 < $info['avdataend'] && ($tagParseCount++ <= $this->max_frames || !$found_valid_meta_playtime)) { $ThisTagHeader = fread($this->getid3->fp, 16); $PreviousTagLength = Helper::BigEndian2Int(substr($ThisTagHeader, 0, 4)); $TagType = Helper::BigEndian2Int(substr($ThisTagHeader, 4, 1)); $DataLength = Helper::BigEndian2Int(substr($ThisTagHeader, 5, 3)); $Timestamp = Helper::BigEndian2Int(substr($ThisTagHeader, 8, 3)); $LastHeaderByte = Helper::BigEndian2Int(substr($ThisTagHeader, 15, 1)); $NextOffset = ftell($this->getid3->fp) - 1 + $DataLength; if ($Timestamp > $Duration) { $Duration = $Timestamp; } ++$flv_framecount['total']; switch ($TagType) { case self::GETID3_FLV_TAG_AUDIO: $flv_framecount['audio']++; if (!$found_audio) { $found_audio = true; $info['flv']['audio']['audioFormat'] = $LastHeaderByte >> 4 & 0xf; $info['flv']['audio']['audioRate'] = $LastHeaderByte >> 2 & 0x3; $info['flv']['audio']['audioSampleSize'] = $LastHeaderByte >> 1 & 0x1; $info['flv']['audio']['audioType'] = $LastHeaderByte & 0x1; } break; case self::GETID3_FLV_TAG_VIDEO: $flv_framecount['video']++; if (!$found_video) { $found_video = true; $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x7; $FLVvideoHeader = fread($this->getid3->fp, 11); if ($info['flv']['video']['videoCodec'] == self::GETID3_FLV_VIDEO_H264) { // this code block contributed by: moysevichØgmail*com $AVCPacketType = Helper::BigEndian2Int(substr($FLVvideoHeader, 0, 1)); if ($AVCPacketType == AVCSequenceParameterSetReader::H264_AVC_SEQUENCE_HEADER) { // read AVCDecoderConfigurationRecord $configurationVersion = Helper::BigEndian2Int(substr($FLVvideoHeader, 4, 1)); $AVCProfileIndication = Helper::BigEndian2Int(substr($FLVvideoHeader, 5, 1)); $profile_compatibility = Helper::BigEndian2Int(substr($FLVvideoHeader, 6, 1)); $lengthSizeMinusOne = Helper::BigEndian2Int(substr($FLVvideoHeader, 7, 1)); $numOfSequenceParameterSets = Helper::BigEndian2Int(substr($FLVvideoHeader, 8, 1)); if (($numOfSequenceParameterSets & 0x1f) != 0) { // there is at least one SequenceParameterSet // read size of the first SequenceParameterSet //$spsSize = GetId3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2)); $spsSize = Helper::LittleEndian2Int(substr($FLVvideoHeader, 9, 2)); // read the first SequenceParameterSet $sps = fread($this->getid3->fp, $spsSize); if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red $spsReader = new AVCSequenceParameterSetReader($sps); $spsReader->readData(); $info['video']['resolution_x'] = $spsReader->getWidth(); $info['video']['resolution_y'] = $spsReader->getHeight(); } } } // end: moysevichØgmail*com } elseif ($info['flv']['video']['videoCodec'] == self::GETID3_FLV_VIDEO_H263) { $PictureSizeType = Helper::BigEndian2Int(substr($FLVvideoHeader, 3, 2)) >> 7; $PictureSizeType = $PictureSizeType & 0x7; $info['flv']['header']['videoSizeType'] = $PictureSizeType; switch ($PictureSizeType) { case 0: //$PictureSizeEnc = GetId3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); //$PictureSizeEnc <<= 1; //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8; //$PictureSizeEnc = GetId3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2)); //$PictureSizeEnc <<= 1; //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8; $PictureSizeEnc['x'] = Helper::BigEndian2Int(substr($FLVvideoHeader, 4, 2)); $PictureSizeEnc['y'] = Helper::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); $PictureSizeEnc['x'] >>= 7; $PictureSizeEnc['y'] >>= 7; $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xff; $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xff; break; case 1: $PictureSizeEnc['x'] = Helper::BigEndian2Int(substr($FLVvideoHeader, 4, 3)); $PictureSizeEnc['y'] = Helper::BigEndian2Int(substr($FLVvideoHeader, 6, 3)); $PictureSizeEnc['x'] >>= 7; $PictureSizeEnc['y'] >>= 7; $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xffff; $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xffff; break; case 2: $info['video']['resolution_x'] = 352; $info['video']['resolution_y'] = 288; break; case 3: $info['video']['resolution_x'] = 176; $info['video']['resolution_y'] = 144; break; case 4: $info['video']['resolution_x'] = 128; $info['video']['resolution_y'] = 96; break; case 5: $info['video']['resolution_x'] = 320; $info['video']['resolution_y'] = 240; break; case 6: $info['video']['resolution_x'] = 160; $info['video']['resolution_y'] = 120; break; default: $info['video']['resolution_x'] = 0; $info['video']['resolution_y'] = 0; break; } } $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y']; } break; // Meta tag // Meta tag case self::GETID3_FLV_TAG_META: if (!$found_meta) { $found_meta = true; fseek($this->getid3->fp, -1, SEEK_CUR); $datachunk = fread($this->getid3->fp, $DataLength); $AMFstream = new AMFStream($datachunk); $reader = new AMFReader($AMFstream); $eventName = $reader->readData(); $info['flv']['meta'][$eventName] = $reader->readData(); unset($reader); $copykeys = array('framerate' => 'frame_rate', 'width' => 'resolution_x', 'height' => 'resolution_y', 'audiodatarate' => 'bitrate', 'videodatarate' => 'bitrate'); foreach ($copykeys as $sourcekey => $destkey) { if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) { switch ($sourcekey) { case 'width': case 'height': $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey])); break; case 'audiodatarate': $info['audio'][$destkey] = Helper::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000)); break; case 'videodatarate': case 'frame_rate': default: $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey]; break; } } } if (!empty($info['flv']['meta']['onMetaData']['duration'])) { $found_valid_meta_playtime = true; } } break; default: // noop break; } fseek($this->getid3->fp, $NextOffset, SEEK_SET); } $info['playtime_seconds'] = $Duration / 1000; if ($info['playtime_seconds'] > 0) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if ($info['flv']['header']['hasAudio']) { $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']); $info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']); $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']); $info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo $info['audio']['lossless'] = $info['flv']['audio']['audioFormat'] ? false : true; // 0=uncompressed $info['audio']['dataformat'] = 'flv'; } if (!empty($info['flv']['header']['hasVideo'])) { $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']); $info['video']['dataformat'] = 'flv'; $info['video']['lossless'] = false; } // Set information from meta if (!empty($info['flv']['meta']['onMetaData']['duration'])) { $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration']; $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) { $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']); } if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) { $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']); } return true; }
/** * public: analyze file * * @param type $filename * * @return type * * @throws Exception */ public function analyze($filename) { try { if (!$this->openfile($filename)) { return $this->info; } // Handle tags foreach (array('id3v2' => 'id3v2', 'id3v1' => 'id3v1', 'apetag' => 'ape', 'lyrics3' => 'lyrics3') as $tag_name => $tag_key) { $option_tag = 'option_tag_' . $tag_name; if ($this->{$option_tag}) { try { $tag_class = 'Helpers\\GetId3\\Module\\Tag\\' . ucfirst($tag_name); $tag = new $tag_class($this); $tag->analyze(); } catch (DefaultException $e) { throw $e; } } } if (isset($this->info['id3v2']['tag_offset_start'])) { $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); } foreach (array('id3v1' => 'id3v1', 'apetag' => 'ape', 'lyrics3' => 'lyrics3') as $tag_name => $tag_key) { if (isset($this->info[$tag_key]['tag_offset_start'])) { $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); } } // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier if (!$this->getOptionTagId3v2()) { fseek($this->getFp(), 0, SEEK_SET); $header = fread($this->getFp(), 10); if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { $this->info['id3v2']['header'] = true; $this->info['id3v2']['majorversion'] = ord($header[3]); $this->info['id3v2']['minorversion'] = ord($header[4]); $this->info['avdataoffset'] += Helper::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length } } // read 32 kb file data fseek($this->getFp(), $this->info['avdataoffset'], SEEK_SET); $formattest = fread($this->getFp(), 32774); // determine format $determined_format = $this->GetFileFormat($formattest, $filename); // unable to determine file format if (!$determined_format) { fclose($this->getFp()); return $this->error('unable to determine file format'); } // check for illegal ID3 tags if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { if ($determined_format['fail_id3'] === 'ERROR') { fclose($this->getFp()); return $this->error('ID3 tags not allowed on this file type.'); } elseif ($determined_format['fail_id3'] === 'WARNING') { $this->warning('ID3 tags not allowed on this file type.'); } } // check for illegal APE tags if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { if ($determined_format['fail_ape'] === 'ERROR') { fclose($this->getFp()); return $this->error('APE tags not allowed on this file type.'); } elseif ($determined_format['fail_ape'] === 'WARNING') { $this->warning('APE tags not allowed on this file type.'); } } // set mime type $this->info['mime_type'] = $determined_format['mime_type']; // supported format signature pattern detected, but module deleted if (!class_exists($determined_format['class'])) { fclose($this->getFp()); return $this->error('Format not supported, module "' . $determined_format['include'] . '" was removed.'); } // module requires iconv support // Check encoding/iconv support if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->getEncoding(), array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { $errormessage = 'iconv() support is required for this module (' . $determined_format['include'] . ') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; if (self::$EnvironmentIsWindows) { $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; } else { $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; } return $this->error($errormessage); } // instantiate module class $class_name = 'Helpers\\GetId3\\Module\\' . Helper::toCamelCase($determined_format['group'], '-', true) . '\\' . ucfirst($determined_format['module']); if (!class_exists($class_name)) { return $this->error('Format not supported, module "' . $determined_format['include'] . '" is corrupt.'); } //if (isset($determined_format['option'])) { // //$class = new $class_name($this->fp, $this->info, $determined_format['option']); //} else { //$class = new $class_name($this->fp, $this->info); $class = new $class_name($this); //} if (!empty($determined_format['set_inline_attachments'])) { $class->inline_attachments = $this->option_save_attachments; } $class->analyze(); unset($class); // close file fclose($this->getFp()); // process all tags - copy to 'tags' and convert charsets if ($this->getOptionTagsProcess()) { $this->HandleAllTags(); } // perform more calculations if ($this->getOptionExtraInfo()) { $this->ChannelsBitratePlaytimeCalculations(); $this->CalculateCompressionRatioVideo(); $this->CalculateCompressionRatioAudio(); $this->CalculateReplayGain(); $this->ProcessAudioStreams(); } // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags if ($this->getOptionMD5Data()) { // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too if (!$this->getOptionMD5DataDource() || empty($this->info['md5_data_source'])) { $this->getHashdata('md5'); } } // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags if ($this->getOptionSha1Data()) { $this->getHashdata('sha1'); } // remove undesired keys $this->CleanUp(); } catch (\Exception $e) { $this->error('Caught exception: ' . $e->getMessage()); } // return info array return $this->info; }
/** * @param type $parsedFrame * * @return bool */ public function ParseID3v2Frame(&$parsedFrame) { // shortcuts $info =& $this->getid3->info; $id3v2_majorversion = $info['id3v2']['majorversion']; $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); if (empty($parsedFrame['framenamelong'])) { unset($parsedFrame['framenamelong']); } $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']); if (empty($parsedFrame['framenameshort'])) { unset($parsedFrame['framenameshort']); } if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard if ($id3v2_majorversion == 3) { // Frame Header Flags // %abc00000 %ijk00000 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x80); // i - Compression $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x40); // j - Encryption $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x20); // k - Grouping identity } elseif ($id3v2_majorversion == 4) { // Frame Header Flags // %0abc0000 %0h00kmnp $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x40); // h - Grouping identity $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8); // k - Compression $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4); // m - Encryption $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2); // n - Unsynchronisation $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1); // p - Data length indicator // Frame-level de-unsynchronisation - ID3v2.4 if ($parsedFrame['flags']['Unsynchronisation']) { $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); } if ($parsedFrame['flags']['DataLengthIndicator']) { $parsedFrame['data_length_indicator'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); $parsedFrame['data'] = substr($parsedFrame['data'], 4); } } // Frame-level de-compression if ($parsedFrame['flags']['compression']) { $parsedFrame['decompressed_size'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); if (!function_exists('gzuncompress')) { $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "' . $parsedFrame['frame_name'] . '"'; } else { if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { $parsedFrame['data'] = $decompresseddata; unset($decompresseddata); } else { $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "' . $parsedFrame['frame_name'] . '"'; } } } } if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { $info['warning'][] = 'ID3v2 frame "' . $parsedFrame['frame_name'] . '" should be ' . $parsedFrame['data_length_indicator'] . ' bytes long according to DataLengthIndicator, but found ' . strlen($parsedFrame['data']) . ' bytes of data'; } } if (isset($parsedFrame['datalength']) && $parsedFrame['datalength'] == 0) { $warning = 'Frame "' . $parsedFrame['frame_name'] . '" at offset ' . $parsedFrame['dataoffset'] . ' has no data portion'; switch ($parsedFrame['frame_name']) { case 'WCOM': $warning .= ' (this is known to happen with files tagged by RioPort)'; break; default: break; } $info['warning'][] = $warning; } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'UFID' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'UFI') { // 4.1 UFI Unique file identifier // There may be more than one 'UFID' frame in a tag, // but only one with the same 'Owner identifier'. // <Header for 'Unique file identifier', ID: 'UFID'> // Owner identifier <text string> $00 // Identifier <up to 64 bytes binary data> $exploded = explode("", $parsedFrame['data'], 2); $parsedFrame['ownerid'] = isset($exploded[0]) ? $exploded[0] : ''; $parsedFrame['data'] = isset($exploded[1]) ? $exploded[1] : ''; } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'TXXX' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'TXX') { // 4.2.2 TXX User defined text information frame // There may be more than one 'TXXX' frame in each tag, // but only one with the same description. // <Header for 'User defined text information frame', ID: 'TXXX'> // Text encoding $xx // Description <text string according to encoding> $00 (00) // Value <text string according to encoding> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); } //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame // There may only be one text information frame of its kind in an tag. // <Header for 'Text information frame', ID: 'T000' - 'TZZZ', // excluding 'TXXX' described in 4.2.6.> // Text encoding $xx // Information <text string(s) according to encoding> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $string = Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); $string = rtrim($string, ""); // remove possible terminating null (put by encoding id or software bug) $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; unset($string); } } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'WXXX' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'WXX') { // 4.3.2 WXX User defined URL link frame // There may be more than one 'WXXX' frame in each tag, // but only one with the same description // <Header for 'User defined URL link frame', ID: 'WXXX'> // Text encoding $xx // Description <text string according to encoding> $00 (00) // URL <text string> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } if ($frame_terminatorpos) { // there are null bytes after the data - this is not according to spec // only use data up to first null byte $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); } else { // no null bytes following data, just use all data $frame_urldata = (string) $parsedFrame['data']; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['url'] = $frame_urldata; $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); } unset($parsedFrame['data']); } elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames // There may only be one URL link frame of its kind in a tag, // except when stated otherwise in the frame description // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX' // described in 4.3.2.> // URL <text string> $parsedFrame['url'] = trim($parsedFrame['data']); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; } unset($parsedFrame['data']); } elseif ($id3v2_majorversion == 3 && $parsedFrame['frame_name'] == 'IPLS' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'IPL') { // 4.4 IPL Involved people list (ID3v2.2 only) // There may only be one 'IPL' frame in each tag // <Header for 'User defined URL link frame', ID: 'IPL'> // Text encoding $xx // People list strings <textstrings> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'MCDI' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'MCI') { // 4.5 MCI Music CD identifier // There may only be one 'MCDI' frame in each tag // <Header for 'Music CD identifier', ID: 'MCDI'> // CD TOC <binary data> if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; } } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'ETCO' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'ETC') { // 4.6 ETC Event timing codes // There may only be one 'ETCO' frame in each tag // <Header for 'Event timing codes', ID: 'ETCO'> // Time stamp format $xx // Where time stamp format is: // $01 (32-bit value) MPEG frames from beginning of file // $02 (32-bit value) milliseconds from beginning of file // Followed by a list of key events in the following format: // Type of event $xx // Time stamp $xx (xx ...) // The 'Time stamp' is set to zero if directly at the beginning of the sound // or after the previous event. All events MUST be sorted in chronological order. $frame_offset = 0; $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); while ($frame_offset < strlen($parsedFrame['data'])) { $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1); $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']); $parsedFrame['timestamp'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); $frame_offset += 4; } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'MLLT' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'MLL') { // 4.7 MLL MPEG location lookup table // There may only be one 'MLLT' frame in each tag // <Header for 'Location lookup table', ID: 'MLLT'> // MPEG frames between reference $xx xx // Bytes between reference $xx xx xx // Milliseconds between reference $xx xx xx // Bits for bytes deviation $xx // Bits for milliseconds dev. $xx // Then for every reference the following data is included; // Deviation in bytes %xxx.... // Deviation in milliseconds %xxx.... $frame_offset = 0; $parsedFrame['framesbetweenreferences'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 0, 2)); $parsedFrame['bytesbetweenreferences'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 2, 3)); $parsedFrame['msbetweenreferences'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 5, 3)); $parsedFrame['bitsforbytesdeviation'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); $parsedFrame['bitsformsdeviation'] = Helper::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); $parsedFrame['data'] = substr($parsedFrame['data'], 10); while ($frame_offset < strlen($parsedFrame['data'])) { $deviationbitstream .= Helper::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); } $reference_counter = 0; while (strlen($deviationbitstream) > 0) { $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation'])); $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation'])); $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']); ++$reference_counter; } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'SYTC' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'STC') { // 4.8 STC Synchronised tempo codes // There may only be one 'SYTC' frame in each tag // <Header for 'Synchronised tempo codes', ID: 'SYTC'> // Time stamp format $xx // Tempo data <binary data> // Where time stamp format is: // $01 (32-bit value) MPEG frames from beginning of file // $02 (32-bit value) milliseconds from beginning of file $frame_offset = 0; $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $timestamp_counter = 0; while ($frame_offset < strlen($parsedFrame['data'])) { $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($parsedFrame[$timestamp_counter]['tempo'] == 255) { $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1)); } $parsedFrame[$timestamp_counter]['timestamp'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); $frame_offset += 4; ++$timestamp_counter; } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'USLT' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'ULT') { // 4.9 ULT Unsynchronised lyric/text transcription // There may be more than one 'Unsynchronised lyrics/text transcription' frame // in each tag, but only one with the same language and content descriptor. // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'> // Text encoding $xx // Language $xx xx xx // Content descriptor <text string according to encoding> $00 (00) // Lyrics/text <full text string according to encoding> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['data'] = $parsedFrame['data']; $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $parsedFrame['description'] = $frame_description; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'SYLT' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'SLT') { // 4.10 SLT Synchronised lyric/text // There may be more than one 'SYLT' frame in each tag, // but only one with the same language and content descriptor. // <Header for 'Synchronised lyrics/text', ID: 'SYLT'> // Text encoding $xx // Language $xx xx xx // Time stamp format $xx // $01 (32-bit value) MPEG frames from beginning of file // $02 (32-bit value) milliseconds from beginning of file // Content type $xx // Content descriptor <text string according to encoding> $00 (00) // Terminated text to be synced (typically a syllable) // Sync identifier (terminator to above string) $00 (00) // Time stamp $xx (xx ...) $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $timestampindex = 0; $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); while (strlen($frame_remainingdata)) { $frame_offset = 0; $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); if ($frame_terminatorpos === false) { $frame_remainingdata = ''; } else { if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); if ($timestampindex == 0 && ord($frame_remainingdata[0]) != 0) { // timestamp probably omitted for first data item } else { $parsedFrame['lyrics'][$timestampindex]['timestamp'] = Helper::BigEndian2Int(substr($frame_remainingdata, 0, 4)); $frame_remainingdata = substr($frame_remainingdata, 4); } ++$timestampindex; } } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'COMM' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'COM') { // 4.11 COM Comments // There may be more than one comment frame in each tag, // but only one with the same language and content descriptor. // <Header for 'Comment', ID: 'COMM'> // Text encoding $xx // Language $xx xx xx // Short content descrip. <text string according to encoding> $00 (00) // The actual text <full text string according to encoding> if (strlen($parsedFrame['data']) < 5) { $info['warning'][] = 'Invalid data (too short) for "' . $parsedFrame['frame_name'] . '" frame at offset ' . $parsedFrame['dataoffset']; } else { $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = $frame_text; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } } } elseif ($id3v2_majorversion >= 4 && $parsedFrame['frame_name'] == 'RVA2') { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) // There may be more than one 'RVA2' frame in each tag, // but only one with the same identification string // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'> // Identification <text string> $00 // The 'identification' string is used to identify the situation and/or // device where this adjustment should apply. The following is then // repeated for every channel: // Type of channel $xx // Volume adjustment $xx xx // Bits representing peak $xx // Peak volume $xx (xx ...) $frame_terminatorpos = strpos($parsedFrame['data'], ""); $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); if (ord($frame_idstring) === 0) { $frame_idstring = ''; } $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("")); $parsedFrame['description'] = $frame_idstring; $RVA2channelcounter = 0; while (strlen($frame_remainingdata) >= 5) { $frame_offset = 0; $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid; $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); $parsedFrame[$RVA2channelcounter]['volumeadjust'] = Helper::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed $frame_offset += 2; $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); if ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1 || $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4) { $info['warning'][] = 'ID3v2::RVA2 frame[' . $RVA2channelcounter . '] contains invalid ' . $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] . '-byte bits-representing-peak value'; break; } $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); $parsedFrame[$RVA2channelcounter]['peakvolume'] = Helper::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); ++$RVA2channelcounter; } unset($parsedFrame['data']); } elseif ($id3v2_majorversion == 3 && $parsedFrame['frame_name'] == 'RVAD' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'RVA') { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) // There may only be one 'RVA' frame in each tag // <Header for 'Relative volume adjustment', ID: 'RVA'> // ID3v2.2 => Increment/decrement %000000ba // ID3v2.3 => Increment/decrement %00fedcba // Bits used for volume descr. $xx // Relative volume change, right $xx xx (xx ...) // a // Relative volume change, left $xx xx (xx ...) // b // Peak volume right $xx xx (xx ...) // Peak volume left $xx xx (xx ...) // ID3v2.3 only, optional (not present in ID3v2.2): // Relative volume change, right back $xx xx (xx ...) // c // Relative volume change, left back $xx xx (xx ...) // d // Peak volume right back $xx xx (xx ...) // Peak volume left back $xx xx (xx ...) // ID3v2.3 only, optional (not present in ID3v2.2): // Relative volume change, center $xx xx (xx ...) // e // Peak volume center $xx xx (xx ...) // ID3v2.3 only, optional (not present in ID3v2.2): // Relative volume change, bass $xx xx (xx ...) // f // Peak volume bass $xx xx (xx ...) $frame_offset = 0; $frame_incrdecrflags = Helper::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1); $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1); $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8); $parsedFrame['volumechange']['right'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); if ($parsedFrame['incdec']['right'] === false) { $parsedFrame['volumechange']['right'] *= -1; } $frame_offset += $frame_bytesvolume; $parsedFrame['volumechange']['left'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); if ($parsedFrame['incdec']['left'] === false) { $parsedFrame['volumechange']['left'] *= -1; } $frame_offset += $frame_bytesvolume; $parsedFrame['peakvolume']['right'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); $frame_offset += $frame_bytesvolume; $parsedFrame['peakvolume']['left'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); $frame_offset += $frame_bytesvolume; if ($id3v2_majorversion == 3) { $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); if (strlen($parsedFrame['data']) > 0) { $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1); $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1); $parsedFrame['volumechange']['rightrear'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); if ($parsedFrame['incdec']['rightrear'] === false) { $parsedFrame['volumechange']['rightrear'] *= -1; } $frame_offset += $frame_bytesvolume; $parsedFrame['volumechange']['leftrear'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); if ($parsedFrame['incdec']['leftrear'] === false) { $parsedFrame['volumechange']['leftrear'] *= -1; } $frame_offset += $frame_bytesvolume; $parsedFrame['peakvolume']['rightrear'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); $frame_offset += $frame_bytesvolume; $parsedFrame['peakvolume']['leftrear'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); $frame_offset += $frame_bytesvolume; } $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); if (strlen($parsedFrame['data']) > 0) { $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1); $parsedFrame['volumechange']['center'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); if ($parsedFrame['incdec']['center'] === false) { $parsedFrame['volumechange']['center'] *= -1; } $frame_offset += $frame_bytesvolume; $parsedFrame['peakvolume']['center'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); $frame_offset += $frame_bytesvolume; } $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); if (strlen($parsedFrame['data']) > 0) { $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1); $parsedFrame['volumechange']['bass'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); if ($parsedFrame['incdec']['bass'] === false) { $parsedFrame['volumechange']['bass'] *= -1; } $frame_offset += $frame_bytesvolume; $parsedFrame['peakvolume']['bass'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); $frame_offset += $frame_bytesvolume; } } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 4 && $parsedFrame['frame_name'] == 'EQU2') { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) // There may be more than one 'EQU2' frame in each tag, // but only one with the same identification string // <Header of 'Equalisation (2)', ID: 'EQU2'> // Interpolation method $xx // $00 Band // $01 Linear // Identification <text string> $00 // The following is then repeated for every adjustment point // Frequency $xx xx // Volume adjustment $xx xx $frame_offset = 0; $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_idstring) === 0) { $frame_idstring = ''; } $parsedFrame['description'] = $frame_idstring; $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("")); while (strlen($frame_remainingdata)) { $frame_frequency = Helper::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2; $parsedFrame['data'][$frame_frequency] = Helper::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true); $frame_remainingdata = substr($frame_remainingdata, 4); } $parsedFrame['interpolationmethod'] = $frame_interpolationmethod; unset($parsedFrame['data']); } elseif ($id3v2_majorversion == 3 && $parsedFrame['frame_name'] == 'EQUA' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'EQU') { // 4.13 EQU Equalisation (ID3v2.2 only) // There may only be one 'EQUA' frame in each tag // <Header for 'Relative volume adjustment', ID: 'EQU'> // Adjustment bits $xx // This is followed by 2 bytes + ('adjustment bits' rounded up to the // nearest byte) for every equalisation band in the following format, // giving a frequency range of 0 - 32767Hz: // Increment/decrement %x (MSB of the Frequency) // Frequency (lower 15 bits) // Adjustment $xx (xx ...) $frame_offset = 0; $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1); $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8); $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset); while (strlen($frame_remainingdata) > 0) { $frame_frequencystr = Helper::BigEndian2Bin(substr($frame_remainingdata, 0, 2)); $frame_incdec = (bool) substr($frame_frequencystr, 0, 1); $frame_frequency = bindec(substr($frame_frequencystr, 1, 15)); $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec; $parsedFrame[$frame_frequency]['adjustment'] = Helper::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes)); if ($parsedFrame[$frame_frequency]['incdec'] === false) { $parsedFrame[$frame_frequency]['adjustment'] *= -1; } $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes); } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'RVRB' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'REV') { // 4.14 REV Reverb // There may only be one 'RVRB' frame in each tag. // <Header for 'Reverb', ID: 'RVRB'> // Reverb left (ms) $xx xx // Reverb right (ms) $xx xx // Reverb bounces, left $xx // Reverb bounces, right $xx // Reverb feedback, left to left $xx // Reverb feedback, left to right $xx // Reverb feedback, right to right $xx // Reverb feedback, right to left $xx // Premix left to right $xx // Premix right to left $xx $frame_offset = 0; $parsedFrame['left'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $parsedFrame['right'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'APIC' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'PIC') { // 4.15 PIC Attached picture // There may be several pictures attached to one file, // each in their individual 'APIC' frame, but only one // with the same content descriptor // <Header for 'Attached picture', ID: 'APIC'> // Text encoding $xx // ID3v2.3+ => MIME type <text string> $00 // ID3v2.2 => Image format $xx xx xx // Picture type $xx // Description <text string according to encoding> $00 (00) // Picture data <binary data> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); if (strtolower($frame_imagetype) == 'ima') { // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; } $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype))); if ($frame_imagetype == 'JPEG') { $frame_imagetype = 'JPG'; } $frame_offset = $frame_terminatorpos + strlen(""); } else { $frame_offset += 3; } } if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; } $frame_offset = $frame_terminatorpos + strlen(""); } $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($frame_offset >= $parsedFrame['datalength']) { $info['warning'][] = 'data portion of APIC frame is missing at offset ' . ($parsedFrame['dataoffset'] + 8 + $frame_offset); } else { $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); if ($id3v2_majorversion == 2) { $parsedFrame['imagetype'] = $frame_imagetype; } else { $parsedFrame['mime'] = $frame_mimetype; } $parsedFrame['picturetypeid'] = $frame_picturetype; $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); $parsedFrame['datalength'] = strlen($parsedFrame['data']); $parsedFrame['image_mime'] = ''; $imageinfo = array(); $imagechunkcheck = Helper::GetDataImageSize($parsedFrame['data'], $imageinfo); if ($imagechunkcheck[2] >= 1 && $imagechunkcheck[2] <= 3) { $parsedFrame['image_mime'] = 'image/' . Helper::ImageTypesLookup($imagechunkcheck[2]); if ($imagechunkcheck[0]) { $parsedFrame['image_width'] = $imagechunkcheck[0]; } if ($imagechunkcheck[1]) { $parsedFrame['image_height'] = $imagechunkcheck[1]; } } do { if ($this->inline_attachments === false) { // skip entirely unset($parsedFrame['data']); break; } if ($this->inline_attachments === true) { // great } elseif (is_int($this->inline_attachments)) { if ($this->inline_attachments < $parsedFrame['data_length']) { // too big, skip $info['warning'][] = 'attachment at ' . $frame_offset . ' is too large to process inline (' . number_format($parsedFrame['data_length']) . ' bytes)'; unset($parsedFrame['data']); 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 $info['warning'][] = 'attachment at ' . $frame_offset . ' cannot be saved to "' . $this->inline_attachments . '" (not writable)'; unset($parsedFrame['data']); break; } } // if we get this far, must be OK if (is_string($this->inline_attachments)) { $destination_filename = $this->inline_attachments . DIRECTORY_SEPARATOR . md5($info['filenamepath']) . '_' . $frame_offset; if (!file_exists($destination_filename) || is_writable($destination_filename)) { file_put_contents($destination_filename, $parsedFrame['data']); } else { $info['warning'][] = 'attachment at ' . $frame_offset . ' cannot be saved to "' . $destination_filename . '" (not writable)'; } $parsedFrame['data_filename'] = $destination_filename; unset($parsedFrame['data']); } else { if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { if (!isset($info['id3v2']['comments']['picture'])) { $info['id3v2']['comments']['picture'] = array(); } $info['id3v2']['comments']['picture'][] = array('data' => $parsedFrame['data'], 'image_mime' => $parsedFrame['image_mime']); } } } while (false); } } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'GEOB' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'GEO') { // 4.16 GEO General encapsulated object // There may be more than one 'GEOB' frame in each tag, // but only one with the same content descriptor // <Header for 'General encapsulated object', ID: 'GEOB'> // Text encoding $xx // MIME type <text string> $00 // Filename <text string according to encoding> $00 (00) // Content description <text string according to encoding> $00 (00) // Encapsulated object <binary data> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { $frame_mimetype = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_filename) === 0) { $frame_filename = ''; } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['mime'] = $frame_mimetype; $parsedFrame['filename'] = $frame_filename; $parsedFrame['description'] = $frame_description; unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'PCNT' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'CNT') { // 4.17 CNT Play counter // There may only be one 'PCNT' frame in each tag. // When the counter reaches all one's, one byte is inserted in // front of the counter thus making the counter eight bits bigger // <Header for 'Play counter', ID: 'PCNT'> // Counter $xx xx xx xx (xx ...) $parsedFrame['data'] = Helper::BigEndian2Int($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'POPM' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'POP') { // 4.18 POP Popularimeter // There may be more than one 'POPM' frame in each tag, // but only one with the same email address // <Header for 'Popularimeter', ID: 'POPM'> // Email to user <text string> $00 // Rating $xx // Counter $xx xx xx xx (xx ...) $frame_offset = 0; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_emailaddress) === 0) { $frame_emailaddress = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['counter'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); $parsedFrame['email'] = $frame_emailaddress; $parsedFrame['rating'] = $frame_rating; unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'RBUF' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'BUF') { // 4.19 BUF Recommended buffer size // There may only be one 'RBUF' frame in each tag // <Header for 'Recommended buffer size', ID: 'RBUF'> // Buffer size $xx xx xx // Embedded info flag %0000000x // Offset to next tag $xx xx xx xx $frame_offset = 0; $parsedFrame['buffersize'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3)); $frame_offset += 3; $frame_embeddedinfoflags = Helper::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1); $parsedFrame['nexttagoffset'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); unset($parsedFrame['data']); } elseif ($id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'CRM') { // 4.20 Encrypted meta frame (ID3v2.2 only) // There may be more than one 'CRM' frame in a tag, // but only one with the same 'owner identifier' // <Header for 'Encrypted meta frame', ID: 'CRM'> // Owner identifier <textstring> $00 (00) // Content/explanation <textstring> $00 (00) // Encrypted datablock <binary data> $frame_offset = 0; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen(""); $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['ownerid'] = $frame_ownerid; $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); $parsedFrame['description'] = $frame_description; unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'AENC' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'CRA') { // 4.21 CRA Audio encryption // There may be more than one 'AENC' frames in a tag, // but only one with the same 'Owner identifier' // <Header for 'Audio encryption', ID: 'AENC'> // Owner identifier <text string> $00 // Preview start $xx xx // Preview length $xx xx // Encryption info <binary data> $frame_offset = 0; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid == ''; } $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['ownerid'] = $frame_ownerid; $parsedFrame['previewstart'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $parsedFrame['previewlength'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset); unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'LINK' || $id3v2_majorversion == 2 && $parsedFrame['frame_name'] == 'LNK') { // 4.22 LNK Linked information // There may be more than one 'LINK' frame in a tag, // but only one with the same contents // <Header for 'Linked information', ID: 'LINK'> // ID3v2.3+ => Frame identifier $xx xx xx xx // ID3v2.2 => Frame identifier $xx xx xx // URL <text string> $00 // ID and additional data <text string(s)> $frame_offset = 0; if ($id3v2_majorversion == 2) { $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; } else { $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4); $frame_offset += 4; } $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_url) === 0) { $frame_url = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['url'] = $frame_url; $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'POSS') { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) // There may only be one 'POSS' frame in each tag // <Head for 'Position synchronisation', ID: 'POSS'> // Time stamp format $xx // Position $xx (xx ...) $frame_offset = 0; $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['position'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'USER') { // 4.22 USER Terms of use (ID3v2.3+ only) // There may be more than one 'Terms of use' frame in a tag, // but only one with the same 'Language' // <Header for 'Terms of use frame', ID: 'USER'> // Text encoding $xx // Language $xx xx xx // The actual text <text string according to encoding> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; $parsedFrame['language'] = $frame_language; $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = Helper::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'OWNE') { // 4.23 OWNE Ownership frame (ID3v2.3+ only) // There may only be one 'OWNE' frame in a tag // <Header for 'Ownership frame', ID: 'OWNE'> // Text encoding $xx // Price paid <text string> $00 // Date of purch. <text string> // Seller <text string according to encoding> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) { $parsedFrame['purchasedateunix'] = mktime(0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); } $frame_offset += 8; $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'COMR') { // 4.24 COMR Commercial frame (ID3v2.3+ only) // There may be more than one 'commercial frame' in a tag, // but no two may be identical // <Header for 'Commercial frame', ID: 'COMR'> // Text encoding $xx // Price string <text string> $00 // Valid until <text string> // Contact URL <text string> $00 // Received as $xx // Name of seller <text string according to encoding> $00 (00) // Description <text string according to encoding> $00 (00) // Picture MIME type <string> $00 // Seller logo <binary data> $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); if ($id3v2_majorversion <= 3 && $frame_textencoding > 1 || $id3v2_majorversion == 4 && $frame_textencoding > 3) { $info['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding'; } $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen(""); $frame_rawpricearray = explode('/', $frame_pricestring); foreach ($frame_rawpricearray as $key => $val) { $frame_currencyid = substr($val, 0, 3); $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid); $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3); } $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); $frame_offset += 8; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen(""); $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_sellername) === 0) { $frame_sellername = ''; } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { ++$frame_terminatorpos; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); $frame_offset = $frame_terminatorpos + strlen(""); $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); $parsedFrame['pricevaliduntil'] = $frame_datestring; $parsedFrame['contacturl'] = $frame_contacturl; $parsedFrame['receivedasid'] = $frame_receivedasid; $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid); $parsedFrame['sellername'] = $frame_sellername; $parsedFrame['description'] = $frame_description; $parsedFrame['mime'] = $frame_mimetype; $parsedFrame['logo'] = $frame_sellerlogo; unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'ENCR') { // 4.25 ENCR Encryption method registration (ID3v2.3+ only) // There may be several 'ENCR' frames in a tag, // but only one containing the same symbol // and only one containing the same owner identifier // <Header for 'Encryption method registration', ID: 'ENCR'> // Owner identifier <text string> $00 // Method symbol $xx // Encryption data <binary data> $frame_offset = 0; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['ownerid'] = $frame_ownerid; $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'GRID') { // 4.26 GRID Group identification registration (ID3v2.3+ only) // There may be several 'GRID' frames in a tag, // but only one containing the same symbol // and only one containing the same owner identifier // <Header for 'Group ID registration', ID: 'GRID'> // Owner identifier <text string> $00 // Group symbol $xx // Group dependent data <binary data> $frame_offset = 0; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['ownerid'] = $frame_ownerid; $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'PRIV') { // 4.27 PRIV Private frame (ID3v2.3+ only) // The tag may contain more than one 'PRIV' frame // but only with different contents // <Header for 'Private frame', ID: 'PRIV'> // Owner identifier <text string> $00 // The private data <binary data> $frame_offset = 0; $frame_terminatorpos = strpos($parsedFrame['data'], "", $frame_offset); $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_ownerid) === 0) { $frame_ownerid = ''; } $frame_offset = $frame_terminatorpos + strlen(""); $parsedFrame['ownerid'] = $frame_ownerid; $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); } elseif ($id3v2_majorversion >= 4 && $parsedFrame['frame_name'] == 'SIGN') { // 4.28 SIGN Signature frame (ID3v2.4+ only) // There may be more than one 'signature frame' in a tag, // but no two may be identical // <Header for 'Signature frame', ID: 'SIGN'> // Group symbol $xx // Signature <binary data> $frame_offset = 0; $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); } elseif ($id3v2_majorversion >= 4 && $parsedFrame['frame_name'] == 'SEEK') { // 4.29 SEEK Seek frame (ID3v2.4+ only) // There may only be one 'seek frame' in a tag // <Header for 'Seek frame', ID: 'SEEK'> // Minimum offset to next tag $xx xx xx xx $frame_offset = 0; $parsedFrame['data'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); } elseif ($id3v2_majorversion >= 4 && $parsedFrame['frame_name'] == 'ASPI') { // 4.30 ASPI Audio seek point index (ID3v2.4+ only) // There may only be one 'audio seek point index' frame in a tag // <Header for 'Seek Point Index', ID: 'ASPI'> // Indexed data start (S) $xx xx xx xx // Indexed data length (L) $xx xx xx xx // Number of index points (N) $xx xx // Bits per index point (b) $xx // Then for every index point the following data is included: // Fraction at index (Fi) $xx (xx) $frame_offset = 0; $parsedFrame['datastart'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); $frame_offset += 4; $parsedFrame['indexeddatalength'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); $frame_offset += 4; $parsedFrame['indexpoints'] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); for ($i = 0; $i < $frame_indexpoints; ++$i) { $parsedFrame['indexes'][$i] = Helper::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); $frame_offset += $frame_bytesperpoint; } unset($parsedFrame['data']); } elseif ($id3v2_majorversion >= 3 && $parsedFrame['frame_name'] == 'RGAD') { // Replay Gain Adjustment // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html // There may only be one 'RGAD' frame in a tag // <Header for 'Replay Gain Adjustment', ID: 'RGAD'> // Peak Amplitude $xx $xx $xx $xx // Radio Replay Gain Adjustment %aaabbbcd %dddddddd // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd // a - name code // b - originator code // c - sign bit // d - replay gain adjustment $frame_offset = 0; $parsedFrame['peakamplitude'] = Helper::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); $frame_offset += 4; $rg_track_adjustment = Helper::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $rg_album_adjustment = Helper::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); $frame_offset += 2; $parsedFrame['raw']['track']['name'] = Helper::Bin2Dec(substr($rg_track_adjustment, 0, 3)); $parsedFrame['raw']['track']['originator'] = Helper::Bin2Dec(substr($rg_track_adjustment, 3, 3)); $parsedFrame['raw']['track']['signbit'] = Helper::Bin2Dec(substr($rg_track_adjustment, 6, 1)); $parsedFrame['raw']['track']['adjustment'] = Helper::Bin2Dec(substr($rg_track_adjustment, 7, 9)); $parsedFrame['raw']['album']['name'] = Helper::Bin2Dec(substr($rg_album_adjustment, 0, 3)); $parsedFrame['raw']['album']['originator'] = Helper::Bin2Dec(substr($rg_album_adjustment, 3, 3)); $parsedFrame['raw']['album']['signbit'] = Helper::Bin2Dec(substr($rg_album_adjustment, 6, 1)); $parsedFrame['raw']['album']['adjustment'] = Helper::Bin2Dec(substr($rg_album_adjustment, 7, 9)); $parsedFrame['track']['name'] = Helper::RGADnameLookup($parsedFrame['raw']['track']['name']); $parsedFrame['track']['originator'] = Helper::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); $parsedFrame['track']['adjustment'] = Helper::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); $parsedFrame['album']['name'] = Helper::RGADnameLookup($parsedFrame['raw']['album']['name']); $parsedFrame['album']['originator'] = Helper::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); $parsedFrame['album']['adjustment'] = Helper::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; unset($parsedFrame['data']); } return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; $info['fileformat'] = 'swf'; $info['video']['dataformat'] = 'swf'; // http://www.openswf.org/spec/SWFfileformat.html fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $SWFfileData = fread($this->getid3->fp, $info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data $info['swf']['header']['signature'] = substr($SWFfileData, 0, 3); switch ($info['swf']['header']['signature']) { case 'FWS': $info['swf']['header']['compressed'] = false; break; case 'CWS': $info['swf']['header']['compressed'] = true; break; default: $info['error'][] = 'Expecting "FWS" or "CWS" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes($info['swf']['header']['signature']) . '"'; unset($info['swf']); unset($info['fileformat']); return false; break; } $info['swf']['header']['version'] = Helper::LittleEndian2Int(substr($SWFfileData, 3, 1)); $info['swf']['header']['length'] = Helper::LittleEndian2Int(substr($SWFfileData, 4, 4)); if ($info['swf']['header']['compressed']) { $SWFHead = substr($SWFfileData, 0, 8); $SWFfileData = substr($SWFfileData, 8); if ($decompressed = @gzuncompress($SWFfileData)) { $SWFfileData = $SWFHead . $decompressed; } else { $info['error'][] = 'Error decompressing compressed SWF data (' . strlen($SWFfileData) . ' bytes compressed, should be ' . ($info['swf']['header']['length'] - 8) . ' bytes uncompressed)'; return false; } } $FrameSizeBitsPerValue = (ord(substr($SWFfileData, 8, 1)) & 0xf8) >> 3; $FrameSizeDataLength = ceil((5 + 4 * $FrameSizeBitsPerValue) / 8); $FrameSizeDataString = str_pad(decbin(ord(substr($SWFfileData, 8, 1)) & 0x7), 3, '0', STR_PAD_LEFT); for ($i = 1; $i < $FrameSizeDataLength; ++$i) { $FrameSizeDataString .= str_pad(decbin(ord(substr($SWFfileData, 8 + $i, 1))), 8, '0', STR_PAD_LEFT); } list($X1, $X2, $Y1, $Y2) = explode("\n", wordwrap($FrameSizeDataString, $FrameSizeBitsPerValue, "\n", 1)); $info['swf']['header']['frame_width'] = Helper::Bin2Dec($X2); $info['swf']['header']['frame_height'] = Helper::Bin2Dec($Y2); // http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm // Next in the header is the frame rate, which is kind of weird. // It is supposed to be stored as a 16bit integer, but the first byte // (or last depending on how you look at it) is completely ignored. // Example: 0x000C -> 0x0C -> 12 So the frame rate is 12 fps. // Byte at (8 + $FrameSizeDataLength) is always zero and ignored $info['swf']['header']['frame_rate'] = Helper::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1)); $info['swf']['header']['frame_count'] = Helper::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2)); $info['video']['frame_rate'] = $info['swf']['header']['frame_rate']; $info['video']['resolution_x'] = intval(round($info['swf']['header']['frame_width'] / 20)); $info['video']['resolution_y'] = intval(round($info['swf']['header']['frame_height'] / 20)); $info['video']['pixel_aspect_ratio'] = (double) 1; if ($info['swf']['header']['frame_count'] > 0 && $info['swf']['header']['frame_rate'] > 0) { $info['playtime_seconds'] = $info['swf']['header']['frame_count'] / $info['swf']['header']['frame_rate']; } //echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>'; // SWF tags $CurrentOffset = 12 + $FrameSizeDataLength; $SWFdataLength = strlen($SWFfileData); while ($CurrentOffset < $SWFdataLength) { //echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>'; $TagIDTagLength = Helper::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 2)); $TagID = ($TagIDTagLength & 0xfffc) >> 6; $TagLength = $TagIDTagLength & 0x3f; $CurrentOffset += 2; if ($TagLength == 0x3f) { $TagLength = Helper::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 4)); $CurrentOffset += 4; } unset($TagData); $TagData['offset'] = $CurrentOffset; $TagData['size'] = $TagLength; $TagData['id'] = $TagID; $TagData['data'] = substr($SWFfileData, $CurrentOffset, $TagLength); switch ($TagID) { case 0: // end of movie break 2; case 9: // Set background color //$info['swf']['tags'][] = $TagData; $info['swf']['bgcolor'] = strtoupper(str_pad(dechex(Helper::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT)); break; default: if ($this->ReturnAllTagData) { $info['swf']['tags'][] = $TagData; } break; } $CurrentOffset += $TagLength; } return true; }
/** * @param type $Block * * @return bool */ public function parsePICTURE($Block = '') { $info =& $this->getid3->info; $picture['typeid'] = Helper::BigEndian2Int($this->fread(4)); $picture['type'] = self::pictureTypeLookup($picture['typeid']); $picture['image_mime'] = $this->fread(Helper::BigEndian2Int($this->fread(4))); $descr_length = Helper::BigEndian2Int($this->fread(4)); if ($descr_length) { $picture['description'] = $this->fread($descr_length); } $picture['width'] = Helper::BigEndian2Int($this->fread(4)); $picture['height'] = Helper::BigEndian2Int($this->fread(4)); $picture['color_depth'] = Helper::BigEndian2Int($this->fread(4)); $picture['colors_indexed'] = Helper::BigEndian2Int($this->fread(4)); $data_length = Helper::BigEndian2Int($this->fread(4)); if ($picture['image_mime'] == '-->') { $picture['data'] = $this->fread($data_length); } else { $this->saveAttachment($picture['data'], $picture['type'] . '_' . $this->ftell() . '.' . substr($picture['image_mime'], 6), $this->ftell(), $data_length); } $info['flac']['PICTURE'][] = $picture; return true; }
public function analyze() { $info =& $this->getid3->info; // http://cui.unige.ch/OSG/info/AudioFormats/ap11.html // http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html // offset type length name comments // --------------------------------------------------------------------- // 0 char 4 ID format ID == "2BIT" // 4 char 8 name sample name (unused space filled with 0) // 12 short 1 mono/stereo 0=mono, -1 (0xFFFF)=stereo // With stereo, samples are alternated, // the first voice is the left : // (LRLRLRLRLRLRLRLRLR...) // 14 short 1 resolution 8, 12 or 16 (bits) // 16 short 1 signed or not 0=unsigned, -1 (0xFFFF)=signed // 18 short 1 loop or not 0=no loop, -1 (0xFFFF)=loop on // 20 short 1 MIDI note 0xFFnn, where 0 <= nn <= 127 // 0xFFFF means "no MIDI note defined" // 22 byte 1 Replay speed Frequence in the Replay software // 0=5.485 Khz, 1=8.084 Khz, 2=10.971 Khz, // 3=16.168 Khz, 4=21.942 Khz, 5=32.336 Khz // 6=43.885 Khz, 7=47.261 Khz // -1 (0xFF)=no defined Frequence // 23 byte 3 sample rate in Hertz // 26 long 1 size in bytes (2 * bytes in stereo) // 30 long 1 loop begin 0 for no loop // 34 long 1 loop size equal to 'size' for no loop // 38 short 2 Reserved, MIDI keyboard split */ // 40 short 2 Reserved, sample compression */ // 42 short 2 Reserved */ // 44 char 20; Additional filename space, used if (name[7] != 0) // 64 byte 64 user data // 128 bytes ? sample data (12 bits samples are coded on 16 bits: // 0000 xxxx xxxx xxxx) // --------------------------------------------------------------------- // Note that all values are in motorola (big-endian) format, and that long is // assumed to be 4 bytes, and short 2 bytes. // When reading the samples, you should handle both signed and unsigned data, // and be prepared to convert 16->8 bit, or mono->stereo if needed. To convert // 8-bit data between signed/unsigned just add 127 to the sample values. // Simularly for 16-bit data you should add 32769 $info['fileformat'] = 'avr'; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $AVRheader = fread($this->getid3->fp, 128); $info['avr']['raw']['magic'] = substr($AVRheader, 0, 4); $magic = '2BIT'; if ($info['avr']['raw']['magic'] != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes($info['avr']['raw']['magic']) . '"'; unset($info['fileformat']); unset($info['avr']); return false; } $info['avdataoffset'] += 128; $info['avr']['sample_name'] = rtrim(substr($AVRheader, 4, 8)); $info['avr']['raw']['mono'] = Helper::BigEndian2Int(substr($AVRheader, 12, 2)); $info['avr']['bits_per_sample'] = Helper::BigEndian2Int(substr($AVRheader, 14, 2)); $info['avr']['raw']['signed'] = Helper::BigEndian2Int(substr($AVRheader, 16, 2)); $info['avr']['raw']['loop'] = Helper::BigEndian2Int(substr($AVRheader, 18, 2)); $info['avr']['raw']['midi'] = Helper::BigEndian2Int(substr($AVRheader, 20, 2)); $info['avr']['raw']['replay_freq'] = Helper::BigEndian2Int(substr($AVRheader, 22, 1)); $info['avr']['sample_rate'] = Helper::BigEndian2Int(substr($AVRheader, 23, 3)); $info['avr']['sample_length'] = Helper::BigEndian2Int(substr($AVRheader, 26, 4)); $info['avr']['loop_start'] = Helper::BigEndian2Int(substr($AVRheader, 30, 4)); $info['avr']['loop_end'] = Helper::BigEndian2Int(substr($AVRheader, 34, 4)); $info['avr']['midi_split'] = Helper::BigEndian2Int(substr($AVRheader, 38, 2)); $info['avr']['sample_compression'] = Helper::BigEndian2Int(substr($AVRheader, 40, 2)); $info['avr']['reserved'] = Helper::BigEndian2Int(substr($AVRheader, 42, 2)); $info['avr']['sample_name_extra'] = rtrim(substr($AVRheader, 44, 20)); $info['avr']['comment'] = rtrim(substr($AVRheader, 64, 64)); $info['avr']['flags']['stereo'] = $info['avr']['raw']['mono'] == 0 ? false : true; $info['avr']['flags']['signed'] = $info['avr']['raw']['signed'] == 0 ? false : true; $info['avr']['flags']['loop'] = $info['avr']['raw']['loop'] == 0 ? false : true; $info['avr']['midi_notes'] = array(); if (($info['avr']['raw']['midi'] & 0xff00) != 0xff00) { $info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0xff00) >> 8; } if (($info['avr']['raw']['midi'] & 0xff) != 0xff) { $info['avr']['midi_notes'][] = $info['avr']['raw']['midi'] & 0xff; } if ($info['avdataend'] - $info['avdataoffset'] != $info['avr']['sample_length'] * ($info['avr']['bits_per_sample'] == 8 ? 1 : 2)) { $info['warning'][] = 'Probable truncated file: expecting ' . $info['avr']['sample_length'] * ($info['avr']['bits_per_sample'] == 8 ? 1 : 2) . ' bytes of audio data, found ' . ($info['avdataend'] - $info['avdataoffset']); } $info['audio']['dataformat'] = 'avr'; $info['audio']['lossless'] = true; $info['audio']['bitrate_mode'] = 'cbr'; $info['audio']['bits_per_sample'] = $info['avr']['bits_per_sample']; $info['audio']['sample_rate'] = $info['avr']['sample_rate']; $info['audio']['channels'] = $info['avr']['flags']['stereo'] ? 2 : 1; $info['playtime_seconds'] = $info['avr']['sample_length'] / $info['audio']['channels'] / $info['avr']['sample_rate']; $info['audio']['bitrate'] = $info['avr']['sample_length'] * ($info['avr']['bits_per_sample'] == 8 ? 8 : 16) / $info['playtime_seconds']; return true; }
/** * @staticvar array $decbin * * @param type $MaxFramesToScan * @param type $ReturnExtendedInfo * * @return bool */ public function getAACADTSheaderFilepointer($MaxFramesToScan = 1000000, $ReturnExtendedInfo = false) { $info =& $this->getid3->info; // based loosely on code from AACfile by Jurgen Faul <jfaulØgmx.de> // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html // http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link // http://wiki.multimedia.cx/index.php?title=ADTS // * ADTS Fixed Header: these don't change from frame to frame // syncword 12 always: '111111111111' // ID 1 0: MPEG-4, 1: MPEG-2 // MPEG layer 2 If you send AAC in MPEG-TS, set to 0 // protection_absent 1 0: CRC present; 1: no CRC // profile 2 0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction) // sampling_frequency_index 4 15 not allowed // private_bit 1 usually 0 // channel_configuration 3 // original/copy 1 0: original; 1: copy // home 1 usually 0 // emphasis 2 only if ID == 0 (ie MPEG-4) // not present in some documentation? // * ADTS Variable Header: these can change from frame to frame // copyright_identification_bit 1 // copyright_identification_start 1 // aac_frame_length 13 length of the frame including header (in bytes) // adts_buffer_fullness 11 0x7FF indicates VBR // no_raw_data_blocks_in_frame 2 // * ADTS Error check // crc_check 16 only if protection_absent == 0 $byteoffset = $info['avdataoffset']; $framenumber = 0; // Init bit pattern array static $decbin = array(); // Populate $bindec for ($i = 0; $i < 256; ++$i) { $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT); } // used to calculate bitrate below $BitrateCache = array(); while (true) { // breaks out when end-of-file encountered, or invalid data found, // or MaxFramesToScan frames have been scanned if (!Helper::intValueSupported($byteoffset)) { $info['warning'][] = 'Unable to parse AAC file beyond ' . ftell($this->getid3->fp) . ' (PHP does not support file operations beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB)'; return false; } fseek($this->getid3->fp, $byteoffset, SEEK_SET); // First get substring $substring = fread($this->getid3->fp, 9); // header is 7 bytes (or 9 if CRC is present) $substringlength = strlen($substring); if ($substringlength != 9) { $info['error'][] = 'Failed to read 7 bytes at offset ' . (ftell($this->getid3->fp) - $substringlength) . ' (only read ' . $substringlength . ' bytes)'; return false; } // this would be easier with 64-bit math, but split it up to allow for 32-bit: $header1 = Helper::BigEndian2Int(substr($substring, 0, 2)); $header2 = Helper::BigEndian2Int(substr($substring, 2, 4)); $header3 = Helper::BigEndian2Int(substr($substring, 6, 1)); $info['aac']['header']['raw']['syncword'] = ($header1 & 0xfff0) >> 4; if ($info['aac']['header']['raw']['syncword'] != 0xfff) { $info['error'][] = 'Synch pattern (0x0FFF) not found at offset ' . (ftell($this->getid3->fp) - $substringlength) . ' (found 0x0' . strtoupper(dechex($info['aac']['header']['raw']['syncword'])) . ' instead)'; //if ($info['fileformat'] == 'aac') { // return true; //} unset($info['aac']); return false; } // Gather info for first frame only - this takes time to do 1000 times! if ($framenumber == 0) { $info['aac']['header_type'] = 'ADTS'; $info['fileformat'] = 'aac'; $info['audio']['dataformat'] = 'aac'; $info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x8) >> 3; $info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x6) >> 1; $info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x1) >> 0; $info['aac']['header']['raw']['profile_code'] = ($header2 & 0xc0000000) >> 30; $info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3c000000) >> 26; $info['aac']['header']['raw']['private_stream'] = ($header2 & 0x2000000) >> 25; $info['aac']['header']['raw']['channels_code'] = ($header2 & 0x1c00000) >> 22; $info['aac']['header']['raw']['original'] = ($header2 & 0x200000) >> 21; $info['aac']['header']['raw']['home'] = ($header2 & 0x100000) >> 20; $info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x80000) >> 19; $info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x40000) >> 18; $info['aac']['header']['raw']['frame_length'] = ($header2 & 0x3ffe0) >> 5; $info['aac']['header']['mpeg_version'] = $info['aac']['header']['raw']['mpeg_version'] ? 2 : 4; $info['aac']['header']['crc_present'] = $info['aac']['header']['raw']['protection_absent'] ? false : true; $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']); $info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']); $info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream']; $info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original']; $info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home']; $info['aac']['header']['channels'] = $info['aac']['header']['raw']['channels_code'] == 7 ? 8 : $info['aac']['header']['raw']['channels_code']; if ($ReturnExtendedInfo) { $info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream']; $info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start']; } if ($info['aac']['header']['raw']['mpeg_layer'] != 0) { $info['warning'][] = 'Layer error - expected "0", found "' . $info['aac']['header']['raw']['mpeg_layer'] . '" instead'; } if ($info['aac']['header']['sample_frequency'] == 0) { $info['error'][] = 'Corrupt AAC file: sample_frequency == zero'; return false; } $info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency']; $info['audio']['channels'] = $info['aac']['header']['channels']; } $FrameLength = ($header2 & 0x3ffe0) >> 5; if (!isset($BitrateCache[$FrameLength])) { $BitrateCache[$FrameLength] = $info['aac']['header']['sample_frequency'] / 1024 * $FrameLength * 8; } Helper::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1); $info['aac'][$framenumber]['aac_frame_length'] = $FrameLength; $info['aac'][$framenumber]['adts_buffer_fullness'] = ($header2 & 0x1f) << 6 & ($header3 & 0xfc) >> 2; if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x7ff) { $info['audio']['bitrate_mode'] = 'vbr'; } else { $info['audio']['bitrate_mode'] = 'cbr'; } $info['aac'][$framenumber]['num_raw_data_blocks'] = ($header3 & 0x3) >> 0; if ($info['aac']['header']['crc_present']) { //$info['aac'][$framenumber]['crc'] = GetId3_lib::BigEndian2Int(substr($substring, 7, 2); } if (!$ReturnExtendedInfo) { unset($info['aac'][$framenumber]); } /* $rounded_precision = 5000; $info['aac']['bitrate_distribution_rounded'] = array(); foreach ($info['aac']['bitrate_distribution'] as $bitrate => $count) { $rounded_bitrate = round($bitrate / $rounded_precision) * $rounded_precision; getid3_lib::safe_inc($info['aac']['bitrate_distribution_rounded'][$rounded_bitrate], $count); } ksort($info['aac']['bitrate_distribution_rounded']); */ $byteoffset += $FrameLength; if (++$framenumber < $MaxFramesToScan && $byteoffset + 10 < $info['avdataend']) { // keep scanning } else { $info['aac']['frames'] = $framenumber; $info['playtime_seconds'] = $info['avdataend'] / $byteoffset * ($framenumber * 1024 / $info['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds if ($info['playtime_seconds'] == 0) { $info['error'][] = 'Corrupt AAC file: playtime_seconds == zero'; return false; } $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; ksort($info['aac']['bitrate_distribution']); $info['audio']['encoder_options'] = $info['aac']['header_type'] . ' ' . $info['aac']['header']['profile']; return true; } } // should never get here. }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $TSheader = fread($this->getid3->fp, 19); $magic = "G"; if (substr($TSheader, 0, 1) != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" at ' . $info['avdataoffset'] . ', found ' . Helper::PrintHexBytes(substr($TSheader, 0, 1)) . ' instead.'; return false; } $info['fileformat'] = 'ts'; // http://en.wikipedia.org/wiki/.ts $offset = 0; $info['ts']['packet']['sync'] = Helper::BigEndian2Int(substr($TSheader, $offset, 1)); $offset += 1; $pid_flags_raw = Helper::BigEndian2Int(substr($TSheader, $offset, 2)); $offset += 2; $SAC_raw = Helper::BigEndian2Int(substr($TSheader, $offset, 1)); $offset += 1; $info['ts']['packet']['flags']['transport_error_indicator'] = (bool) ($pid_flags_raw & 0x8000); // Set by demodulator if can't correct errors in the stream, to tell the demultiplexer that the packet has an uncorrectable error $info['ts']['packet']['flags']['payload_unit_start_indicator'] = (bool) ($pid_flags_raw & 0x4000); // 1 means start of PES data or PSI otherwise zero only. $info['ts']['packet']['flags']['transport_high_priority'] = (bool) ($pid_flags_raw & 0x2000); // 1 means higher priority than other packets with the same PID. $info['ts']['packet']['packet_id'] = ($pid_flags_raw & 0x1fff) >> 0; $info['ts']['packet']['raw']['scrambling_control'] = ($SAC_raw & 0xc0) >> 6; $info['ts']['packet']['flags']['adaption_field_exists'] = (bool) ($SAC_raw & 0x20); $info['ts']['packet']['flags']['payload_exists'] = (bool) ($SAC_raw & 0x10); $info['ts']['packet']['continuity_counter'] = ($SAC_raw & 0xf) >> 0; // Incremented only when a payload is present $info['ts']['packet']['scrambling_control'] = $this->TSscramblingControlLookup($info['ts']['packet']['raw']['scrambling_control']); if ($info['ts']['packet']['flags']['adaption_field_exists']) { $AdaptionField_raw = Helper::BigEndian2Int(substr($TSheader, $offset, 2)); $offset += 2; $info['ts']['packet']['adaption']['field_length'] = ($AdaptionField_raw & 0xff00) >> 8; // Number of bytes in the adaptation field immediately following this byte $info['ts']['packet']['adaption']['flags']['discontinuity'] = (bool) ($AdaptionField_raw & 0x80); // Set to 1 if current TS packet is in a discontinuity state with respect to either the continuity counter or the program clock reference $info['ts']['packet']['adaption']['flags']['random_access'] = (bool) ($AdaptionField_raw & 0x40); // Set to 1 if the PES packet in this TS packet starts a video/audio sequence $info['ts']['packet']['adaption']['flags']['high_priority'] = (bool) ($AdaptionField_raw & 0x20); // 1 = higher priority $info['ts']['packet']['adaption']['flags']['pcr'] = (bool) ($AdaptionField_raw & 0x10); // 1 means adaptation field does contain a PCR field $info['ts']['packet']['adaption']['flags']['opcr'] = (bool) ($AdaptionField_raw & 0x8); // 1 means adaptation field does contain an OPCR field $info['ts']['packet']['adaption']['flags']['splice_point'] = (bool) ($AdaptionField_raw & 0x4); // 1 means presence of splice countdown field in adaptation field $info['ts']['packet']['adaption']['flags']['private_data'] = (bool) ($AdaptionField_raw & 0x2); // 1 means presence of private data bytes in adaptation field $info['ts']['packet']['adaption']['flags']['extension'] = (bool) ($AdaptionField_raw & 0x1); // 1 means presence of adaptation field extension if ($info['ts']['packet']['adaption']['flags']['pcr']) { $info['ts']['packet']['adaption']['raw']['pcr'] = Helper::BigEndian2Int(substr($TSheader, $offset, 6)); $offset += 6; } if ($info['ts']['packet']['adaption']['flags']['opcr']) { $info['ts']['packet']['adaption']['raw']['opcr'] = Helper::BigEndian2Int(substr($TSheader, $offset, 6)); $offset += 6; } } $info['error'][] = 'MPEG Transport Stream (.ts) parsing not enabled in this version of GetId3Core() [' . $this->getid3->version() . ']'; return false; }
/** * @param type $bytestring * @param type $byteorder * * @return bool */ public function TIFFendian2Int($bytestring, $byteorder) { if ($byteorder == 'Intel') { return Helper::LittleEndian2Int($bytestring); } elseif ($byteorder == 'Motorola') { return Helper::BigEndian2Int($bytestring); } return false; }
/** * @param type $byteword * @param type $signed * * @return type */ public function EitherEndian2Int($byteword, $signed = false) { if ($this->getid3->info['fileformat'] == 'riff') { return Helper::LittleEndian2Int($byteword, $signed); } return Helper::BigEndian2Int($byteword, false, $signed); }
/** * @link http://trac.musepack.net/trac/wiki/SV8Specification */ public function ParseMPCsv8() { // this is SV8 $info =& $this->getid3->info; $thisfile_mpc_header =& $info['mpc']['header']; $keyNameSize = 2; $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" $offset = ftell($this->getid3->fp); while ($offset < $info['avdataend']) { $thisPacket = array(); $thisPacket['offset'] = $offset; $packet_offset = 0; // Size is a variable-size field, could be 1-4 bytes (possibly more?) // read enough data in and figure out the exact size later $MPCheaderData = fread($this->getid3->fp, $keyNameSize + $maxHandledPacketLength); $packet_offset += $keyNameSize; $thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize); $thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']); if ($thisPacket['key'] == $thisPacket['key_name']) { $info['error'][] = 'Found unexpected key value "' . $thisPacket['key'] . '" at offset ' . $thisPacket['offset']; return false; } $packetLength = 0; $thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field if ($thisPacket['packet_size'] === false) { $info['error'][] = 'Did not find expected packet length within ' . $maxHandledPacketLength . ' bytes at offset ' . ($thisPacket['offset'] + $keyNameSize); return false; } $packet_offset += $packetLength; $offset += $thisPacket['packet_size']; switch ($thisPacket['key']) { case 'SH': // Stream Header $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $thisPacket['crc'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4)); $packet_offset += 4; $thisPacket['stream_version'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; $packetLength = 0; $thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); $packet_offset += $packetLength; $packetLength = 0; $thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); $packet_offset += $packetLength; $otherUsefulData = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; $thisPacket['sample_frequency_raw'] = ($otherUsefulData & 0xe000) >> 13; $thisPacket['max_bands_used'] = ($otherUsefulData & 0x1f00) >> 8; $thisPacket['channels'] = (($otherUsefulData & 0xf0) >> 4) + 1; $thisPacket['ms_used'] = (bool) (($otherUsefulData & 0x8) >> 3); $thisPacket['audio_block_frames'] = ($otherUsefulData & 0x7) >> 0; $thisPacket['sample_frequency'] = $this->MPCfrequencyLookup($thisPacket['sample_frequency_raw']); $thisfile_mpc_header['mid_side_stereo'] = $thisPacket['ms_used']; $thisfile_mpc_header['sample_rate'] = $thisPacket['sample_frequency']; $thisfile_mpc_header['samples'] = $thisPacket['sample_count']; $thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version']; $info['audio']['channels'] = $thisPacket['channels']; $info['audio']['sample_rate'] = $thisPacket['sample_frequency']; $info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency']; $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; break; case 'RG': // Replay Gain $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $thisPacket['replaygain_version'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; $thisPacket['replaygain_title_gain'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; $thisPacket['replaygain_title_peak'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; $thisPacket['replaygain_album_gain'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; $thisPacket['replaygain_album_peak'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); $packet_offset += 2; if ($thisPacket['replaygain_title_gain']) { $info['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; } if ($thisPacket['replaygain_title_peak']) { $info['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; } if ($thisPacket['replaygain_album_gain']) { $info['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; } if ($thisPacket['replaygain_album_peak']) { $info['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; } break; case 'EI': // Encoder Info $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); } $profile_pns = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; $quality_int = ($profile_pns & 0xf0) >> 4; $quality_dec = ($profile_pns & 0xe) >> 3; $thisPacket['quality'] = (double) $quality_int + $quality_dec / 8; $thisPacket['pns_tool'] = (bool) (($profile_pns & 0x1) >> 0); $thisPacket['version_major'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; $thisPacket['version_minor'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; $thisPacket['version_build'] = Helper::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; $thisPacket['version'] = $thisPacket['version_major'] . '.' . $thisPacket['version_minor'] . '.' . $thisPacket['version_build']; $info['audio']['encoder'] = 'MPC v' . $thisPacket['version'] . ' (' . ($thisPacket['version_minor'] % 2 ? 'unstable' : 'stable') . ')'; $thisfile_mpc_header['encoder_version'] = $info['audio']['encoder']; //$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0 $thisfile_mpc_header['quality'] = (double) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 break; case 'SO': // Seek Table Offset $packetLength = 0; $thisPacket['seek_table_offset'] = $thisPacket['offset'] + $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); $packet_offset += $packetLength; break; case 'ST': // Seek Table // Seek Table case 'SE': // Stream End // Stream End case 'AP': // Audio Data // nothing useful here, just skip this packet $thisPacket = array(); break; default: $info['error'][] = 'Found unhandled key type "' . $thisPacket['key'] . '" at offset ' . $thisPacket['offset']; return false; break; } if (!empty($thisPacket)) { $info['mpc']['packets'][] = $thisPacket; } fseek($this->getid3->fp, $offset); } $thisfile_mpc_header['size'] = $offset; return true; }
/** * @param type $Header4Bytes * * @return bool */ public static function MPEGaudioHeaderDecode($Header4Bytes) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // A - Frame sync (all bits set) // B - MPEG Audio version ID // C - Layer description // D - Protection bit // E - Bitrate index // F - Sampling rate frequency index // G - Padding bit // H - Private bit // I - Channel Mode // J - Mode extension (Only if Joint stereo) // K - Copyright // L - Original // M - Emphasis if (strlen($Header4Bytes) != 4) { return false; } $MPEGrawHeader['synch'] = (Helper::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xffe0) >> 4; $MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB $MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x6) >> 1; // CC $MPEGrawHeader['protection'] = ord($Header4Bytes[1]) & 0x1; // D $MPEGrawHeader['bitrate'] = (ord($Header4Bytes[2]) & 0xf0) >> 4; // EEEE $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes[2]) & 0xc) >> 2; // FF $MPEGrawHeader['padding'] = (ord($Header4Bytes[2]) & 0x2) >> 1; // G $MPEGrawHeader['private'] = ord($Header4Bytes[2]) & 0x1; // H $MPEGrawHeader['channelmode'] = (ord($Header4Bytes[3]) & 0xc0) >> 6; // II $MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; // JJ $MPEGrawHeader['copyright'] = (ord($Header4Bytes[3]) & 0x8) >> 3; // K $MPEGrawHeader['original'] = (ord($Header4Bytes[3]) & 0x4) >> 2; // L $MPEGrawHeader['emphasis'] = ord($Header4Bytes[3]) & 0x3; // MM return $MPEGrawHeader; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; // shortcut $info['midi']['raw'] = array(); $thisfile_midi =& $info['midi']; $thisfile_midi_raw =& $thisfile_midi['raw']; $info['fileformat'] = 'midi'; $info['audio']['dataformat'] = 'midi'; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $MIDIdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); $offset = 0; $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd' if ($MIDIheaderID != self::GETID3_MIDI_MAGIC_MTHD) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes(self::GETID3_MIDI_MAGIC_MTHD) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes($MIDIheaderID) . '"'; unset($info['fileformat']); return false; } $offset += 4; $thisfile_midi_raw['headersize'] = Helper::BigEndian2Int(substr($MIDIdata, $offset, 4)); $offset += 4; $thisfile_midi_raw['fileformat'] = Helper::BigEndian2Int(substr($MIDIdata, $offset, 2)); $offset += 2; $thisfile_midi_raw['tracks'] = Helper::BigEndian2Int(substr($MIDIdata, $offset, 2)); $offset += 2; $thisfile_midi_raw['ticksperqnote'] = Helper::BigEndian2Int(substr($MIDIdata, $offset, 2)); $offset += 2; for ($i = 0; $i < $thisfile_midi_raw['tracks']; ++$i) { while (strlen($MIDIdata) - $offset < 8) { $MIDIdata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); } $trackID = substr($MIDIdata, $offset, 4); $offset += 4; if ($trackID == self::GETID3_MIDI_MAGIC_MTRK) { $tracksize = Helper::BigEndian2Int(substr($MIDIdata, $offset, 4)); $offset += 4; // $thisfile_midi['tracks'][$i]['size'] = $tracksize; $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize); $offset += $tracksize; } else { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes(self::GETID3_MIDI_MAGIC_MTRK) . '" at ' . ($offset - 4) . ', found "' . Helper::PrintHexBytes($trackID) . '" instead'; return false; } } if (!isset($trackdataarray) || !is_array($trackdataarray)) { $info['error'][] = 'Cannot find MIDI track information'; unset($thisfile_midi); unset($info['fileformat']); return false; } if ($this->scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important $thisfile_midi['totalticks'] = 0; $info['playtime_seconds'] = 0; $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat $MicroSecondsPerQuarterNoteAfter = array(); foreach ($trackdataarray as $tracknumber => $trackdata) { $eventsoffset = 0; $LastIssuedMIDIcommand = 0; $LastIssuedMIDIchannel = 0; $CumulativeDeltaTime = 0; $TicksAtCurrentBPM = 0; while ($eventsoffset < strlen($trackdata)) { $eventid = 0; if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) { $eventid = count($MIDIevents[$tracknumber]); } $deltatime = 0; for ($i = 0; $i < 4; ++$i) { $deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1)); $deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7f); if ($deltatimebyte & 0x80) { // another byte follows } else { break; } } $CumulativeDeltaTime += $deltatime; $TicksAtCurrentBPM += $deltatime; $MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime; $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1)); if ($MIDI_event_channel & 0x80) { // OK, normal event - MIDI command has MSB set $LastIssuedMIDIcommand = $MIDI_event_channel >> 4; $LastIssuedMIDIchannel = $MIDI_event_channel & 0xf; } else { // running event - assume last command --$eventsoffset; } $MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand; $MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel; if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x8) { // Note off (key is released) $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); $velocity = ord(substr($trackdata, $eventsoffset++, 1)); } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x9) { // Note on (key is pressed) $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); $velocity = ord(substr($trackdata, $eventsoffset++, 1)); } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0xa) { // Key after-touch $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); $velocity = ord(substr($trackdata, $eventsoffset++, 1)); } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0xb) { // Control Change $controllernum = ord(substr($trackdata, $eventsoffset++, 1)); $newvalue = ord(substr($trackdata, $eventsoffset++, 1)); } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0xc) { // Program (patch) change $newprogramnum = ord(substr($trackdata, $eventsoffset++, 1)); $thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum; if ($tracknumber == 10) { $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum); } else { $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum); } } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0xd) { // Channel after-touch $channelnumber = ord(substr($trackdata, $eventsoffset++, 1)); } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0xe) { // Pitch wheel change (2000H is normal or no change) $changeLSB = ord(substr($trackdata, $eventsoffset++, 1)); $changeMSB = ord(substr($trackdata, $eventsoffset++, 1)); $pitchwheelchange = ($changeMSB & 0x7f) << 7 & ($changeLSB & 0x7f); } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0xf && $MIDIevents[$tracknumber][$eventid]['channel'] == 0xf) { $METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1)); $METAeventLength = ord(substr($trackdata, $eventsoffset++, 1)); $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength); $eventsoffset += $METAeventLength; switch ($METAeventCommand) { case 0x0: // Set track sequence number $track_sequence_number = Helper::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number; break; case 0x1: // Text: generic $text_generic = substr($METAeventData, 0, $METAeventLength); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic; $thisfile_midi['comments']['comment'][] = $text_generic; break; case 0x2: // Text: copyright $text_copyright = substr($METAeventData, 0, $METAeventLength); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright; $thisfile_midi['comments']['copyright'][] = $text_copyright; break; case 0x3: // Text: track name $text_trackname = substr($METAeventData, 0, $METAeventLength); $thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname; break; case 0x4: // Text: track instrument name $text_instrument = substr($METAeventData, 0, $METAeventLength); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument; break; case 0x5: // Text: lyrics $text_lyrics = substr($METAeventData, 0, $METAeventLength); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics; if (!isset($thisfile_midi['lyrics'])) { $thisfile_midi['lyrics'] = ''; } $thisfile_midi['lyrics'] .= $text_lyrics . "\n"; break; case 0x6: // Text: marker $text_marker = substr($METAeventData, 0, $METAeventLength); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker; break; case 0x7: // Text: cue point $text_cuepoint = substr($METAeventData, 0, $METAeventLength); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint; break; case 0x2f: // End Of Track //$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime; break; case 0x51: // Tempo: microseconds / quarter note $CurrentMicroSecondsPerBeat = Helper::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); if ($CurrentMicroSecondsPerBeat == 0) { $info['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'; return false; } $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat; $CurrentBeatsPerMinute = 1000000 / $CurrentMicroSecondsPerBeat * 60; $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat; $TicksAtCurrentBPM = 0; break; case 0x58: // Time signature $timesig_numerator = Helper::BigEndian2Int($METAeventData[0]); $timesig_denominator = pow(2, Helper::BigEndian2Int($METAeventData[1])); // $02 -> x/4, $03 -> x/8, etc $timesig_32inqnote = Helper::BigEndian2Int($METAeventData[2]); // number of 32nd notes to the quarter note //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote; //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator; //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator; //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator; $thisfile_midi['timesignature'][] = $timesig_numerator . '/' . $timesig_denominator; break; case 0x59: // Keysignature $keysig_sharpsflats = Helper::BigEndian2Int($METAeventData[0]); if ($keysig_sharpsflats & 0x80) { // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps) $keysig_sharpsflats -= 256; } $keysig_majorminor = Helper::BigEndian2Int($METAeventData[1]); // 0 -> major, 1 -> minor $keysigs = array(-7 => 'Cb', -6 => 'Gb', -5 => 'Db', -4 => 'Ab', -3 => 'Eb', -2 => 'Bb', -1 => 'F', 0 => 'C', 1 => 'G', 2 => 'D', 3 => 'A', 4 => 'E', 5 => 'B', 6 => 'F#', 7 => 'C#'); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0); //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor; //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major'); // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect) $thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats] . ' ' . ((bool) $keysig_majorminor ? 'minor' : 'major'); break; case 0x7f: // Sequencer specific information $custom_data = substr($METAeventData, 0, $METAeventLength); break; default: $info['warning'][] = 'Unhandled META Event Command: ' . $METAeventCommand; break; } } else { $info['warning'][] = 'Unhandled MIDI Event ID: ' . $MIDIevents[$tracknumber][$eventid]['eventid'] . ' + Channel ID: ' . $MIDIevents[$tracknumber][$eventid]['channel']; } } if ($tracknumber > 0 || count($trackdataarray) == 1) { $thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime); } } $previoustickoffset = null; ksort($MicroSecondsPerQuarterNoteAfter); foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) { if (is_null($previoustickoffset)) { $prevmicrosecondsperbeat = $microsecondsperbeat; $previoustickoffset = $tickoffset; continue; } if ($thisfile_midi['totalticks'] > $tickoffset) { if ($thisfile_midi_raw['ticksperqnote'] == 0) { $info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; return false; } $info['playtime_seconds'] += ($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote'] * ($prevmicrosecondsperbeat / 1000000); $prevmicrosecondsperbeat = $microsecondsperbeat; $previoustickoffset = $tickoffset; } } if ($thisfile_midi['totalticks'] > $previoustickoffset) { if ($thisfile_midi_raw['ticksperqnote'] == 0) { $info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; return false; } $info['playtime_seconds'] += ($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote'] * ($microsecondsperbeat / 1000000); } } if (!empty($info['playtime_seconds'])) { $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } if (!empty($thisfile_midi['lyrics'])) { $thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics']; } return true; }
/** * @return type */ public function getBit() { $result = Helper::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> 7 - $this->currentBits & 0x1; $this->skipBits(1); return $result; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; // shortcut $info['png'] = array(); $thisfile_png =& $info['png']; $info['fileformat'] = 'png'; $info['video']['dataformat'] = 'png'; $info['video']['lossless'] = false; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $PNGfiledata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); $offset = 0; $PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A $offset += 8; if ($PNGidentifier != "‰PNG\r\n\n") { $info['error'][] = 'First 8 bytes of file (' . Helper::PrintHexBytes($PNGidentifier) . ') did not match expected PNG identifier'; unset($info['fileformat']); return false; } while (ftell($this->getid3->fp) - (strlen($PNGfiledata) - $offset) < $info['filesize']) { $chunk['data_length'] = Helper::BigEndian2Int(substr($PNGfiledata, $offset, 4)); $offset += 4; while (strlen($PNGfiledata) - $offset < $chunk['data_length'] + 4 && ftell($this->getid3->fp) < $info['filesize']) { $PNGfiledata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); } $chunk['type_text'] = substr($PNGfiledata, $offset, 4); $offset += 4; $chunk['type_raw'] = Helper::BigEndian2Int($chunk['type_text']); $chunk['data'] = substr($PNGfiledata, $offset, $chunk['data_length']); $offset += $chunk['data_length']; $chunk['crc'] = Helper::BigEndian2Int(substr($PNGfiledata, $offset, 4)); $offset += 4; $chunk['flags']['ancilliary'] = (bool) ($chunk['type_raw'] & 0x20000000); $chunk['flags']['private'] = (bool) ($chunk['type_raw'] & 0x200000); $chunk['flags']['reserved'] = (bool) ($chunk['type_raw'] & 0x2000); $chunk['flags']['safe_to_copy'] = (bool) ($chunk['type_raw'] & 0x20); // shortcut $thisfile_png[$chunk['type_text']] = array(); $thisfile_png_chunk_type_text =& $thisfile_png[$chunk['type_text']]; switch ($chunk['type_text']) { case 'IHDR': // Image Header $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['width'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 4)); $thisfile_png_chunk_type_text['height'] = Helper::BigEndian2Int(substr($chunk['data'], 4, 4)); $thisfile_png_chunk_type_text['raw']['bit_depth'] = Helper::BigEndian2Int(substr($chunk['data'], 8, 1)); $thisfile_png_chunk_type_text['raw']['color_type'] = Helper::BigEndian2Int(substr($chunk['data'], 9, 1)); $thisfile_png_chunk_type_text['raw']['compression_method'] = Helper::BigEndian2Int(substr($chunk['data'], 10, 1)); $thisfile_png_chunk_type_text['raw']['filter_method'] = Helper::BigEndian2Int(substr($chunk['data'], 11, 1)); $thisfile_png_chunk_type_text['raw']['interlace_method'] = Helper::BigEndian2Int(substr($chunk['data'], 12, 1)); $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['raw']['compression_method']); $thisfile_png_chunk_type_text['color_type']['palette'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x1); $thisfile_png_chunk_type_text['color_type']['true_color'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x2); $thisfile_png_chunk_type_text['color_type']['alpha'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x4); $info['video']['resolution_x'] = $thisfile_png_chunk_type_text['width']; $info['video']['resolution_y'] = $thisfile_png_chunk_type_text['height']; $info['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']); break; case 'PLTE': // Palette $thisfile_png_chunk_type_text['header'] = $chunk; $paletteoffset = 0; for ($i = 0; $i <= 255; ++$i) { //$thisfile_png_chunk_type_text['red'][$i] = GetId3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1)); //$thisfile_png_chunk_type_text['green'][$i] = GetId3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1)); //$thisfile_png_chunk_type_text['blue'][$i] = GetId3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1)); $red = Helper::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1)); $green = Helper::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1)); $blue = Helper::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1)); $thisfile_png_chunk_type_text[$i] = $red << 16 | $green << 8 | $blue; } break; case 'tRNS': // Transparency $thisfile_png_chunk_type_text['header'] = $chunk; switch ($thisfile_png['IHDR']['raw']['color_type']) { case 0: $thisfile_png_chunk_type_text['transparent_color_gray'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 2)); break; case 2: $thisfile_png_chunk_type_text['transparent_color_red'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 2)); $thisfile_png_chunk_type_text['transparent_color_green'] = Helper::BigEndian2Int(substr($chunk['data'], 2, 2)); $thisfile_png_chunk_type_text['transparent_color_blue'] = Helper::BigEndian2Int(substr($chunk['data'], 4, 2)); break; case 3: for ($i = 0; $i < strlen($chunk['data']); ++$i) { $thisfile_png_chunk_type_text['palette_opacity'][$i] = Helper::BigEndian2Int(substr($chunk['data'], $i, 1)); } break; case 4: case 6: $info['error'][] = 'Invalid color_type in tRNS chunk: ' . $thisfile_png['IHDR']['raw']['color_type']; default: $info['warning'][] = 'Unhandled color_type in tRNS chunk: ' . $thisfile_png['IHDR']['raw']['color_type']; break; } break; case 'gAMA': // Image Gamma $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['gamma'] = Helper::BigEndian2Int($chunk['data']) / 100000; break; case 'cHRM': // Primary Chromaticities $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['white_x'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 4)) / 100000; $thisfile_png_chunk_type_text['white_y'] = Helper::BigEndian2Int(substr($chunk['data'], 4, 4)) / 100000; $thisfile_png_chunk_type_text['red_y'] = Helper::BigEndian2Int(substr($chunk['data'], 8, 4)) / 100000; $thisfile_png_chunk_type_text['red_y'] = Helper::BigEndian2Int(substr($chunk['data'], 12, 4)) / 100000; $thisfile_png_chunk_type_text['green_y'] = Helper::BigEndian2Int(substr($chunk['data'], 16, 4)) / 100000; $thisfile_png_chunk_type_text['green_y'] = Helper::BigEndian2Int(substr($chunk['data'], 20, 4)) / 100000; $thisfile_png_chunk_type_text['blue_y'] = Helper::BigEndian2Int(substr($chunk['data'], 24, 4)) / 100000; $thisfile_png_chunk_type_text['blue_y'] = Helper::BigEndian2Int(substr($chunk['data'], 28, 4)) / 100000; break; case 'sRGB': // Standard RGB Color Space $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['reindering_intent'] = Helper::BigEndian2Int($chunk['data']); $thisfile_png_chunk_type_text['reindering_intent_text'] = $this->PNGsRGBintentLookup($thisfile_png_chunk_type_text['reindering_intent']); break; case 'iCCP': // Embedded ICC Profile $thisfile_png_chunk_type_text['header'] = $chunk; list($profilename, $compressiondata) = explode("", $chunk['data'], 2); $thisfile_png_chunk_type_text['profile_name'] = $profilename; $thisfile_png_chunk_type_text['compression_method'] = Helper::BigEndian2Int(substr($compressiondata, 0, 1)); $thisfile_png_chunk_type_text['compression_profile'] = substr($compressiondata, 1); $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']); break; case 'tEXt': // Textual Data $thisfile_png_chunk_type_text['header'] = $chunk; list($keyword, $text) = explode("", $chunk['data'], 2); $thisfile_png_chunk_type_text['keyword'] = $keyword; $thisfile_png_chunk_type_text['text'] = $text; $thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text']; break; case 'zTXt': // Compressed Textual Data $thisfile_png_chunk_type_text['header'] = $chunk; list($keyword, $otherdata) = explode("", $chunk['data'], 2); $thisfile_png_chunk_type_text['keyword'] = $keyword; $thisfile_png_chunk_type_text['compression_method'] = Helper::BigEndian2Int(substr($otherdata, 0, 1)); $thisfile_png_chunk_type_text['compressed_text'] = substr($otherdata, 1); $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']); switch ($thisfile_png_chunk_type_text['compression_method']) { case 0: $thisfile_png_chunk_type_text['text'] = gzuncompress($thisfile_png_chunk_type_text['compressed_text']); break; default: // unknown compression method break; } if (isset($thisfile_png_chunk_type_text['text'])) { $thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text']; } break; case 'iTXt': // International Textual Data $thisfile_png_chunk_type_text['header'] = $chunk; list($keyword, $otherdata) = explode("", $chunk['data'], 2); $thisfile_png_chunk_type_text['keyword'] = $keyword; $thisfile_png_chunk_type_text['compression'] = (bool) Helper::BigEndian2Int(substr($otherdata, 0, 1)); $thisfile_png_chunk_type_text['compression_method'] = Helper::BigEndian2Int(substr($otherdata, 1, 1)); $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']); list($languagetag, $translatedkeyword, $text) = explode("", substr($otherdata, 2), 3); $thisfile_png_chunk_type_text['language_tag'] = $languagetag; $thisfile_png_chunk_type_text['translated_keyword'] = $translatedkeyword; if ($thisfile_png_chunk_type_text['compression']) { switch ($thisfile_png_chunk_type_text['compression_method']) { case 0: $thisfile_png_chunk_type_text['text'] = gzuncompress($text); break; default: // unknown compression method break; } } else { $thisfile_png_chunk_type_text['text'] = $text; } if (isset($thisfile_png_chunk_type_text['text'])) { $thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text']; } break; case 'bKGD': // Background Color $thisfile_png_chunk_type_text['header'] = $chunk; switch ($thisfile_png['IHDR']['raw']['color_type']) { case 0: case 4: $thisfile_png_chunk_type_text['background_gray'] = Helper::BigEndian2Int($chunk['data']); break; case 2: case 6: $thisfile_png_chunk_type_text['background_red'] = Helper::BigEndian2Int(substr($chunk['data'], 0 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth'])); $thisfile_png_chunk_type_text['background_green'] = Helper::BigEndian2Int(substr($chunk['data'], 1 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth'])); $thisfile_png_chunk_type_text['background_blue'] = Helper::BigEndian2Int(substr($chunk['data'], 2 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth'])); break; case 3: $thisfile_png_chunk_type_text['background_index'] = Helper::BigEndian2Int($chunk['data']); break; default: break; } break; case 'pHYs': // Physical Pixel Dimensions $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['pixels_per_unit_x'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 4)); $thisfile_png_chunk_type_text['pixels_per_unit_y'] = Helper::BigEndian2Int(substr($chunk['data'], 4, 4)); $thisfile_png_chunk_type_text['unit_specifier'] = Helper::BigEndian2Int(substr($chunk['data'], 8, 1)); $thisfile_png_chunk_type_text['unit'] = $this->PNGpHYsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']); break; case 'sBIT': // Significant Bits $thisfile_png_chunk_type_text['header'] = $chunk; switch ($thisfile_png['IHDR']['raw']['color_type']) { case 0: $thisfile_png_chunk_type_text['significant_bits_gray'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 1)); break; case 2: case 3: $thisfile_png_chunk_type_text['significant_bits_red'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 1)); $thisfile_png_chunk_type_text['significant_bits_green'] = Helper::BigEndian2Int(substr($chunk['data'], 1, 1)); $thisfile_png_chunk_type_text['significant_bits_blue'] = Helper::BigEndian2Int(substr($chunk['data'], 2, 1)); break; case 4: $thisfile_png_chunk_type_text['significant_bits_gray'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 1)); $thisfile_png_chunk_type_text['significant_bits_alpha'] = Helper::BigEndian2Int(substr($chunk['data'], 1, 1)); break; case 6: $thisfile_png_chunk_type_text['significant_bits_red'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 1)); $thisfile_png_chunk_type_text['significant_bits_green'] = Helper::BigEndian2Int(substr($chunk['data'], 1, 1)); $thisfile_png_chunk_type_text['significant_bits_blue'] = Helper::BigEndian2Int(substr($chunk['data'], 2, 1)); $thisfile_png_chunk_type_text['significant_bits_alpha'] = Helper::BigEndian2Int(substr($chunk['data'], 3, 1)); break; default: break; } break; case 'sPLT': // Suggested Palette $thisfile_png_chunk_type_text['header'] = $chunk; list($palettename, $otherdata) = explode("", $chunk['data'], 2); $thisfile_png_chunk_type_text['palette_name'] = $palettename; $sPLToffset = 0; $thisfile_png_chunk_type_text['sample_depth_bits'] = Helper::BigEndian2Int(substr($otherdata, $sPLToffset, 1)); $sPLToffset += 1; $thisfile_png_chunk_type_text['sample_depth_bytes'] = $thisfile_png_chunk_type_text['sample_depth_bits'] / 8; $paletteCounter = 0; while ($sPLToffset < strlen($otherdata)) { $thisfile_png_chunk_type_text['red'][$paletteCounter] = Helper::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; $thisfile_png_chunk_type_text['green'][$paletteCounter] = Helper::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; $thisfile_png_chunk_type_text['blue'][$paletteCounter] = Helper::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; $thisfile_png_chunk_type_text['alpha'][$paletteCounter] = Helper::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; $thisfile_png_chunk_type_text['frequency'][$paletteCounter] = Helper::BigEndian2Int(substr($otherdata, $sPLToffset, 2)); $sPLToffset += 2; ++$paletteCounter; } break; case 'hIST': // Palette Histogram $thisfile_png_chunk_type_text['header'] = $chunk; $hISTcounter = 0; while ($hISTcounter < strlen($chunk['data'])) { $thisfile_png_chunk_type_text[$hISTcounter] = Helper::BigEndian2Int(substr($chunk['data'], $hISTcounter / 2, 2)); $hISTcounter += 2; } break; case 'tIME': // Image Last-Modification Time $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['year'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 2)); $thisfile_png_chunk_type_text['month'] = Helper::BigEndian2Int(substr($chunk['data'], 2, 1)); $thisfile_png_chunk_type_text['day'] = Helper::BigEndian2Int(substr($chunk['data'], 3, 1)); $thisfile_png_chunk_type_text['hour'] = Helper::BigEndian2Int(substr($chunk['data'], 4, 1)); $thisfile_png_chunk_type_text['minute'] = Helper::BigEndian2Int(substr($chunk['data'], 5, 1)); $thisfile_png_chunk_type_text['second'] = Helper::BigEndian2Int(substr($chunk['data'], 6, 1)); $thisfile_png_chunk_type_text['unix'] = gmmktime($thisfile_png_chunk_type_text['hour'], $thisfile_png_chunk_type_text['minute'], $thisfile_png_chunk_type_text['second'], $thisfile_png_chunk_type_text['month'], $thisfile_png_chunk_type_text['day'], $thisfile_png_chunk_type_text['year']); break; case 'oFFs': // Image Offset $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['position_x'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 4), false, true); $thisfile_png_chunk_type_text['position_y'] = Helper::BigEndian2Int(substr($chunk['data'], 4, 4), false, true); $thisfile_png_chunk_type_text['unit_specifier'] = Helper::BigEndian2Int(substr($chunk['data'], 8, 1)); $thisfile_png_chunk_type_text['unit'] = $this->PNGoFFsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']); break; case 'pCAL': // Calibration Of Pixel Values $thisfile_png_chunk_type_text['header'] = $chunk; list($calibrationname, $otherdata) = explode("", $chunk['data'], 2); $thisfile_png_chunk_type_text['calibration_name'] = $calibrationname; $pCALoffset = 0; $thisfile_png_chunk_type_text['original_zero'] = Helper::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true); $pCALoffset += 4; $thisfile_png_chunk_type_text['original_max'] = Helper::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true); $pCALoffset += 4; $thisfile_png_chunk_type_text['equation_type'] = Helper::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1)); $pCALoffset += 1; $thisfile_png_chunk_type_text['equation_type_text'] = $this->PNGpCALequationTypeLookup($thisfile_png_chunk_type_text['equation_type']); $thisfile_png_chunk_type_text['parameter_count'] = Helper::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1)); $pCALoffset += 1; $thisfile_png_chunk_type_text['parameters'] = explode("", substr($chunk['data'], $pCALoffset)); break; case 'sCAL': // Physical Scale Of Image Subject $thisfile_png_chunk_type_text['header'] = $chunk; $thisfile_png_chunk_type_text['unit_specifier'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 1)); $thisfile_png_chunk_type_text['unit'] = $this->PNGsCALUnitLookup($thisfile_png_chunk_type_text['unit_specifier']); list($pixelwidth, $pixelheight) = explode("", substr($chunk['data'], 1)); $thisfile_png_chunk_type_text['pixel_width'] = $pixelwidth; $thisfile_png_chunk_type_text['pixel_height'] = $pixelheight; break; case 'gIFg': // GIF Graphic Control Extension $gIFgCounter = 0; if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) { $gIFgCounter = count($thisfile_png_chunk_type_text); } $thisfile_png_chunk_type_text[$gIFgCounter]['header'] = $chunk; $thisfile_png_chunk_type_text[$gIFgCounter]['disposal_method'] = Helper::BigEndian2Int(substr($chunk['data'], 0, 1)); $thisfile_png_chunk_type_text[$gIFgCounter]['user_input_flag'] = Helper::BigEndian2Int(substr($chunk['data'], 1, 1)); $thisfile_png_chunk_type_text[$gIFgCounter]['delay_time'] = Helper::BigEndian2Int(substr($chunk['data'], 2, 2)); break; case 'gIFx': // GIF Application Extension $gIFxCounter = 0; if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) { $gIFxCounter = count($thisfile_png_chunk_type_text); } $thisfile_png_chunk_type_text[$gIFxCounter]['header'] = $chunk; $thisfile_png_chunk_type_text[$gIFxCounter]['application_identifier'] = substr($chunk['data'], 0, 8); $thisfile_png_chunk_type_text[$gIFxCounter]['authentication_code'] = substr($chunk['data'], 8, 3); $thisfile_png_chunk_type_text[$gIFxCounter]['application_data'] = substr($chunk['data'], 11); break; case 'IDAT': // Image Data $idatinformationfieldindex = 0; if (isset($thisfile_png['IDAT']) && is_array($thisfile_png['IDAT'])) { $idatinformationfieldindex = count($thisfile_png['IDAT']); } unset($chunk['data']); $thisfile_png_chunk_type_text[$idatinformationfieldindex]['header'] = $chunk; break; case 'IEND': // Image Trailer $thisfile_png_chunk_type_text['header'] = $chunk; break; default: //unset($chunk['data']); $thisfile_png_chunk_type_text['header'] = $chunk; $info['warning'][] = 'Unhandled chunk type: ' . $chunk['type_text']; break; } } return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; // based loosely on code from TTwinVQ by Jurgen Faul <jfaulØgmx*de> // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html $info['fileformat'] = 'vqf'; $info['audio']['dataformat'] = 'vqf'; $info['audio']['bitrate_mode'] = 'cbr'; $info['audio']['lossless'] = false; // shortcut $info['vqf']['raw'] = array(); $thisfile_vqf =& $info['vqf']; $thisfile_vqf_raw =& $thisfile_vqf['raw']; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $VQFheaderData = fread($this->getid3->fp, 16); $offset = 0; $thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4); $magic = 'TWIN'; if ($thisfile_vqf_raw['header_tag'] != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes($thisfile_vqf_raw['header_tag']) . '"'; unset($info['vqf']); unset($info['fileformat']); return false; } $offset += 4; $thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8); $offset += 8; $thisfile_vqf_raw['size'] = Helper::BigEndian2Int(substr($VQFheaderData, $offset, 4)); $offset += 4; while (ftell($this->getid3->fp) < $info['avdataend']) { $ChunkBaseOffset = ftell($this->getid3->fp); $chunkoffset = 0; $ChunkData = fread($this->getid3->fp, 8); $ChunkName = substr($ChunkData, $chunkoffset, 4); if ($ChunkName == 'DATA') { $info['avdataoffset'] = $ChunkBaseOffset; break; } $chunkoffset += 4; $ChunkSize = Helper::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; if ($ChunkSize > $info['avdataend'] - ftell($this->getid3->fp)) { $info['error'][] = 'Invalid chunk size (' . $ChunkSize . ') for chunk "' . $ChunkName . '" at offset ' . $ChunkBaseOffset; break; } if ($ChunkSize > 0) { $ChunkData .= fread($this->getid3->fp, $ChunkSize); } switch ($ChunkName) { case 'COMM': // shortcut $thisfile_vqf['COMM'] = array(); $thisfile_vqf_COMM =& $thisfile_vqf['COMM']; $thisfile_vqf_COMM['channel_mode'] = Helper::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; $thisfile_vqf_COMM['bitrate'] = Helper::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; $thisfile_vqf_COMM['sample_rate'] = Helper::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; $thisfile_vqf_COMM['security_level'] = Helper::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; $info['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1; $info['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']); $info['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000; $info['audio']['encoder_options'] = 'CBR' . ceil($info['audio']['bitrate'] / 1000); if ($info['audio']['bitrate'] == 0) { $info['error'][] = 'Corrupt VQF file: bitrate_audio == zero'; return false; } break; case 'NAME': case 'AUTH': case '(c) ': case 'FILE': case 'COMT': case 'ALBM': $thisfile_vqf['comments'][$this->VQFcommentNiceNameLookup($ChunkName)][] = trim(substr($ChunkData, 8)); break; case 'DSIZ': $thisfile_vqf['DSIZ'] = Helper::BigEndian2Int(substr($ChunkData, 8, 4)); break; default: $info['warning'][] = 'Unhandled chunk type "' . $ChunkName . '" at offset ' . $ChunkBaseOffset; break; } } $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate']; if (isset($thisfile_vqf['DSIZ']) && $thisfile_vqf['DSIZ'] != $info['avdataend'] - $info['avdataoffset'] - strlen('DATA')) { switch ($thisfile_vqf['DSIZ']) { case 0: case 1: $info['warning'][] = 'Invalid DSIZ value "' . $thisfile_vqf['DSIZ'] . '". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v' . ($thisfile_vqf['DSIZ'] + 1) . '.0'; $info['audio']['encoder'] = 'Ahead Nero'; break; default: $info['warning'][] = 'Probable corrupted file - should be ' . $thisfile_vqf['DSIZ'] . ' bytes, actually ' . ($info['avdataend'] - $info['avdataoffset'] - strlen('DATA')); break; } } return true; }
/** * @param type $OldRAheaderData * @param type $ParsedArray * * @return bool * * @link http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html */ public function ParseOldRAheader($OldRAheaderData, &$ParsedArray) { $ParsedArray = array(); $ParsedArray['magic'] = substr($OldRAheaderData, 0, 4); if ($ParsedArray['magic'] != '.ra' . "ý") { return false; } $ParsedArray['version1'] = Helper::BigEndian2Int(substr($OldRAheaderData, 4, 2)); if ($ParsedArray['version1'] < 3) { return false; } elseif ($ParsedArray['version1'] == 3) { $ParsedArray['fourcc1'] = '.ra3'; $ParsedArray['bits_per_sample'] = 16; // hard-coded for old versions? $ParsedArray['sample_rate'] = 8000; // hard-coded for old versions? $ParsedArray['header_size'] = Helper::BigEndian2Int(substr($OldRAheaderData, 6, 2)); $ParsedArray['channels'] = Helper::BigEndian2Int(substr($OldRAheaderData, 8, 2)); // always 1 (?) //$ParsedArray['unknown1'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 10, 2)); //$ParsedArray['unknown2'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 2)); //$ParsedArray['unknown3'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 14, 2)); $ParsedArray['bytes_per_minute'] = Helper::BigEndian2Int(substr($OldRAheaderData, 16, 2)); $ParsedArray['audio_bytes'] = Helper::BigEndian2Int(substr($OldRAheaderData, 18, 4)); $ParsedArray['comments_raw'] = substr($OldRAheaderData, 22, $ParsedArray['header_size'] - 22 + 1); // not including null terminator $commentoffset = 0; $commentlength = Helper::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); $ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); $commentoffset += $commentlength; $commentlength = Helper::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); $ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); $commentoffset += $commentlength; $commentlength = Helper::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); $ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); $commentoffset += $commentlength; ++$commentoffset; // final null terminator (?) ++$commentoffset; // fourcc length (?) should be 4 $ParsedArray['fourcc'] = substr($OldRAheaderData, 23 + $commentoffset, 4); } elseif ($ParsedArray['version1'] <= 5) { //$ParsedArray['unknown1'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2)); $ParsedArray['fourcc1'] = substr($OldRAheaderData, 8, 4); $ParsedArray['file_size'] = Helper::BigEndian2Int(substr($OldRAheaderData, 12, 4)); $ParsedArray['version2'] = Helper::BigEndian2Int(substr($OldRAheaderData, 16, 2)); $ParsedArray['header_size'] = Helper::BigEndian2Int(substr($OldRAheaderData, 18, 4)); $ParsedArray['codec_flavor_id'] = Helper::BigEndian2Int(substr($OldRAheaderData, 22, 2)); $ParsedArray['coded_frame_size'] = Helper::BigEndian2Int(substr($OldRAheaderData, 24, 4)); $ParsedArray['audio_bytes'] = Helper::BigEndian2Int(substr($OldRAheaderData, 28, 4)); $ParsedArray['bytes_per_minute'] = Helper::BigEndian2Int(substr($OldRAheaderData, 32, 4)); //$ParsedArray['unknown5'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 36, 4)); $ParsedArray['sub_packet_h'] = Helper::BigEndian2Int(substr($OldRAheaderData, 40, 2)); $ParsedArray['frame_size'] = Helper::BigEndian2Int(substr($OldRAheaderData, 42, 2)); $ParsedArray['sub_packet_size'] = Helper::BigEndian2Int(substr($OldRAheaderData, 44, 2)); //$ParsedArray['unknown6'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 46, 2)); switch ($ParsedArray['version1']) { case 4: $ParsedArray['sample_rate'] = Helper::BigEndian2Int(substr($OldRAheaderData, 48, 2)); //$ParsedArray['unknown8'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 50, 2)); $ParsedArray['bits_per_sample'] = Helper::BigEndian2Int(substr($OldRAheaderData, 52, 2)); $ParsedArray['channels'] = Helper::BigEndian2Int(substr($OldRAheaderData, 54, 2)); $ParsedArray['length_fourcc2'] = Helper::BigEndian2Int(substr($OldRAheaderData, 56, 1)); $ParsedArray['fourcc2'] = substr($OldRAheaderData, 57, 4); $ParsedArray['length_fourcc3'] = Helper::BigEndian2Int(substr($OldRAheaderData, 61, 1)); $ParsedArray['fourcc3'] = substr($OldRAheaderData, 62, 4); //$ParsedArray['unknown9'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 66, 1)); //$ParsedArray['unknown10'] = GetId3_lib::BigEndian2Int(substr($OldRAheaderData, 67, 2)); $ParsedArray['comments_raw'] = substr($OldRAheaderData, 69, $ParsedArray['header_size'] - 69 + 16); $commentoffset = 0; $commentlength = Helper::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); $ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); $commentoffset += $commentlength; $commentlength = Helper::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); $ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); $commentoffset += $commentlength; $commentlength = Helper::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); $ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); $commentoffset += $commentlength; break; case 5: $ParsedArray['sample_rate'] = Helper::BigEndian2Int(substr($OldRAheaderData, 48, 4)); $ParsedArray['sample_rate2'] = Helper::BigEndian2Int(substr($OldRAheaderData, 52, 4)); $ParsedArray['bits_per_sample'] = Helper::BigEndian2Int(substr($OldRAheaderData, 56, 4)); $ParsedArray['channels'] = Helper::BigEndian2Int(substr($OldRAheaderData, 60, 2)); $ParsedArray['genr'] = substr($OldRAheaderData, 62, 4); $ParsedArray['fourcc3'] = substr($OldRAheaderData, 66, 4); $ParsedArray['comments'] = array(); break; } $ParsedArray['fourcc'] = $ParsedArray['fourcc3']; } foreach ($ParsedArray['comments'] as $key => $value) { if ($ParsedArray['comments'][$key][0] === false) { $ParsedArray['comments'][$key][0] = ''; } } return true; }