File Coverage

blib/lib/Image/ExifTool/ID3.pm
Criterion Covered Total %
statement 198 457 43.3
branch 90 312 28.8
condition 34 110 30.9
subroutine 12 17 70.5
pod 0 12 0.0
total 334 908 36.7


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