- Timestamp:
- 2021-02-26T19:39:51+13:00 (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/greenstone2/perllib/cpan/Image/ExifTool/ID3.pm
r24107 r34921 2 2 # File: ID3.pm 3 3 # 4 # Description: Read ID3 meta information4 # Description: Read ID3 and Lyrics3 meta information 5 5 # 6 6 # Revisions: 09/12/2005 - P. Harvey Created 7 # 09/08/2020 - PH Added Lyrics3 support 7 8 # 8 9 # References: 1) http://www.id3.org/ 9 10 # 2) http://www.mp3-tech.org/ 10 11 # 3) http://www.fortunecity.com/underworld/sonic/3/id3tag.html 12 # 4) https://id3.org/Lyrics3 11 13 #------------------------------------------------------------------------------ 12 14 … … 17 19 use Image::ExifTool qw(:DataAccess :Utils); 18 20 19 $VERSION = '1. 28';21 $VERSION = '1.55'; 20 22 21 23 sub ProcessID3v2($$$); 22 24 sub ProcessPrivate($$$); 25 sub ProcessSynText($$$); 23 26 sub ConvertID3v1Text($$); 27 sub ConvertTimeStamp($); 24 28 25 29 # audio formats that we process after an ID3v2 header (in order) 26 30 my @audioFormats = qw(APE MPC FLAC OGG MP3); 27 31 28 # audio formats where the processing proc is in a different module32 # audio formats where the processing proc is in a differently-named module 29 33 my %audioModule = ( 30 34 MP3 => 'ID3', 31 OGG => ' Vorbis',35 OGG => 'Ogg', 32 36 ); 33 37 … … 67 71 VARS => { NO_ID => 1 }, 68 72 NOTES => q{ 69 ExifTool extracts ID3 information from MP3, MPEG, AIFF, OGG, FLAC, APE and 70 RealAudio files. ID3v2 tags which support multiple languages (ie. Comment 71 and Lyrics) are extracted by specifying the tag name, followed by a dash 72 ('-'), then a 3-character 73 ISO 639-2 74 language code (ie. "Comment-spa"). See L<http://www.id3.org/> for the 75 official ID3 specification and 76 L<http://www.loc.gov/standards/iso639-2/php/code_list.php> for a list of ISO 77 639-2 language codes. 73 ExifTool extracts ID3 and Lyrics3 information from MP3, MPEG, AIFF, OGG, 74 FLAC, APE, MPC and RealAudio files. ID3v2 tags which support multiple 75 languages (eg. Comment and Lyrics) are extracted by specifying the tag name, 76 followed by a dash ('-'), then a 3-character ISO 639-2 language code (eg. 77 "Comment-spa"). See L<http://www.id3.org/> for the official ID3 78 specification and L<http://www.loc.gov/standards/iso639-2/php/code_list.php> 79 for a list of ISO 639-2 language codes. 78 80 }, 79 81 ID3v1 => { … … 97 99 SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_4' }, 98 100 }, 101 ); 102 103 # Lyrics3 tags (ref 4) 104 %Image::ExifTool::ID3::Lyrics3 = ( 105 GROUPS => { 1 => 'Lyrics3', 2 => 'Audio' }, 106 NOTES => q{ 107 ExifTool extracts Lyrics3 version 1.00 and 2.00 tags from any file that 108 supports ID3. See L<https://id3.org/Lyrics3> for the specification. 109 }, 110 IND => 'Indications', 111 LYR => 'Lyrics', 112 INF => 'AdditionalInfo', 113 AUT => { Name => 'Author', Groups => { 2 => 'Author' } }, 114 EAL => 'ExtendedAlbumName', 115 EAR => 'ExtendedArtistName', 116 ETT => 'ExtendedTrackTitle', 117 IMG => 'AssociatedImageFile', 118 CRC => 'CRC', #PH 99 119 ); 100 120 … … 141 161 38 => 'Gospel', 142 162 39 => 'Noise', 143 40 => 'Alt ernRock',163 40 => 'Alt. Rock', # (was AlternRock) 144 164 41 => 'Bass', 145 165 42 => 'Soul', … … 160 180 57 => 'Comedy', 161 181 58 => 'Cult', 162 59 => 'Gangsta ',182 59 => 'Gangsta Rap', # (was Gansta) 163 183 60 => 'Top 40', 164 184 61 => 'Christian Rap', … … 168 188 65 => 'Cabaret', 169 189 66 => 'New Wave', 170 67 => 'Psych adelic',190 67 => 'Psychedelic', # (was misspelt) 171 191 68 => 'Rave', 172 192 69 => 'Showtunes', … … 186 206 82 => 'National Folk', 187 207 83 => 'Swing', 188 84 => 'Fast Fusion',189 85 => 'Bebo b',208 84 => 'Fast-Fusion', # (was Fast Fusion) 209 85 => 'Bebop', # (was misspelt) 190 210 86 => 'Latin', 191 211 87 => 'Revival', … … 225 245 121 => 'Punk Rock', 226 246 122 => 'Drum Solo', 227 123 => 'A capella',247 123 => 'A Cappella', # (was Acapella) 228 248 124 => 'Euro-House', 229 249 125 => 'Dance Hall', … … 236 256 131 => 'Indie', 237 257 132 => 'BritPop', 238 133 => ' Negerpunk',258 133 => 'Afro-Punk', # (was Negerpunk) 239 259 134 => 'Polsk Punk', 240 260 135 => 'Beat', 241 136 => 'Christian Gangsta ',261 136 => 'Christian Gangsta Rap', # (was Christian Gangsta) 242 262 137 => 'Heavy Metal', 243 263 138 => 'Black Metal', 244 264 139 => 'Crossover', 245 140 => 'Contemporary C ',265 140 => 'Contemporary Christian', # (was Contemporary C) 246 266 141 => 'Christian Rock', 247 267 142 => 'Merengue', … … 250 270 145 => 'Anime', 251 271 146 => 'JPop', 252 147 => 'SynthPop', 272 147 => 'Synthpop', # (was SynthPop) 273 # ref http://alicja.homelinux.com/~mats/text/Music/MP3/ID3/Genres.txt 274 # (also used to update some Genres above) 275 148 => 'Abstract', 276 149 => 'Art Rock', 277 150 => 'Baroque', 278 151 => 'Bhangra', 279 152 => 'Big Beat', 280 153 => 'Breakbeat', 281 154 => 'Chillout', 282 155 => 'Downtempo', 283 156 => 'Dub', 284 157 => 'EBM', 285 158 => 'Eclectic', 286 159 => 'Electro', 287 160 => 'Electroclash', 288 161 => 'Emo', 289 162 => 'Experimental', 290 163 => 'Garage', 291 164 => 'Global', 292 165 => 'IDM', 293 166 => 'Illbient', 294 167 => 'Industro-Goth', 295 168 => 'Jam Band', 296 169 => 'Krautrock', 297 170 => 'Leftfield', 298 171 => 'Lounge', 299 172 => 'Math Rock', 300 173 => 'New Romantic', 301 174 => 'Nu-Breakz', 302 175 => 'Post-Punk', 303 176 => 'Post-Rock', 304 177 => 'Psytrance', 305 178 => 'Shoegaze', 306 179 => 'Space Rock', 307 180 => 'Trop Rock', 308 181 => 'World Music', 309 182 => 'Neoclassical', 310 183 => 'Audiobook', 311 184 => 'Audio Theatre', 312 185 => 'Neue Deutsche Welle', 313 186 => 'Podcast', 314 187 => 'Indie Rock', 315 188 => 'G-Funk', 316 189 => 'Dubstep', 317 190 => 'Garage Rock', 318 191 => 'Psybient', 253 319 255 => 'None', 254 320 # ID3v2 adds some text short forms... … … 358 424 in the tables below are those extracted by ExifTool, and don't represent a 359 425 complete list of available ID3v2 tags. 360 426 361 427 ID3 version 2.2 tags. (These are the tags written by iTunes 5.0.) 362 428 }, … … 366 432 PIC => { 367 433 Name => 'Picture', 368 Groups => { 2 => ' Image' },434 Groups => { 2 => 'Preview' }, 369 435 Binary => 1, 370 436 Notes => 'the 3 tags below are also extracted from this PIC frame', … … 378 444 }, 379 445 'PIC-3' => { Name => 'PictureDescription', Groups => { 2 => 'Image' } }, 380 # POP => 'Popularimeter', 446 POP => { 447 Name => 'Popularimeter', 448 PrintConv => '$val=~s/^(.*?) (\d+) (\d+)$/$1 Rating=$2 Count=$3/s; $val', 449 }, 381 450 SLT => { 382 Name => 'Syn chronizedLyricText',383 Binary => 1,451 Name => 'SynLyrics', 452 SubDirectory => { TagTable => 'Image::ExifTool::ID3::SynLyrics' }, 384 453 }, 385 454 TAL => 'Album', … … 391 460 PrintConv => 'Image::ExifTool::ID3::PrintGenre($val)', 392 461 }, 393 TCP => 'Compilation', # not part of spec, but used byiTunes462 TCP => { Name => 'Compilation', PrintConv => { 0 => 'No', 1 => 'Yes' } }, # iTunes 394 463 TCR => { Name => 'Copyright', Groups => { 2 => 'Author' } }, 395 464 TDA => { Name => 'Date', Groups => { 2 => 'Time' } }, … … 403 472 TMT => 'Media', 404 473 TOA => { Name => 'OriginalArtist', Groups => { 2 => 'Author' } }, 405 TOF => 'OriginalFile name',474 TOF => 'OriginalFileName', 406 475 TOL => 'OriginalLyricist', 407 476 TOR => 'OriginalReleaseYear', … … 432 501 WPB => 'PublisherURL', 433 502 WXX => 'UserDefinedURL', 503 # the following written by iTunes 10.5 (ref PH) 504 RVA => 'RelativeVolumeAdjustment', 505 TST => 'TitleSortOrder', 506 TSA => 'AlbumSortOrder', 507 TSP => 'PerformerSortOrder', 508 TS2 => 'AlbumArtistSortOrder', 509 TSC => 'ComposerSortOrder', 510 ITU => { Name => 'iTunesU', Description => 'iTunes U', Binary => 1, Unknown => 1 }, 511 PCS => { Name => 'Podcast', Binary => 1, Unknown => 1 }, 434 512 ); 435 513 … … 439 517 APIC => { 440 518 Name => 'Picture', 441 Groups => { 2 => ' Image' },519 Groups => { 2 => 'Preview' }, 442 520 Binary => 1, 443 Notes => 'the 3 tags below are also extracted from this PIC frame',444 }, 445 'APIC-1' => { Name => 'PictureM imeType', Groups => { 2 => 'Image' } },521 Notes => 'the 3 tags below are also extracted from this APIC frame', 522 }, 523 'APIC-1' => { Name => 'PictureMIMEType', Groups => { 2 => 'Image' } }, 446 524 'APIC-2' => { 447 525 Name => 'PictureType', … … 460 538 MCDI => { Name => 'MusicCDIdentifier', Binary => 1 }, 461 539 # MLLT => 'MPEGLocationLookupTable', 462 # OWNE => 'Ownership', # enc(1), _price, 00, _date(8), Seller540 OWNE => 'Ownership', 463 541 PCNT => 'PlayCounter', 464 # POPM => 'Popularimeter', # _email, 00, rating(1), counter(4-N) 542 POPM => { 543 Name => 'Popularimeter', 544 PrintConv => '$val=~s/^(.*?) (\d+) (\d+)$/$1 Rating=$2 Count=$3/s; $val', 545 }, 465 546 # POSS => 'PostSynchronization', 466 547 PRIV => { … … 471 552 # RVRB => 'Reverb', 472 553 SYLT => { 473 Name => 'Syn chronizedLyricText',474 Binary => 1,554 Name => 'SynLyrics', 555 SubDirectory => { TagTable => 'Image::ExifTool::ID3::SynLyrics' }, 475 556 }, 476 557 # SYTC => 'SynchronizedTempoCodes', … … 501 582 TMED => 'Media', 502 583 TOAL => 'OriginalAlbum', 503 TOFN => 'OriginalFile name',584 TOFN => 'OriginalFileName', 504 585 TOLY => 'OriginalLyricist', 505 586 TOPE => { Name => 'OriginalArtist', Groups => { 2 => 'Author' } }, … … 517 598 TSSE => 'EncoderSettings', 518 599 TXXX => 'UserDefinedText', 519 # UFID => 'UniqueFileID', 600 # UFID => 'UniqueFileID', (not extracted because it is long and nasty and not very useful) 520 601 USER => 'TermsOfUse', 521 602 USLT => 'Lyrics', … … 529 610 WPUB => 'PublisherURL', 530 611 WXXX => 'UserDefinedURL', 612 # 613 # non-standard frames 614 # 615 # the following are written by iTunes 10.5 (ref PH) 616 TSO2 => 'AlbumArtistSortOrder', 617 TSOC => 'ComposerSortOrder', 618 ITNU => { Name => 'iTunesU', Description => 'iTunes U', Binary => 1, Unknown => 1 }, 619 PCST => { Name => 'Podcast', Binary => 1, Unknown => 1 }, 620 # other proprietary Apple tags (ref http://help.mp3tag.de/main_tags.html) 621 TDES => 'PodcastDescription', 622 TGID => 'PodcastID', 623 WFED => 'PodcastURL', 624 TKWD => 'PodcastKeywords', 625 TCAT => 'PodcastCategory', 626 # more non-standard tags (ref http://eyed3.nicfit.net/compliance.html) 627 # NCON - unknown MusicMatch binary data 628 XDOR => { Name => 'OriginalReleaseTime',Groups => { 2 => 'Time' }, %dateTimeConv }, 629 XSOA => 'AlbumSortOrder', 630 XSOP => 'PerformerSortOrder', 631 XSOT => 'TitleSortOrder', 632 XOLY => { 633 Name => 'OlympusDSS', 634 SubDirectory => { TagTable => 'Image::ExifTool::Olympus::DSS' }, 635 }, 636 GRP1 => 'Grouping', 637 MVNM => 'MovementName', # (NC) 638 MVIN => 'MovementNumber', # (NC) 531 639 ); 532 640 … … 535 643 PROCESS_PROC => \&Image::ExifTool::ID3::ProcessID3v2, 536 644 GROUPS => { 1 => 'ID3v2_3', 2 => 'Audio' }, 537 NOTES => 'ID3 version 2.3 tags', 645 NOTES => q{ 646 ID3 version 2.3 tags. Includes some non-standard tags written by other 647 software. 648 }, 538 649 %id3v2_common, # include common tags 539 650 # EQUA => 'Equalization', … … 552 663 PROCESS_PROC => \&Image::ExifTool::ID3::ProcessID3v2, 553 664 GROUPS => { 1 => 'ID3v2_4', 2 => 'Audio' }, 554 NOTES => 'ID3 version 2.4 tags', 665 NOTES => q{ 666 ID3 version 2.4 tags. Includes some non-standard tags written by other 667 software. 668 }, 555 669 %id3v2_common, # include common tags 556 670 # EQU2 => 'Equalization', 557 #RVA2 => 'RelativeVolumeAdjustment',671 RVA2 => 'RelativeVolumeAdjustment', 558 672 # SEEK => 'Seek', 559 673 # SIGN => 'Signature', … … 573 687 ); 574 688 689 # Synchronized lyrics/text 690 %Image::ExifTool::ID3::SynLyrics = ( 691 GROUPS => { 1 => 'ID3', 2 => 'Audio' }, 692 VARS => { NO_ID => 1 }, 693 PROCESS_PROC => \&ProcessSynText, 694 NOTES => 'The following tags are extracted from synchronized lyrics/text frames.', 695 desc => { Name => 'SynchronizedLyricsDescription' }, 696 type => { 697 Name => 'SynchronizedLyricsType', 698 PrintConv => { 699 0 => 'Other', 700 1 => 'Lyrics', 701 2 => 'Text Transcription', 702 3 => 'Movement/part Name', 703 4 => 'Events', 704 5 => 'Chord', 705 6 => 'Trivia/"pop-up" Information', 706 7 => 'Web Page URL', 707 8 => 'Image URL', 708 }, 709 }, 710 text => { 711 Name => 'SynchronizedLyricsText', 712 List => 1, 713 Notes => q{ 714 each list item has a leading time stamp in square brackets. Time stamps may 715 be in seconds with format [MM:SS.ss], or MPEG frames with format [FFFF], 716 depending on how this information was stored 717 }, 718 PrintConv => \&ConvertTimeStamp, 719 }, 720 ); 721 575 722 # ID3 PRIV tags (ref PH) 576 723 %Image::ExifTool::ID3::Private = ( 577 724 PROCESS_PROC => \&Image::ExifTool::ID3::ProcessPrivate, 578 725 GROUPS => { 1 => 'ID3', 2 => 'Audio' }, 579 NOTES => 'ID3 private (PRIV) tags.', 726 VARS => { NO_ID => 1 }, 727 NOTES => q{ 728 ID3 private (PRIV) tags. ExifTool will decode any private tags found, even 729 if they do not appear in this table. 730 }, 580 731 XMP => { 581 732 SubDirectory => { … … 590 741 ValueConv => 'length($val)==4 ? unpack("V",$val) : \$val', 591 742 }, 743 # Windows Media attributes ("/" in tag ID is converted to "_" by ProcessPrivate) 744 WM_WMContentID => { 745 Name => 'WM_ContentID', 746 ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', 747 }, 748 WM_WMCollectionID => { 749 Name => 'WM_CollectionID', 750 ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', 751 }, 752 WM_WMCollectionGroupID => { 753 Name => 'WM_CollectionGroupID', 754 ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', 755 }, 756 WM_MediaClassPrimaryID => { 757 ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', 758 }, 759 WM_MediaClassSecondaryID => { 760 ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)', 761 }, 762 WM_Provider => { 763 ValueConv => '$self->Decode($val,"UCS2","II")', #PH (NC) 764 }, 765 # there are lots more WM tags that could be decoded if I had samples or documentation - PH 766 # WM/AlbumArtist 767 # WM/AlbumTitle 768 # WM/Category 769 # WM/Composer 770 # WM/Conductor 771 # WM/ContentDistributor 772 # WM/ContentGroupDescription 773 # WM/EncodingTime 774 # WM/Genre 775 # WM/GenreID 776 # WM/InitialKey 777 # WM/Language 778 # WM/Lyrics 779 # WM/MCDI 780 # WM/MediaClassPrimaryID 781 # WM/MediaClassSecondaryID 782 # WM/Mood 783 # WM/ParentalRating 784 # WM/Period 785 # WM/ProtectionType 786 # WM/Provider 787 # WM/ProviderRating 788 # WM/ProviderStyle 789 # WM/Publisher 790 # WM/SubscriptionContentID 791 # WM/SubTitle 792 # WM/TrackNumber 793 # WM/UniqueFileIdentifier 794 # WM/WMCollectionGroupID 795 # WM/WMCollectionID 796 # WM/WMContentID 797 # WM/Writer 798 # WM/Year 799 ); 800 801 # lookup to check for existence of tags in other ID3 versions 802 my %otherTable = ( 803 \%Image::ExifTool::ID3::v2_4 => \%Image::ExifTool::ID3::v2_3, 804 \%Image::ExifTool::ID3::v2_3 => \%Image::ExifTool::ID3::v2_4, 592 805 ); 593 806 … … 640 853 sub ConvertID3v1Text($$) 641 854 { 642 my ($exifTool, $val) = @_; 643 return $exifTool->Decode($val, $exifTool->Options('CharsetID3')); 855 my ($et, $val) = @_; 856 return $et->Decode($val, $et->Options('CharsetID3')); 857 } 858 859 #------------------------------------------------------------------------------ 860 # Re-format time stamp in synchronized lyrics 861 # Inputs: 0) synchronized lyrics entry (eg. "[84.030]Da do do do") 862 # Returns: entry with formatted timestamp (eg. "[01:24.03]Da do do do") 863 sub ConvertTimeStamp($) 864 { 865 my $val = shift; 866 # do nothing if this isn't a time stamp (frame count doesn't contain a decimal) 867 return $val unless $val =~ /^\[(\d+\.\d+)\]/g; 868 my $time = $1; 869 # print hours only if more than 60 minutes 870 my $h = int($time / 3600); 871 if ($h) { 872 $time -= $h * 3600; 873 $h = "$h:"; 874 } else { 875 $h = ''; 876 } 877 my $m = int($time / 60); 878 my $s = $time - $m * 60; 879 my $ss = sprintf('%05.2f', $s); 880 if ($ss >= 60) { 881 $ss = '00.00'; 882 ++$m >= 60 and $m -= 60, ++$h; 883 } 884 return sprintf('[%s%.2d:%s]', $h, $m, $ss) . substr($val, pos($val)); 885 } 886 887 #------------------------------------------------------------------------------ 888 # Process ID3 synchronized lyrics/text 889 # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref 890 sub ProcessSynText($$$) 891 { 892 my ($et, $dirInfo, $tagTablePtr) = @_; 893 my $dataPt = $$dirInfo{DataPt}; 894 895 $et->VerboseDir('SynLyrics', 0, length $$dataPt); 896 return unless length $$dataPt > 6; 897 898 my ($enc,$lang,$timeCode,$type) = unpack('Ca3CC', $$dataPt); 899 $lang = lc $lang; 900 undef $lang if $lang !~ /^[a-z]{3}$/ or $lang eq 'eng'; 901 pos($$dataPt) = 6; 902 my ($termLen, $pat); 903 if ($enc == 1 or $enc == 2) { 904 $$dataPt =~ /\G(..)*?\0\0/sg or return; 905 $termLen = 2; 906 $pat = '\G(?:..)*?\0\0(....)'; 907 } else { 908 $$dataPt =~ /\0/g or return; 909 $termLen = 1; 910 $pat = '\0(....)'; 911 } 912 my $desc = substr($$dataPt, 6, pos($$dataPt) - 6 - $termLen); 913 $desc = DecodeString($et, $desc, $enc); 914 915 my $tagInfo = $et->GetTagInfo($tagTablePtr, 'desc'); 916 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang) if $lang; 917 $et->HandleTag($tagTablePtr, 'type', $type); 918 $et->HandleTag($tagTablePtr, 'desc', $desc, TagInfo => $tagInfo); 919 $tagInfo = $et->GetTagInfo($tagTablePtr, 'text'); 920 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang) if $lang; 921 922 for (;;) { 923 my $pos = pos $$dataPt; 924 last unless $$dataPt =~ /$pat/sg; 925 my $time = unpack('N', $1); 926 my $text = substr($$dataPt, $pos, pos($$dataPt) - $pos - 4 - $termLen); 927 $text = DecodeString($et, $text, $enc); 928 my $timeStr; 929 if ($timeCode == 2) { # time in ms 930 $timeStr = sprintf('%.3f', $time / 1000); 931 } else { # time in MPEG frames 932 $timeStr = sprintf('%.4d', $time); 933 $timeStr .= '?' if $timeCode != 1; 934 } 935 $et->HandleTag($tagTablePtr, 'text', "[$timeStr]$text", TagInfo => $tagInfo); 936 } 644 937 } 645 938 … … 649 942 sub ProcessPrivate($$$) 650 943 { 651 my ($e xifTool, $dirInfo, $tagTablePtr) = @_;944 my ($et, $dirInfo, $tagTablePtr) = @_; 652 945 my $dataPt = $$dirInfo{DataPt}; 653 946 my ($tag, $start); 654 if ($$dataPt =~ /^(.*?)\0/) { 947 $et->VerboseDir('PRIV', 0, length $$dataPt); 948 if ($$dataPt =~ /^(.*?)\0/s) { 655 949 $tag = $1; 656 950 $start = length($tag) + 1; … … 663 957 $tag = 'private' unless $tag =~ /^[-\w]{1,24}$/; 664 958 unless ($$tagTablePtr{$tag}) { 665 Image::ExifTool::AddTagToTable($tagTablePtr, $tag,959 AddTagToTable($tagTablePtr, $tag, 666 960 { Name => ucfirst($tag), Binary => 1 }); 667 961 } 668 962 } 669 my $key = $e xifTool->HandleTag($tagTablePtr, $tag, undef,963 my $key = $et->HandleTag($tagTablePtr, $tag, undef, 670 964 Size => length($$dataPt) - $start, 671 965 Start => $start, … … 673 967 ); 674 968 # set group1 name 675 $e xifTool->SetGroup($key, $$exifTool{ID3_Ver}) if $key;969 $et->SetGroup($key, $$et{ID3_Ver}) if $key; 676 970 } 677 971 … … 690 984 # (genre numbers are separated by nulls in ID3v2.4, 691 985 # but nulls are converted to '/' by DecodeString()) 692 while ($val =~ /(?:^|\/)(\d+) /g) {986 while ($val =~ /(?:^|\/)(\d+)(\/|$)/g) { 693 987 $genre{$1} or $genre{$1} = "Unknown ($1)"; 694 988 } 695 989 $val =~ s/\((\d+)\)/\($genre{$1}\)/g; 696 $val =~ s/(^|\/)(\d+) /$1$genre{$2}/g;990 $val =~ s/(^|\/)(\d+)(?=\/|$)/$1$genre{$2}/g; 697 991 $val =~ s/^\(([^)]+)\)\1?$/$1/; # clean up by removing brackets and duplicates 698 992 return $val; 993 } 994 995 #------------------------------------------------------------------------------ 996 # Get Genre ID 997 # Inputs: 0) Genre name 998 # Returns: genre ID number, or undef 999 sub GetGenreID($) 1000 { 1001 return Image::ExifTool::ReverseLookup(shift, \%genre); 699 1002 } 700 1003 … … 707 1010 sub DecodeString($$;$) 708 1011 { 709 my ($e xifTool, $val, $enc) = @_;1012 my ($et, $val, $enc) = @_; 710 1013 return '' unless length $val; 711 1014 unless (defined $enc) { … … 719 1022 @vals = split "\0", $val; 720 1023 foreach $val (@vals) { 721 $val = $e xifTool->Decode($val, $enc ? 'UTF8' : 'Latin');1024 $val = $et->Decode($val, $enc ? 'UTF8' : 'Latin'); 722 1025 } 723 1026 } elsif ($enc == 1 or $enc == 2) { # UTF-16 with BOM, or UTF-16BE … … 727 1030 my $v; 728 1031 # split string at null terminators on word boundaries 729 if ($val =~ s/((..)*?)\0\0// ) {1032 if ($val =~ s/((..)*?)\0\0//s) { 730 1033 $v = $1; 731 1034 } else { … … 735 1038 } 736 1039 $bom = $1 if $v =~ s/^(\xfe\xff|\xff\xfe)//; 737 push @vals, $e xifTool->Decode($v, 'UCS2', $order{$bom});1040 push @vals, $et->Decode($v, 'UCS2', $order{$bom}); 738 1041 } 739 1042 } else { … … 764 1067 sub ProcessID3v2($$$) 765 1068 { 766 my ($e xifTool, $dirInfo, $tagTablePtr) = @_;1069 my ($et, $dirInfo, $tagTablePtr) = @_; 767 1070 my $dataPt = $$dirInfo{DataPt}; 768 1071 my $offset = $$dirInfo{DirStart}; 769 1072 my $size = $$dirInfo{DirLen}; 770 1073 my $vers = $$dirInfo{Version}; 771 my $verbose = $e xifTool->Options('Verbose');1074 my $verbose = $et->Options('Verbose'); 772 1075 my $len; # frame data length 773 1076 774 $verbose and $exifTool->VerboseDir($tagTablePtr->{GROUPS}->{1}, 0, $size); 1077 $et->VerboseDir($tagTablePtr->{GROUPS}->{1}, 0, $size); 1078 $et->VerboseDump($dataPt, Len => $size, Start => $offset); 775 1079 776 1080 for (;;$offset+=$len) { … … 796 1100 $len = UnSyncSafe($len); 797 1101 if (not defined $len or $offset + $len + 10 > $size) { 798 $e xifTool->Warn('Invalid ID3 frame size');1102 $et->Warn('Invalid ID3 frame size'); 799 1103 last; 800 1104 } … … 810 1114 } 811 1115 last if $offset + $len > $size; 812 my $tagInfo = $e xifTool->GetTagInfo($tagTablePtr, $id);1116 my $tagInfo = $et->GetTagInfo($tagTablePtr, $id); 813 1117 unless ($tagInfo) { 814 next unless $verbose or $exifTool->Options('Unknown'); 815 $id =~ tr/-A-Za-z0-9_//dc; 816 $id = 'unknown' unless length $id; 817 unless ($$tagTablePtr{$id}) { 818 $tagInfo = { Name => "ID3_$id", Binary => 1 }; 819 Image::ExifTool::AddTagToTable($tagTablePtr, $id, $tagInfo); 1118 my $otherTable = $otherTable{$tagTablePtr}; 1119 $tagInfo = $et->GetTagInfo($otherTable, $id) if $otherTable; 1120 if ($tagInfo) { 1121 $et->WarnOnce("Frame '${id}' is not valid for this ID3 version", 1); 1122 } else { 1123 next unless $verbose or $et->Options('Unknown'); 1124 $id =~ tr/-A-Za-z0-9_//dc; 1125 $id = 'unknown' unless length $id; 1126 unless ($$tagTablePtr{$id}) { 1127 $tagInfo = { Name => "ID3_$id", Binary => 1 }; 1128 AddTagToTable($tagTablePtr, $id, $tagInfo); 1129 } 820 1130 } 821 1131 } 822 1132 # decode v2.3 and v2.4 flags 823 my %flags;1133 my (%flags, %extra); 824 1134 if ($flags) { 825 1135 if ($vers < 0x0400) { … … 838 1148 } 839 1149 if ($flags{Encrypt}) { 840 $e xifTool->WarnOnce('Encrypted frames currently not supported');1150 $et->WarnOnce('Encrypted frames currently not supported'); 841 1151 next; 842 1152 } … … 849 1159 # read grouping identity 850 1160 if ($flags{GroupID}) { 851 length($val) >= 1 or $e xifTool->Warn("Short $id frame"), next;1161 length($val) >= 1 or $et->Warn("Short $id frame"), next; 852 1162 $val = substr($val, 1); # (ignore it) 853 1163 } … … 855 1165 my $dataLen; 856 1166 if ($flags{DataLen} or $flags{Compress}) { 857 length($val) >= 4 or $e xifTool->Warn("Short $id frame"), next;1167 length($val) >= 4 or $et->Warn("Short $id frame"), next; 858 1168 $dataLen = unpack('N', $val); # save the data length word 859 1169 $val = substr($val, 4); … … 861 1171 # uncompress data 862 1172 if ($flags{Compress}) { 863 if (eval 'require Compress::Zlib') {1173 if (eval { require Compress::Zlib }) { 864 1174 my $inflate = Compress::Zlib::inflateInit(); 865 1175 my ($buff, $stat); … … 868 1178 $val = $buff; 869 1179 } else { 870 $e xifTool->Warn("Error inflating $id frame");1180 $et->Warn("Error inflating $id frame"); 871 1181 next; 872 1182 } 873 1183 } else { 874 $e xifTool->WarnOnce('Install Compress::Zlib to decode compressed frames');1184 $et->WarnOnce('Install Compress::Zlib to decode compressed frames'); 875 1185 next; 876 1186 } … … 879 1189 if (defined $dataLen) { 880 1190 $dataLen = UnSyncSafe($dataLen); 881 defined $dataLen or $exifTool->Warn("Invalid length for $id frame"), next; 882 $dataLen == length($val) or $exifTool->Warn("Wrong length for $id frame"), next; 883 } 884 $verbose and $exifTool->VerboseInfo($id, $tagInfo, 885 Table => $tagTablePtr, 886 Value => $val, 887 DataPt => $dataPt, 888 DataPos => $$dirInfo{DataPos}, 889 Size => $len, 890 Start => $offset, 891 ); 892 next unless $tagInfo; 1191 defined $dataLen or $et->Warn("Invalid length for $id frame"), next; 1192 $dataLen == length($val) or $et->Warn("Wrong length for $id frame"), next; 1193 } 1194 unless ($tagInfo) { 1195 next unless $verbose; 1196 %flags and $extra{Extra} = ', Flags=' . join(',', sort keys %flags); 1197 $et->VerboseInfo($id, $tagInfo, 1198 Table => $tagTablePtr, 1199 Value => $val, 1200 DataPt => $dataPt, 1201 DataPos => $$dirInfo{DataPos}, 1202 Size => $len, 1203 Start => $offset, 1204 %extra 1205 ); 1206 next; 1207 } 893 1208 # 894 # decode data in this frame 1209 # decode data in this frame (it is bad form to hard-code these, but the ID3 frame formats 1210 # are so variable that it would be more work to define format types for each of them) 895 1211 # 896 1212 my $lang; … … 898 1214 if ($id =~ /^(TXX|TXXX)$/) { 899 1215 # two encoded strings separated by a null 900 my @vals = DecodeString($e xifTool, $val);1216 my @vals = DecodeString($et, $val); 901 1217 foreach (0..1) { $vals[$_] = '' unless defined $vals[$_]; } 902 1218 ($val = "($vals[0]) $vals[1]") =~ s/^\(\) //; 903 1219 } elsif ($id =~ /^T/ or $id =~ /^(IPL|IPLS)$/) { 904 $val = DecodeString($e xifTool, $val);1220 $val = DecodeString($et, $val); 905 1221 } elsif ($id =~ /^(WXX|WXXX)$/) { 906 1222 # one encoded string and one Latin string separated by a null … … 908 1224 my $url; 909 1225 if ($enc == 1 or $enc == 2) { 910 ($val, $url) = ($val =~ /^(.(?:..)*?)\0\0(.*)/ );1226 ($val, $url) = ($val =~ /^(.(?:..)*?)\0\0(.*)/s); 911 1227 } else { 912 ($val, $url) = ($val =~ /^(..*?)\0(.*)/ );1228 ($val, $url) = ($val =~ /^(..*?)\0(.*)/s); 913 1229 } 914 1230 unless (defined $val and defined $url) { 915 $e xifTool->Warn("Invalid $id frame value");1231 $et->Warn("Invalid $id frame value"); 916 1232 next; 917 1233 } 918 $val = DecodeString($e xifTool, $val);919 $url =~ s/\0.*// ;1234 $val = DecodeString($et, $val); 1235 $url =~ s/\0.*//s; 920 1236 $val = length($val) ? "($val) $url" : $url; 921 1237 } elsif ($id =~ /^W/) { 922 $val =~ s/\0.*// ;# truncate at null1238 $val =~ s/\0.*//s; # truncate at null 923 1239 } elsif ($id =~ /^(COM|COMM|ULT|USLT)$/) { 924 $valLen > 4 or $e xifTool->Warn("Short $id frame"), next;1240 $valLen > 4 or $et->Warn("Short $id frame"), next; 925 1241 $lang = substr($val,1,3); 926 my @vals = DecodeString($e xifTool, substr($val,4), Get8u(\$val,0));1242 my @vals = DecodeString($et, substr($val,4), Get8u(\$val,0)); 927 1243 foreach (0..1) { $vals[$_] = '' unless defined $vals[$_]; } 928 1244 $val = length($vals[0]) ? "($vals[0]) $vals[1]" : $vals[1]; 929 1245 } elsif ($id eq 'USER') { 930 $valLen > 4 or $e xifTool->Warn('Short USER frame'), next;1246 $valLen > 4 or $et->Warn("Short $id frame"), next; 931 1247 $lang = substr($val,1,3); 932 $val = DecodeString($e xifTool, substr($val,4), Get8u(\$val,0));1248 $val = DecodeString($et, substr($val,4), Get8u(\$val,0)); 933 1249 } elsif ($id =~ /^(CNT|PCNT)$/) { 934 $valLen >= 4 or $exifTool->Warn("Short $id frame"), next; 935 my $cnt = unpack('N', $val); 936 my $i; 937 for ($i=4; $i<$valLen; ++$i) { 938 $cnt = $cnt * 256 + unpack("x${i}C", $val); 939 } 1250 $valLen >= 4 or $et->Warn("Short $id frame"), next; 1251 my ($cnt, @xtra) = unpack('NC*', $val); 1252 $cnt = ($cnt << 8) + $_ foreach @xtra; 940 1253 $val = $cnt; 941 1254 } elsif ($id =~ /^(PIC|APIC)$/) { 942 $valLen >= 4 or $e xifTool->Warn("Short $id frame"), next;1255 $valLen >= 4 or $et->Warn("Short $id frame"), next; 943 1256 my ($hdr, $attr); 944 1257 my $enc = unpack('C', $val); … … 949 1262 } 950 1263 # remove header (encoding, image format or MIME type, picture type, description) 951 $val =~ s/^$hdr//s or $e xifTool->Warn("Invalid $id frame"), next;952 my @attrs = ($1, ord($2), DecodeString($e xifTool, $3, $enc));1264 $val =~ s/^$hdr//s or $et->Warn("Invalid $id frame"), next; 1265 my @attrs = ($1, ord($2), DecodeString($et, $3, $enc)); 953 1266 my $i = 1; 954 1267 foreach $attr (@attrs) { 955 1268 # must store descriptions even if they are empty to maintain 956 1269 # sync between copy numbers when multiple images 957 $e xifTool->HandleTag($tagTablePtr, "$id-$i", $attr);1270 $et->HandleTag($tagTablePtr, "$id-$i", $attr); 958 1271 ++$i; 959 1272 } 1273 } elsif ($id eq 'POP' or $id eq 'POPM') { 1274 # _email, 00, rating(1), counter(4-N) 1275 my ($email, $dat) = ($val =~ /^([^\0]*)\0(.*)$/s); 1276 unless (defined $dat and length($dat)) { 1277 $et->Warn("Invalid $id frame"); 1278 next; 1279 } 1280 my ($rating, @xtra) = unpack('C*', $dat); 1281 my $cnt = 0; 1282 $cnt = ($cnt << 8) + $_ foreach @xtra; 1283 $val = "$email $rating $cnt"; 1284 } elsif ($id eq 'OWNE') { 1285 # enc(1), _price, 00, _date(8), Seller 1286 my @strs = DecodeString($et, $val); 1287 $strs[1] =~ s/^(\d{4})(\d{2})(\d{2})/$1:$2:$3 /s if $strs[1]; # format date 1288 $val = "@strs"; 1289 } elsif ($id eq 'RVA' or $id eq 'RVAD') { 1290 my @dat = unpack('C*', $val); 1291 my $flag = shift @dat; 1292 my $bits = shift @dat or $et->Warn("Short $id frame"), next; 1293 my $bytes = int(($bits + 7) / 8); 1294 my @parse = (['Right',0,2,0x01],['Left',1,3,0x02],['Back-right',4,6,0x04], 1295 ['Back-left',5,7,0x08],['Center',8,9,0x10],['Bass',10,11,0x20]); 1296 $val = ''; 1297 while (@parse) { 1298 my $elem = shift @parse; 1299 my $j = $$elem[2] * $bytes; 1300 last if scalar(@dat) < $j + $bytes; 1301 my $i = $$elem[1] * $bytes; 1302 $val .= ', ' if $val; 1303 my ($rel, $pk, $b); 1304 for ($rel=0, $pk=0, $b=0; $b<$bytes; ++$b) { 1305 $rel = $rel * 256 + $dat[$i + $b]; 1306 $pk = $pk * 256 + $dat[$j + $b]; # (peak - not used in printout) 1307 } 1308 $rel =-$rel unless $flag & $$elem[3]; 1309 $val .= sprintf("%+.1f%% %s", 100 * $rel / ((1<<$bits)-1), $$elem[0]); 1310 } 1311 } elsif ($id eq 'RVA2') { 1312 my ($pos, $id) = $val=~/^([^\0]*)\0/s ? (length($1)+1, $1) : (1, ''); 1313 my @vals; 1314 while ($pos + 4 <= $valLen) { 1315 my $type = Get8u(\$val, $pos); 1316 my $str = ({ 1317 0 => 'Other', 1318 1 => 'Master', 1319 2 => 'Front-right', 1320 3 => 'Front-left', 1321 4 => 'Back-right', 1322 5 => 'Back-left', 1323 6 => 'Front-centre', 1324 7 => 'Back-centre', 1325 8 => 'Subwoofer', 1326 }->{$type} || "Unknown($type)"); 1327 my $db = Get16s(\$val,$pos+1) / 512; 1328 # convert dB to percent as displayed by iTunes 10.5 1329 # (not sure why I need to divide by 20 instead of 10 as expected - PH) 1330 push @vals, sprintf('%+.1f%% %s', 10**($db/20+2)-100, $str); 1331 # step to next channel (ignoring peak volume) 1332 $pos += 4 + int((Get8u(\$val,$pos+3) + 7) / 8); 1333 } 1334 $val = join ', ', @vals; 1335 $val .= " ($id)" if $id; 960 1336 } elsif ($id eq 'PRIV') { 961 1337 # save version number to set group1 name for tag later 962 $ exifTool->{ID3_Ver} = $tagTablePtr->{GROUPS}->{1};963 $e xifTool->HandleTag($tagTablePtr, $id, $val);1338 $$et{ID3_Ver} = $$tagTablePtr{GROUPS}{1}; 1339 $et->HandleTag($tagTablePtr, $id, $val); 964 1340 next; 1341 } elsif ($$tagInfo{Format} or $$tagInfo{SubDirectory}) { 1342 $et->HandleTag($tagTablePtr, $id, undef, DataPt => \$val); 1343 next; 1344 } elsif ($id eq 'GRP1' or $id eq 'MVNM' or $id eq 'MVIN') { 1345 $val =~ s/(^\0+|\0+$)//g; # (PH guess) 965 1346 } elsif (not $$tagInfo{Binary}) { 966 $e xifTool->Warn("Don't know how to handle $id frame");1347 $et->Warn("Don't know how to handle $id frame"); 967 1348 next; 968 1349 } … … 970 1351 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, lc $lang); 971 1352 } 972 $exifTool->FoundTag($tagInfo, $val); 1353 %flags and $extra{Extra} = ', Flags=' . join(',', sort keys %flags); 1354 $et->HandleTag($tagTablePtr, $id, $val, 1355 TagInfo => $tagInfo, 1356 DataPt => $dataPt, 1357 DataPos => $$dirInfo{DataPos}, 1358 Size => $len, 1359 Start => $offset, 1360 %extra 1361 ); 973 1362 } 974 1363 } … … 979 1368 # Returns: 1 on success, 0 if this file didn't contain ID3 information 980 1369 # - also processes audio data if any ID3 information was found 981 # - sets ExifTool DoneID3 to 1 when called, or to 2if an ID3v1 trailer exists1370 # - sets ExifTool DoneID3 to 1 when called, or to trailer size if an ID3v1 trailer exists 982 1371 sub ProcessID3($$) 983 1372 { 984 my ($e xifTool, $dirInfo) = @_;985 986 return 0 if $ exifTool->{DoneID3}; # avoid infinite recursion987 $ exifTool->{DoneID3} = 1;1373 my ($et, $dirInfo) = @_; 1374 1375 return 0 if $$et{DoneID3}; # avoid infinite recursion 1376 $$et{DoneID3} = 1; 988 1377 989 1378 # allow this to be called with either RAF or DataPt … … 1002 1391 while ($buff =~ /^ID3/) { 1003 1392 $rtnVal = 1; 1004 $raf->Read($hBuff, 7) == 7 or $e xifTool->Warn('Short ID3 header'), last;1393 $raf->Read($hBuff, 7) == 7 or $et->Warn('Short ID3 header'), last; 1005 1394 my ($vers, $flags, $size) = unpack('nCN', $hBuff); 1006 1395 $size = UnSyncSafe($size); 1007 defined $size or $e xifTool->Warn('Invalid ID3 header'), last;1396 defined $size or $et->Warn('Invalid ID3 header'), last; 1008 1397 my $verStr = sprintf("2.%d.%d", $vers >> 8, $vers & 0xff); 1009 1398 if ($vers >= 0x0500) { 1010 $e xifTool->Warn("Unsupported ID3 version: $verStr");1399 $et->Warn("Unsupported ID3 version: $verStr"); 1011 1400 last; 1012 1401 } 1013 1402 unless ($raf->Read($hBuff, $size) == $size) { 1014 $e xifTool->Warn('Truncated ID3 data');1403 $et->Warn('Truncated ID3 data'); 1015 1404 last; 1016 1405 } … … 1023 1412 if ($flags & 0x40) { 1024 1413 # skip the extended header 1025 $size >= 4 or $e xifTool->Warn('Bad ID3 extended header'), last;1414 $size >= 4 or $et->Warn('Bad ID3 extended header'), last; 1026 1415 my $len = unpack('N', $hBuff); 1027 1416 if ($len > length($hBuff) - 4) { 1028 $e xifTool->Warn('Truncated ID3 extended header');1417 $et->Warn('Truncated ID3 extended header'); 1029 1418 last; 1030 1419 } … … 1058 1447 # read ID3v1 trailer if it exists 1059 1448 # 1449 my $trailSize = 0; 1060 1450 if ($raf->Seek(-128, 2) and $raf->Read($tBuff, 128) == 128 and $tBuff =~ /^TAG/) { 1061 $ exifTool->{DoneID3} = 2; # set to 2 as flag that trailer exists1451 $trailSize = 128; 1062 1452 %id3Trailer = ( 1063 1453 DataPt => \$tBuff, … … 1069 1459 $rtnVal = 1; 1070 1460 # load 'Enhanced TAG' information if available 1071 if ($raf->Seek(-355, 2) and $raf->Read($eBuff, 227) == 227 and $eBuff =~ /^TAG+/) { 1461 my $eSize = 227; # size of ID3 Enhanced TAG info 1462 if ($raf->Seek(-$trailSize - $eSize, 2) and $raf->Read($eBuff, $eSize) == $eSize and $eBuff =~ /^TAG+/) { 1072 1463 $id3Trailer{EnhancedTAG} = \$eBuff; 1464 $trailSize += $eSize; 1465 } 1466 $$et{DoneID3} = $trailSize; # save trailer size 1467 } 1468 # 1469 # read Lyrics3 trailer if it exists 1470 # 1471 if ($raf->Seek(-$trailSize-15, 2) and $raf->Read($buff, 15) == 15 and $buff =~ /^(.{6})LYRICS(END|200)$/) { 1472 my $ver = $2; # Lyrics3 version ('END' for version 1) 1473 my $len = ($ver eq 'END') ? 5100 : $1 + 15; # max Lyrics3 length 1474 my $tbl = GetTagTable('Image::ExifTool::ID3::Lyrics3'); 1475 $len = $raf->Tell() if $len > $raf->Tell(); 1476 if ($raf->Seek(-$len, 1) and $raf->Read($buff, $len) == $len and $buff =~ /LYRICSBEGIN/g) { 1477 my $pos = pos($buff); 1478 $$et{DoneID3} = $trailSize + $len - $pos + 11; # update trailer length 1479 my $oldIndent = $$et{INDENT}; 1480 $$et{INDENT} .= '| '; 1481 if ($et->Options('Verbose')) { 1482 $et->VPrint(0, "Lyrics3:\n"); 1483 $et->VerboseDir('Lyrics3', undef, $len); 1484 if ($pos > 11) { 1485 $buff = substr($buff, $pos - 11); 1486 $pos = 11; 1487 } 1488 $et->VerboseDump(\$buff); 1489 } 1490 if ($ver eq 'END') { 1491 # Lyrics3 v1.00 1492 my $val = substr($buff, $pos, $len - $pos - 9); 1493 $et->HandleTag($tbl, 'LYR', $et->Decode($val, 'Latin')); 1494 } else { 1495 # Lyrics3 v2.00 1496 for (;;) { 1497 # (note: the size field is 5 digits,, not 6 as per the documentation) 1498 last unless $buff =~ /\G(.{3})(\d{5})/g; 1499 my ($tag, $size) = ($1, $2); 1500 $pos += 8; 1501 last if $pos + $size > length($buff); 1502 unless ($$tbl{$tag}) { 1503 AddTagToTable($tbl, $tag, { Name => Image::ExifTool::MakeTagName("Lyrics3_$tag") }); 1504 } 1505 $et->HandleTag($tbl, $tag, $et->Decode(substr($buff, $pos, $size), 'Latin')); 1506 $pos += $size; 1507 pos($buff) = $pos; 1508 } 1509 $pos == length($buff) - 15 or $et->Warn('Malformed Lyrics3 v2.00 block'); 1510 } 1511 $$et{INDENT} = $oldIndent; 1512 } else { 1513 $et->Warn('Error reading Lyrics3 trailer'); 1073 1514 } 1074 1515 } … … 1079 1520 # first process audio data if it exists 1080 1521 if ($$dirInfo{RAF}) { 1081 my $oldType = $ exifTool->{FILE_TYPE}; # save file type1522 my $oldType = $$et{FILE_TYPE}; # save file type 1082 1523 # check current file type first 1083 1524 my @types = grep /^$oldType$/, @audioFormats; … … 1088 1529 $raf->Seek($hdrEnd, 0); 1089 1530 # set type for this file if we are successful 1090 $ exifTool->{FILE_TYPE} = $type;1531 $$et{FILE_TYPE} = $type; 1091 1532 my $module = $audioModule{$type} || $type; 1092 1533 require "Image/ExifTool/$module.pm" or next; … … 1094 1535 # process the file 1095 1536 no strict 'refs'; 1096 &$func($e xifTool, $dirInfo) and last;1537 &$func($et, $dirInfo) and last; 1097 1538 use strict 'refs'; 1098 1539 } 1099 $ exifTool->{FILE_TYPE} = $oldType; # restore original file type1540 $$et{FILE_TYPE} = $oldType; # restore original file type 1100 1541 } 1101 1542 # set file type to MP3 if we didn't find audio data 1102 $e xifTool->SetFileType('MP3');1103 # record the size if the ID3 metadata1104 $e xifTool->FoundTag('ID3Size', $id3Len);1543 $et->SetFileType('MP3'); 1544 # record the size of the ID3 metadata 1545 $et->FoundTag('ID3Size', $id3Len); 1105 1546 # process ID3v2 header if it exists 1106 1547 if (%id3Header) { 1107 $e xifTool->VPrint(0, "$id3Header{DirName}:\n");1108 $e xifTool->ProcessDirectory(\%id3Header, $tagTablePtr);1548 $et->VPrint(0, "$id3Header{DirName}:\n"); 1549 $et->ProcessDirectory(\%id3Header, $tagTablePtr); 1109 1550 } 1110 1551 # process ID3v1 trailer if it exists 1111 1552 if (%id3Trailer) { 1112 $e xifTool->VPrint(0, "ID3v1:\n");1553 $et->VPrint(0, "ID3v1:\n"); 1113 1554 SetByteOrder('MM'); 1114 1555 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1'); 1115 $e xifTool->ProcessDirectory(\%id3Trailer, $tagTablePtr);1556 $et->ProcessDirectory(\%id3Trailer, $tagTablePtr); 1116 1557 # process "Enhanced TAG" information if available 1117 1558 if ($id3Trailer{EnhancedTAG}) { 1118 $e xifTool->VPrint(0, "ID3v1 Enhanced TAG:\n");1559 $et->VPrint(0, "ID3v1 Enhanced TAG:\n"); 1119 1560 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1_Enh'); 1120 1561 $id3Trailer{DataPt} = $id3Trailer{EnhancedTAG}; 1121 1562 $id3Trailer{DataPos} -= 227; # (227 = length of Enhanced TAG block) 1122 1563 $id3Trailer{DirLen} = 227; 1123 $e xifTool->ProcessDirectory(\%id3Trailer, $tagTablePtr);1564 $et->ProcessDirectory(\%id3Trailer, $tagTablePtr); 1124 1565 } 1125 1566 } … … 1136 1577 sub ProcessMP3($$) 1137 1578 { 1138 my ($exifTool, $dirInfo) = @_; 1579 my ($et, $dirInfo) = @_; 1580 my $rtnVal = 0; 1139 1581 1140 1582 # must first check for leading/trailing ID3 information 1141 unless ($exifTool->{DoneID3}) { 1142 ProcessID3($exifTool, $dirInfo) and return 1; 1143 } 1144 my $raf = $$dirInfo{RAF}; 1145 my $rtnVal = 0; 1146 my $buff; 1583 # (and process the rest of the file if found) 1584 unless ($$et{DoneID3}) { 1585 $rtnVal = ProcessID3($et, $dirInfo); 1586 } 1587 1588 # check for MPEG A/V data if not already processed above 1589 unless ($rtnVal) { 1590 my $raf = $$dirInfo{RAF}; 1591 my $buff; 1147 1592 # 1148 1593 # extract information from first audio/video frame headers 1149 1594 # (if found in the first $scanLen bytes) 1150 1595 # 1151 # scan further into a file that should be an MP3 1152 my $scanLen = ($$exifTool{FILE_EXT} and $$exifTool{FILE_EXT} eq 'MP3') ? 8192 : 256; 1153 if ($raf->Read($buff, $scanLen)) { 1154 require Image::ExifTool::MPEG; 1155 if ($buff =~ /\0\0\x01(\xb3|\xc0)/) { 1156 # look for A/V headers in first 64kB 1157 my $buf2; 1158 $raf->Read($buf2, 0x10000 - $scanLen) and $buff .= $buf2; 1159 $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudioVideo($exifTool, \$buff); 1160 } else { 1161 # look for audio frame sync in first $scanLen bytes 1162 $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudio($exifTool, \$buff); 1163 } 1596 # scan further into a file that should be an MP3 1597 my $scanLen = ($$et{FILE_EXT} and $$et{FILE_EXT} eq 'MP3') ? 8192 : 256; 1598 if ($raf->Read($buff, $scanLen)) { 1599 require Image::ExifTool::MPEG; 1600 if ($buff =~ /\0\0\x01(\xb3|\xc0)/) { 1601 # look for A/V headers in first 64kB 1602 my $buf2; 1603 $raf->Read($buf2, 0x10000 - $scanLen) and $buff .= $buf2; 1604 $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudioVideo($et, \$buff); 1605 } else { 1606 # look for audio frame sync in first $scanLen bytes 1607 # (set MP3 flag to 1 so this will fail unless layer 3 audio) 1608 my $ext = $$et{FILE_EXT} || ''; 1609 my $mp3 = ($ext eq 'MUS') ? 0 : 1; # MUS files are MP2 1610 $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudio($et, \$buff, $mp3); 1611 } 1612 } 1613 } 1614 1615 # check for an APE trailer if this was a valid A/V file and we haven't already done it 1616 if ($rtnVal and not $$et{DoneAPE}) { 1617 require Image::ExifTool::APE; 1618 Image::ExifTool::APE::ProcessAPE($et, $dirInfo); 1164 1619 } 1165 1620 return $rtnVal; … … 1186 1641 =head1 AUTHOR 1187 1642 1188 Copyright 2003-20 11, Phil Harvey (phil at owl.phy.queensu.ca)1643 Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com) 1189 1644 1190 1645 This library is free software; you can redistribute it and/or modify it … … 1201 1656 =item L<http://www.fortunecity.com/underworld/sonic/3/id3tag.html> 1202 1657 1658 =item L<https://id3.org/Lyrics3> 1659 1203 1660 =back 1204 1661
Note:
See TracChangeset
for help on using the changeset viewer.