+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
+ // http://id3.org/id3v2-chapters-1.0
+ // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes)
+ // Element ID <text string> $00
+ // Start time $xx xx xx xx
+ // End time $xx xx xx xx
+ // Start offset $xx xx xx xx
+ // End offset $xx xx xx xx
+ // <Optional embedded sub-frames>
+
+ $frame_offset = 0;
+ @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
+ $frame_offset += strlen($parsedFrame['element_id']."\x00");
+ $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
+ $frame_offset += 4;
+ $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
+ $frame_offset += 4;
+ if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
+ // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
+ $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
+ }
+ $frame_offset += 4;
+ if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
+ // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
+ $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
+ }
+ $frame_offset += 4;
+
+ if ($frame_offset < strlen($parsedFrame['data'])) {
+ $parsedFrame['subframes'] = array();
+ while ($frame_offset < strlen($parsedFrame['data'])) {
+ // <Optional embedded sub-frames>
+ $subframe = array();
+ $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
+ $frame_offset += 4;
+ $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
+ $frame_offset += 4;
+ $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
+ $frame_offset += 2;
+ if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
+ $info['warning'][] = 'CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)';
+ break;
+ }
+ $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
+ $frame_offset += $subframe['size'];
+
+ $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
+ $subframe['text'] = substr($subframe_rawdata, 1);
+ $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
+ $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
+ switch (substr($encoding_converted_text, 0, 2)) {
+ case "\xFF\xFE":
+ case "\xFE\xFF":
+ switch (strtoupper($info['id3v2']['encoding'])) {
+ case 'ISO-8859-1':
+ case 'UTF-8':
+ $encoding_converted_text = substr($encoding_converted_text, 2);
+ // remove unwanted byte-order-marks
+ break;
+ default:
+ // ignore
+ break;
+ }
+ break;
+ default:
+ // do not remove BOM
+ break;
+ }
+
+ if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
+ if ($subframe['name'] == 'TIT2') {
+ $parsedFrame['chapter_name'] = $encoding_converted_text;
+ } elseif ($subframe['name'] == 'TIT3') {
+ $parsedFrame['chapter_description'] = $encoding_converted_text;
+ }
+ $parsedFrame['subframes'][] = $subframe;
+ } else {
+ $info['warning'][] = 'ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)';
+ }
+ }
+ unset($subframe_rawdata, $subframe, $encoding_converted_text);
+ }
+
+ $id3v2_chapter_entry = array();
+ foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) {
+ if (isset($parsedFrame[$id3v2_chapter_key])) {
+ $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
+ }
+ }
+ if (!isset($info['id3v2']['chapters'])) {
+ $info['id3v2']['chapters'] = array();
+ }
+ $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
+ unset($id3v2_chapter_entry, $id3v2_chapter_key);
+
+
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
+ // http://id3.org/id3v2-chapters-1.0
+ // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes)
+ // Element ID <text string> $00
+ // CTOC flags %xx
+ // Entry count $xx
+ // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */
+ // <Optional embedded sub-frames>
+
+ $frame_offset = 0;
+ @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
+ $frame_offset += strlen($parsedFrame['element_id']."\x00");
+ $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
+ $frame_offset += 1;
+ $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
+ $frame_offset += 1;
+
+ $terminator_position = null;
+ for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
+ $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
+ $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
+ $frame_offset = $terminator_position + 1;
+ }
+
+ $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01);
+ $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
+
+ unset($ctoc_flags_raw, $terminator_position);
+
+ if ($frame_offset < strlen($parsedFrame['data'])) {
+ $parsedFrame['subframes'] = array();
+ while ($frame_offset < strlen($parsedFrame['data'])) {
+ // <Optional embedded sub-frames>
+ $subframe = array();
+ $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
+ $frame_offset += 4;
+ $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
+ $frame_offset += 4;
+ $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
+ $frame_offset += 2;
+ if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
+ $info['warning'][] = 'CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)';
+ break;
+ }
+ $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
+ $frame_offset += $subframe['size'];
+
+ $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
+ $subframe['text'] = substr($subframe_rawdata, 1);
+ $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
+ $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
+ switch (substr($encoding_converted_text, 0, 2)) {
+ case "\xFF\xFE":
+ case "\xFE\xFF":
+ switch (strtoupper($info['id3v2']['encoding'])) {
+ case 'ISO-8859-1':
+ case 'UTF-8':
+ $encoding_converted_text = substr($encoding_converted_text, 2);
+ // remove unwanted byte-order-marks
+ break;
+ default:
+ // ignore
+ break;
+ }
+ break;
+ default:
+ // do not remove BOM
+ break;
+ }
+
+ if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
+ if ($subframe['name'] == 'TIT2') {
+ $parsedFrame['toc_name'] = $encoding_converted_text;
+ } elseif ($subframe['name'] == 'TIT3') {
+ $parsedFrame['toc_description'] = $encoding_converted_text;
+ }
+ $parsedFrame['subframes'][] = $subframe;
+ } else {
+ $info['warning'][] = 'ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)';
+ }
+ }
+ unset($subframe_rawdata, $subframe, $encoding_converted_text);
+ }
+