320 $len = strlen($framedata); |
320 $len = strlen($framedata); |
321 for ($i = 0; $i < $len; $i++) { |
321 for ($i = 0; $i < $len; $i++) { |
322 if ($framedata{$i} != "\x00") { |
322 if ($framedata{$i} != "\x00") { |
323 $thisfile_id3v2['padding']['valid'] = false; |
323 $thisfile_id3v2['padding']['valid'] = false; |
324 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; |
324 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; |
325 $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; |
325 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); |
326 break; |
326 break; |
327 } |
327 } |
328 } |
328 } |
329 break; // skip rest of ID3v2 header |
329 break; // skip rest of ID3v2 header |
330 } |
330 } |
331 |
331 |
332 if ($frame_name == 'COM ') { |
332 if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) { |
333 $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; |
333 $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.'); |
334 $frame_name = 'COMM'; |
334 $frame_name = $iTunesBrokenFrameNameFixed; |
335 } |
335 } |
336 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { |
336 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { |
337 |
337 |
338 unset($parsedFrame); |
338 unset($parsedFrame); |
339 $parsedFrame['frame_name'] = $frame_name; |
339 $parsedFrame['frame_name'] = $frame_name; |
353 |
353 |
354 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { |
354 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { |
355 |
355 |
356 // next frame is valid, just skip the current frame |
356 // next frame is valid, just skip the current frame |
357 $framedata = substr($framedata, $frame_size); |
357 $framedata = substr($framedata, $frame_size); |
358 $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; |
358 $this->warning('Next ID3v2 frame is valid, skipping current frame.'); |
359 |
359 |
360 } else { |
360 } else { |
361 |
361 |
362 // next frame is invalid too, abort processing |
362 // next frame is invalid too, abort processing |
363 //unset($framedata); |
363 //unset($framedata); |
364 $framedata = null; |
364 $framedata = null; |
365 $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; |
365 $this->error('Next ID3v2 frame is also invalid, aborting processing.'); |
366 |
366 |
367 } |
367 } |
368 |
368 |
369 } elseif ($frame_size == strlen($framedata)) { |
369 } elseif ($frame_size == strlen($framedata)) { |
370 |
370 |
371 // this is the last frame, just skip |
371 // this is the last frame, just skip |
372 $info['warning'][] = 'This was the last ID3v2 frame.'; |
372 $this->warning('This was the last ID3v2 frame.'); |
373 |
373 |
374 } else { |
374 } else { |
375 |
375 |
376 // next frame is invalid too, abort processing |
376 // next frame is invalid too, abort processing |
377 //unset($framedata); |
377 //unset($framedata); |
378 $framedata = null; |
378 $framedata = null; |
379 $info['warning'][] = 'Invalid ID3v2 frame size, aborting.'; |
379 $this->warning('Invalid ID3v2 frame size, aborting.'); |
380 |
380 |
381 } |
381 } |
382 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { |
382 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { |
383 |
383 |
384 switch ($frame_name) { |
384 switch ($frame_name) { |
498 public function ParseID3v2GenreString($genrestring) { |
502 public function ParseID3v2GenreString($genrestring) { |
499 // Parse genres into arrays of genreName and genreID |
503 // Parse genres into arrays of genreName and genreID |
500 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' |
504 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' |
501 // ID3v2.4.x: '21' $00 'Eurodisco' $00 |
505 // ID3v2.4.x: '21' $00 'Eurodisco' $00 |
502 $clean_genres = array(); |
506 $clean_genres = array(); |
|
507 |
|
508 // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags |
|
509 if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) { |
|
510 // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here: |
|
511 // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name |
|
512 if (preg_match('#/#', $genrestring)) { |
|
513 $genrestring = str_replace('/', "\x00", $genrestring); |
|
514 $genrestring = str_replace('Pop'."\x00".'Funk', 'Pop/Funk', $genrestring); |
|
515 $genrestring = str_replace('Rock'."\x00".'Rock', 'Folk/Rock', $genrestring); |
|
516 } |
|
517 |
|
518 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal" |
|
519 if (preg_match('#;#', $genrestring)) { |
|
520 $genrestring = str_replace(';', "\x00", $genrestring); |
|
521 } |
|
522 } |
|
523 |
|
524 |
503 if (strpos($genrestring, "\x00") === false) { |
525 if (strpos($genrestring, "\x00") === false) { |
504 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); |
526 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); |
505 } |
527 } |
|
528 |
506 $genre_elements = explode("\x00", $genrestring); |
529 $genre_elements = explode("\x00", $genrestring); |
507 foreach ($genre_elements as $element) { |
530 foreach ($genre_elements as $element) { |
508 $element = trim($element); |
531 $element = trim($element); |
509 if ($element) { |
532 if ($element) { |
510 if (preg_match('#^[0-9]{1,3}#', $element)) { |
533 if (preg_match('#^[0-9]{1,3}#', $element)) { |
569 |
592 |
570 // Frame-level de-compression |
593 // Frame-level de-compression |
571 if ($parsedFrame['flags']['compression']) { |
594 if ($parsedFrame['flags']['compression']) { |
572 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); |
595 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); |
573 if (!function_exists('gzuncompress')) { |
596 if (!function_exists('gzuncompress')) { |
574 $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; |
597 $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'); |
575 } else { |
598 } else { |
576 if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { |
599 if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { |
577 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { |
600 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { |
578 $parsedFrame['data'] = $decompresseddata; |
601 $parsedFrame['data'] = $decompresseddata; |
579 unset($decompresseddata); |
602 unset($decompresseddata); |
580 } else { |
603 } else { |
581 $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; |
604 $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'); |
582 } |
605 } |
583 } |
606 } |
584 } |
607 } |
585 } |
608 } |
586 |
609 |
587 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { |
610 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { |
588 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { |
611 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { |
589 $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'; |
612 $this->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'); |
590 } |
613 } |
591 } |
614 } |
592 |
615 |
593 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { |
616 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { |
594 |
617 |
623 // Description <text string according to encoding> $00 (00) |
646 // Description <text string according to encoding> $00 (00) |
624 // Value <text string according to encoding> |
647 // Value <text string according to encoding> |
625 |
648 |
626 $frame_offset = 0; |
649 $frame_offset = 0; |
627 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
650 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
628 |
651 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); |
629 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
652 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
630 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; |
653 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); |
631 } |
654 $frame_textencoding_terminator = "\x00"; |
632 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
655 } |
633 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
656 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
|
657 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
634 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
658 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
635 } |
659 } |
636 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
660 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
637 if (ord($frame_description) === 0) { |
661 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
|
662 // if description only contains a BOM or terminator then make it blank |
638 $frame_description = ''; |
663 $frame_description = ''; |
639 } |
664 } |
640 $parsedFrame['encodingid'] = $frame_textencoding; |
665 $parsedFrame['encodingid'] = $frame_textencoding; |
641 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
666 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
642 |
667 |
643 $parsedFrame['description'] = $frame_description; |
668 $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description)); |
644 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); |
669 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); |
645 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { |
670 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { |
646 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); |
671 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); |
647 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { |
672 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { |
648 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); |
673 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); |
649 } else { |
674 } else { |
715 // Description <text string according to encoding> $00 (00) |
740 // Description <text string according to encoding> $00 (00) |
716 // URL <text string> |
741 // URL <text string> |
717 |
742 |
718 $frame_offset = 0; |
743 $frame_offset = 0; |
719 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
744 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
|
745 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); |
720 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
746 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
721 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; |
747 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); |
722 } |
748 $frame_textencoding_terminator = "\x00"; |
723 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
749 } |
724 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
750 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
|
751 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
725 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
752 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
726 } |
753 } |
727 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
754 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
728 |
755 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
729 if (ord($frame_description) === 0) { |
756 // if description only contains a BOM or terminator then make it blank |
730 $frame_description = ''; |
757 $frame_description = ''; |
731 } |
758 } |
732 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); |
759 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); |
733 |
760 |
734 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); |
761 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator); |
735 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
762 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
736 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
763 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
737 } |
764 } |
738 if ($frame_terminatorpos) { |
765 if ($frame_terminatorpos) { |
739 // there are null bytes after the data - this is not according to spec |
766 // there are null bytes after the data - this is not according to spec |
740 // only use data up to first null byte |
767 // only use data up to first null byte |
954 // Content descriptor <text string according to encoding> $00 (00) |
981 // Content descriptor <text string according to encoding> $00 (00) |
955 // Lyrics/text <full text string according to encoding> |
982 // Lyrics/text <full text string according to encoding> |
956 |
983 |
957 $frame_offset = 0; |
984 $frame_offset = 0; |
958 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
985 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
|
986 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); |
959 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
987 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
960 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; |
988 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); |
|
989 $frame_textencoding_terminator = "\x00"; |
961 } |
990 } |
962 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); |
991 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); |
963 $frame_offset += 3; |
992 $frame_offset += 3; |
964 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
993 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
965 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
994 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
966 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
995 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
967 } |
996 } |
968 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
997 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
969 if (ord($frame_description) === 0) { |
998 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
|
999 // if description only contains a BOM or terminator then make it blank |
970 $frame_description = ''; |
1000 $frame_description = ''; |
971 } |
1001 } |
972 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); |
1002 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); |
973 |
1003 |
974 $parsedFrame['encodingid'] = $frame_textencoding; |
1004 $parsedFrame['encodingid'] = $frame_textencoding; |
975 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1005 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
976 |
1006 |
977 $parsedFrame['data'] = $parsedFrame['data']; |
|
978 $parsedFrame['language'] = $frame_language; |
1007 $parsedFrame['language'] = $frame_language; |
979 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); |
1008 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); |
980 $parsedFrame['description'] = $frame_description; |
1009 $parsedFrame['description'] = $frame_description; |
981 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { |
1010 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { |
982 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); |
1011 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); |
1018 |
1049 |
1019 $timestampindex = 0; |
1050 $timestampindex = 0; |
1020 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); |
1051 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); |
1021 while (strlen($frame_remainingdata)) { |
1052 while (strlen($frame_remainingdata)) { |
1022 $frame_offset = 0; |
1053 $frame_offset = 0; |
1023 $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); |
1054 $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator); |
1024 if ($frame_terminatorpos === false) { |
1055 if ($frame_terminatorpos === false) { |
1025 $frame_remainingdata = ''; |
1056 $frame_remainingdata = ''; |
1026 } else { |
1057 } else { |
1027 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1058 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1028 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1059 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1029 } |
1060 } |
1030 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); |
1061 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); |
1031 |
1062 |
1032 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); |
1063 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator)); |
1033 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { |
1064 if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { |
1034 // timestamp probably omitted for first data item |
1065 // timestamp probably omitted for first data item |
1035 } else { |
1066 } else { |
1036 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); |
1067 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); |
1037 $frame_remainingdata = substr($frame_remainingdata, 4); |
1068 $frame_remainingdata = substr($frame_remainingdata, 4); |
1052 // Short content descrip. <text string according to encoding> $00 (00) |
1083 // Short content descrip. <text string according to encoding> $00 (00) |
1053 // The actual text <full text string according to encoding> |
1084 // The actual text <full text string according to encoding> |
1054 |
1085 |
1055 if (strlen($parsedFrame['data']) < 5) { |
1086 if (strlen($parsedFrame['data']) < 5) { |
1056 |
1087 |
1057 $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; |
1088 $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']); |
1058 |
1089 |
1059 } else { |
1090 } else { |
1060 |
1091 |
1061 $frame_offset = 0; |
1092 $frame_offset = 0; |
1062 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
1093 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
|
1094 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); |
1063 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
1095 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
1064 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; |
1096 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); |
|
1097 $frame_textencoding_terminator = "\x00"; |
1065 } |
1098 } |
1066 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); |
1099 $frame_language = substr($parsedFrame['data'], $frame_offset, 3); |
1067 $frame_offset += 3; |
1100 $frame_offset += 3; |
1068 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
1101 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
1069 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1102 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1070 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1103 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1071 } |
1104 } |
1072 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1105 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1073 if (ord($frame_description) === 0) { |
1106 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
|
1107 // if description only contains a BOM or terminator then make it blank |
1074 $frame_description = ''; |
1108 $frame_description = ''; |
1075 } |
1109 } |
1076 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); |
1110 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); |
1077 |
1111 |
1078 $parsedFrame['encodingid'] = $frame_textencoding; |
1112 $parsedFrame['encodingid'] = $frame_textencoding; |
1079 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1113 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1080 |
1114 |
1081 $parsedFrame['language'] = $frame_language; |
1115 $parsedFrame['language'] = $frame_language; |
1363 } |
1399 } |
1364 |
1400 |
1365 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
1401 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
1366 |
1402 |
1367 if ($frame_offset >= $parsedFrame['datalength']) { |
1403 if ($frame_offset >= $parsedFrame['datalength']) { |
1368 $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); |
1404 $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset)); |
1369 } else { |
1405 } else { |
1370 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
1406 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
1371 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1407 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1372 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1408 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1373 } |
1409 } |
1374 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1410 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1375 if (ord($frame_description) === 0) { |
1411 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
|
1412 // if description only contains a BOM or terminator then make it blank |
1376 $frame_description = ''; |
1413 $frame_description = ''; |
1377 } |
1414 } |
1378 $parsedFrame['encodingid'] = $frame_textencoding; |
1415 $parsedFrame['encodingid'] = $frame_textencoding; |
1379 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1416 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1380 |
1417 |
1384 $parsedFrame['mime'] = $frame_mimetype; |
1421 $parsedFrame['mime'] = $frame_mimetype; |
1385 } |
1422 } |
1386 $parsedFrame['picturetypeid'] = $frame_picturetype; |
1423 $parsedFrame['picturetypeid'] = $frame_picturetype; |
1387 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); |
1424 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); |
1388 $parsedFrame['description'] = $frame_description; |
1425 $parsedFrame['description'] = $frame_description; |
1389 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); |
1426 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); |
1390 $parsedFrame['datalength'] = strlen($parsedFrame['data']); |
1427 $parsedFrame['datalength'] = strlen($parsedFrame['data']); |
1391 |
1428 |
1392 $parsedFrame['image_mime'] = ''; |
1429 $parsedFrame['image_mime'] = ''; |
1393 $imageinfo = array(); |
1430 $imageinfo = array(); |
1394 $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); |
1431 if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) { |
1395 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { |
1432 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { |
1396 $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); |
1433 $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); |
1397 if ($imagechunkcheck[0]) { |
1434 if ($imagechunkcheck[0]) { |
1398 $parsedFrame['image_width'] = $imagechunkcheck[0]; |
1435 $parsedFrame['image_width'] = $imagechunkcheck[0]; |
1399 } |
1436 } |
1400 if ($imagechunkcheck[1]) { |
1437 if ($imagechunkcheck[1]) { |
1401 $parsedFrame['image_height'] = $imagechunkcheck[1]; |
1438 $parsedFrame['image_height'] = $imagechunkcheck[1]; |
|
1439 } |
1402 } |
1440 } |
1403 } |
1441 } |
1404 |
1442 |
1405 do { |
1443 do { |
1406 if ($this->getid3->option_save_attachments === false) { |
1444 if ($this->getid3->option_save_attachments === false) { |
1412 // great |
1450 // great |
1413 /* |
1451 /* |
1414 } elseif (is_int($this->getid3->option_save_attachments)) { |
1452 } elseif (is_int($this->getid3->option_save_attachments)) { |
1415 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { |
1453 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { |
1416 // too big, skip |
1454 // too big, skip |
1417 $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; |
1455 $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'); |
1418 unset($parsedFrame['data']); |
1456 unset($parsedFrame['data']); |
1419 break; |
1457 break; |
1420 } |
1458 } |
1421 */ |
1459 */ |
1422 } elseif (is_string($this->getid3->option_save_attachments)) { |
1460 } elseif (is_string($this->getid3->option_save_attachments)) { |
1423 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); |
1461 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); |
1424 if (!is_dir($dir) || !is_writable($dir)) { |
1462 if (!is_dir($dir) || !getID3::is_writable($dir)) { |
1425 // cannot write, skip |
1463 // cannot write, skip |
1426 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'; |
1464 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'); |
1427 unset($parsedFrame['data']); |
1465 unset($parsedFrame['data']); |
1428 break; |
1466 break; |
1429 } |
1467 } |
1430 } |
1468 } |
1431 // if we get this far, must be OK |
1469 // if we get this far, must be OK |
1432 if (is_string($this->getid3->option_save_attachments)) { |
1470 if (is_string($this->getid3->option_save_attachments)) { |
1433 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; |
1471 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; |
1434 if (!file_exists($destination_filename) || is_writable($destination_filename)) { |
1472 if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) { |
1435 file_put_contents($destination_filename, $parsedFrame['data']); |
1473 file_put_contents($destination_filename, $parsedFrame['data']); |
1436 } else { |
1474 } else { |
1437 $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'; |
1475 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'); |
1438 } |
1476 } |
1439 $parsedFrame['data_filename'] = $destination_filename; |
1477 $parsedFrame['data_filename'] = $destination_filename; |
1440 unset($parsedFrame['data']); |
1478 unset($parsedFrame['data']); |
1441 } else { |
1479 } else { |
1442 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { |
1480 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { |
1443 if (!isset($info['id3v2']['comments']['picture'])) { |
1481 if (!isset($info['id3v2']['comments']['picture'])) { |
1444 $info['id3v2']['comments']['picture'] = array(); |
1482 $info['id3v2']['comments']['picture'] = array(); |
1445 } |
1483 } |
1446 $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); |
1484 $comments_picture_data = array(); |
|
1485 foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { |
|
1486 if (isset($parsedFrame[$picture_key])) { |
|
1487 $comments_picture_data[$picture_key] = $parsedFrame[$picture_key]; |
|
1488 } |
|
1489 } |
|
1490 $info['id3v2']['comments']['picture'][] = $comments_picture_data; |
|
1491 unset($comments_picture_data); |
1447 } |
1492 } |
1448 } |
1493 } |
1449 } while (false); |
1494 } while (false); |
1450 } |
1495 } |
1451 |
1496 |
1460 // Content description <text string according to encoding> $00 (00) |
1505 // Content description <text string according to encoding> $00 (00) |
1461 // Encapsulated object <binary data> |
1506 // Encapsulated object <binary data> |
1462 |
1507 |
1463 $frame_offset = 0; |
1508 $frame_offset = 0; |
1464 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
1509 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
|
1510 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); |
1465 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
1511 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { |
1466 $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; |
1512 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); |
|
1513 $frame_textencoding_terminator = "\x00"; |
1467 } |
1514 } |
1468 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); |
1515 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); |
1469 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1516 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1470 if (ord($frame_mimetype) === 0) { |
1517 if (ord($frame_mimetype) === 0) { |
1471 $frame_mimetype = ''; |
1518 $frame_mimetype = ''; |
1472 } |
1519 } |
1473 $frame_offset = $frame_terminatorpos + strlen("\x00"); |
1520 $frame_offset = $frame_terminatorpos + strlen("\x00"); |
1474 |
1521 |
1475 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
1522 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
1476 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1523 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1477 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1524 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1478 } |
1525 } |
1479 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1526 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1480 if (ord($frame_filename) === 0) { |
1527 if (ord($frame_filename) === 0) { |
1481 $frame_filename = ''; |
1528 $frame_filename = ''; |
1482 } |
1529 } |
1483 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); |
1530 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); |
1484 |
1531 |
1485 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
1532 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
1486 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1533 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1487 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1534 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1488 } |
1535 } |
1489 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1536 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1490 if (ord($frame_description) === 0) { |
1537 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
|
1538 // if description only contains a BOM or terminator then make it blank |
1491 $frame_description = ''; |
1539 $frame_description = ''; |
1492 } |
1540 } |
1493 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); |
1541 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); |
1494 |
1542 |
1495 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); |
1543 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); |
1496 $parsedFrame['encodingid'] = $frame_textencoding; |
1544 $parsedFrame['encodingid'] = $frame_textencoding; |
1497 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1545 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); |
1498 |
1546 |
1702 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); |
1751 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); |
1703 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); |
1752 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); |
1704 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); |
1753 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); |
1705 |
1754 |
1706 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); |
1755 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); |
1707 if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) { |
1756 if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) { |
1708 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); |
1757 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); |
1709 } |
1758 } |
1710 $frame_offset += 8; |
1759 $frame_offset += 8; |
1711 |
1760 |
1712 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); |
1761 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); |
1750 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1801 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1751 $frame_offset = $frame_terminatorpos + strlen("\x00"); |
1802 $frame_offset = $frame_terminatorpos + strlen("\x00"); |
1752 |
1803 |
1753 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
1804 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); |
1754 |
1805 |
1755 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
1806 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
1756 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1807 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1757 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1808 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1758 } |
1809 } |
1759 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1810 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1760 if (ord($frame_sellername) === 0) { |
1811 if (ord($frame_sellername) === 0) { |
1761 $frame_sellername = ''; |
1812 $frame_sellername = ''; |
1762 } |
1813 } |
1763 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); |
1814 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); |
1764 |
1815 |
1765 $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); |
1816 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); |
1766 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { |
1817 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { |
1767 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1818 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 |
1768 } |
1819 } |
1769 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1820 $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1770 if (ord($frame_description) === 0) { |
1821 if (in_array($frame_description, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { |
|
1822 // if description only contains a BOM or terminator then make it blank |
1771 $frame_description = ''; |
1823 $frame_description = ''; |
1772 } |
1824 } |
1773 $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); |
1825 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); |
1774 |
1826 |
1775 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); |
1827 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); |
1776 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1828 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); |
1777 $frame_offset = $frame_terminatorpos + strlen("\x00"); |
1829 $frame_offset = $frame_terminatorpos + strlen("\x00"); |
1778 |
1830 |
1941 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; |
1993 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; |
1942 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; |
1994 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; |
1943 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; |
1995 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; |
1944 |
1996 |
1945 unset($parsedFrame['data']); |
1997 unset($parsedFrame['data']); |
|
1998 |
|
1999 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only) |
|
2000 // http://id3.org/id3v2-chapters-1.0 |
|
2001 // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes) |
|
2002 // Element ID <text string> $00 |
|
2003 // Start time $xx xx xx xx |
|
2004 // End time $xx xx xx xx |
|
2005 // Start offset $xx xx xx xx |
|
2006 // End offset $xx xx xx xx |
|
2007 // <Optional embedded sub-frames> |
|
2008 |
|
2009 $frame_offset = 0; |
|
2010 @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); |
|
2011 $frame_offset += strlen($parsedFrame['element_id']."\x00"); |
|
2012 $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); |
|
2013 $frame_offset += 4; |
|
2014 $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); |
|
2015 $frame_offset += 4; |
|
2016 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { |
|
2017 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." |
|
2018 $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); |
|
2019 } |
|
2020 $frame_offset += 4; |
|
2021 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { |
|
2022 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." |
|
2023 $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); |
|
2024 } |
|
2025 $frame_offset += 4; |
|
2026 |
|
2027 if ($frame_offset < strlen($parsedFrame['data'])) { |
|
2028 $parsedFrame['subframes'] = array(); |
|
2029 while ($frame_offset < strlen($parsedFrame['data'])) { |
|
2030 // <Optional embedded sub-frames> |
|
2031 $subframe = array(); |
|
2032 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); |
|
2033 $frame_offset += 4; |
|
2034 $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); |
|
2035 $frame_offset += 4; |
|
2036 $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); |
|
2037 $frame_offset += 2; |
|
2038 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { |
|
2039 $this->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)'); |
|
2040 break; |
|
2041 } |
|
2042 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); |
|
2043 $frame_offset += $subframe['size']; |
|
2044 |
|
2045 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); |
|
2046 $subframe['text'] = substr($subframe_rawdata, 1); |
|
2047 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); |
|
2048 $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; |
|
2049 switch (substr($encoding_converted_text, 0, 2)) { |
|
2050 case "\xFF\xFE": |
|
2051 case "\xFE\xFF": |
|
2052 switch (strtoupper($info['id3v2']['encoding'])) { |
|
2053 case 'ISO-8859-1': |
|
2054 case 'UTF-8': |
|
2055 $encoding_converted_text = substr($encoding_converted_text, 2); |
|
2056 // remove unwanted byte-order-marks |
|
2057 break; |
|
2058 default: |
|
2059 // ignore |
|
2060 break; |
|
2061 } |
|
2062 break; |
|
2063 default: |
|
2064 // do not remove BOM |
|
2065 break; |
|
2066 } |
|
2067 |
|
2068 if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { |
|
2069 if ($subframe['name'] == 'TIT2') { |
|
2070 $parsedFrame['chapter_name'] = $encoding_converted_text; |
|
2071 } elseif ($subframe['name'] == 'TIT3') { |
|
2072 $parsedFrame['chapter_description'] = $encoding_converted_text; |
|
2073 } |
|
2074 $parsedFrame['subframes'][] = $subframe; |
|
2075 } else { |
|
2076 $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'); |
|
2077 } |
|
2078 } |
|
2079 unset($subframe_rawdata, $subframe, $encoding_converted_text); |
|
2080 } |
|
2081 |
|
2082 $id3v2_chapter_entry = array(); |
|
2083 foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) { |
|
2084 if (isset($parsedFrame[$id3v2_chapter_key])) { |
|
2085 $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key]; |
|
2086 } |
|
2087 } |
|
2088 if (!isset($info['id3v2']['chapters'])) { |
|
2089 $info['id3v2']['chapters'] = array(); |
|
2090 } |
|
2091 $info['id3v2']['chapters'][] = $id3v2_chapter_entry; |
|
2092 unset($id3v2_chapter_entry, $id3v2_chapter_key); |
|
2093 |
|
2094 |
|
2095 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only) |
|
2096 // http://id3.org/id3v2-chapters-1.0 |
|
2097 // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes) |
|
2098 // Element ID <text string> $00 |
|
2099 // CTOC flags %xx |
|
2100 // Entry count $xx |
|
2101 // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */ |
|
2102 // <Optional embedded sub-frames> |
|
2103 |
|
2104 $frame_offset = 0; |
|
2105 @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); |
|
2106 $frame_offset += strlen($parsedFrame['element_id']."\x00"); |
|
2107 $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1)); |
|
2108 $frame_offset += 1; |
|
2109 $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1)); |
|
2110 $frame_offset += 1; |
|
2111 |
|
2112 $terminator_position = null; |
|
2113 for ($i = 0; $i < $parsedFrame['entry_count']; $i++) { |
|
2114 $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset); |
|
2115 $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset); |
|
2116 $frame_offset = $terminator_position + 1; |
|
2117 } |
|
2118 |
|
2119 $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01); |
|
2120 $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03); |
|
2121 |
|
2122 unset($ctoc_flags_raw, $terminator_position); |
|
2123 |
|
2124 if ($frame_offset < strlen($parsedFrame['data'])) { |
|
2125 $parsedFrame['subframes'] = array(); |
|
2126 while ($frame_offset < strlen($parsedFrame['data'])) { |
|
2127 // <Optional embedded sub-frames> |
|
2128 $subframe = array(); |
|
2129 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); |
|
2130 $frame_offset += 4; |
|
2131 $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); |
|
2132 $frame_offset += 4; |
|
2133 $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); |
|
2134 $frame_offset += 2; |
|
2135 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { |
|
2136 $this->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)'); |
|
2137 break; |
|
2138 } |
|
2139 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); |
|
2140 $frame_offset += $subframe['size']; |
|
2141 |
|
2142 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); |
|
2143 $subframe['text'] = substr($subframe_rawdata, 1); |
|
2144 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); |
|
2145 $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; |
|
2146 switch (substr($encoding_converted_text, 0, 2)) { |
|
2147 case "\xFF\xFE": |
|
2148 case "\xFE\xFF": |
|
2149 switch (strtoupper($info['id3v2']['encoding'])) { |
|
2150 case 'ISO-8859-1': |
|
2151 case 'UTF-8': |
|
2152 $encoding_converted_text = substr($encoding_converted_text, 2); |
|
2153 // remove unwanted byte-order-marks |
|
2154 break; |
|
2155 default: |
|
2156 // ignore |
|
2157 break; |
|
2158 } |
|
2159 break; |
|
2160 default: |
|
2161 // do not remove BOM |
|
2162 break; |
|
2163 } |
|
2164 |
|
2165 if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { |
|
2166 if ($subframe['name'] == 'TIT2') { |
|
2167 $parsedFrame['toc_name'] = $encoding_converted_text; |
|
2168 } elseif ($subframe['name'] == 'TIT3') { |
|
2169 $parsedFrame['toc_description'] = $encoding_converted_text; |
|
2170 } |
|
2171 $parsedFrame['subframes'][] = $subframe; |
|
2172 } else { |
|
2173 $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'); |
|
2174 } |
|
2175 } |
|
2176 unset($subframe_rawdata, $subframe, $encoding_converted_text); |
|
2177 } |
1946 |
2178 |
1947 } |
2179 } |
1948 |
2180 |
1949 return true; |
2181 return true; |
1950 } |
2182 } |
3419 |
3651 |
3420 public static function ID3v2HeaderLength($majorversion) { |
3652 public static function ID3v2HeaderLength($majorversion) { |
3421 return (($majorversion == 2) ? 6 : 10); |
3653 return (($majorversion == 2) ? 6 : 10); |
3422 } |
3654 } |
3423 |
3655 |
|
3656 public static function ID3v22iTunesBrokenFrameName($frame_name) { |
|
3657 // iTunes (multiple versions) has been known to write ID3v2.3 style frames |
|
3658 // but use ID3v2.2 frame names, right-padded using either [space] or [null] |
|
3659 // to make them fit in the 4-byte frame name space of the ID3v2.3 frame. |
|
3660 // This function will detect and translate the corrupt frame name into ID3v2.3 standard. |
|
3661 static $ID3v22_iTunes_BrokenFrames = array( |
|
3662 'BUF' => 'RBUF', // Recommended buffer size |
|
3663 'CNT' => 'PCNT', // Play counter |
|
3664 'COM' => 'COMM', // Comments |
|
3665 'CRA' => 'AENC', // Audio encryption |
|
3666 'EQU' => 'EQUA', // Equalisation |
|
3667 'ETC' => 'ETCO', // Event timing codes |
|
3668 'GEO' => 'GEOB', // General encapsulated object |
|
3669 'IPL' => 'IPLS', // Involved people list |
|
3670 'LNK' => 'LINK', // Linked information |
|
3671 'MCI' => 'MCDI', // Music CD identifier |
|
3672 'MLL' => 'MLLT', // MPEG location lookup table |
|
3673 'PIC' => 'APIC', // Attached picture |
|
3674 'POP' => 'POPM', // Popularimeter |
|
3675 'REV' => 'RVRB', // Reverb |
|
3676 'RVA' => 'RVAD', // Relative volume adjustment |
|
3677 'SLT' => 'SYLT', // Synchronised lyric/text |
|
3678 'STC' => 'SYTC', // Synchronised tempo codes |
|
3679 'TAL' => 'TALB', // Album/Movie/Show title |
|
3680 'TBP' => 'TBPM', // BPM (beats per minute) |
|
3681 'TCM' => 'TCOM', // Composer |
|
3682 'TCO' => 'TCON', // Content type |
|
3683 'TCP' => 'TCMP', // Part of a compilation |
|
3684 'TCR' => 'TCOP', // Copyright message |
|
3685 'TDA' => 'TDAT', // Date |
|
3686 'TDY' => 'TDLY', // Playlist delay |
|
3687 'TEN' => 'TENC', // Encoded by |
|
3688 'TFT' => 'TFLT', // File type |
|
3689 'TIM' => 'TIME', // Time |
|
3690 'TKE' => 'TKEY', // Initial key |
|
3691 'TLA' => 'TLAN', // Language(s) |
|
3692 'TLE' => 'TLEN', // Length |
|
3693 'TMT' => 'TMED', // Media type |
|
3694 'TOA' => 'TOPE', // Original artist(s)/performer(s) |
|
3695 'TOF' => 'TOFN', // Original filename |
|
3696 'TOL' => 'TOLY', // Original lyricist(s)/text writer(s) |
|
3697 'TOR' => 'TORY', // Original release year |
|
3698 'TOT' => 'TOAL', // Original album/movie/show title |
|
3699 'TP1' => 'TPE1', // Lead performer(s)/Soloist(s) |
|
3700 'TP2' => 'TPE2', // Band/orchestra/accompaniment |
|
3701 'TP3' => 'TPE3', // Conductor/performer refinement |
|
3702 'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by |
|
3703 'TPA' => 'TPOS', // Part of a set |
|
3704 'TPB' => 'TPUB', // Publisher |
|
3705 'TRC' => 'TSRC', // ISRC (international standard recording code) |
|
3706 'TRD' => 'TRDA', // Recording dates |
|
3707 'TRK' => 'TRCK', // Track number/Position in set |
|
3708 'TS2' => 'TSO2', // Album-Artist sort order |
|
3709 'TSA' => 'TSOA', // Album sort order |
|
3710 'TSC' => 'TSOC', // Composer sort order |
|
3711 'TSI' => 'TSIZ', // Size |
|
3712 'TSP' => 'TSOP', // Performer sort order |
|
3713 'TSS' => 'TSSE', // Software/Hardware and settings used for encoding |
|
3714 'TST' => 'TSOT', // Title sort order |
|
3715 'TT1' => 'TIT1', // Content group description |
|
3716 'TT2' => 'TIT2', // Title/songname/content description |
|
3717 'TT3' => 'TIT3', // Subtitle/Description refinement |
|
3718 'TXT' => 'TEXT', // Lyricist/Text writer |
|
3719 'TXX' => 'TXXX', // User defined text information frame |
|
3720 'TYE' => 'TYER', // Year |
|
3721 'UFI' => 'UFID', // Unique file identifier |
|
3722 'ULT' => 'USLT', // Unsynchronised lyric/text transcription |
|
3723 'WAF' => 'WOAF', // Official audio file webpage |
|
3724 'WAR' => 'WOAR', // Official artist/performer webpage |
|
3725 'WAS' => 'WOAS', // Official audio source webpage |
|
3726 'WCM' => 'WCOM', // Commercial information |
|
3727 'WCP' => 'WCOP', // Copyright/Legal information |
|
3728 'WPB' => 'WPUB', // Publishers official webpage |
|
3729 'WXX' => 'WXXX', // User defined URL link frame |
|
3730 ); |
|
3731 if (strlen($frame_name) == 4) { |
|
3732 if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) { |
|
3733 if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) { |
|
3734 return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)]; |
|
3735 } |
|
3736 } |
|
3737 } |
|
3738 return false; |
|
3739 } |
|
3740 |
3424 } |
3741 } |