Beispiel #1
0
 /**
  * @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;
 }
Beispiel #2
0
 /**
  * @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;
 }
Beispiel #3
0
 /**
  * @return bool
  */
 public function analyze()
 {
     $info =& $this->getid3->info;
     ///AH
     $info['ac3']['raw']['bsi'] = array();
     $thisfile_ac3 =& $info['ac3'];
     $thisfile_ac3_raw =& $thisfile_ac3['raw'];
     $thisfile_ac3_raw_bsi =& $thisfile_ac3_raw['bsi'];
     // http://www.atsc.org/standards/a_52a.pdf
     $info['fileformat'] = 'ac3';
     // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
     // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
     // new audio samples per channel. A synchronization information (SI) header at the beginning
     // of each frame contains information needed to acquire and maintain synchronization. A
     // bit stream information (BSI) header follows SI, and contains parameters describing the coded
     // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
     // end of each frame is an error check field that includes a CRC word for error detection. An
     // additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
     //
     // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC
     // syncinfo() {
     // 	 syncword    16
     // 	 crc1        16
     // 	 fscod        2
     // 	 frmsizecod   6
     // } /* end of syncinfo */
     $this->fseek($info['avdataoffset']);
     $this->AC3header['syncinfo'] = $this->fread(5);
     $magic = "\vw";
     if (strpos($this->AC3header['syncinfo'], $magic) === 0) {
         $thisfile_ac3_raw['synchinfo']['synchword'] = $magic;
         $offset = 2;
     } else {
         if (!$this->isDependencyFor('matroska')) {
             unset($info['fileformat'], $info['ac3']);
             return $this->error('Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)) . '"');
         }
         $offset = 0;
         $this->fseek(-2, SEEK_CUR);
     }
     $info['audio']['dataformat'] = 'ac3';
     $info['audio']['bitrate_mode'] = 'cbr';
     $info['audio']['lossless'] = false;
     $thisfile_ac3_raw['synchinfo']['crc1'] = Helper::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2));
     $ac3_synchinfo_fscod_frmsizecod = Helper::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset + 2, 1));
     $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xc0) >> 6;
     $thisfile_ac3_raw['synchinfo']['frmsizecod'] = $ac3_synchinfo_fscod_frmsizecod & 0x3f;
     $thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']);
     if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) {
         $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
     }
     $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']);
     $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']);
     $info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
     $this->AC3header['bsi'] = Helper::BigEndian2Bin($this->fread(15));
     $ac3_bsi_offset = 0;
     $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5);
     if ($thisfile_ac3_raw_bsi['bsid'] > 8) {
         // Decoders which can decode version 8 will thus be able to decode version numbers less than 8.
         // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used.
         // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8.
         $this->error('Bit stream identification is version ' . $thisfile_ac3_raw_bsi['bsid'] . ', but GetId3Core() only understands up to version 8');
         unset($info['ac3']);
         return false;
     }
     $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
     $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
     $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
     $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
     foreach ($ac3_coding_mode as $key => $value) {
         $thisfile_ac3[$key] = $value;
     }
     switch ($thisfile_ac3_raw_bsi['acmod']) {
         case 0:
         case 1:
             $info['audio']['channelmode'] = 'mono';
             break;
         case 3:
         case 4:
             $info['audio']['channelmode'] = 'stereo';
             break;
         default:
             $info['audio']['channelmode'] = 'surround';
             break;
     }
     $info['audio']['channels'] = $thisfile_ac3['num_channels'];
     if ($thisfile_ac3_raw_bsi['acmod'] & 0x1) {
         // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
         $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
         $thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
     }
     if ($thisfile_ac3_raw_bsi['acmod'] & 0x4) {
         // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
         $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
         $thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
     }
     if ($thisfile_ac3_raw_bsi['acmod'] == 0x2) {
         // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
         $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
         $thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
     }
     $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1);
     $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon'];
     if ($thisfile_ac3_raw_bsi['lfeon']) {
         //$info['audio']['channels']++;
         $info['audio']['channels'] .= '.1';
     }
     $thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']);
     // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31.
     // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
     $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);
     $thisfile_ac3['dialogue_normalization'] = '-' . $thisfile_ac3_raw_bsi['dialnorm'] . 'dB';
     $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1);
     if ($thisfile_ac3_raw_bsi['compre_flag']) {
         $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
         $thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
     }
     $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1);
     if ($thisfile_ac3_raw_bsi['langcode_flag']) {
         $thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);
     }
     $thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1);
     if ($thisfile_ac3_raw_bsi['audprodie']) {
         $thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
         $thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2);
         $thisfile_ac3['mixing_level'] = 80 + $thisfile_ac3_raw_bsi['mixlevel'] . 'dB';
         $thisfile_ac3['room_type'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
     }
     if ($thisfile_ac3_raw_bsi['acmod'] == 0x0) {
         // If acmod is 0, then two completely independent program channels (dual mono)
         // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case,
         // a number of additional items are present in BSI or audblk to fully describe Ch2.
         // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31.
         // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
         $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);
         $thisfile_ac3['dialogue_normalization2'] = '-' . $thisfile_ac3_raw_bsi['dialnorm2'] . 'dB';
         $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1);
         if ($thisfile_ac3_raw_bsi['compre_flag2']) {
             $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
             $thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
         }
         $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1);
         if ($thisfile_ac3_raw_bsi['langcode_flag2']) {
             $thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);
         }
         $thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1);
         if ($thisfile_ac3_raw_bsi['audprodie2']) {
             $thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
             $thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2);
             $thisfile_ac3['mixing_level2'] = 80 + $thisfile_ac3_raw_bsi['mixlevel2'] . 'dB';
             $thisfile_ac3['room_type2'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
         }
     }
     $thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);
     $thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1);
     $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1);
     if ($thisfile_ac3_raw_bsi['timecode1_flag']) {
         $thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14);
     }
     $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1);
     if ($thisfile_ac3_raw_bsi['timecode2_flag']) {
         $thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14);
     }
     $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1);
     if ($thisfile_ac3_raw_bsi['addbsi_flag']) {
         $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6);
         $this->AC3header['bsi'] .= Helper::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));
         $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
         $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
     }
     return true;
 }
Beispiel #4
0
 /**
  * @param  type    $BlockData
  *
  * @return bool
  */
 private function parseSTREAMINFO($BlockData)
 {
     $info =& $this->getid3->info;
     $info['flac']['STREAMINFO'] = array();
     $streaminfo =& $info['flac']['STREAMINFO'];
     $streaminfo['min_block_size'] = Helper::BigEndian2Int(substr($BlockData, 0, 2));
     $streaminfo['max_block_size'] = Helper::BigEndian2Int(substr($BlockData, 2, 2));
     $streaminfo['min_frame_size'] = Helper::BigEndian2Int(substr($BlockData, 4, 3));
     $streaminfo['max_frame_size'] = Helper::BigEndian2Int(substr($BlockData, 7, 3));
     $SRCSBSS = Helper::BigEndian2Bin(substr($BlockData, 10, 8));
     $streaminfo['sample_rate'] = Helper::Bin2Dec(substr($SRCSBSS, 0, 20));
     $streaminfo['channels'] = Helper::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1;
     $streaminfo['bits_per_sample'] = Helper::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1;
     $streaminfo['samples_stream'] = Helper::Bin2Dec(substr($SRCSBSS, 28, 36));
     $streaminfo['audio_signature'] = substr($BlockData, 18, 16);
     if (!empty($streaminfo['sample_rate'])) {
         $info['audio']['bitrate_mode'] = 'vbr';
         $info['audio']['sample_rate'] = $streaminfo['sample_rate'];
         $info['audio']['channels'] = $streaminfo['channels'];
         $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample'];
         $info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate'];
         if ($info['playtime_seconds'] > 0) {
             if (!$this->isDependencyFor('matroska')) {
                 $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds'];
             } else {
                 $this->warning('Cannot determine audio bitrate because total stream size is unknown');
             }
         }
     } else {
         return $this->error('Corrupt METAdata block: STREAMINFO');
     }
     return true;
 }
Beispiel #5
0
 /**
  * @return bool
  */
 public function getAACADIFheaderFilepointer()
 {
     $info =& $this->getid3->info;
     $info['fileformat'] = 'aac';
     $info['audio']['dataformat'] = 'aac';
     $info['audio']['lossless'] = false;
     fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
     $AACheader = fread($this->getid3->fp, 1024);
     $offset = 0;
     if (substr($AACheader, 0, 4) == 'ADIF') {
         // http://faac.sourceforge.net/wiki/index.php?page=ADIF
         // http://libmpeg.org/mpeg4/doc/w2203tfs.pdf
         // adif_header() {
         //     adif_id                                32
         //     copyright_id_present                    1
         //     if( copyright_id_present )
         //         copyright_id                       72
         //     original_copy                           1
         //     home                                    1
         //     bitstream_type                          1
         //     bitrate                                23
         //     num_program_config_elements             4
         //     for (i = 0; i < num_program_config_elements + 1; i++) {
         //         if( bitstream_type == '0' )
         //             adif_buffer_fullness           20
         //         program_config_element()
         //     }
         // }
         $AACheaderBitstream = Helper::BigEndian2Bin($AACheader);
         $bitoffset = 0;
         $info['aac']['header_type'] = 'ADIF';
         $bitoffset += 32;
         $info['aac']['header']['mpeg_version'] = 4;
         $info['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
         $bitoffset += 1;
         if ($info['aac']['header']['copyright']) {
             $info['aac']['header']['copyright_id'] = Helper::Bin2String(substr($AACheaderBitstream, $bitoffset, 72));
             $bitoffset += 72;
         }
         $info['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
         $bitoffset += 1;
         $info['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
         $bitoffset += 1;
         $info['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
         $bitoffset += 1;
         if ($info['aac']['header']['is_vbr']) {
             $info['audio']['bitrate_mode'] = 'vbr';
             $info['aac']['header']['bitrate_max'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
             $bitoffset += 23;
         } else {
             $info['audio']['bitrate_mode'] = 'cbr';
             $info['aac']['header']['bitrate'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
             $bitoffset += 23;
             $info['audio']['bitrate'] = $info['aac']['header']['bitrate'];
         }
         if ($info['audio']['bitrate'] == 0) {
             $info['error'][] = 'Corrupt AAC file: bitrate_audio == zero';
             return false;
         }
         $info['aac']['header']['num_program_configs'] = 1 + Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
         $bitoffset += 4;
         for ($i = 0; $i < $info['aac']['header']['num_program_configs']; ++$i) {
             // http://www.audiocoding.com/wiki/index.php?page=program_config_element
             // buffer_fullness                       20
             // element_instance_tag                   4
             // object_type                            2
             // sampling_frequency_index               4
             // num_front_channel_elements             4
             // num_side_channel_elements              4
             // num_back_channel_elements              4
             // num_lfe_channel_elements               2
             // num_assoc_data_elements                3
             // num_valid_cc_elements                  4
             // mono_mixdown_present                   1
             // mono_mixdown_element_number            4   if mono_mixdown_present == 1
             // stereo_mixdown_present                 1
             // stereo_mixdown_element_number          4   if stereo_mixdown_present == 1
             // matrix_mixdown_idx_present             1
             // matrix_mixdown_idx                     2   if matrix_mixdown_idx_present == 1
             // pseudo_surround_enable                 1   if matrix_mixdown_idx_present == 1
             // for (i = 0; i < num_front_channel_elements; i++) {
             //     front_element_is_cpe[i]            1
             //     front_element_tag_select[i]        4
             // }
             // for (i = 0; i < num_side_channel_elements; i++) {
             //     side_element_is_cpe[i]             1
             //     side_element_tag_select[i]         4
             // }
             // for (i = 0; i < num_back_channel_elements; i++) {
             //     back_element_is_cpe[i]             1
             //     back_element_tag_select[i]         4
             // }
             // for (i = 0; i < num_lfe_channel_elements; i++) {
             //     lfe_element_tag_select[i]          4
             // }
             // for (i = 0; i < num_assoc_data_elements; i++) {
             //     assoc_data_element_tag_select[i]   4
             // }
             // for (i = 0; i < num_valid_cc_elements; i++) {
             //     cc_element_is_ind_sw[i]            1
             //     valid_cc_element_tag_select[i]     4
             // }
             // byte_alignment()                       VAR
             // comment_field_bytes                    8
             // for (i = 0; i < comment_field_bytes; i++) {
             //     comment_field_data[i]              8
             // }
             if (!$info['aac']['header']['is_vbr']) {
                 $info['aac']['program_configs'][$i]['buffer_fullness'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20));
                 $bitoffset += 20;
             }
             $info['aac']['program_configs'][$i]['element_instance_tag'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
             $bitoffset += 4;
             $info['aac']['program_configs'][$i]['object_type'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
             $bitoffset += 2;
             $info['aac']['program_configs'][$i]['sampling_frequency_index'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
             $bitoffset += 4;
             $info['aac']['program_configs'][$i]['num_front_channel_elements'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
             $bitoffset += 4;
             $info['aac']['program_configs'][$i]['num_side_channel_elements'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
             $bitoffset += 4;
             $info['aac']['program_configs'][$i]['num_back_channel_elements'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
             $bitoffset += 4;
             $info['aac']['program_configs'][$i]['num_lfe_channel_elements'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
             $bitoffset += 2;
             $info['aac']['program_configs'][$i]['num_assoc_data_elements'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3));
             $bitoffset += 3;
             $info['aac']['program_configs'][$i]['num_valid_cc_elements'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
             $bitoffset += 4;
             $info['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
             $bitoffset += 1;
             if ($info['aac']['program_configs'][$i]['mono_mixdown_present']) {
                 $info['aac']['program_configs'][$i]['mono_mixdown_element_number'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             $info['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
             $bitoffset += 1;
             if ($info['aac']['program_configs'][$i]['stereo_mixdown_present']) {
                 $info['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             $info['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
             $bitoffset += 1;
             if ($info['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) {
                 $info['aac']['program_configs'][$i]['matrix_mixdown_idx'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
                 $bitoffset += 2;
                 $info['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
                 $bitoffset += 1;
             }
             for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_front_channel_elements']; ++$j) {
                 $info['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
                 $bitoffset += 1;
                 $info['aac']['program_configs'][$i]['front_element_tag_select'][$j] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_side_channel_elements']; ++$j) {
                 $info['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
                 $bitoffset += 1;
                 $info['aac']['program_configs'][$i]['side_element_tag_select'][$j] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_back_channel_elements']; ++$j) {
                 $info['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
                 $bitoffset += 1;
                 $info['aac']['program_configs'][$i]['back_element_tag_select'][$j] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_lfe_channel_elements']; ++$j) {
                 $info['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_assoc_data_elements']; ++$j) {
                 $info['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_valid_cc_elements']; ++$j) {
                 $info['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
                 $bitoffset += 1;
                 $info['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
                 $bitoffset += 4;
             }
             $bitoffset = ceil($bitoffset / 8) * 8;
             $info['aac']['program_configs'][$i]['comment_field_bytes'] = Helper::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8));
             $bitoffset += 8;
             $info['aac']['program_configs'][$i]['comment_field'] = Helper::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $info['aac']['program_configs'][$i]['comment_field_bytes']));
             $bitoffset += 8 * $info['aac']['program_configs'][$i]['comment_field_bytes'];
             $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['program_configs'][$i]['object_type'], $info['aac']['header']['mpeg_version']);
             $info['aac']['program_configs'][$i]['sampling_frequency'] = self::AACsampleRateLookup($info['aac']['program_configs'][$i]['sampling_frequency_index']);
             $info['audio']['sample_rate'] = $info['aac']['program_configs'][$i]['sampling_frequency'];
             $info['audio']['channels'] = self::AACchannelCountCalculate($info['aac']['program_configs'][$i]);
             if ($info['aac']['program_configs'][$i]['comment_field']) {
                 $info['aac']['comments'][] = $info['aac']['program_configs'][$i]['comment_field'];
             }
         }
         $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate'];
         $info['audio']['encoder_options'] = $info['aac']['header_type'] . ' ' . $info['aac']['header']['profile'];
         return true;
     } else {
         unset($info['fileformat']);
         unset($info['aac']);
         $info['error'][] = 'AAC-ADIF synch not found at offset ' . $info['avdataoffset'] . ' (expected "ADIF", found "' . substr($AACheader, 0, 4) . '" instead)';
         return false;
     }
 }
Beispiel #6
0
 /**
  * @return bool
  */
 public function analyze()
 {
     $info =& $this->getid3->info;
     // Specs taken from "DTS Coherent Acoustics;Core and Extensions,  ETSI TS 102 114 V1.2.1 (2002-12)"
     // (http://pda.etsi.org/pda/queryform.asp)
     // With thanks to Gambit <*****@*****.**> http://mac.sourceforge.net/atl/
     $info['fileformat'] = 'dts';
     $this->fseek($info['avdataoffset']);
     $DTSheader = $this->fread(16);
     $magic = "þ€";
     if (strpos($DTSheader, $magic) === 0) {
         $info['dts']['raw']['magic'] = $magic;
         $offset = 4;
     } else {
         if (!$this->isDependencyFor('matroska')) {
             unset($info['fileformat']);
             return $this->error('Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::PrintHexBytes(substr($DTSheader, 0, 4)) . '"');
         }
         $offset = 0;
     }
     $fhBS = Helper::BigEndian2Bin(substr($DTSheader, $offset, 12));
     $bsOffset = 0;
     $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, $bsOffset, 5);
     $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, $bsOffset, 7);
     $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, $bsOffset, 14);
     $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, $bsOffset, 6);
     $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, $bsOffset, 4);
     $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, $bsOffset, 5);
     $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, $bsOffset, 3);
     $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, $bsOffset, 2);
     $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     if ($info['dts']['flags']['crc_present']) {
         $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, $bsOffset, 16);
     }
     $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, $bsOffset, 4);
     $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, $bsOffset, 2);
     $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, $bsOffset, 2);
     $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
     $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, $bsOffset, 4);
     $info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']);
     $info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
     $info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']);
     $info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
     $info['dts']['flags']['lossless'] = $info['dts']['raw']['bitrate'] == 31 ? true : false;
     $info['dts']['bitrate_mode'] = $info['dts']['raw']['bitrate'] == 30 ? 'vbr' : 'cbr';
     $info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']);
     $info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']);
     $info['audio']['dataformat'] = 'dts';
     $info['audio']['lossless'] = $info['dts']['flags']['lossless'];
     $info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode'];
     $info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample'];
     $info['audio']['sample_rate'] = $info['dts']['sample_rate'];
     $info['audio']['channels'] = $info['dts']['channels'];
     $info['audio']['bitrate'] = $info['dts']['bitrate'];
     if (isset($info['avdataend'])) {
         $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
     }
     return true;
 }