File Coverage

blib/lib/Image/ExifTool/ID3.pm
Criterion Covered Total %
statement 199 493 40.3
branch 92 338 27.2
condition 36 124 29.0
subroutine 12 19 63.1
pod 0 14 0.0
total 339 988 34.3


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: ID3.pm
3             #
4             # Description: Read ID3 and Lyrics3 meta information
5             #
6             # Revisions: 09/12/2005 - P. Harvey Created
7             # 09/08/2020 - PH Added Lyrics3 support
8             #
9             # References: 1) http://www.id3.org/ (now https://id3.org)
10             # 2) http://www.mp3-tech.org/
11             # 3) http://www.fortunecity.com/underworld/sonic/3/id3tag.html
12             # 4) https://id3.org/Lyrics3
13             #------------------------------------------------------------------------------
14              
15             package Image::ExifTool::ID3;
16              
17 12     12   7372 use strict;
  12         42  
  12         540  
18 12     12   69 use vars qw($VERSION);
  12         24  
  12         704  
19 12     12   63 use Image::ExifTool qw(:DataAccess :Utils);
  12         23  
  12         120955  
20              
21             $VERSION = '1.64';
22              
23             sub ProcessID3v2($$$);
24             sub ProcessPrivate($$$);
25             sub ProcessSynText($$$);
26             sub ProcessID3Dir($$$);
27             sub ProcessGEOB($$$);
28             sub ConvertID3v1Text($$);
29             sub ConvertTimeStamp($);
30              
31             # audio formats that we process after an ID3v2 header (in order)
32             my @audioFormats = qw(APE MPC FLAC OGG MP3);
33              
34             # audio formats where the processing proc is in a differently-named module
35             my %audioModule = (
36             MP3 => 'ID3',
37             OGG => 'Ogg',
38             );
39              
40             # picture types for 'PIC' and 'APIC' tags
41             # (Note: Duplicated in ID3, ASF and FLAC modules!)
42             my %pictureType = (
43             0 => 'Other',
44             1 => '32x32 PNG Icon',
45             2 => 'Other Icon',
46             3 => 'Front Cover',
47             4 => 'Back Cover',
48             5 => 'Leaflet',
49             6 => 'Media',
50             7 => 'Lead Artist',
51             8 => 'Artist',
52             9 => 'Conductor',
53             10 => 'Band',
54             11 => 'Composer',
55             12 => 'Lyricist',
56             13 => 'Recording Studio or Location',
57             14 => 'Recording Session',
58             15 => 'Performance',
59             16 => 'Capture from Movie or Video',
60             17 => 'Bright(ly) Colored Fish',
61             18 => 'Illustration',
62             19 => 'Band Logo',
63             20 => 'Publisher Logo',
64             );
65              
66             my %dateTimeConv = (
67             ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)',
68             PrintConv => '$self->ConvertDateTime($val)',
69             );
70              
71             # patch for names of user-defined tags which don't automatically generate very well
72             my %userTagName = (
73             ALBUMARTISTSORT => 'AlbumArtistSort',
74             ASIN => 'ASIN',
75             );
76              
77             # This table is just for documentation purposes
78             %Image::ExifTool::ID3::Main = (
79             VARS => { ID_FMT => 'none' },
80             PROCESS_PROC => \&ProcessID3Dir, # (used to process 'id3 ' chunk in WAV files)
81             NOTES => q{
82             ExifTool extracts ID3 and Lyrics3 information from MP3, MPEG, WAV, WV, AIFF,
83             OGG, FLAC, APE, DSF, MPC and RealAudio files. ID3v2 tags which support
84             multiple languages (eg. Comment and Lyrics) are extracted by specifying the
85             tag name, followed by a dash ('-'), then a 3-character ISO 639-2 language
86             code (eg. "Comment-spa"). See L for the official ID3
87             specification and L
88             for a list of ISO 639-2 language codes.
89             },
90             ID3v1 => {
91             Name => 'ID3v1',
92             SubDirectory => { TagTable => 'Image::ExifTool::ID3::v1' },
93             },
94             ID3v1Enh => {
95             Name => 'ID3v1_Enh',
96             SubDirectory => { TagTable => 'Image::ExifTool::ID3::v1_Enh' },
97             },
98             ID3v22 => {
99             Name => 'ID3v2_2',
100             SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_2' },
101             },
102             ID3v23 => {
103             Name => 'ID3v2_3',
104             SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_3' },
105             },
106             ID3v24 => {
107             Name => 'ID3v2_4',
108             SubDirectory => { TagTable => 'Image::ExifTool::ID3::v2_4' },
109             },
110             );
111              
112             # Lyrics3 tags (ref 4)
113             %Image::ExifTool::ID3::Lyrics3 = (
114             GROUPS => { 1 => 'Lyrics3', 2 => 'Audio' },
115             NOTES => q{
116             ExifTool extracts Lyrics3 version 1.00 and 2.00 tags from any file that
117             supports ID3. See L for the specification.
118             },
119             IND => 'Indications',
120             LYR => 'Lyrics',
121             INF => 'AdditionalInfo',
122             AUT => { Name => 'Author', Groups => { 2 => 'Author' } },
123             EAL => 'ExtendedAlbumName',
124             EAR => 'ExtendedArtistName',
125             ETT => 'ExtendedTrackTitle',
126             IMG => 'AssociatedImageFile',
127             CRC => 'CRC', #PH
128             );
129              
130             # Mapping for ID3v1 Genre numbers
131             my %genre = (
132             0 => 'Blues',
133             1 => 'Classic Rock',
134             2 => 'Country',
135             3 => 'Dance',
136             4 => 'Disco',
137             5 => 'Funk',
138             6 => 'Grunge',
139             7 => 'Hip-Hop',
140             8 => 'Jazz',
141             9 => 'Metal',
142             10 => 'New Age',
143             11 => 'Oldies',
144             12 => 'Other',
145             13 => 'Pop',
146             14 => 'R&B',
147             15 => 'Rap',
148             16 => 'Reggae',
149             17 => 'Rock',
150             18 => 'Techno',
151             19 => 'Industrial',
152             20 => 'Alternative',
153             21 => 'Ska',
154             22 => 'Death Metal',
155             23 => 'Pranks',
156             24 => 'Soundtrack',
157             25 => 'Euro-Techno',
158             26 => 'Ambient',
159             27 => 'Trip-Hop',
160             28 => 'Vocal',
161             29 => 'Jazz+Funk',
162             30 => 'Fusion',
163             31 => 'Trance',
164             32 => 'Classical',
165             33 => 'Instrumental',
166             34 => 'Acid',
167             35 => 'House',
168             36 => 'Game',
169             37 => 'Sound Clip',
170             38 => 'Gospel',
171             39 => 'Noise',
172             40 => 'Alt. Rock', # (was AlternRock)
173             41 => 'Bass',
174             42 => 'Soul',
175             43 => 'Punk',
176             44 => 'Space',
177             45 => 'Meditative',
178             46 => 'Instrumental Pop',
179             47 => 'Instrumental Rock',
180             48 => 'Ethnic',
181             49 => 'Gothic',
182             50 => 'Darkwave',
183             51 => 'Techno-Industrial',
184             52 => 'Electronic',
185             53 => 'Pop-Folk',
186             54 => 'Eurodance',
187             55 => 'Dream',
188             56 => 'Southern Rock',
189             57 => 'Comedy',
190             58 => 'Cult',
191             59 => 'Gangsta Rap', # (was Gansta)
192             60 => 'Top 40',
193             61 => 'Christian Rap',
194             62 => 'Pop/Funk',
195             63 => 'Jungle',
196             64 => 'Native American',
197             65 => 'Cabaret',
198             66 => 'New Wave',
199             67 => 'Psychedelic', # (was misspelt)
200             68 => 'Rave',
201             69 => 'Showtunes',
202             70 => 'Trailer',
203             71 => 'Lo-Fi',
204             72 => 'Tribal',
205             73 => 'Acid Punk',
206             74 => 'Acid Jazz',
207             75 => 'Polka',
208             76 => 'Retro',
209             77 => 'Musical',
210             78 => 'Rock & Roll',
211             79 => 'Hard Rock',
212             # The following genres are Winamp extensions
213             80 => 'Folk',
214             81 => 'Folk-Rock',
215             82 => 'National Folk',
216             83 => 'Swing',
217             84 => 'Fast-Fusion', # (was Fast Fusion)
218             85 => 'Bebop', # (was misspelt)
219             86 => 'Latin',
220             87 => 'Revival',
221             88 => 'Celtic',
222             89 => 'Bluegrass',
223             90 => 'Avantgarde',
224             91 => 'Gothic Rock',
225             92 => 'Progressive Rock',
226             93 => 'Psychedelic Rock',
227             94 => 'Symphonic Rock',
228             95 => 'Slow Rock',
229             96 => 'Big Band',
230             97 => 'Chorus',
231             98 => 'Easy Listening',
232             99 => 'Acoustic',
233             100 => 'Humour',
234             101 => 'Speech',
235             102 => 'Chanson',
236             103 => 'Opera',
237             104 => 'Chamber Music',
238             105 => 'Sonata',
239             106 => 'Symphony',
240             107 => 'Booty Bass',
241             108 => 'Primus',
242             109 => 'Porn Groove',
243             110 => 'Satire',
244             111 => 'Slow Jam',
245             112 => 'Club',
246             113 => 'Tango',
247             114 => 'Samba',
248             115 => 'Folklore',
249             116 => 'Ballad',
250             117 => 'Power Ballad',
251             118 => 'Rhythmic Soul',
252             119 => 'Freestyle',
253             120 => 'Duet',
254             121 => 'Punk Rock',
255             122 => 'Drum Solo',
256             123 => 'A Cappella', # (was Acapella)
257             124 => 'Euro-House',
258             125 => 'Dance Hall',
259             # ref http://yar.hole.ru/MP3Tech/lamedoc/id3.html
260             126 => 'Goa',
261             127 => 'Drum & Bass',
262             128 => 'Club-House',
263             129 => 'Hardcore',
264             130 => 'Terror',
265             131 => 'Indie',
266             132 => 'BritPop',
267             133 => 'Afro-Punk', # (was Negerpunk)
268             134 => 'Polsk Punk',
269             135 => 'Beat',
270             136 => 'Christian Gangsta Rap', # (was Christian Gangsta)
271             137 => 'Heavy Metal',
272             138 => 'Black Metal',
273             139 => 'Crossover',
274             140 => 'Contemporary Christian', # (was Contemporary C)
275             141 => 'Christian Rock',
276             142 => 'Merengue',
277             143 => 'Salsa',
278             144 => 'Thrash Metal',
279             145 => 'Anime',
280             146 => 'JPop',
281             147 => 'Synthpop', # (was SynthPop)
282             # ref http://alicja.homelinux.com/~mats/text/Music/MP3/ID3/Genres.txt
283             # (also used to update some Genres above)
284             148 => 'Abstract',
285             149 => 'Art Rock',
286             150 => 'Baroque',
287             151 => 'Bhangra',
288             152 => 'Big Beat',
289             153 => 'Breakbeat',
290             154 => 'Chillout',
291             155 => 'Downtempo',
292             156 => 'Dub',
293             157 => 'EBM',
294             158 => 'Eclectic',
295             159 => 'Electro',
296             160 => 'Electroclash',
297             161 => 'Emo',
298             162 => 'Experimental',
299             163 => 'Garage',
300             164 => 'Global',
301             165 => 'IDM',
302             166 => 'Illbient',
303             167 => 'Industro-Goth',
304             168 => 'Jam Band',
305             169 => 'Krautrock',
306             170 => 'Leftfield',
307             171 => 'Lounge',
308             172 => 'Math Rock',
309             173 => 'New Romantic',
310             174 => 'Nu-Breakz',
311             175 => 'Post-Punk',
312             176 => 'Post-Rock',
313             177 => 'Psytrance',
314             178 => 'Shoegaze',
315             179 => 'Space Rock',
316             180 => 'Trop Rock',
317             181 => 'World Music',
318             182 => 'Neoclassical',
319             183 => 'Audiobook',
320             184 => 'Audio Theatre',
321             185 => 'Neue Deutsche Welle',
322             186 => 'Podcast',
323             187 => 'Indie Rock',
324             188 => 'G-Funk',
325             189 => 'Dubstep',
326             190 => 'Garage Rock',
327             191 => 'Psybient',
328             255 => 'None',
329             # ID3v2 adds some text short forms...
330             CR => 'Cover',
331             RX => 'Remix',
332             );
333              
334             # Tags for ID3v1
335             %Image::ExifTool::ID3::v1 = (
336             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
337             GROUPS => { 1 => 'ID3v1', 2 => 'Audio' },
338             PRIORITY => 0, # let ID3v2 tags replace these if they come later
339             3 => {
340             Name => 'Title',
341             Format => 'string[30]',
342             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
343             },
344             33 => {
345             Name => 'Artist',
346             Groups => { 2 => 'Author' },
347             Format => 'string[30]',
348             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
349             },
350             63 => {
351             Name => 'Album',
352             Format => 'string[30]',
353             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
354             },
355             93 => {
356             Name => 'Year',
357             Groups => { 2 => 'Time' },
358             Format => 'string[4]',
359             },
360             97 => {
361             Name => 'Comment',
362             Format => 'string[30]',
363             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
364             },
365             125 => { # ID3v1.1 (ref http://en.wikipedia.org/wiki/ID3#Layout)
366             Name => 'Track',
367             Format => 'int8u[2]',
368             Notes => 'v1.1 addition -- last 2 bytes of v1.0 Comment field',
369             RawConv => '($val =~ s/^0 // and $val) ? $val : undef',
370             },
371             127 => {
372             Name => 'Genre',
373             Notes => 'CR and RX are ID3v2 only',
374             Format => 'int8u',
375             PrintConv => \%genre,
376             PrintConvColumns => 3,
377             },
378             );
379              
380             # ID3v1 "Enhanced TAG" information (ref 3)
381             %Image::ExifTool::ID3::v1_Enh = (
382             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
383             GROUPS => { 1 => 'ID3v1_Enh', 2 => 'Audio' },
384             NOTES => 'ID3 version 1 "Enhanced TAG" information (not part of the official spec).',
385             PRIORITY => 0, # let ID3v2 tags replace these if they come later
386             4 => {
387             Name => 'Title2',
388             Format => 'string[60]',
389             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
390             },
391             64 => {
392             Name => 'Artist2',
393             Groups => { 2 => 'Author' },
394             Format => 'string[60]',
395             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
396             },
397             124 => {
398             Name => 'Album2',
399             Format => 'string[60]',
400             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
401             },
402             184 => {
403             Name => 'Speed',
404             Format => 'int8u',
405             PrintConv => {
406             1 => 'Slow',
407             2 => 'Medium',
408             3 => 'Fast',
409             4 => 'Hardcore',
410             },
411             },
412             185 => {
413             Name => 'Genre',
414             Format => 'string[30]',
415             ValueConv => 'Image::ExifTool::ID3::ConvertID3v1Text($self,$val)',
416             },
417             215 => {
418             Name => 'StartTime',
419             Format => 'string[6]',
420             },
421             221 => {
422             Name => 'EndTime',
423             Format => 'string[6]',
424             },
425             );
426              
427             # Tags for ID2v2.2
428             %Image::ExifTool::ID3::v2_2 = (
429             PROCESS_PROC => \&ProcessID3v2,
430             GROUPS => { 1 => 'ID3v2_2', 2 => 'Audio' },
431             NOTES => q{
432             ExifTool extracts mainly text-based tags from ID3v2 information. The tags
433             in the tables below are those extracted by ExifTool, and don't represent a
434             complete list of available ID3v2 tags.
435              
436             ID3 version 2.2 tags. (These are the tags written by iTunes 5.0.)
437             },
438             CNT => 'PlayCounter',
439             COM => 'Comment',
440             IPL => 'InvolvedPeople',
441             PIC => {
442             Name => 'Picture',
443             Groups => { 2 => 'Preview' },
444             Binary => 1,
445             Notes => 'the 3 tags below are also extracted from this PIC frame',
446             },
447             'PIC-1' => { Name => 'PictureFormat', Groups => { 2 => 'Image' } },
448             'PIC-2' => {
449             Name => 'PictureType',
450             Groups => { 2 => 'Image' },
451             PrintConv => \%pictureType,
452             SeparateTable => 1,
453             },
454             'PIC-3' => { Name => 'PictureDescription', Groups => { 2 => 'Image' } },
455             POP => {
456             Name => 'Popularimeter',
457             PrintConv => '$val=~s/^(.*?) (\d+) (\d+)$/$1 Rating=$2 Count=$3/s; $val',
458             },
459             SLT => {
460             Name => 'SynLyrics',
461             SubDirectory => { TagTable => 'Image::ExifTool::ID3::SynLyrics' },
462             },
463             TAL => 'Album',
464             TBP => 'BeatsPerMinute',
465             TCM => 'Composer',
466             TCO =>{
467             Name => 'Genre',
468             Notes => 'uses same lookup table as ID3v1 Genre',
469             PrintConv => 'Image::ExifTool::ID3::PrintGenre($val)',
470             },
471             TCP => { Name => 'Compilation', PrintConv => { 0 => 'No', 1 => 'Yes' } }, # iTunes
472             TCR => { Name => 'Copyright', Groups => { 2 => 'Author' } },
473             TDA => { Name => 'Date', Groups => { 2 => 'Time' } },
474             TDY => 'PlaylistDelay',
475             TEN => 'EncodedBy',
476             TFT => 'FileType',
477             TIM => { Name => 'Time', Groups => { 2 => 'Time' } },
478             TKE => 'InitialKey',
479             TLA => 'Language',
480             TLE => 'Length',
481             TMT => 'Media',
482             TOA => { Name => 'OriginalArtist', Groups => { 2 => 'Author' } },
483             TOF => 'OriginalFileName',
484             TOL => 'OriginalLyricist',
485             TOR => 'OriginalReleaseYear',
486             TOT => 'OriginalAlbum',
487             TP1 => { Name => 'Artist', Groups => { 2 => 'Author' } },
488             TP2 => 'Band',
489             TP3 => 'Conductor',
490             TP4 => 'InterpretedBy',
491             TPA => 'PartOfSet',
492             TPB => 'Publisher',
493             TRC => 'ISRC', # (international standard recording code)
494             TRD => 'RecordingDates',
495             TRK => 'Track',
496             TSI => 'Size',
497             TSS => 'EncoderSettings',
498             TT1 => 'Grouping',
499             TT2 => 'Title',
500             TT3 => 'Subtitle',
501             TXT => 'Lyricist',
502             TXX => 'UserDefinedText',
503             TYE => { Name => 'Year', Groups => { 2 => 'Time' } },
504             ULT => 'Lyrics',
505             WAF => 'FileURL',
506             WAR => { Name => 'ArtistURL', Groups => { 2 => 'Author' } },
507             WAS => 'SourceURL',
508             WCM => 'CommercialURL',
509             WCP => { Name => 'CopyrightURL', Groups => { 2 => 'Author' } },
510             WPB => 'PublisherURL',
511             WXX => 'UserDefinedURL',
512             # the following written by iTunes 10.5 (ref PH)
513             RVA => 'RelativeVolumeAdjustment',
514             TST => 'TitleSortOrder',
515             TSA => 'AlbumSortOrder',
516             TSP => 'PerformerSortOrder',
517             TS2 => 'AlbumArtistSortOrder',
518             TSC => 'ComposerSortOrder',
519             ITU => { Name => 'iTunesU', Description => 'iTunes U', Binary => 1, Unknown => 1 },
520             PCS => { Name => 'Podcast', Binary => 1, Unknown => 1 },
521             GP1 => 'Grouping', #github142 (NC)
522             MVN => 'MovementName', #github142 (NC)
523             MVI => 'MovementNumber', #github142 (NC)
524             );
525              
526             # tags common to ID3v2.3 and ID3v2.4
527             my %id3v2_common = (
528             # AENC => 'AudioEncryption', # Owner, preview start, preview length, encr data
529             APIC => {
530             Name => 'Picture',
531             Groups => { 2 => 'Preview' },
532             Binary => 1,
533             Notes => 'the 3 tags below are also extracted from this APIC frame',
534             },
535             'APIC-1' => { Name => 'PictureMIMEType', Groups => { 2 => 'Image' } },
536             'APIC-2' => {
537             Name => 'PictureType',
538             Groups => { 2 => 'Image' },
539             PrintConv => \%pictureType,
540             SeparateTable => 1,
541             },
542             'APIC-3' => { Name => 'PictureDescription', Groups => { 2 => 'Image' } },
543             COMM => 'Comment',
544             # COMR => 'Commercial',
545             # ENCR => 'EncryptionMethod',
546             # ETCO => 'EventTimingCodes',
547             GEOB => {
548             Name => 'GeneralEncapsulatedObject',
549             SubDirectory => { TagTable => 'Image::ExifTool::ID3::GEOB' },
550             },
551             # GRID => 'GroupIdentification',
552             # LINK => 'LinkedInformation',
553             MCDI => { Name => 'MusicCDIdentifier', Binary => 1 },
554             # MLLT => 'MPEGLocationLookupTable',
555             OWNE => 'Ownership',
556             PCNT => 'PlayCounter',
557             POPM => {
558             Name => 'Popularimeter',
559             PrintConv => '$val=~s/^(.*?) (\d+) (\d+)$/$1 Rating=$2 Count=$3/s; $val',
560             },
561             # POSS => 'PostSynchronization',
562             PRIV => {
563             Name => 'Private',
564             SubDirectory => { TagTable => 'Image::ExifTool::ID3::Private' },
565             },
566             # RBUF => 'RecommendedBufferSize',
567             # RVRB => 'Reverb',
568             SYLT => {
569             Name => 'SynLyrics',
570             SubDirectory => { TagTable => 'Image::ExifTool::ID3::SynLyrics' },
571             },
572             # SYTC => 'SynchronizedTempoCodes',
573             TALB => 'Album',
574             TBPM => 'BeatsPerMinute',
575             TCMP => { Name => 'Compilation', PrintConv => { 0 => 'No', 1 => 'Yes' } }, #PH (iTunes)
576             TCOM => 'Composer',
577             TCON =>{
578             Name => 'Genre',
579             Notes => 'uses same lookup table as ID3v1 Genre',
580             PrintConv => 'Image::ExifTool::ID3::PrintGenre($val)',
581             },
582             TCOP => { Name => 'Copyright', Groups => { 2 => 'Author' } },
583             TDLY => 'PlaylistDelay',
584             TENC => 'EncodedBy',
585             TEXT => 'Lyricist',
586             TFLT => 'FileType',
587             TIT1 => 'Grouping',
588             TIT2 => 'Title',
589             TIT3 => 'Subtitle',
590             TKEY => 'InitialKey',
591             TLAN => 'Language',
592             TLEN => {
593             Name => 'Length',
594             ValueConv => '$val / 1000',
595             PrintConv => '"$val s"',
596             },
597             TMED => 'Media',
598             TOAL => 'OriginalAlbum',
599             TOFN => 'OriginalFileName',
600             TOLY => 'OriginalLyricist',
601             TOPE => { Name => 'OriginalArtist', Groups => { 2 => 'Author' } },
602             TOWN => 'FileOwner',
603             TPE1 => { Name => 'Artist', Groups => { 2 => 'Author' } },
604             TPE2 => 'Band',
605             TPE3 => 'Conductor',
606             TPE4 => 'InterpretedBy',
607             TPOS => 'PartOfSet',
608             TPUB => 'Publisher',
609             TRCK => 'Track',
610             TRSN => 'InternetRadioStationName',
611             TRSO => 'InternetRadioStationOwner',
612             TSRC => 'ISRC', # (international standard recording code)
613             TSSE => 'EncoderSettings',
614             TXXX => 'UserDefinedText',
615             # UFID => 'UniqueFileID', (not extracted because it is long and nasty and not very useful)
616             USER => 'TermsOfUse',
617             USLT => 'Lyrics',
618             WCOM => 'CommercialURL',
619             WCOP => 'CopyrightURL',
620             WOAF => 'FileURL',
621             WOAR => { Name => 'ArtistURL', Groups => { 2 => 'Author' } },
622             WOAS => 'SourceURL',
623             WORS => 'InternetRadioStationURL',
624             WPAY => 'PaymentURL',
625             WPUB => 'PublisherURL',
626             WXXX => 'UserDefinedURL',
627             #
628             # non-standard frames
629             #
630             # the following are written by iTunes 10.5 (ref PH)
631             TSO2 => 'AlbumArtistSortOrder',
632             TSOC => 'ComposerSortOrder',
633             ITNU => { Name => 'iTunesU', Description => 'iTunes U', Binary => 1, Unknown => 1 },
634             PCST => { Name => 'Podcast', Binary => 1, Unknown => 1 },
635             # other proprietary Apple tags (ref http://help.mp3tag.de/main_tags.html)
636             TDES => 'PodcastDescription',
637             TGID => 'PodcastID',
638             WFED => 'PodcastURL',
639             TKWD => 'PodcastKeywords',
640             TCAT => 'PodcastCategory',
641             # more non-standard tags (ref http://eyed3.nicfit.net/compliance.html)
642             # NCON - unknown MusicMatch binary data
643             XDOR => { Name => 'OriginalReleaseTime',Groups => { 2 => 'Time' }, %dateTimeConv },
644             XSOA => 'AlbumSortOrder',
645             XSOP => 'PerformerSortOrder',
646             XSOT => 'TitleSortOrder',
647             XOLY => {
648             Name => 'OlympusDSS',
649             SubDirectory => { TagTable => 'Image::ExifTool::Olympus::DSS' },
650             },
651             GRP1 => 'Grouping',
652             MVNM => 'MovementName', # (NC)
653             MVIN => 'MovementNumber', # (NC)
654             );
655              
656             %Image::ExifTool::ID3::GEOB = (
657             GROUPS => { 1 => 'ID3v2_3', 2 => 'Other' },
658             PROCESS_PROC => \&ProcessGEOB,
659             'application/x-c2pa-manifest-store' => {
660             Name => 'JUMBF',
661             SubDirectory => {
662             TagTable => 'Image::ExifTool::Jpeg2000::Main',
663             ByteOrder => 'BigEndian',
664             },
665             },
666             'GEOB-Mime' => { },
667             'GEOB-File' => { },
668             'GEOB-Desc' => { },
669             'GEOB-Data' => { },
670             );
671              
672             # Tags for ID3v2.3 (http://www.id3.org/id3v2.3.0)
673             %Image::ExifTool::ID3::v2_3 = (
674             PROCESS_PROC => \&ProcessID3v2,
675             GROUPS => { 1 => 'ID3v2_3', 2 => 'Audio' },
676             NOTES => q{
677             ID3 version 2.3 tags. Includes some non-standard tags written by other
678             software.
679             },
680             %id3v2_common, # include common tags
681             # EQUA => 'Equalization',
682             IPLS => 'InvolvedPeople',
683             # RVAD => 'RelativeVolumeAdjustment',
684             TDAT => { Name => 'Date', Groups => { 2 => 'Time' } },
685             TIME => { Name => 'Time', Groups => { 2 => 'Time' } },
686             TORY => 'OriginalReleaseYear',
687             TRDA => 'RecordingDates',
688             TSIZ => 'Size',
689             TYER => { Name => 'Year', Groups => { 2 => 'Time' } },
690             );
691              
692             # Tags for ID3v2.4 (http://www.id3.org/id3v2.4.0-frames)
693             %Image::ExifTool::ID3::v2_4 = (
694             PROCESS_PROC => \&ProcessID3v2,
695             GROUPS => { 1 => 'ID3v2_4', 2 => 'Audio' },
696             NOTES => q{
697             ID3 version 2.4 tags. Includes some non-standard tags written by other
698             software.
699             },
700             %id3v2_common, # include common tags
701             # EQU2 => 'Equalization',
702             RVA2 => 'RelativeVolumeAdjustment',
703             # SEEK => 'Seek',
704             # SIGN => 'Signature',
705             TDEN => { Name => 'EncodingTime', Groups => { 2 => 'Time' }, %dateTimeConv },
706             TDOR => { Name => 'OriginalReleaseTime',Groups => { 2 => 'Time' }, %dateTimeConv },
707             TDRC => { Name => 'RecordingTime', Groups => { 2 => 'Time' }, %dateTimeConv },
708             TDRL => { Name => 'ReleaseTime', Groups => { 2 => 'Time' }, %dateTimeConv },
709             TDTG => { Name => 'TaggingTime', Groups => { 2 => 'Time' }, %dateTimeConv },
710             TIPL => 'InvolvedPeople',
711             TMCL => 'MusicianCredits',
712             TMOO => 'Mood',
713             TPRO => 'ProducedNotice',
714             TSOA => 'AlbumSortOrder',
715             TSOP => 'PerformerSortOrder',
716             TSOT => 'TitleSortOrder',
717             TSST => 'SetSubtitle',
718             );
719              
720             # Synchronized lyrics/text
721             %Image::ExifTool::ID3::SynLyrics = (
722             GROUPS => { 1 => 'ID3', 2 => 'Audio' },
723             VARS => { ID_FMT => 'none' },
724             PROCESS_PROC => \&ProcessSynText,
725             NOTES => 'The following tags are extracted from synchronized lyrics/text frames.',
726             desc => { Name => 'SynchronizedLyricsDescription' },
727             type => {
728             Name => 'SynchronizedLyricsType',
729             PrintConv => {
730             0 => 'Other',
731             1 => 'Lyrics',
732             2 => 'Text Transcription',
733             3 => 'Movement/part Name',
734             4 => 'Events',
735             5 => 'Chord',
736             6 => 'Trivia/"pop-up" Information',
737             7 => 'Web Page URL',
738             8 => 'Image URL',
739             },
740             },
741             text => {
742             Name => 'SynchronizedLyricsText',
743             List => 1,
744             Notes => q{
745             each list item has a leading time stamp in square brackets. Time stamps may
746             be in seconds with format [MM:SS.ss], or MPEG frames with format [FFFF],
747             depending on how this information was stored
748             },
749             PrintConv => \&ConvertTimeStamp,
750             },
751             );
752              
753             # ID3 PRIV tags (ref PH)
754             %Image::ExifTool::ID3::Private = (
755             PROCESS_PROC => \&Image::ExifTool::ID3::ProcessPrivate,
756             GROUPS => { 1 => 'ID3', 2 => 'Audio' },
757             VARS => { ID_FMT => 'none' },
758             NOTES => q{
759             ID3 private (PRIV) tags. ExifTool will decode any private tags found, even
760             if they do not appear in this table.
761             },
762             XMP => {
763             SubDirectory => {
764             DirName => 'XMP',
765             TagTable => 'Image::ExifTool::XMP::Main',
766             },
767             },
768             PeakValue => {
769             ValueConv => 'length($val)==4 ? unpack("V",$val) : \$val',
770             },
771             AverageLevel => {
772             ValueConv => 'length($val)==4 ? unpack("V",$val) : \$val',
773             },
774             # Windows Media attributes ("/" in tag ID is converted to "_" by ProcessPrivate)
775             WM_WMContentID => {
776             Name => 'WM_ContentID',
777             ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)',
778             },
779             WM_WMCollectionID => {
780             Name => 'WM_CollectionID',
781             ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)',
782             },
783             WM_WMCollectionGroupID => {
784             Name => 'WM_CollectionGroupID',
785             ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)',
786             },
787             WM_MediaClassPrimaryID => {
788             ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)',
789             },
790             WM_MediaClassSecondaryID => {
791             ValueConv => 'require Image::ExifTool::ASF; Image::ExifTool::ASF::GetGUID($val)',
792             },
793             WM_Provider => {
794             ValueConv => '$self->Decode($val,"UCS2","II")', #PH (NC)
795             },
796             # there are lots more WM tags that could be decoded if I had samples or documentation - PH
797             # WM/AlbumArtist
798             # WM/AlbumTitle
799             # WM/Category
800             # WM/Composer
801             # WM/Conductor
802             # WM/ContentDistributor
803             # WM/ContentGroupDescription
804             # WM/EncodingTime
805             # WM/Genre
806             # WM/GenreID
807             # WM/InitialKey
808             # WM/Language
809             # WM/Lyrics
810             # WM/MCDI
811             # WM/MediaClassPrimaryID
812             # WM/MediaClassSecondaryID
813             # WM/Mood
814             # WM/ParentalRating
815             # WM/Period
816             # WM/ProtectionType
817             # WM/Provider
818             # WM/ProviderRating
819             # WM/ProviderStyle
820             # WM/Publisher
821             # WM/SubscriptionContentID
822             # WM/SubTitle
823             # WM/TrackNumber
824             # WM/UniqueFileIdentifier
825             # WM/WMCollectionGroupID
826             # WM/WMCollectionID
827             # WM/WMContentID
828             # WM/Writer
829             # WM/Year
830             );
831              
832             # lookup to check for existence of tags in other ID3 versions
833             my %otherTable = (
834             \%Image::ExifTool::ID3::v2_4 => 'Image::ExifTool::ID3::v2_3',
835             \%Image::ExifTool::ID3::v2_3 => 'Image::ExifTool::ID3::v2_4',
836             );
837              
838             # ID3 Composite tags
839             %Image::ExifTool::ID3::Composite = (
840             GROUPS => { 2 => 'Image' },
841             DateTimeOriginal => {
842             Description => 'Date/Time Original',
843             Groups => { 2 => 'Time' },
844             Priority => 0,
845             Desire => {
846             0 => 'ID3:RecordingTime',
847             1 => 'ID3:Year',
848             2 => 'ID3:Date',
849             3 => 'ID3:Time',
850             },
851             ValueConv => q{
852             return $val[0] if $val[0];
853             return undef unless $val[1];
854             return $val[1] unless $val[2] and $val[2] =~ /^(\d{2})(\d{2})$/;
855             $val[1] .= ":$1:$2";
856             return $val[1] unless $val[3] and $val[3] =~ /^(\d{2})(\d{2})$/;
857             return "$val[1] $1:$2";
858             },
859             PrintConv => '$self->ConvertDateTime($val)',
860             },
861             );
862              
863             # add our composite tags
864             Image::ExifTool::AddCompositeTags('Image::ExifTool::ID3');
865              
866             # can't share tagInfo hashes between two tables, so we must make
867             # copies of the necessary hashes
868             {
869             my $tag;
870             foreach $tag (keys %id3v2_common) {
871             next unless ref $id3v2_common{$tag} eq 'HASH';
872             my %tagInfo = %{$id3v2_common{$tag}};
873             # must also copy Groups hash if it exists
874             my $groups = $tagInfo{Groups};
875             $tagInfo{Groups} = { %$groups } if $groups;
876             $Image::ExifTool::ID3::v2_4{$tag} = \%tagInfo;
877             }
878             }
879              
880             #------------------------------------------------------------------------------
881             # Make tag name for user-defined tag
882             # Inputs: 0) User defined tag description
883             # Returns: Tag name
884             sub MakeTagName($)
885             {
886 0     0 0 0 my $name = shift;
887 0 0       0 return $userTagName{$name} if $userTagName{$name};
888 0 0       0 $name = ucfirst(lc $name) unless $name =~ /[a-z]/; # convert all uppercase to mixed case
889 0         0 $name =~ s/([a-z])[_ ]([a-z])/$1\U$2/g;
890 0         0 return Image::ExifTool::MakeTagName($name);
891             }
892              
893             #------------------------------------------------------------------------------
894             # Convert ID3v1 text to exiftool character set
895             # Inputs: 0) ExifTool object ref, 1) text string
896             # Returns: converted text
897             sub ConvertID3v1Text($$)
898             {
899 12     12 0 65 my ($et, $val) = @_;
900 12         52 return $et->Decode($val, $et->Options('CharsetID3'));
901             }
902              
903             #------------------------------------------------------------------------------
904             # Re-format time stamp in synchronized lyrics
905             # Inputs: 0) synchronized lyrics entry (eg. "[84.030]Da do do do")
906             # Returns: entry with formatted timestamp (eg. "[01:24.03]Da do do do")
907             sub ConvertTimeStamp($)
908             {
909 0     0 0 0 my $val = shift;
910             # do nothing if this isn't a time stamp (frame count doesn't contain a decimal)
911 0 0       0 return $val unless $val =~ /^\[(\d+\.\d+)\]/g;
912 0         0 my $time = $1;
913             # print hours only if more than 60 minutes
914 0         0 my $h = int($time / 3600);
915 0 0       0 if ($h) {
916 0         0 $time -= $h * 3600;
917 0         0 $h = "$h:";
918             } else {
919 0         0 $h = '';
920             }
921 0         0 my $m = int($time / 60);
922 0         0 my $s = $time - $m * 60;
923 0         0 my $ss = sprintf('%05.2f', $s);
924 0 0       0 if ($ss >= 60) {
925 0         0 $ss = '00.00';
926 0 0       0 ++$m >= 60 and $m -= 60, ++$h;
927             }
928 0         0 return sprintf('[%s%.2d:%s]', $h, $m, $ss) . substr($val, pos($val));
929             }
930              
931             #------------------------------------------------------------------------------
932             # Process ID3 synchronized lyrics/text
933             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
934             sub ProcessSynText($$$)
935             {
936 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
937 0         0 my $dataPt = $$dirInfo{DataPt};
938              
939 0         0 $et->VerboseDir('SynLyrics', 0, length $$dataPt);
940 0 0       0 return unless length $$dataPt > 6;
941              
942 0         0 my ($enc,$lang,$timeCode,$type) = unpack('Ca3CC', $$dataPt);
943 0         0 $lang = lc $lang;
944 0 0 0     0 undef $lang if $lang !~ /^[a-z]{3}$/ or $lang eq 'eng';
945 0         0 pos($$dataPt) = 6;
946 0         0 my ($termLen, $pat);
947 0 0 0     0 if ($enc == 1 or $enc == 2) {
948 0 0       0 $$dataPt =~ /\G(..)*?\0\0/sg or return;
949 0         0 $termLen = 2;
950 0         0 $pat = '\G(?:..)*?\0\0(....)';
951             } else {
952 0 0       0 $$dataPt =~ /\0/g or return;
953 0         0 $termLen = 1;
954 0         0 $pat = '\0(....)';
955             }
956 0         0 my $desc = substr($$dataPt, 6, pos($$dataPt) - 6 - $termLen);
957 0         0 $desc = DecodeString($et, $desc, $enc);
958              
959 0         0 my $tagInfo = $et->GetTagInfo($tagTablePtr, 'desc');
960 0 0       0 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang) if $lang;
961 0         0 $et->HandleTag($tagTablePtr, 'type', $type);
962 0         0 $et->HandleTag($tagTablePtr, 'desc', $desc, TagInfo => $tagInfo);
963 0         0 $tagInfo = $et->GetTagInfo($tagTablePtr, 'text');
964 0 0       0 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang) if $lang;
965              
966 0         0 for (;;) {
967 0         0 my $pos = pos $$dataPt;
968 0 0       0 last unless $$dataPt =~ /$pat/sg;
969 0         0 my $time = unpack('N', $1);
970 0         0 my $text = substr($$dataPt, $pos, pos($$dataPt) - $pos - 4 - $termLen);
971 0         0 $text = DecodeString($et, $text, $enc);
972 0         0 my $timeStr;
973 0 0       0 if ($timeCode == 2) { # time in ms
974 0         0 $timeStr = sprintf('%.3f', $time / 1000);
975             } else { # time in MPEG frames
976 0         0 $timeStr = sprintf('%.4d', $time);
977 0 0       0 $timeStr .= '?' if $timeCode != 1;
978             }
979 0         0 $et->HandleTag($tagTablePtr, 'text', "[$timeStr]$text", TagInfo => $tagInfo);
980             }
981             }
982              
983             #------------------------------------------------------------------------------
984             # Process ID3 PRIV data
985             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
986             sub ProcessPrivate($$$)
987             {
988 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
989 0         0 my $dataPt = $$dirInfo{DataPt};
990 0         0 my ($tag, $start);
991 0         0 $et->VerboseDir('PRIV', 0, length $$dataPt);
992 0 0       0 if ($$dataPt =~ /^(.*?)\0/s) {
993 0         0 $tag = $1;
994 0         0 $start = length($tag) + 1;
995             } else {
996 0         0 $tag = '';
997 0         0 $start = 0;
998             }
999 0 0       0 unless ($$tagTablePtr{$tag}) {
1000 0         0 $tag =~ tr{/ }{_}d; # translate '/' to '_' and remove spaces
1001 0 0       0 $tag = 'private' unless $tag =~ /^[-\w]{1,24}$/;
1002 0 0       0 unless ($$tagTablePtr{$tag}) {
1003 0         0 AddTagToTable($tagTablePtr, $tag,
1004             { Name => ucfirst($tag), Binary => 1 });
1005             }
1006             }
1007 0         0 my $key = $et->HandleTag($tagTablePtr, $tag, undef,
1008             Size => length($$dataPt) - $start,
1009             Start => $start,
1010             DataPt => $dataPt,
1011             );
1012             # set group1 name
1013 0 0       0 $et->SetGroup($key, $$et{ID3_Ver}) if $key;
1014             }
1015              
1016             #------------------------------------------------------------------------------
1017             # Print ID3v2 Genre
1018             # Inputs: TCON or TCO frame data
1019             # Returns: Content type with decoded genre numbers
1020             sub PrintGenre($)
1021             {
1022 3     3 0 10 my $val = shift;
1023             # make sure that %genre has an entry for all numbers we are interested in
1024             # (genre numbers are in brackets for ID3v2.2 and v2.3)
1025 3         22 while ($val =~ /\((\d+)\)/g) {
1026 1 50       8 $genre{$1} or $genre{$1} = "Unknown ($1)";
1027             }
1028             # (genre numbers are separated by nulls in ID3v2.4,
1029             # but nulls are converted to '/' by DecodeString())
1030 3         24 while ($val =~ /(?:^|\/)(\d+)(\/|$)/g) {
1031 0 0       0 $genre{$1} or $genre{$1} = "Unknown ($1)";
1032             }
1033 3         18 $val =~ s/\((\d+)\)/\($genre{$1}\)/g;
1034 3         16 $val =~ s/(^|\/)(\d+)(?=\/|$)/$1$genre{$2}/g;
1035 3         13 $val =~ s/^\(([^)]+)\)\1?$/$1/; # clean up by removing brackets and duplicates
1036 3         34 return $val;
1037             }
1038              
1039             #------------------------------------------------------------------------------
1040             # Get Genre ID
1041             # Inputs: 0) Genre name
1042             # Returns: genre ID number, or undef
1043             sub GetGenreID($)
1044             {
1045 0     0 0 0 return Image::ExifTool::ReverseLookup(shift, \%genre);
1046             }
1047              
1048             #------------------------------------------------------------------------------
1049             # Decode ID3 string
1050             # Inputs: 0) ExifTool object reference
1051             # 1) string beginning with encoding byte unless specified as argument
1052             # 2) optional encoding (0=ISO-8859-1, 1=UTF-16 BOM, 2=UTF-16BE, 3=UTF-8)
1053             # Returns: Decoded string in scalar context, or list of strings in list context
1054             sub DecodeString($$;$)
1055             {
1056 35     35 0 114 my ($et, $val, $enc) = @_;
1057 35 50       127 return '' unless length $val;
1058 35 100       90 unless (defined $enc) {
1059 27         97 $enc = unpack('C', $val);
1060 27         66 $val = substr($val, 1); # remove encoding byte
1061             }
1062 35         61 my @vals;
1063 35 50 33     106 if ($enc == 0 or $enc == 3) { # ISO 8859-1 or UTF-8
    0 0        
1064 35         226 $val =~ s/\0+$//; # remove any null padding
1065             # (must split before converting because conversion routines truncate at null)
1066 35         142 @vals = split "\0", $val;
1067 35         81 foreach $val (@vals) {
1068 41 50       213 $val = $et->Decode($val, $enc ? 'UTF8' : 'Latin');
1069             }
1070             } elsif ($enc == 1 or $enc == 2) { # UTF-16 with BOM, or UTF-16BE
1071 0         0 my $bom = "\xfe\xff";
1072 0         0 my %order = ( "\xfe\xff" => 'MM', "\xff\xfe", => 'II' );
1073 0         0 for (;;) {
1074 0         0 my $v;
1075             # split string at null terminators on word boundaries
1076 0 0       0 if ($val =~ s/((..)*?)\0\0//s) {
1077 0         0 $v = $1;
1078             } else {
1079 0 0       0 last unless length $val > 1;
1080 0         0 $v = $val;
1081 0         0 $val = '';
1082             }
1083 0 0       0 $bom = $1 if $v =~ s/^(\xfe\xff|\xff\xfe)//;
1084 0         0 push @vals, $et->Decode($v, 'UCS2', $order{$bom});
1085             }
1086             } else {
1087 0         0 $val =~ s/\0+$//;
1088 0         0 return " $val";
1089             }
1090 35 100       131 return @vals if wantarray;
1091 27         130 return join('/',@vals);
1092             }
1093              
1094             #------------------------------------------------------------------------------
1095             # Convert sync-safe integer to a number we can use
1096             # Inputs: 0) int32u sync-safe value
1097             # Returns: actual number or undef on invalid value
1098             sub UnSyncSafe($)
1099             {
1100 3     3 0 8 my $val = shift;
1101 3 50       20 return undef if $val & 0x80808080;
1102 3         16 return ($val & 0x0000007f) |
1103             (($val & 0x00007f00) >> 1) |
1104             (($val & 0x007f0000) >> 2) |
1105             (($val & 0x7f000000) >> 3);
1106             }
1107              
1108             #------------------------------------------------------------------------------
1109             # Process ID3v2 information
1110             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
1111             sub ProcessID3v2($$$)
1112             {
1113 3     3 0 9 my ($et, $dirInfo, $tagTablePtr) = @_;
1114 3         10 my $dataPt = $$dirInfo{DataPt};
1115 3         9 my $offset = $$dirInfo{DirStart};
1116 3         8 my $size = $$dirInfo{DirLen};
1117 3         10 my $vers = $$dirInfo{Version};
1118 3         31 my $verbose = $et->Options('Verbose');
1119 3         11 my $len; # frame data length
1120             my $otherTable;
1121              
1122 3         81 $et->VerboseDir($tagTablePtr->{GROUPS}->{1}, 0, $size);
1123 3         19 $et->VerboseDump($dataPt, Len => $size, Start => $offset);
1124              
1125 3         6 for (;;$offset+=$len) {
1126 40         85 my ($id, $flags, $hi);
1127 40 50       103 if ($vers < 0x0300) {
1128             # version 2.2 frame header is 6 bytes
1129 40 100       105 last if $offset + 6 > $size;
1130 37         224 ($id, $hi, $len) = unpack("x${offset}a3Cn",$$dataPt);
1131 37 50       108 last if $id eq "\0\0\0";
1132 37         89 $len += $hi << 16;
1133 37         61 $offset += 6;
1134             } else {
1135             # version 2.3/2.4 frame header is 10 bytes
1136 0 0       0 last if $offset + 10 > $size;
1137 0         0 ($id, $len, $flags) = unpack("x${offset}a4Nn",$$dataPt);
1138 0 0       0 last if $id eq "\0\0\0\0";
1139 0         0 $offset += 10;
1140             # length is a "sync-safe" integer by the ID3v2.4 specification, but
1141             # reportedly some versions of iTunes write this as a normal integer
1142             # (ref http://www.id3.org/iTunes)
1143 0   0     0 while ($vers >= 0x0400 and $len > 0x7f and not $len & 0x80808080) {
      0        
1144 0         0 my $oldLen = $len;
1145 0         0 $len = UnSyncSafe($len);
1146 0 0 0     0 if (not defined $len or $offset + $len + 10 > $size) {
1147 0 0       0 if ($offset + $len == $size) {
1148 0         0 $et->Warn('Missing ID3 terminating frame', 1);
1149             } else {
1150 0         0 $et->Warn('Invalid ID3 frame size');
1151             }
1152 0         0 last;
1153             }
1154             # check next ID to see if it makes sense
1155 0         0 my $nextID = substr($$dataPt, $offset + $len, 4);
1156 0 0       0 last if $$tagTablePtr{$nextID};
1157             # try again with the incorrect length word (patch for iTunes bug)
1158 0 0       0 last if $offset + $oldLen + 10 > $size;
1159 0         0 $nextID = substr($$dataPt, $offset + $len, 4);
1160 0 0       0 $len = $oldLen if $$tagTablePtr{$nextID};
1161 0         0 last; # yes, "while" was really a "goto" in disguise
1162             }
1163             }
1164 37 50       89 last if $offset + $len > $size;
1165 37         163 my $tagInfo = $et->GetTagInfo($tagTablePtr, $id);
1166 37 50       99 unless ($tagInfo) {
1167 0 0 0     0 if (not $otherTable and $otherTable{$tagTablePtr}) {
1168 0         0 $otherTable = GetTagTable($otherTable{$tagTablePtr});
1169             }
1170 0 0       0 $tagInfo = $et->GetTagInfo($otherTable, $id) if $otherTable;
1171 0 0       0 if ($tagInfo) {
1172 0         0 $et->Warn("Frame '${id}' is not valid for this ID3 version", 1);
1173             } else {
1174 0 0 0     0 next unless $verbose or $et->Options('Unknown');
1175 0         0 $id =~ tr/-A-Za-z0-9_//dc;
1176 0 0       0 $id = 'unknown' unless length $id;
1177 0 0       0 unless ($$tagTablePtr{$id}) {
1178 0         0 $tagInfo = { Name => "ID3_$id", Binary => 1 };
1179 0         0 AddTagToTable($tagTablePtr, $id, $tagInfo);
1180             }
1181             }
1182             }
1183             # decode v2.3 and v2.4 flags
1184 37         76 my (%flags, %extra);
1185 37 50       93 if ($flags) {
1186 0 0       0 if ($vers < 0x0400) {
1187             # version 2.3 flags
1188 0 0       0 $flags & 0x80 and $flags{Compress} = 1;
1189 0 0       0 $flags & 0x40 and $flags{Encrypt} = 1;
1190 0 0       0 $flags & 0x20 and $flags{GroupID} = 1;
1191             } else {
1192             # version 2.4 flags
1193 0 0       0 $flags & 0x40 and $flags{GroupID} = 1;
1194 0 0       0 $flags & 0x08 and $flags{Compress} = 1;
1195 0 0       0 $flags & 0x04 and $flags{Encrypt} = 1;
1196 0 0       0 $flags & 0x02 and $flags{Unsync} = 1;
1197 0 0       0 $flags & 0x01 and $flags{DataLen} = 1;
1198             }
1199             }
1200 37 50       101 if ($flags{Encrypt}) {
1201 0         0 $et->Warn('Encrypted frames currently not supported');
1202 0         0 next;
1203             }
1204             # extract the value
1205 37         138 my $val = substr($$dataPt, $offset, $len);
1206              
1207             # reverse the unsynchronization
1208 37 50       96 $val =~ s/\xff\x00/\xff/g if $flags{Unsync};
1209              
1210             # read grouping identity
1211 37 50       92 if ($flags{GroupID}) {
1212 0 0       0 length($val) >= 1 or $et->Warn("Short $id frame"), next;
1213 0         0 $val = substr($val, 1); # (ignore it)
1214             }
1215             # read data length
1216 37         57 my $dataLen;
1217 37 50 33     174 if ($flags{DataLen} or $flags{Compress}) {
1218 0 0       0 length($val) >= 4 or $et->Warn("Short $id frame"), next;
1219 0         0 $dataLen = unpack('N', $val); # save the data length word
1220 0         0 $val = substr($val, 4);
1221             }
1222             # uncompress data
1223 37 50       99 if ($flags{Compress}) {
1224 0 0       0 if (eval { require Compress::Zlib }) {
  0         0  
1225 0         0 my $inflate = Compress::Zlib::inflateInit();
1226 0         0 my ($buff, $stat);
1227 0 0       0 $inflate and ($buff, $stat) = $inflate->inflate($val);
1228 0 0 0     0 if ($inflate and $stat == Compress::Zlib::Z_STREAM_END()) {
1229 0         0 $val = $buff;
1230             } else {
1231 0         0 $et->Warn("Error inflating $id frame");
1232 0         0 next;
1233             }
1234             } else {
1235 0         0 $et->Warn('Install Compress::Zlib to decode compressed frames');
1236 0         0 next;
1237             }
1238             }
1239             # validate data length
1240 37 50       83 if (defined $dataLen) {
1241 0         0 $dataLen = UnSyncSafe($dataLen);
1242 0 0       0 defined $dataLen or $et->Warn("Invalid length for $id frame"), next;
1243 0 0       0 $dataLen == length($val) or $et->Warn("Wrong length for $id frame"), next;
1244             }
1245 37 50       95 unless ($tagInfo) {
1246 0 0       0 next unless $verbose;
1247 0 0       0 %flags and $extra{Extra} = ', Flags=' . join(',', sort keys %flags);
1248             $et->VerboseInfo($id, $tagInfo,
1249             Table => $tagTablePtr,
1250             Value => $val,
1251             DataPt => $dataPt,
1252             DataPos => $$dirInfo{DataPos},
1253 0         0 Size => $len,
1254             Start => $offset,
1255             %extra
1256             );
1257 0         0 next;
1258             }
1259             #
1260             # decode data in this frame (it is bad form to hard-code these, but the ID3 frame formats
1261             # are so variable that it would be more work to define format types for each of them)
1262             #
1263 37         58 my $lang;
1264 37         68 my $valLen = length($val); # actual value length (after decompression, etc)
1265 37 50 66     366 if ($id =~ /^(TXX|TXXX)$/) {
    100 33        
    50 33        
    50 0        
    100 0        
    50 0        
    50          
    100          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
1266             # two encoded strings separated by a null
1267 0         0 my @vals = DecodeString($et, $val);
1268 0 0       0 foreach (0..1) { $vals[$_] = '' unless defined $vals[$_]; }
  0         0  
1269 0 0       0 if (length $vals[0]) {
1270 0         0 $id .= "_$vals[0]";
1271 0   0     0 $tagInfo = $$tagTablePtr{$id} || AddTagToTable($tagTablePtr, $id, MakeTagName($vals[0]));
1272             }
1273 0         0 $val = $vals[1];
1274             } elsif ($id =~ /^T/ or $id =~ /^(IPL|IPLS|GP1|MVI|MVN)$/) {
1275 27         85 $val = DecodeString($et, $val);
1276             } elsif ($id =~ /^(WXX|WXXX)$/) {
1277             # one encoded string and one Latin string separated by a null
1278 0         0 my $enc = unpack('C', $val);
1279 0         0 my ($tag, $url);
1280 0 0 0     0 if ($enc == 1 or $enc == 2) {
1281 0         0 ($tag, $url) = ($val =~ /^(.(?:..)*?)\0\0(.*)/s);
1282             } else {
1283 0         0 ($tag, $url) = ($val =~ /^(..*?)\0(.*)/s);
1284             }
1285 0 0 0     0 unless (defined $tag and defined $url) {
1286 0         0 $et->Warn("Invalid $id frame value");
1287 0         0 next;
1288             }
1289 0         0 $tag = DecodeString($et, $tag);
1290 0 0       0 if (length $tag) {
1291 0         0 $id .= "_$tag";
1292 0 0       0 $tag .= '_URL' unless $tag =~ /url/i;
1293 0   0     0 $tagInfo = $$tagTablePtr{$id} || AddTagToTable($tagTablePtr, $id, MakeTagName($tag));
1294             }
1295 0         0 $url =~ s/\0.*//s;
1296 0         0 $val = $et->Decode($url, 'Latin');
1297             } elsif ($id =~ /^W/) {
1298 0         0 $val =~ s/\0.*//s; # truncate at null
1299             } elsif ($id =~ /^(COM|COMM|ULT|USLT)$/) {
1300 6 50       44 $valLen > 4 or $et->Warn("Short $id frame"), next;
1301 6         20 $lang = substr($val,1,3);
1302 6         40 my @vals = DecodeString($et, substr($val,4), Get8u(\$val,0));
1303 6 50       31 foreach (0..1) { $vals[$_] = '' unless defined $vals[$_]; }
  12         39  
1304 6 50       27 $val = length($vals[0]) ? "($vals[0]) $vals[1]" : $vals[1];
1305             } elsif ($id eq 'USER') {
1306 0 0       0 $valLen > 4 or $et->Warn("Short $id frame"), next;
1307 0         0 $lang = substr($val,1,3);
1308 0         0 $val = DecodeString($et, substr($val,4), Get8u(\$val,0));
1309             } elsif ($id =~ /^(CNT|PCNT)$/) {
1310 0 0       0 $valLen >= 4 or $et->Warn("Short $id frame"), next;
1311 0         0 my ($cnt, @xtra) = unpack('NC*', $val);
1312 0         0 $cnt = ($cnt << 8) + $_ foreach @xtra;
1313 0         0 $val = $cnt;
1314             } elsif ($id =~ /^(PIC|APIC)$/) {
1315 2 50       26 $valLen >= 4 or $et->Warn("Short $id frame"), next;
1316 2         6 my ($hdr, $attr);
1317 2         7 my $enc = unpack('C', $val);
1318 2 50 33     34 if ($enc == 1 or $enc == 2) {
1319 0 0       0 $hdr = ($id eq 'PIC') ? ".(...)(.)((?:..)*?)\0\0" : ".(.*?)\0(.)((?:..)*?)\0\0";
1320             } else {
1321 2 50       10 $hdr = ($id eq 'PIC') ? ".(...)(.)(.*?)\0" : ".(.*?)\0(.)(.*?)\0";
1322             }
1323             # remove header (encoding, image format or MIME type, picture type, description)
1324 2 50       76 $val =~ s/^$hdr//s or $et->Warn("Invalid $id frame"), next;
1325 2         14 my @attrs = ($1, ord($2), DecodeString($et, $3, $enc));
1326 2         6 my $i = 1;
1327 2         5 foreach $attr (@attrs) {
1328             # must store descriptions even if they are empty to maintain
1329             # sync between copy numbers when multiple images
1330 6         37 $et->HandleTag($tagTablePtr, "$id-$i", $attr);
1331 6         20 ++$i;
1332             }
1333             } elsif ($id eq 'POP' or $id eq 'POPM') {
1334             # _email, 00, rating(1), counter(4-N)
1335 0         0 my ($email, $dat) = ($val =~ /^([^\0]*)\0(.*)$/s);
1336 0 0 0     0 unless (defined $dat and length($dat)) {
1337 0         0 $et->Warn("Invalid $id frame");
1338 0         0 next;
1339             }
1340 0         0 my ($rating, @xtra) = unpack('C*', $dat);
1341 0         0 my $cnt = 0;
1342 0         0 $cnt = ($cnt << 8) + $_ foreach @xtra;
1343 0         0 $val = "$email $rating $cnt";
1344             } elsif ($id eq 'OWNE') {
1345             # enc(1), _price, 00, _date(8), Seller
1346 0         0 my @strs = DecodeString($et, $val);
1347 0 0       0 $strs[1] =~ s/^(\d{4})(\d{2})(\d{2})/$1:$2:$3 /s if $strs[1]; # format date
1348 0         0 $val = "@strs";
1349             } elsif ($id eq 'RVA' or $id eq 'RVAD') {
1350 2         12 my @dat = unpack('C*', $val);
1351 2         6 my $flag = shift @dat;
1352 2 50       11 my $bits = shift @dat or $et->Warn("Short $id frame"), next;
1353 2         10 my $bytes = int(($bits + 7) / 8);
1354 2         35 my @parse = (['Right',0,2,0x01],['Left',1,3,0x02],['Back-right',4,6,0x04],
1355             ['Back-left',5,7,0x08],['Center',8,9,0x10],['Bass',10,11,0x20]);
1356 2         6 $val = '';
1357 2         33 while (@parse) {
1358 6         13 my $elem = shift @parse;
1359 6         14 my $j = $$elem[2] * $bytes;
1360 6 100       24 last if scalar(@dat) < $j + $bytes;
1361 4         11 my $i = $$elem[1] * $bytes;
1362 4 100       12 $val .= ', ' if $val;
1363 4         9 my ($rel, $pk, $b);
1364 4         14 for ($rel=0, $pk=0, $b=0; $b<$bytes; ++$b) {
1365 8         17 $rel = $rel * 256 + $dat[$i + $b];
1366 8         19 $pk = $pk * 256 + $dat[$j + $b]; # (peak - not used in printout)
1367             }
1368 4 50       15 $rel =-$rel unless $flag & $$elem[3];
1369 4         51 $val .= sprintf("%+.1f%% %s", 100 * $rel / ((1<<$bits)-1), $$elem[0]);
1370             }
1371             } elsif ($id eq 'RVA2') {
1372 0 0       0 my ($pos, $id) = $val=~/^([^\0]*)\0/s ? (length($1)+1, $1) : (1, '');
1373 0         0 my @vals;
1374 0         0 while ($pos + 4 <= $valLen) {
1375 0         0 my $type = Get8u(\$val, $pos);
1376             my $str = ({
1377             0 => 'Other',
1378             1 => 'Master',
1379             2 => 'Front-right',
1380             3 => 'Front-left',
1381             4 => 'Back-right',
1382             5 => 'Back-left',
1383             6 => 'Front-centre',
1384             7 => 'Back-centre',
1385             8 => 'Subwoofer',
1386 0   0     0 }->{$type} || "Unknown($type)");
1387 0         0 my $db = Get16s(\$val,$pos+1) / 512;
1388             # convert dB to percent as displayed by iTunes 10.5
1389             # (not sure why I need to divide by 20 instead of 10 as expected - PH)
1390 0         0 push @vals, sprintf('%+.1f%% %s', 10**($db/20+2)-100, $str);
1391             # step to next channel (ignoring peak volume)
1392 0         0 $pos += 4 + int((Get8u(\$val,$pos+3) + 7) / 8);
1393             }
1394 0         0 $val = join ', ', @vals;
1395 0 0       0 $val .= " ($id)" if $id;
1396             } elsif ($id eq 'PRIV') {
1397             # save version number to set group1 name for tag later
1398 0         0 $$et{ID3_Ver} = $$tagTablePtr{GROUPS}{1};
1399 0         0 $et->HandleTag($tagTablePtr, $id, $val);
1400 0         0 next;
1401             } elsif ($$tagInfo{Format} or $$tagInfo{SubDirectory}) {
1402 0         0 $et->HandleTag($tagTablePtr, $id, undef, DataPt => \$val);
1403 0         0 next;
1404             } elsif ($id eq 'GRP1' or $id eq 'MVNM' or $id eq 'MVIN') {
1405 0         0 $val =~ s/(^\0+|\0+$)//g; # (PH guess)
1406             } elsif (not $$tagInfo{Binary}) {
1407 0         0 $et->Warn("Don't know how to handle $id frame");
1408 0         0 next;
1409             }
1410 37 50 66     181 if ($lang and $lang =~ /^[a-z]{3}$/i and $lang ne 'eng') {
      66        
1411 0         0 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, lc $lang);
1412             }
1413 37 50       99 %flags and $extra{Extra} = ', Flags=' . join(',', sort keys %flags);
1414             $et->HandleTag($tagTablePtr, $id, $val,
1415             TagInfo => $tagInfo,
1416             DataPt => $dataPt,
1417             DataPos => $$dirInfo{DataPos},
1418 37         197 Size => $len,
1419             Start => $offset,
1420             %extra
1421             );
1422             }
1423             }
1424              
1425             #------------------------------------------------------------------------------
1426             # Extract ID3 information from an audio file
1427             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1428             # Returns: 1 on success, 0 if this file didn't contain ID3 information
1429             # - also processes audio data if any ID3 information was found
1430             # - sets ExifTool DoneID3 to 1 when called, or to trailer size if an ID3v1 trailer exists
1431             sub ProcessID3($$)
1432             {
1433 19     19 0 59 my ($et, $dirInfo) = @_;
1434              
1435 19 50       82 return 0 if $$et{DoneID3}; # avoid infinite recursion
1436 19         56 $$et{DoneID3} = 1;
1437              
1438             # allow this to be called with either RAF or DataPt
1439 19   66     126 my $raf = $$dirInfo{RAF} || File::RandomAccess->new($$dirInfo{DataPt});
1440 19   100     157 my $dataPos = $$dirInfo{DataPos} || 0;
1441 19         50 my ($buff, %id3Header, %id3Trailer, $hBuff, $tBuff, $eBuff, $tagTablePtr);
1442 19         41 my $rtnVal = 0;
1443 19         39 my $hdrEnd = 0;
1444 19         36 my $id3Len = 0;
1445              
1446             # read first 3 bytes of file
1447 19         107 $raf->Seek(0, 0);
1448 19 100       115 return 0 unless $raf->Read($buff, 3) == 3;
1449             #
1450             # identify ID3v2 header
1451             #
1452 16         80 while ($buff =~ /^ID3/) {
1453 3         9 $rtnVal = 1;
1454 3 50       13 $raf->Read($hBuff, 7) == 7 or $et->Warn('Short ID3 header'), last;
1455 3         21 my ($vers, $flags, $size) = unpack('nCN', $hBuff);
1456 3         27 $size = UnSyncSafe($size);
1457 3 50       24 defined $size or $et->Warn('Invalid ID3 header'), last;
1458 3         21 my $verStr = sprintf("2.%d.%d", $vers >> 8, $vers & 0xff);
1459 3 50       11 if ($vers >= 0x0500) {
1460 0         0 $et->Warn("Unsupported ID3 version: $verStr");
1461 0         0 last;
1462             }
1463 3 50       20 unless ($raf->Read($hBuff, $size) == $size) {
1464 0         0 $et->Warn('Truncated ID3 data');
1465 0         0 last;
1466             }
1467             # this flag only indicates use of unsynchronized frames in ID3v2.4
1468 3 50 33     18 if ($flags & 0x80 and $vers < 0x0400) {
1469             # reverse the unsynchronization
1470 0         0 $hBuff =~ s/\xff\x00/\xff/g;
1471             }
1472 3         7 my $pos = 10;
1473 3 50       12 if ($flags & 0x40) {
1474             # skip the extended header
1475 0 0       0 $size >= 4 or $et->Warn('Bad ID3 extended header'), last;
1476 0         0 my $len = UnSyncSafe(unpack('N', $hBuff));
1477 0 0       0 if ($len > length($hBuff)) {
1478 0         0 $et->Warn('Truncated ID3 extended header');
1479 0         0 last;
1480             }
1481 0         0 $hBuff = substr($hBuff, $len);
1482 0         0 $pos += $len;
1483             }
1484 3 50       16 if ($flags & 0x10) {
1485             # ignore v2.4 footer (10 bytes long)
1486 0         0 $raf->Seek(10, 1);
1487             }
1488             %id3Header = (
1489 3         35 DataPt => \$hBuff,
1490             DataPos => $dataPos + $pos,
1491             DirStart => 0,
1492             DirLen => length($hBuff),
1493             Version => $vers,
1494             DirName => "ID3v$verStr",
1495             );
1496 3         27 $id3Len += length($hBuff) + 10;
1497 3 50       18 if ($vers >= 0x0400) {
    50          
1498 0         0 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v2_4');
1499             } elsif ($vers >= 0x0300) {
1500 0         0 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v2_3');
1501             } else {
1502 3         35 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v2_2');
1503             }
1504 3         22 $hdrEnd = $raf->Tell();
1505 3         14 last;
1506             }
1507             #
1508             # read ID3v1 trailer if it exists
1509             #
1510 16         36 my $trailSize = 0;
1511 16 100 66     87 if ($raf->Seek(-128, 2) and $raf->Read($tBuff, 128) == 128 and $tBuff =~ /^TAG/) {
      100        
1512 2         6 $trailSize = 128;
1513 2         9 %id3Trailer = (
1514             DataPt => \$tBuff,
1515             DataPos => $dataPos + $raf->Tell() - 128,
1516             DirStart => 0,
1517             DirLen => length($tBuff),
1518             );
1519 2         6 $id3Len += length($tBuff);
1520 2         6 $rtnVal = 1;
1521             # load 'Enhanced TAG' information if available
1522 2         5 my $eSize = 227; # size of ID3 Enhanced TAG info
1523 2 50 33     52 if ($raf->Seek(-$trailSize - $eSize, 2) and $raf->Read($eBuff, $eSize) == $eSize and $eBuff =~ /^TAG+/) {
      33        
1524 0         0 $id3Trailer{EnhancedTAG} = \$eBuff;
1525 0         0 $trailSize += $eSize;
1526             }
1527 2         8 $$et{DoneID3} = $trailSize; # save trailer size
1528             }
1529             #
1530             # read Lyrics3 trailer if it exists
1531             #
1532 16 50 66     73 if ($raf->Seek(-$trailSize-15, 2) and $raf->Read($buff, 15) == 15 and $buff =~ /^(.{6})LYRICS(END|200)$/) {
      66        
1533 0         0 my $ver = $2; # Lyrics3 version ('END' for version 1)
1534 0 0       0 my $len = ($ver eq 'END') ? 5100 : $1 + 15; # max Lyrics3 length
1535 0         0 my $tbl = GetTagTable('Image::ExifTool::ID3::Lyrics3');
1536 0 0       0 $len = $raf->Tell() if $len > $raf->Tell();
1537 0 0 0     0 if ($raf->Seek(-$len, 1) and $raf->Read($buff, $len) == $len and $buff =~ /LYRICSBEGIN/g) {
      0        
1538 0         0 my $pos = pos($buff);
1539 0         0 $$et{DoneID3} = $trailSize + $len - $pos + 11; # update trailer length
1540 0         0 my $oldIndent = $$et{INDENT};
1541 0         0 $$et{INDENT} .= '| ';
1542 0 0       0 if ($et->Options('Verbose')) {
1543 0         0 $et->VPrint(0, "Lyrics3:\n");
1544 0         0 $et->VerboseDir('Lyrics3', undef, $len);
1545 0 0       0 if ($pos > 11) {
1546 0         0 $buff = substr($buff, $pos - 11);
1547 0         0 $pos = 11;
1548             }
1549 0         0 $et->VerboseDump(\$buff);
1550             }
1551 0 0       0 if ($ver eq 'END') {
1552             # Lyrics3 v1.00
1553 0         0 my $val = substr($buff, $pos, $len - $pos - 9);
1554 0         0 $et->HandleTag($tbl, 'LYR', $et->Decode($val, 'Latin'));
1555             } else {
1556             # Lyrics3 v2.00
1557 0         0 for (;;) {
1558             # (note: the size field is 5 digits,, not 6 as per the documentation)
1559 0 0       0 last unless $buff =~ /\G(.{3})(\d{5})/g;
1560 0         0 my ($tag, $size) = ($1, $2);
1561 0         0 $pos += 8;
1562 0 0       0 last if $pos + $size > length($buff);
1563 0 0       0 unless ($$tbl{$tag}) {
1564 0         0 AddTagToTable($tbl, $tag, { Name => Image::ExifTool::MakeTagName("Lyrics3_$tag") });
1565             }
1566 0         0 $et->HandleTag($tbl, $tag, $et->Decode(substr($buff, $pos, $size), 'Latin'));
1567 0         0 $pos += $size;
1568 0         0 pos($buff) = $pos;
1569             }
1570 0 0       0 $pos == length($buff) - 15 or $et->Warn('Malformed Lyrics3 v2.00 block');
1571             }
1572 0         0 $$et{INDENT} = $oldIndent;
1573             } else {
1574 0         0 $et->Warn('Error reading Lyrics3 trailer');
1575             }
1576             }
1577             #
1578             # process the information
1579             #
1580 16 100       84 if ($rtnVal) {
1581             # first process audio data if it exists
1582 3 100       31 if ($$dirInfo{RAF}) {
1583 2         7 my $oldType = $$et{FILE_TYPE}; # save file type
1584             # check current file type first
1585 2         99 my @types = grep /^$oldType$/, @audioFormats;
1586 2         42 push @types, grep(!/^$oldType$/, @audioFormats);
1587 2         6 my $type;
1588 2         6 foreach $type (@types) {
  0         0  
1589             # seek to end of ID3 header
1590 2         14 $raf->Seek($hdrEnd, 0);
1591             # set type for this file if we are successful
1592 2         6 $$et{FILE_TYPE} = $type;
1593 2   66     16 my $module = $audioModule{$type} || $type;
1594 2 50       34 require "Image/ExifTool/$module.pm" or next;
1595 2         8 my $func = "Image::ExifTool::${module}::Process$type";
1596             # process the file
1597 12     12   152 no strict 'refs';
  12         45  
  12         840  
1598 2 50       19 &$func($et, $dirInfo) and last;
1599 12     12   77 use strict 'refs';
  12         29  
  12         16069  
1600             }
1601 2         10 $$et{FILE_TYPE} = $oldType; # restore original file type
1602             }
1603             # set file type to MP3 if we didn't find audio data
1604 3         22 $et->SetFileType('MP3');
1605             # record the size of the ID3 metadata
1606 3         14 $et->FoundTag('ID3Size', $id3Len);
1607             # process ID3v2 header if it exists
1608 3 50       13 if (%id3Header) {
1609 3         30 $et->VPrint(0, "$id3Header{DirName}:\n");
1610 3         19 $et->ProcessDirectory(\%id3Header, $tagTablePtr);
1611             }
1612             # process ID3v1 trailer if it exists
1613 3 100       16 if (%id3Trailer) {
1614 2         14 $et->VPrint(0, "ID3v1:\n");
1615 2         9 SetByteOrder('MM');
1616 2         9 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1');
1617 2         13 $et->ProcessDirectory(\%id3Trailer, $tagTablePtr);
1618             # process "Enhanced TAG" information if available
1619 2 50       13 if ($id3Trailer{EnhancedTAG}) {
1620 0         0 $et->VPrint(0, "ID3v1 Enhanced TAG:\n");
1621 0         0 $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1_Enh');
1622 0         0 $id3Trailer{DataPt} = $id3Trailer{EnhancedTAG};
1623 0         0 $id3Trailer{DataPos} -= 227; # (227 = length of Enhanced TAG block)
1624 0         0 $id3Trailer{DirLen} = 227;
1625 0         0 $et->ProcessDirectory(\%id3Trailer, $tagTablePtr);
1626             }
1627             }
1628             }
1629             # return file pointer to start of file to read audio data if necessary
1630 16         315 $raf->Seek(0, 0);
1631 16         115 return $rtnVal;
1632             }
1633              
1634             #------------------------------------------------------------------------------
1635             # Process ID3 directory
1636             # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) dummy tag table ref
1637             sub ProcessID3Dir($$$)
1638             {
1639 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
1640 0         0 $et->VerboseDir('ID3', undef, length ${$$dirInfo{DataPt}});
  0         0  
1641 0         0 return ProcessID3($et, $dirInfo);
1642             }
1643              
1644             #------------------------------------------------------------------------------
1645             # Process ID3 General Encapsulated Object
1646             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1647             # Returns: 1 on success
1648             sub ProcessGEOB($$$)
1649             {
1650 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
1651 0         0 $et->VerboseDir('GEOB', undef, length ${$$dirInfo{DataPt}});
  0         0  
1652 0         0 my $dataPt = $$dirInfo{DataPt};
1653 0         0 my $len = length $$dataPt;
1654 0 0       0 $len >= 4 or $et->Warn("Short GEOB frame"), return 0;
1655 0         0 my ($hdr, $attr);
1656 0         0 my $enc = unpack('C', $$dataPt);
1657 0 0 0     0 if ($enc == 1 or $enc == 2) {
1658 0         0 $hdr = ".(.*?)\0((?:..)*?)\0\0((?:..)*?)\0\0";
1659             } else {
1660 0         0 $hdr = ".(.*?)\0(.*?)\0(.*?)\0";
1661             }
1662             # remove header (encoding, mime, filename, description)
1663 0 0       0 $$dataPt =~ s/^$hdr//s or $et->Warn("Invalid GEOB frame"), return 0;
1664 0         0 my ($mime, $file, $desc) = ($1, DecodeString($et, $2, $enc), DecodeString($et, $3, $enc));
1665 0 0       0 $et->HandleTag($tagTablePtr, 'GEOB-Mime', $mime) if length $mime;
1666 0 0       0 $et->HandleTag($tagTablePtr, 'GEOB-File', $file) if length $file;
1667 0 0       0 $et->HandleTag($tagTablePtr, 'GEOB-Desc', $desc) if length $desc;
1668 0 0       0 if ($$tagTablePtr{$mime}) {
1669 0         0 $et->HandleTag($tagTablePtr, $mime, undef,
1670             DataPt => $dataPt,
1671             Start => 0,
1672             Size => length($$dataPt),
1673             );
1674             } else {
1675 0         0 $et->HandleTag($tagTablePtr, 'GEOB-Data', $dataPt);
1676             }
1677 0         0 return 1;
1678             }
1679              
1680             #------------------------------------------------------------------------------
1681             # Extract ID3 information from an MP3 audio file
1682             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1683             # Returns: 1 on success, 0 if this wasn't a valid MP3 file
1684             sub ProcessMP3($$)
1685             {
1686 13     13 0 46 my ($et, $dirInfo) = @_;
1687 13         37 my $rtnVal = 0;
1688              
1689             # must first check for leading/trailing ID3 information
1690             # (and process the rest of the file if found)
1691 13 100       60 unless ($$et{DoneID3}) {
1692 12         70 $rtnVal = ProcessID3($et, $dirInfo);
1693             }
1694              
1695             # check for MPEG A/V data if not already processed above
1696 13 100       45 unless ($rtnVal) {
1697 12         49 my $raf = $$dirInfo{RAF};
1698 12         25 my $buff;
1699             #
1700             # extract information from first audio/video frame headers
1701             # (if found in the first $scanLen bytes)
1702             #
1703             # scan further into a file that should be an MP3
1704 12 100 66     57 my $scanLen = ($$et{FILE_EXT} and $$et{FILE_EXT} eq 'MP3') ? 8192 : 256;
1705 12 100       37 if ($raf->Read($buff, $scanLen)) {
1706 9         3815 require Image::ExifTool::MPEG;
1707 9 50       59 if ($buff =~ /\0\0\x01(\xb3|\xc0)/) {
1708             # look for A/V headers in first 64kB
1709 0         0 my $buf2;
1710 0 0       0 $raf->Read($buf2, 0x10000 - $scanLen) and $buff .= $buf2;
1711 0 0       0 $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudioVideo($et, \$buff);
1712             } else {
1713             # look for audio frame sync in first $scanLen bytes
1714             # (set MP3 flag to 1 so this will fail unless layer 3 audio)
1715 9   100     58 my $ext = $$et{FILE_EXT} || '';
1716 9 50       32 my $mp3 = ($ext eq 'MUS') ? 0 : 1; # MUS files are MP2
1717 9 100       49 $rtnVal = 1 if Image::ExifTool::MPEG::ParseMPEGAudio($et, \$buff, $mp3);
1718             }
1719             }
1720             }
1721              
1722             # check for an APE trailer if this was a valid A/V file and we haven't already done it
1723 13 100 100     66 if ($rtnVal and not $$et{DoneAPE}) {
1724 1         790 require Image::ExifTool::APE;
1725 1         6 Image::ExifTool::APE::ProcessAPE($et, $dirInfo);
1726             }
1727 13         47 return $rtnVal;
1728             }
1729              
1730             1; # end
1731              
1732             __END__