ID3.pm 53 KB

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