ASF.pm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. #------------------------------------------------------------------------------
  2. # File: ASF.pm
  3. #
  4. # Description: Read ASF/WMA/WMV meta information
  5. #
  6. # Revisions: 12/23/2005 - P. Harvey Created
  7. #
  8. # References: 1) http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx
  9. # 2) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
  10. #------------------------------------------------------------------------------
  11. package Image::ExifTool::ASF;
  12. use strict;
  13. use vars qw($VERSION);
  14. use Image::ExifTool qw(:DataAccess :Utils);
  15. use Image::ExifTool::Exif;
  16. use Image::ExifTool::RIFF;
  17. $VERSION = '1.23';
  18. sub ProcessASF($$;$);
  19. sub ProcessContentDescription($$$);
  20. sub ProcessExtendedContentDescription($$$);
  21. sub ProcessMetadata($$$);
  22. sub ProcessPicture($$$);
  23. sub ProcessCodecList($$$);
  24. # GUID definitions
  25. my %errorCorrection = (
  26. '20FB5700-5B55-11CF-A8FD-00805F5C442B' => 'No Error Correction',
  27. 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220' => 'Audio Spread',
  28. );
  29. my %streamType = (
  30. 'F8699E40-5B4D-11CF-A8FD-00805F5C442B' => 'Audio',
  31. 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B' => 'Video',
  32. '59DACFC0-59E6-11D0-A3AC-00A0C90348F6' => 'Command',
  33. 'B61BE100-5B4E-11CF-A8FD-00805F5C442B' => 'JFIF',
  34. '35907DE0-E415-11CF-A917-00805F5C442B' => 'Degradable JPEG',
  35. '91BD222C-F21C-497A-8B6D-5AA86BFC0185' => 'File Transfer',
  36. '3AFB65E2-47EF-40F2-AC2C-70A90D71D343' => 'Binary',
  37. );
  38. my %mutex = (
  39. 'D6E22A00-35DA-11D1-9034-00A0C90349BE' => 'MutexLanguage',
  40. 'D6E22A01-35DA-11D1-9034-00A0C90349BE' => 'MutexBitrate',
  41. 'D6E22A02-35DA-11D1-9034-00A0C90349BE' => 'MutexUnknown',
  42. );
  43. my %bandwidthSharing = (
  44. 'AF6060AA-5197-11D2-B6AF-00C04FD908E9' => 'SharingExclusive',
  45. 'AF6060AB-5197-11D2-B6AF-00C04FD908E9' => 'SharingPartial',
  46. );
  47. my %typeSpecific = (
  48. '776257D4-C627-41CB-8F81-7AC7FF1C40CC' => 'WebStreamMediaSubtype',
  49. 'DA1E6B13-8359-4050-B398-388E965BF00C' => 'WebStreamFormat',
  50. );
  51. my %advancedContentEncryption = (
  52. '7A079BB6-DAA4-4e12-A5CA-91D38DC11A8D' => 'DRMNetworkDevices',
  53. );
  54. # ASF top level objects
  55. %Image::ExifTool::ASF::Main = (
  56. PROCESS_PROC => \&ProcessASF,
  57. NOTES => q{
  58. The ASF format is used by Windows WMA and WMV files, and DIVX videos. Tag
  59. ID's aren't listed because they are huge 128-bit GUID's that would ruin the
  60. formatting of this table.
  61. },
  62. '75B22630-668E-11CF-A6D9-00AA0062CE6C' => {
  63. Name => 'Header',
  64. SubDirectory => { TagTable => 'Image::ExifTool::ASF::Header', Size => 6 },
  65. },
  66. '75B22636-668E-11CF-A6D9-00AA0062CE6C' => 'Data',
  67. '33000890-E5B1-11CF-89F4-00A0C90349CB' => 'SimpleIndex',
  68. 'D6E229D3-35DA-11D1-9034-00A0C90349BE' => 'Index',
  69. 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C' => 'MediaIndex',
  70. '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C' => 'TimecodeIndex',
  71. 'BE7ACFCB-97A9-42E8-9C71-999491E3AFAC' => { #2
  72. Name => 'XMP',
  73. SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
  74. },
  75. );
  76. # ASF header objects
  77. %Image::ExifTool::ASF::Header = (
  78. PROCESS_PROC => \&ProcessASF,
  79. '8CABDCA1-A947-11CF-8EE4-00C00C205365' => {
  80. Name => 'FileProperties',
  81. SubDirectory => { TagTable => 'Image::ExifTool::ASF::FileProperties' },
  82. },
  83. 'B7DC0791-A9B7-11CF-8EE6-00C00C205365' => {
  84. Name => 'StreamProperties',
  85. SubDirectory => { TagTable => 'Image::ExifTool::ASF::StreamProperties' },
  86. },
  87. '5FBF03B5-A92E-11CF-8EE3-00C00C205365' => {
  88. Name => 'HeaderExtension',
  89. SubDirectory => { TagTable => 'Image::ExifTool::ASF::HeaderExtension', Size => 22 },
  90. },
  91. '86D15240-311D-11D0-A3A4-00A0C90348F6' => {
  92. Name => 'CodecList',
  93. SubDirectory => { TagTable => 'Image::ExifTool::ASF::CodecList' },
  94. },
  95. '1EFB1A30-0B62-11D0-A39B-00A0C90348F6' => 'ScriptCommand',
  96. 'F487CD01-A951-11CF-8EE6-00C00C205365' => 'Marker',
  97. 'D6E229DC-35DA-11D1-9034-00A0C90349BE' => 'BitrateMutualExclusion',
  98. '75B22635-668E-11CF-A6D9-00AA0062CE6C' => 'ErrorCorrection',
  99. '75B22633-668E-11CF-A6D9-00AA0062CE6C' => {
  100. Name => 'ContentDescription',
  101. SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentDescr' },
  102. },
  103. '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E' => {
  104. Name => 'ContentBranding',
  105. SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentBranding' },
  106. },
  107. 'D2D0A440-E307-11D2-97F0-00A0C95EA850' => {
  108. Name => 'ExtendedContentDescr',
  109. SubDirectory => { TagTable => 'Image::ExifTool::ASF::ExtendedDescr' },
  110. },
  111. '7BF875CE-468D-11D1-8D82-006097C9A2B2' => 'StreamBitrateProps',
  112. '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E' => 'ContentEncryption',
  113. '298AE614-2622-4C17-B935-DAE07EE9289C' => 'ExtendedContentEncryption',
  114. '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E' => 'DigitalSignature',
  115. '1806D474-CADF-4509-A4BA-9AABCB96AAE8' => 'Padding',
  116. );
  117. %Image::ExifTool::ASF::ContentDescr = (
  118. PROCESS_PROC => \&ProcessContentDescription,
  119. GROUPS => { 2 => 'Video' },
  120. 0 => 'Title',
  121. 1 => { Name => 'Author', Groups => { 2 => 'Author' } },
  122. 2 => { Name => 'Copyright', Groups => { 2 => 'Author' } },
  123. 3 => 'Description',
  124. 4 => 'Rating',
  125. );
  126. %Image::ExifTool::ASF::ContentBranding = (
  127. PROCESS_PROC => \&ProcessContentBranding,
  128. GROUPS => { 2 => 'Author' },
  129. 0 => {
  130. Name => 'BannerImageType',
  131. PrintConv => {
  132. 0 => 'None',
  133. 1 => 'Bitmap',
  134. 2 => 'JPEG',
  135. 3 => 'GIF',
  136. },
  137. },
  138. 1 => { Name => 'BannerImage', Groups => { 2 => 'Preview' }, Binary => 1 },
  139. 2 => 'BannerImageURL',
  140. 3 => 'CopyrightURL',
  141. );
  142. %Image::ExifTool::ASF::ExtendedDescr = (
  143. PROCESS_PROC => \&ProcessExtendedContentDescription,
  144. GROUPS => { 2 => 'Video' },
  145. ASFLeakyBucketPairs => { Binary => 1 },
  146. AspectRatioX => {},
  147. AspectRatioY => {},
  148. Author => { Groups => { 2 => 'Author' } },
  149. AverageLevel => {},
  150. BannerImageData => {},
  151. BannerImageType => {},
  152. BannerImageURL => {},
  153. Bitrate => { PrintConv => 'ConvertBitrate($val)' },
  154. Broadcast => {},
  155. BufferAverage => {},
  156. Can_Skip_Backward => {},
  157. Can_Skip_Forward => {},
  158. Copyright => { Groups => { 2 => 'Author' } },
  159. CopyrightURL => { Groups => { 2 => 'Author' } },
  160. CurrentBitrate => { PrintConv => 'ConvertBitrate($val)' },
  161. Description => {},
  162. DRM_ContentID => {},
  163. DRM_DRMHeader_ContentDistributor => {},
  164. DRM_DRMHeader_ContentID => {},
  165. DRM_DRMHeader_IndividualizedVersion => {},
  166. DRM_DRMHeader_KeyID => {},
  167. DRM_DRMHeader_LicenseAcqURL => {},
  168. DRM_DRMHeader_SubscriptionContentID => {},
  169. DRM_DRMHeader => {},
  170. DRM_IndividualizedVersion => {},
  171. DRM_KeyID => {},
  172. DRM_LASignatureCert => {},
  173. DRM_LASignatureLicSrvCert => {},
  174. DRM_LASignaturePrivKey => {},
  175. DRM_LASignatureRootCert => {},
  176. DRM_LicenseAcqURL => {},
  177. DRM_V1LicenseAcqURL => {},
  178. Duration => { PrintConv => 'ConvertDuration($val)' },
  179. FileSize => {},
  180. HasArbitraryDataStream => {},
  181. HasAttachedImages => {},
  182. HasAudio => {},
  183. HasFileTransferStream => {},
  184. HasImage => {},
  185. HasScript => {},
  186. HasVideo => {},
  187. Is_Protected => {},
  188. Is_Trusted => {},
  189. IsVBR => {},
  190. NSC_Address => {},
  191. NSC_Description => {},
  192. NSC_Email => {},
  193. NSC_Name => {},
  194. NSC_Phone => {},
  195. NumberOfFrames => {},
  196. OptimalBitrate => { PrintConv => 'ConvertBitrate($val)' },
  197. PeakValue => {},
  198. Rating => {},
  199. Seekable => {},
  200. Signature_Name => {},
  201. Stridable => {},
  202. Title => {},
  203. VBRPeak => {},
  204. # "WM/" tags...
  205. AlbumArtist => {},
  206. AlbumCoverURL => {},
  207. AlbumTitle => {},
  208. ASFPacketCount => {},
  209. ASFSecurityObjectsSize => {},
  210. AudioFileURL => {},
  211. AudioSourceURL => {},
  212. AuthorURL => { Groups => { 2 => 'Author' } },
  213. BeatsPerMinute => {},
  214. Category => {},
  215. Codec => {},
  216. Composer => {},
  217. Conductor => {},
  218. ContainerFormat => {},
  219. ContentDistributor => {},
  220. ContentGroupDescription => {},
  221. Director => {},
  222. DRM => {},
  223. DVDID => {},
  224. EncodedBy => {},
  225. EncodingSettings => {},
  226. EncodingTime => { Groups => { 2 => 'Time' } },
  227. Genre => {},
  228. GenreID => {},
  229. InitialKey => {},
  230. ISRC => {},
  231. Language => {},
  232. Lyrics => {},
  233. Lyrics_Synchronised => {},
  234. MCDI => {},
  235. MediaClassPrimaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' },
  236. MediaClassSecondaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' },
  237. MediaCredits => {},
  238. MediaIsDelay => {},
  239. MediaIsFinale => {},
  240. MediaIsLive => {},
  241. MediaIsPremiere => {},
  242. MediaIsRepeat => {},
  243. MediaIsSAP => {},
  244. MediaIsStereo => {},
  245. MediaIsSubtitled => {},
  246. MediaIsTape => {},
  247. MediaNetworkAffiliation => {},
  248. MediaOriginalBroadcastDateTime => { Groups => { 2 => 'Time' } },
  249. MediaOriginalChannel => {},
  250. MediaStationCallSign => {},
  251. MediaStationName => {},
  252. ModifiedBy => {},
  253. Mood => {},
  254. OriginalAlbumTitle => {},
  255. OriginalArtist => {},
  256. OriginalFilename => 'OriginalFileName',
  257. OriginalLyricist => {},
  258. OriginalReleaseTime => { Groups => { 2 => 'Time' } },
  259. OriginalReleaseYear => { Groups => { 2 => 'Time' } },
  260. ParentalRating => {},
  261. ParentalRatingReason => {},
  262. PartOfSet => {},
  263. PeakBitrate => { PrintConv => 'ConvertBitrate($val)' },
  264. Period => {},
  265. Picture => {
  266. SubDirectory => {
  267. TagTable => 'Image::ExifTool::ASF::Picture',
  268. },
  269. },
  270. PlaylistDelay => {},
  271. Producer => {},
  272. PromotionURL => {},
  273. ProtectionType => {},
  274. Provider => {},
  275. ProviderCopyright => {},
  276. ProviderRating => {},
  277. ProviderStyle => {},
  278. Publisher => {},
  279. RadioStationName => {},
  280. RadioStationOwner => {},
  281. SharedUserRating => {},
  282. StreamTypeInfo => {},
  283. SubscriptionContentID => {},
  284. SubTitle => 'Subtitle',
  285. SubTitleDescription => 'SubtitleDescription',
  286. Text => {},
  287. ToolName => {},
  288. ToolVersion => {},
  289. Track => {},
  290. TrackNumber => {},
  291. UniqueFileIdentifier => {},
  292. UserWebURL => {},
  293. VideoClosedCaptioning => {},
  294. VideoFrameRate => {},
  295. VideoHeight => {},
  296. VideoWidth => {},
  297. WMADRCAverageReference => {},
  298. WMADRCAverageTarget => {},
  299. WMADRCPeakReference => {},
  300. WMADRCPeakTarget => {},
  301. WMCollectionGroupID => {},
  302. WMCollectionID => {},
  303. WMContentID => {},
  304. Writer => { Groups => { 2 => 'Author' } },
  305. Year => { Groups => { 2 => 'Time' } },
  306. );
  307. %Image::ExifTool::ASF::Picture = (
  308. PROCESS_PROC => \&ProcessPicture,
  309. GROUPS => { 2 => 'Image' },
  310. 0 => {
  311. Name => 'PictureType',
  312. PrintConv => { # (Note: Duplicated in ID3, ASF and FLAC modules!)
  313. 0 => 'Other',
  314. 1 => '32x32 PNG Icon',
  315. 2 => 'Other Icon',
  316. 3 => 'Front Cover',
  317. 4 => 'Back Cover',
  318. 5 => 'Leaflet',
  319. 6 => 'Media',
  320. 7 => 'Lead Artist',
  321. 8 => 'Artist',
  322. 9 => 'Conductor',
  323. 10 => 'Band',
  324. 11 => 'Composer',
  325. 12 => 'Lyricist',
  326. 13 => 'Recording Studio or Location',
  327. 14 => 'Recording Session',
  328. 15 => 'Performance',
  329. 16 => 'Capture from Movie or Video',
  330. 17 => 'Bright(ly) Colored Fish',
  331. 18 => 'Illustration',
  332. 19 => 'Band Logo',
  333. 20 => 'Publisher Logo',
  334. },
  335. },
  336. 1 => 'PictureMIMEType',
  337. 2 => 'PictureDescription',
  338. 3 => {
  339. Name => 'Picture',
  340. Groups => { 2 => 'Preview' },
  341. Binary => 1,
  342. },
  343. );
  344. %Image::ExifTool::ASF::FileProperties = (
  345. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  346. GROUPS => { 2 => 'Video' },
  347. 0 => {
  348. Name => 'FileID',
  349. Format => 'binary[16]',
  350. ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
  351. },
  352. 16 => { Name => 'FileLength', Format => 'int64u' },
  353. 24 => {
  354. Name => 'CreationDate',
  355. Format => 'int64u',
  356. Groups => { 2 => 'Time' },
  357. # time is in 100 ns intervals since 0:00 UTC Jan 1, 1601
  358. ValueConv => q{ # (89 leap years between 1601 and 1970)
  359. my $t = $val / 1e7 - (((1970-1601)*365+89)*24*3600);
  360. return Image::ExifTool::ConvertUnixTime($t) . 'Z';
  361. },
  362. PrintConv => '$self->ConvertDateTime($val)',
  363. },
  364. 32 => { Name => 'DataPackets', Format => 'int64u' },
  365. 40 => {
  366. Name => 'PlayDuration',
  367. Format => 'int64u',
  368. ValueConv => '$val / 1e7',
  369. PrintConv => 'ConvertDuration($val)',
  370. },
  371. 48 => {
  372. Name => 'SendDuration',
  373. Format => 'int64u',
  374. ValueConv => '$val / 1e7',
  375. PrintConv => 'ConvertDuration($val)',
  376. },
  377. 56 => { Name => 'Preroll', Format => 'int64u' },
  378. 64 => { Name => 'Flags', Format => 'int32u' },
  379. 68 => { Name => 'MinPacketSize',Format => 'int32u' },
  380. 72 => { Name => 'MaxPacketSize',Format => 'int32u' },
  381. 76 => { Name => 'MaxBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' },
  382. );
  383. %Image::ExifTool::ASF::StreamProperties = (
  384. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  385. GROUPS => { 2 => 'Video' },
  386. NOTES => 'Tags with index 54 and greater are conditional based on the StreamType.',
  387. 0 => {
  388. Name => 'StreamType',
  389. Format => 'binary[16]',
  390. RawConv => sub { # set ASF_STREAM_TYPE for use in conditional tags
  391. my ($val, $et) = @_;
  392. $$et{ASF_STREAM_TYPE} = $streamType{GetGUID($val)} || '';
  393. return $val;
  394. },
  395. ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
  396. PrintConv => \%streamType,
  397. },
  398. 16 => {
  399. Name => 'ErrorCorrectionType',
  400. Format => 'binary[16]',
  401. ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
  402. PrintConv => \%errorCorrection,
  403. },
  404. 32 => {
  405. Name => 'TimeOffset',
  406. Format => 'int64u',
  407. ValueConv => '$val / 1e7',
  408. PrintConv => '"$val s"',
  409. },
  410. 48 => {
  411. Name => 'StreamNumber',
  412. Format => 'int16u',
  413. PrintConv => '($val & 0x7f) . ($val & 0x8000 ? " (encrypted)" : "")',
  414. },
  415. 54 => [
  416. {
  417. Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
  418. Name => 'AudioCodecID',
  419. Format => 'int16u',
  420. PrintHex => 1,
  421. SeparateTable => 'RIFF AudioEncoding',
  422. PrintConv => \%Image::ExifTool::RIFF::audioEncoding,
  423. },
  424. {
  425. Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/',
  426. Name => 'ImageWidth',
  427. Format => 'int32u',
  428. },
  429. ],
  430. 56 => {
  431. Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
  432. Name => 'AudioChannels',
  433. Format => 'int16u',
  434. },
  435. 58 => [
  436. {
  437. Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
  438. Name => 'AudioSampleRate',
  439. Format => 'int32u',
  440. },
  441. {
  442. Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/',
  443. Name => 'ImageHeight',
  444. Format => 'int32u',
  445. },
  446. ],
  447. );
  448. %Image::ExifTool::ASF::HeaderExtension = (
  449. PROCESS_PROC => \&ProcessASF,
  450. '14E6A5CB-C672-4332-8399-A96952065B5A' => 'ExtendedStreamProps',
  451. 'A08649CF-4775-4670-8A16-6E35357566CD' => 'AdvancedMutualExcl',
  452. 'D1465A40-5A79-4338-B71B-E36B8FD6C249' => 'GroupMutualExclusion',
  453. 'D4FED15B-88D3-454F-81F0-ED5C45999E24' => 'StreamPrioritization',
  454. 'A69609E6-517B-11D2-B6AF-00C04FD908E9' => 'BandwidthSharing',
  455. '7C4346A9-EFE0-4BFC-B229-393EDE415C85' => 'LanguageList',
  456. 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA' => {
  457. Name => 'Metadata',
  458. SubDirectory => {
  459. # have seen some tags same as ExtendedDescr, so use this table - PH
  460. TagTable => 'Image::ExifTool::ASF::ExtendedDescr',
  461. ProcessProc => \&ProcessMetadata,
  462. },
  463. },
  464. '44231C94-9498-49D1-A141-1D134E457054' => {
  465. Name => 'MetadataLibrary',
  466. SubDirectory => {
  467. # have seen some tags same as ExtendedDescr, so use this table - PH
  468. TagTable => 'Image::ExifTool::ASF::ExtendedDescr',
  469. ProcessProc => \&ProcessMetadata,
  470. },
  471. },
  472. 'D6E229DF-35DA-11D1-9034-00A0C90349BE' => 'IndexParameters',
  473. '6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7' => 'TimecodeIndexParms',
  474. '75B22630-668E-11CF-A6D9-00AA0062CE6C' => 'Compatibility',
  475. '43058533-6981-49E6-9B74-AD12CB86D58C' => 'AdvancedContentEncryption',
  476. 'ABD3D211-A9BA-11cf-8EE6-00C00C205365' => 'Reserved1',
  477. );
  478. %Image::ExifTool::ASF::CodecList = (
  479. PROCESS_PROC => \&ProcessCodecList,
  480. VideoCodecName => {},
  481. VideoCodecDescription => {},
  482. AudioCodecName => {},
  483. AudioCodecDescription => {},
  484. OtherCodecName => {},
  485. OtherCodecDescription => {},
  486. );
  487. #------------------------------------------------------------------------------
  488. # Generate GUID from 16 bytes of binary data
  489. # Inputs: 0) data
  490. # Returns: GUID
  491. sub GetGUID($)
  492. {
  493. # must do some byte swapping
  494. my $val = shift;
  495. return $val unless length($val) == 16;
  496. my $buff = unpack('H*',pack('NnnNN',unpack('VvvNN',$val)));
  497. $buff =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/;
  498. return uc($buff);
  499. }
  500. #------------------------------------------------------------------------------
  501. # Process ASF content description
  502. # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
  503. # Returns: 1 on success
  504. sub ProcessContentDescription($$$)
  505. {
  506. my ($et, $dirInfo, $tagTablePtr) = @_;
  507. my $dataPt = $$dirInfo{DataPt};
  508. my $dirLen = $$dirInfo{DirLen};
  509. return 0 if $dirLen < 10;
  510. my @len = unpack('v5', $$dataPt);
  511. my $pos = 10;
  512. my $tag;
  513. foreach $tag (0..4) {
  514. my $len = shift @len;
  515. next unless $len;
  516. return 0 if $pos + $len > $dirLen;
  517. my $val = $et->Decode(substr($$dataPt,$pos,$len),'UCS2','II');
  518. $et->HandleTag($tagTablePtr, $tag, $val);
  519. $pos += $len;
  520. }
  521. return 1;
  522. }
  523. #------------------------------------------------------------------------------
  524. # Process ASF content branding
  525. # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
  526. # Returns: 1 on success
  527. sub ProcessContentBranding($$$)
  528. {
  529. my ($et, $dirInfo, $tagTablePtr) = @_;
  530. my $dataPt = $$dirInfo{DataPt};
  531. my $dirLen = $$dirInfo{DirLen};
  532. return 0 if $dirLen < 40;
  533. # decode banner image type
  534. $et->HandleTag($tagTablePtr, 0, unpack('V', $$dataPt));
  535. # decode banner image, banner URL and copyright URL
  536. my $pos = 4;
  537. my $tag;
  538. foreach $tag (1..3) {
  539. return 0 if $pos + 4 > $dirLen;
  540. my $size = unpack("x${pos}V", $$dataPt);
  541. $pos += 4;
  542. next unless $size;
  543. return 0 if $pos + $size > $dirLen;
  544. my $val = substr($$dataPt, $pos, $size);
  545. $et->HandleTag($tagTablePtr, $tag, $val);
  546. $pos += $size;
  547. }
  548. return 1;
  549. }
  550. #------------------------------------------------------------------------------
  551. # Read ASF value
  552. # Inputs: 0) ExifTool object ref, 1) data reference, 2) value offset,
  553. # 3) format number, 4) size
  554. # Returns: converted value
  555. sub ReadASF($$$$$)
  556. {
  557. my ($et, $dataPt, $pos, $format, $size) = @_;
  558. my @vals;
  559. if ($format == 0) { # unicode string
  560. $vals[0] = $et->Decode(substr($$dataPt,$pos,$size),'UCS2','II');
  561. } elsif ($format == 2) { # 4-byte boolean
  562. @vals = ReadValue($dataPt, $pos, 'int32u', undef, $size);
  563. foreach (@vals) {
  564. $_ = $_ ? 'True' : 'False';
  565. }
  566. } elsif ($format == 3) { # int32u
  567. @vals = ReadValue($dataPt, $pos, 'int32u', undef, $size);
  568. } elsif ($format == 4) { # int64u
  569. @vals = ReadValue($dataPt, $pos, 'int64u', undef, $size);
  570. } elsif ($format == 5) { # int16u
  571. @vals = ReadValue($dataPt, $pos, 'int16u', undef, $size);
  572. } else { # any other format (including 1, byte array): return raw data
  573. $vals[0] = substr($$dataPt,$pos,$size);
  574. }
  575. return join ' ', @vals;
  576. }
  577. #------------------------------------------------------------------------------
  578. # Process extended content description
  579. # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
  580. # Returns: 1 on success
  581. sub ProcessExtendedContentDescription($$$)
  582. {
  583. my ($et, $dirInfo, $tagTablePtr) = @_;
  584. my $dataPt = $$dirInfo{DataPt};
  585. my $dirLen = $$dirInfo{DirLen};
  586. return 0 if $dirLen < 2;
  587. my $count = Get16u($dataPt, 0);
  588. $et->VerboseDir($dirInfo, $count);
  589. my $pos = 2;
  590. my $i;
  591. for ($i=0; $i<$count; ++$i) {
  592. return 0 if $pos + 6 > $dirLen;
  593. my $nameLen = unpack("x${pos}v", $$dataPt);
  594. $pos += 2;
  595. return 0 if $pos + $nameLen + 4 > $dirLen;
  596. my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin');
  597. $tag =~ s/^WM\///; # remove leading "WM/"
  598. $pos += $nameLen;
  599. my ($dType, $dLen) = unpack("x${pos}v2", $$dataPt);
  600. $pos += 4;
  601. return 0 if $pos + $dLen > $dirLen;
  602. my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen);
  603. $et->HandleTag($tagTablePtr, $tag, $val,
  604. DataPt => $dataPt,
  605. Start => $pos,
  606. Size => $dLen,
  607. );
  608. $pos += $dLen;
  609. }
  610. return 1;
  611. }
  612. #------------------------------------------------------------------------------
  613. # Process ASF metadata library (similar to ProcessExtendedContentDescription above)
  614. # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
  615. # Returns: 1 on success
  616. sub ProcessMetadata($$$)
  617. {
  618. my ($et, $dirInfo, $tagTablePtr) = @_;
  619. my $dataPt = $$dirInfo{DataPt};
  620. my $dirLen = $$dirInfo{DirLen};
  621. return 0 if $dirLen < 2;
  622. my $count = Get16u($dataPt, 0);
  623. $et->VerboseDir($dirInfo, $count);
  624. my $pos = 2;
  625. my $i;
  626. for ($i=0; $i<$count; ++$i) {
  627. return 0 if $pos + 12 > $dirLen;
  628. my ($index, $stream, $nameLen, $dType, $dLen) = unpack("x${pos}v4V", $$dataPt);
  629. $pos += 12;
  630. return 0 if $pos + $nameLen + $dLen > $dirLen;
  631. my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin');
  632. $tag =~ s/^WM\///; # remove leading "WM/"
  633. $pos += $nameLen;
  634. my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen);
  635. $et->HandleTag($tagTablePtr, $tag, $val,
  636. DataPt => $dataPt,
  637. Start => $pos,
  638. Size => $dLen,
  639. );
  640. $pos += $dLen;
  641. }
  642. return 1;
  643. }
  644. #------------------------------------------------------------------------------
  645. # Process WM/Picture preview
  646. # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
  647. # Returns: 1 on success
  648. sub ProcessPicture($$$)
  649. {
  650. my ($et, $dirInfo, $tagTablePtr) = @_;
  651. my $dataPt = $$dirInfo{DataPt};
  652. my $dirStart = $$dirInfo{DirStart};
  653. my $dirLen = $$dirInfo{DirLen};
  654. return 0 unless $dirLen > 9;
  655. # extract picture type and length
  656. my ($type, $picLen) = unpack("x${dirStart}CV", $$dataPt);
  657. $et->VerboseDir('Picture');
  658. $et->HandleTag($tagTablePtr, 0, $type);
  659. # extract mime type and description strings (null-terminated unicode strings)
  660. my $n = $dirLen - 5 - $picLen;
  661. return 0 if $n & 0x01 or $n < 4;
  662. my $str = substr($$dataPt, $dirStart+5, $n);
  663. if ($str =~ /^((?:..)*?)\0\0((?:..)*?)\0\0/s) {
  664. my ($mime, $desc) = ($1, $2);
  665. $et->HandleTag($tagTablePtr, 1, $et->Decode($mime,'UCS2','II'));
  666. $et->HandleTag($tagTablePtr, 2, $et->Decode($desc,'UCS2','II')) if length $desc;
  667. }
  668. $et->HandleTag($tagTablePtr, 3, substr($$dataPt, $dirStart+5+$n, $picLen));
  669. return 1;
  670. }
  671. #------------------------------------------------------------------------------
  672. # Process codec list
  673. # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
  674. # Returns: 1 on success
  675. sub ProcessCodecList($$$)
  676. {
  677. my ($et, $dirInfo, $tagTablePtr) = @_;
  678. my $dataPt = $$dirInfo{DataPt};
  679. my $dirLen = $$dirInfo{DirLen};
  680. return 0 if $dirLen < 20;
  681. my $count = Get32u($dataPt, 16);
  682. $et->VerboseDir($dirInfo, $count);
  683. my $pos = 20;
  684. my $i;
  685. my %codecType = ( 1 => 'Video', 2 => 'Audio' );
  686. for ($i=0; $i<$count; ++$i) {
  687. return 0 if $pos + 8 > $dirLen;
  688. my $type = ($codecType{Get16u($dataPt, $pos)} || 'Other') . 'Codec';
  689. # stupid Windows programmers: these lengths are in characters (others are in bytes)
  690. my $nameLen = Get16u($dataPt, $pos + 2) * 2;
  691. $pos += 4;
  692. return 0 if $pos + $nameLen + 2 > $dirLen;
  693. my $name = $et->Decode(substr($$dataPt,$pos,$nameLen),'UCS2','II');
  694. $et->HandleTag($tagTablePtr, "${type}Name", $name);
  695. my $descLen = Get16u($dataPt, $pos + $nameLen) * 2;
  696. $pos += $nameLen + 2;
  697. return 0 if $pos + $descLen + 2 > $dirLen;
  698. my $desc = $et->Decode(substr($$dataPt,$pos,$descLen),'UCS2','II');
  699. $et->HandleTag($tagTablePtr, "${type}Description", $desc);
  700. my $infoLen = Get16u($dataPt, $pos + $descLen);
  701. $pos += $descLen + 2 + $infoLen;
  702. }
  703. return 1;
  704. }
  705. #------------------------------------------------------------------------------
  706. # Extract information from a ASF file
  707. # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
  708. # Returns: 1 on success, 0 if this wasn't a valid ASF file
  709. sub ProcessASF($$;$)
  710. {
  711. my ($et, $dirInfo, $tagTablePtr) = @_;
  712. my $raf = $$dirInfo{RAF};
  713. my $verbose = $et->Options('Verbose');
  714. my $rtnVal = 0;
  715. my $pos = 0;
  716. my ($buff, $err, @parentTable, @childEnd);
  717. for (;;) {
  718. last unless $raf->Read($buff, 24) == 24;
  719. $pos += 24;
  720. my $tag = GetGUID(substr($buff,0,16));
  721. unless ($tagTablePtr) {
  722. # verify this is a valid ASF file
  723. last unless $tag eq '75B22630-668E-11CF-A6D9-00AA0062CE6C';
  724. my $fileType = $$et{FILE_EXT};
  725. $fileType = 'ASF' unless $fileType and $fileType =~ /^(ASF|WMV|WMA|DIVX)$/;
  726. $et->SetFileType($fileType);
  727. SetByteOrder('II');
  728. $tagTablePtr = GetTagTable('Image::ExifTool::ASF::Main');
  729. $rtnVal = 1;
  730. }
  731. my $size = Image::ExifTool::Get64u(\$buff, 16) - 24;
  732. if ($size < 0) {
  733. $err = 'Invalid ASF object size';
  734. last;
  735. }
  736. if ($size > 0x7fffffff) {
  737. if ($size > 0x7fffffff * 4294967296) {
  738. $err = 'Invalid ASF object size';
  739. } elsif ($et->Options('LargeFileSupport')) {
  740. if ($raf->Seek($size, 1)) {
  741. $et->VPrint(0, " Skipped large ASF object ($size bytes)\n");
  742. $pos += $size;
  743. next;
  744. }
  745. $err = 'Error seeking past large ASF object';
  746. } else {
  747. $err = 'Large ASF objects not supported (LargeFileSupport not set)';
  748. }
  749. last;
  750. }
  751. # go back to parent tag table if done with previous children
  752. if (@childEnd and $pos >= $childEnd[-1]) {
  753. pop @childEnd;
  754. $tagTablePtr = pop @parentTable;
  755. $$et{INDENT} = substr($$et{INDENT},0,-2);
  756. }
  757. my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
  758. $verbose and $et->VerboseInfo($tag, $tagInfo);
  759. if ($tagInfo) {
  760. my $subdir = $$tagInfo{SubDirectory};
  761. if ($subdir) {
  762. my $subTable = GetTagTable($$subdir{TagTable});
  763. if ($$subTable{PROCESS_PROC} eq \&ProcessASF) {
  764. if (defined $$subdir{Size}) {
  765. my $s = $$subdir{Size};
  766. if ($verbose > 2) {
  767. $raf->Read($buff, $s) == $s or $err = 'Truncated file', last;
  768. $et->VerboseDump(\$buff);
  769. } elsif (not $raf->Seek($s, 1)) {
  770. $err = 'Seek error';
  771. last;
  772. }
  773. # continue processing linearly using subTable
  774. push @parentTable, $tagTablePtr;
  775. push @childEnd, $pos + $size;
  776. $tagTablePtr = $subTable;
  777. $pos += $$subdir{Size};
  778. if ($verbose) {
  779. $$et{INDENT} .= '| ';
  780. $et->VerboseDir($$tagInfo{Name});
  781. }
  782. next;
  783. }
  784. } elsif ($raf->Read($buff, $size) == $size) {
  785. my %subdirInfo = (
  786. DataPt => \$buff,
  787. DirStart => 0,
  788. DirLen => $size,
  789. DirName => $$tagInfo{Name},
  790. );
  791. $et->VerboseDump(\$buff) if $verbose > 2;
  792. unless ($et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc})) {
  793. $et->Warn("Error processing $$tagInfo{Name} directory");
  794. }
  795. $pos += $size;
  796. next;
  797. } else {
  798. $err = 'Unexpected end of file';
  799. last;
  800. }
  801. }
  802. }
  803. if ($verbose > 2) {
  804. $raf->Read($buff, $size) == $size or $err = 'Truncated file', last;
  805. $et->VerboseDump(\$buff);
  806. } elsif (not $raf->Seek($size, 1)) { # skip the block
  807. $err = 'Seek error';
  808. last;
  809. }
  810. $pos += $size;
  811. }
  812. $err and $et->Warn($err);
  813. return $rtnVal;
  814. }
  815. 1; # end
  816. __END__
  817. =head1 NAME
  818. Image::ExifTool::ASF - Read ASF/WMA/WMV meta information
  819. =head1 SYNOPSIS
  820. This module is used by Image::ExifTool
  821. =head1 DESCRIPTION
  822. This module contains routines required by Image::ExifTool to extract
  823. information from Microsoft Advanced Systems Format (ASF) files, including
  824. Windows Media Audio (WMA) and Windows Media Video (WMV) files.
  825. =head1 AUTHOR
  826. Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
  827. This library is free software; you can redistribute it and/or modify it
  828. under the same terms as Perl itself.
  829. =head1 REFERENCES
  830. =over 4
  831. =item L<http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx>
  832. =back
  833. =head1 SEE ALSO
  834. L<Image::ExifTool::TagNames/ASF Tags>,
  835. L<Image::ExifTool(3pm)|Image::ExifTool>
  836. =cut