899 lines
30 KiB
Perl
899 lines
30 KiB
Perl
|
#------------------------------------------------------------------------------
|
||
|
# File: ASF.pm
|
||
|
#
|
||
|
# Description: Read ASF/WMA/WMV meta information
|
||
|
#
|
||
|
# Revisions: 12/23/2005 - P. Harvey Created
|
||
|
#
|
||
|
# References: 1) http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx
|
||
|
# 2) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
|
||
|
#------------------------------------------------------------------------------
|
||
|
|
||
|
package Image::ExifTool::ASF;
|
||
|
|
||
|
use strict;
|
||
|
use vars qw($VERSION);
|
||
|
use Image::ExifTool qw(:DataAccess :Utils);
|
||
|
use Image::ExifTool::Exif;
|
||
|
use Image::ExifTool::RIFF;
|
||
|
|
||
|
$VERSION = '1.25';
|
||
|
|
||
|
sub ProcessASF($$;$);
|
||
|
sub ProcessContentDescription($$$);
|
||
|
sub ProcessExtendedContentDescription($$$);
|
||
|
sub ProcessMetadata($$$);
|
||
|
sub ProcessPicture($$$);
|
||
|
sub ProcessCodecList($$$);
|
||
|
|
||
|
# GUID definitions
|
||
|
my %errorCorrection = (
|
||
|
'20FB5700-5B55-11CF-A8FD-00805F5C442B' => 'No Error Correction',
|
||
|
'BFC3CD50-618F-11CF-8BB2-00AA00B4E220' => 'Audio Spread',
|
||
|
);
|
||
|
|
||
|
my %streamType = (
|
||
|
'F8699E40-5B4D-11CF-A8FD-00805F5C442B' => 'Audio',
|
||
|
'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B' => 'Video',
|
||
|
'59DACFC0-59E6-11D0-A3AC-00A0C90348F6' => 'Command',
|
||
|
'B61BE100-5B4E-11CF-A8FD-00805F5C442B' => 'JFIF',
|
||
|
'35907DE0-E415-11CF-A917-00805F5C442B' => 'Degradable JPEG',
|
||
|
'91BD222C-F21C-497A-8B6D-5AA86BFC0185' => 'File Transfer',
|
||
|
'3AFB65E2-47EF-40F2-AC2C-70A90D71D343' => 'Binary',
|
||
|
);
|
||
|
|
||
|
my %mutex = (
|
||
|
'D6E22A00-35DA-11D1-9034-00A0C90349BE' => 'MutexLanguage',
|
||
|
'D6E22A01-35DA-11D1-9034-00A0C90349BE' => 'MutexBitrate',
|
||
|
'D6E22A02-35DA-11D1-9034-00A0C90349BE' => 'MutexUnknown',
|
||
|
);
|
||
|
|
||
|
my %bandwidthSharing = (
|
||
|
'AF6060AA-5197-11D2-B6AF-00C04FD908E9' => 'SharingExclusive',
|
||
|
'AF6060AB-5197-11D2-B6AF-00C04FD908E9' => 'SharingPartial',
|
||
|
);
|
||
|
|
||
|
my %typeSpecific = (
|
||
|
'776257D4-C627-41CB-8F81-7AC7FF1C40CC' => 'WebStreamMediaSubtype',
|
||
|
'DA1E6B13-8359-4050-B398-388E965BF00C' => 'WebStreamFormat',
|
||
|
);
|
||
|
|
||
|
my %advancedContentEncryption = (
|
||
|
'7A079BB6-DAA4-4e12-A5CA-91D38DC11A8D' => 'DRMNetworkDevices',
|
||
|
);
|
||
|
|
||
|
# ASF top level objects
|
||
|
%Image::ExifTool::ASF::Main = (
|
||
|
PROCESS_PROC => \&ProcessASF,
|
||
|
NOTES => q{
|
||
|
The ASF format is used by Windows WMA and WMV files, and DIVX videos. Tag
|
||
|
ID's aren't listed because they are huge 128-bit GUID's that would ruin the
|
||
|
formatting of this table.
|
||
|
},
|
||
|
'75B22630-668E-11CF-A6D9-00AA0062CE6C' => {
|
||
|
Name => 'Header',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::Header', Size => 6 },
|
||
|
},
|
||
|
'75B22636-668E-11CF-A6D9-00AA0062CE6C' => 'Data',
|
||
|
'33000890-E5B1-11CF-89F4-00A0C90349CB' => 'SimpleIndex',
|
||
|
'D6E229D3-35DA-11D1-9034-00A0C90349BE' => 'Index',
|
||
|
'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C' => 'MediaIndex',
|
||
|
'3CB73FD0-0C4A-4803-953D-EDF7B6228F0C' => 'TimecodeIndex',
|
||
|
'BE7ACFCB-97A9-42E8-9C71-999491E3AFAC' => { #2
|
||
|
Name => 'XMP',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
|
||
|
},
|
||
|
);
|
||
|
|
||
|
# ASF header objects
|
||
|
%Image::ExifTool::ASF::Header = (
|
||
|
PROCESS_PROC => \&ProcessASF,
|
||
|
'8CABDCA1-A947-11CF-8EE4-00C00C205365' => {
|
||
|
Name => 'FileProperties',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::FileProperties' },
|
||
|
},
|
||
|
'B7DC0791-A9B7-11CF-8EE6-00C00C205365' => {
|
||
|
Name => 'StreamProperties',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::StreamProperties' },
|
||
|
},
|
||
|
'5FBF03B5-A92E-11CF-8EE3-00C00C205365' => {
|
||
|
Name => 'HeaderExtension',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::HeaderExtension', Size => 22 },
|
||
|
},
|
||
|
'86D15240-311D-11D0-A3A4-00A0C90348F6' => {
|
||
|
Name => 'CodecList',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::CodecList' },
|
||
|
},
|
||
|
'1EFB1A30-0B62-11D0-A39B-00A0C90348F6' => 'ScriptCommand',
|
||
|
'F487CD01-A951-11CF-8EE6-00C00C205365' => 'Marker',
|
||
|
'D6E229DC-35DA-11D1-9034-00A0C90349BE' => 'BitrateMutualExclusion',
|
||
|
'75B22635-668E-11CF-A6D9-00AA0062CE6C' => 'ErrorCorrection',
|
||
|
'75B22633-668E-11CF-A6D9-00AA0062CE6C' => {
|
||
|
Name => 'ContentDescription',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentDescr' },
|
||
|
},
|
||
|
'2211B3FA-BD23-11D2-B4B7-00A0C955FC6E' => {
|
||
|
Name => 'ContentBranding',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::ContentBranding' },
|
||
|
},
|
||
|
'D2D0A440-E307-11D2-97F0-00A0C95EA850' => {
|
||
|
Name => 'ExtendedContentDescr',
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::ASF::ExtendedDescr' },
|
||
|
},
|
||
|
'7BF875CE-468D-11D1-8D82-006097C9A2B2' => 'StreamBitrateProps',
|
||
|
'2211B3FB-BD23-11D2-B4B7-00A0C955FC6E' => 'ContentEncryption',
|
||
|
'298AE614-2622-4C17-B935-DAE07EE9289C' => 'ExtendedContentEncryption',
|
||
|
'2211B3FC-BD23-11D2-B4B7-00A0C955FC6E' => 'DigitalSignature',
|
||
|
'1806D474-CADF-4509-A4BA-9AABCB96AAE8' => 'Padding',
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::ContentDescr = (
|
||
|
PROCESS_PROC => \&ProcessContentDescription,
|
||
|
GROUPS => { 2 => 'Video' },
|
||
|
0 => 'Title',
|
||
|
1 => { Name => 'Author', Groups => { 2 => 'Author' } },
|
||
|
2 => { Name => 'Copyright', Groups => { 2 => 'Author' } },
|
||
|
3 => 'Description',
|
||
|
4 => 'Rating',
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::ContentBranding = (
|
||
|
PROCESS_PROC => \&ProcessContentBranding,
|
||
|
GROUPS => { 2 => 'Author' },
|
||
|
0 => {
|
||
|
Name => 'BannerImageType',
|
||
|
PrintConv => {
|
||
|
0 => 'None',
|
||
|
1 => 'Bitmap',
|
||
|
2 => 'JPEG',
|
||
|
3 => 'GIF',
|
||
|
},
|
||
|
},
|
||
|
1 => { Name => 'BannerImage', Groups => { 2 => 'Preview' }, Binary => 1 },
|
||
|
2 => 'BannerImageURL',
|
||
|
3 => 'CopyrightURL',
|
||
|
);
|
||
|
|
||
|
# Note: Many of these tags are similar to those in Image::ExifTool::Microsoft::Xtra
|
||
|
# and Image::ExifTool::WTV::Metadata
|
||
|
# (tags in this table may have a leading "WM/" removed)
|
||
|
%Image::ExifTool::ASF::ExtendedDescr = (
|
||
|
PROCESS_PROC => \&ProcessExtendedContentDescription,
|
||
|
GROUPS => { 2 => 'Video' },
|
||
|
ASFLeakyBucketPairs => { Binary => 1 },
|
||
|
AspectRatioX => {},
|
||
|
AspectRatioY => {},
|
||
|
Author => { Groups => { 2 => 'Author' } },
|
||
|
AverageLevel => {},
|
||
|
BannerImageData => {},
|
||
|
BannerImageType => {},
|
||
|
BannerImageURL => {},
|
||
|
Bitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||
|
Broadcast => {},
|
||
|
BufferAverage => {},
|
||
|
Can_Skip_Backward => {},
|
||
|
Can_Skip_Forward => {},
|
||
|
Copyright => { Groups => { 2 => 'Author' } },
|
||
|
CopyrightURL => { Groups => { 2 => 'Author' } },
|
||
|
CurrentBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||
|
Description => {},
|
||
|
DRM_ContentID => {},
|
||
|
DRM_DRMHeader_ContentDistributor => {},
|
||
|
DRM_DRMHeader_ContentID => {},
|
||
|
DRM_DRMHeader_IndividualizedVersion => {},
|
||
|
DRM_DRMHeader_KeyID => {},
|
||
|
DRM_DRMHeader_LicenseAcqURL => {},
|
||
|
DRM_DRMHeader_SubscriptionContentID => {},
|
||
|
DRM_DRMHeader => {},
|
||
|
DRM_IndividualizedVersion => {},
|
||
|
DRM_KeyID => {},
|
||
|
DRM_LASignatureCert => {},
|
||
|
DRM_LASignatureLicSrvCert => {},
|
||
|
DRM_LASignaturePrivKey => {},
|
||
|
DRM_LASignatureRootCert => {},
|
||
|
DRM_LicenseAcqURL => {},
|
||
|
DRM_V1LicenseAcqURL => {},
|
||
|
Duration => { PrintConv => 'ConvertDuration($val)' },
|
||
|
FileSize => {},
|
||
|
HasArbitraryDataStream => {},
|
||
|
HasAttachedImages => {},
|
||
|
HasAudio => {},
|
||
|
HasFileTransferStream => {},
|
||
|
HasImage => {},
|
||
|
HasScript => {},
|
||
|
HasVideo => {},
|
||
|
Is_Protected => {},
|
||
|
Is_Trusted => {},
|
||
|
IsVBR => {},
|
||
|
NSC_Address => {},
|
||
|
NSC_Description => {},
|
||
|
NSC_Email => {},
|
||
|
NSC_Name => {},
|
||
|
NSC_Phone => {},
|
||
|
NumberOfFrames => {},
|
||
|
OptimalBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||
|
PeakValue => {},
|
||
|
Rating => {},
|
||
|
Seekable => {},
|
||
|
Signature_Name => {},
|
||
|
Stridable => {},
|
||
|
Title => {},
|
||
|
VBRPeak => {},
|
||
|
# "WM/" tags...
|
||
|
AlbumArtist => {},
|
||
|
AlbumCoverURL => {},
|
||
|
AlbumTitle => {},
|
||
|
ASFPacketCount => {},
|
||
|
ASFSecurityObjectsSize => {},
|
||
|
AudioFileURL => {},
|
||
|
AudioSourceURL => {},
|
||
|
AuthorURL => { Groups => { 2 => 'Author' } },
|
||
|
BeatsPerMinute => {},
|
||
|
Category => {},
|
||
|
Codec => {},
|
||
|
Composer => {},
|
||
|
Conductor => {},
|
||
|
ContainerFormat => {},
|
||
|
ContentDistributor => {},
|
||
|
ContentGroupDescription => {},
|
||
|
Director => {},
|
||
|
DRM => {},
|
||
|
DVDID => {},
|
||
|
EncodedBy => {},
|
||
|
EncodingSettings => {},
|
||
|
EncodingTime => { Groups => { 2 => 'Time' }, PrintConv => '$self->ConvertDateTime($val)' },
|
||
|
Genre => {},
|
||
|
GenreID => {},
|
||
|
InitialKey => {},
|
||
|
ISRC => {},
|
||
|
Language => {},
|
||
|
Lyrics => {},
|
||
|
Lyrics_Synchronised => {},
|
||
|
MCDI => {},
|
||
|
MediaClassPrimaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' },
|
||
|
MediaClassSecondaryID => { ValueConv => 'Image::ExifTool::ASF::GetGUID($val)' },
|
||
|
MediaCredits => {},
|
||
|
MediaIsDelay => {},
|
||
|
MediaIsFinale => {},
|
||
|
MediaIsLive => {},
|
||
|
MediaIsPremiere => {},
|
||
|
MediaIsRepeat => {},
|
||
|
MediaIsSAP => {},
|
||
|
MediaIsStereo => {},
|
||
|
MediaIsSubtitled => {},
|
||
|
MediaIsTape => {},
|
||
|
MediaNetworkAffiliation => {},
|
||
|
MediaOriginalBroadcastDateTime => {
|
||
|
Groups => { 2 => 'Time' },
|
||
|
ValueConv => '$val=~tr/-T/: /; $val',
|
||
|
PrintConv => '$self->ConvertDateTime($val)',
|
||
|
},
|
||
|
MediaOriginalChannel => {},
|
||
|
MediaStationCallSign => {},
|
||
|
MediaStationName => {},
|
||
|
ModifiedBy => {},
|
||
|
Mood => {},
|
||
|
OriginalAlbumTitle => {},
|
||
|
OriginalArtist => {},
|
||
|
OriginalFilename => 'OriginalFileName',
|
||
|
OriginalLyricist => {},
|
||
|
OriginalReleaseTime => {
|
||
|
Groups => { 2 => 'Time' },
|
||
|
ValueConv => '$val=~tr/-T/: /; $val',
|
||
|
PrintConv => '$self->ConvertDateTime($val)',
|
||
|
},
|
||
|
OriginalReleaseYear => { Groups => { 2 => 'Time' } },
|
||
|
ParentalRating => {},
|
||
|
ParentalRatingReason => {},
|
||
|
PartOfSet => {},
|
||
|
PeakBitrate => { PrintConv => 'ConvertBitrate($val)' },
|
||
|
Period => {},
|
||
|
Picture => {
|
||
|
SubDirectory => {
|
||
|
TagTable => 'Image::ExifTool::ASF::Picture',
|
||
|
},
|
||
|
},
|
||
|
PlaylistDelay => {},
|
||
|
Producer => {},
|
||
|
PromotionURL => {},
|
||
|
ProtectionType => {},
|
||
|
Provider => {},
|
||
|
ProviderCopyright => {},
|
||
|
ProviderRating => {},
|
||
|
ProviderStyle => {},
|
||
|
Publisher => {},
|
||
|
RadioStationName => {},
|
||
|
RadioStationOwner => {},
|
||
|
SharedUserRating => {},
|
||
|
StreamTypeInfo => {},
|
||
|
SubscriptionContentID => {},
|
||
|
SubTitle => 'Subtitle',
|
||
|
SubTitleDescription => 'SubtitleDescription',
|
||
|
Text => {},
|
||
|
ToolName => {},
|
||
|
ToolVersion => {},
|
||
|
Track => {},
|
||
|
TrackNumber => {},
|
||
|
UniqueFileIdentifier => {},
|
||
|
UserWebURL => {},
|
||
|
VideoClosedCaptioning => {},
|
||
|
VideoFrameRate => {},
|
||
|
VideoHeight => {},
|
||
|
VideoWidth => {},
|
||
|
WMADRCAverageReference => {},
|
||
|
WMADRCAverageTarget => {},
|
||
|
WMADRCPeakReference => {},
|
||
|
WMADRCPeakTarget => {},
|
||
|
WMCollectionGroupID => {},
|
||
|
WMCollectionID => {},
|
||
|
WMContentID => {},
|
||
|
Writer => { Groups => { 2 => 'Author' } },
|
||
|
Year => { Groups => { 2 => 'Time' } },
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::Picture = (
|
||
|
PROCESS_PROC => \&ProcessPicture,
|
||
|
GROUPS => { 2 => 'Image' },
|
||
|
0 => {
|
||
|
Name => 'PictureType',
|
||
|
PrintConv => { # (Note: Duplicated in ID3, ASF and FLAC modules!)
|
||
|
0 => 'Other',
|
||
|
1 => '32x32 PNG Icon',
|
||
|
2 => 'Other Icon',
|
||
|
3 => 'Front Cover',
|
||
|
4 => 'Back Cover',
|
||
|
5 => 'Leaflet',
|
||
|
6 => 'Media',
|
||
|
7 => 'Lead Artist',
|
||
|
8 => 'Artist',
|
||
|
9 => 'Conductor',
|
||
|
10 => 'Band',
|
||
|
11 => 'Composer',
|
||
|
12 => 'Lyricist',
|
||
|
13 => 'Recording Studio or Location',
|
||
|
14 => 'Recording Session',
|
||
|
15 => 'Performance',
|
||
|
16 => 'Capture from Movie or Video',
|
||
|
17 => 'Bright(ly) Colored Fish',
|
||
|
18 => 'Illustration',
|
||
|
19 => 'Band Logo',
|
||
|
20 => 'Publisher Logo',
|
||
|
},
|
||
|
},
|
||
|
1 => 'PictureMIMEType',
|
||
|
2 => 'PictureDescription',
|
||
|
3 => {
|
||
|
Name => 'Picture',
|
||
|
Groups => { 2 => 'Preview' },
|
||
|
Binary => 1,
|
||
|
},
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::FileProperties = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
GROUPS => { 2 => 'Video' },
|
||
|
0 => {
|
||
|
Name => 'FileID',
|
||
|
Format => 'binary[16]',
|
||
|
ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
|
||
|
},
|
||
|
16 => { Name => 'FileLength', Format => 'int64u' },
|
||
|
24 => {
|
||
|
Name => 'CreationDate',
|
||
|
Format => 'int64u',
|
||
|
Groups => { 2 => 'Time' },
|
||
|
# time is in 100 ns intervals since 0:00 UTC Jan 1, 1601
|
||
|
ValueConv => q{ # (89 leap years between 1601 and 1970)
|
||
|
my $t = $val / 1e7 - (((1970-1601)*365+89)*24*3600);
|
||
|
return Image::ExifTool::ConvertUnixTime($t) . 'Z';
|
||
|
},
|
||
|
PrintConv => '$self->ConvertDateTime($val)',
|
||
|
},
|
||
|
32 => { Name => 'DataPackets', Format => 'int64u' },
|
||
|
40 => {
|
||
|
Name => 'Duration',
|
||
|
Format => 'int64u',
|
||
|
Notes => 'called PlayDuration by the ASF spec',
|
||
|
Priority => 0,
|
||
|
ValueConv => '$val / 1e7',
|
||
|
PrintConv => 'ConvertDuration($val)',
|
||
|
},
|
||
|
48 => {
|
||
|
Name => 'SendDuration',
|
||
|
Format => 'int64u',
|
||
|
ValueConv => '$val / 1e7',
|
||
|
PrintConv => 'ConvertDuration($val)',
|
||
|
},
|
||
|
56 => { Name => 'Preroll', Format => 'int64u' },
|
||
|
64 => { Name => 'Flags', Format => 'int32u' },
|
||
|
68 => { Name => 'MinPacketSize',Format => 'int32u' },
|
||
|
72 => { Name => 'MaxPacketSize',Format => 'int32u' },
|
||
|
76 => { Name => 'MaxBitrate', Format => 'int32u', PrintConv => 'ConvertBitrate($val)' },
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::StreamProperties = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
GROUPS => { 2 => 'Video' },
|
||
|
NOTES => 'Tags with index 54 and greater are conditional based on the StreamType.',
|
||
|
0 => {
|
||
|
Name => 'StreamType',
|
||
|
Format => 'binary[16]',
|
||
|
RawConv => sub { # set ASF_STREAM_TYPE for use in conditional tags
|
||
|
my ($val, $et) = @_;
|
||
|
$$et{ASF_STREAM_TYPE} = $streamType{GetGUID($val)} || '';
|
||
|
return $val;
|
||
|
},
|
||
|
ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
|
||
|
PrintConv => \%streamType,
|
||
|
},
|
||
|
16 => {
|
||
|
Name => 'ErrorCorrectionType',
|
||
|
Format => 'binary[16]',
|
||
|
ValueConv => 'Image::ExifTool::ASF::GetGUID($val)',
|
||
|
PrintConv => \%errorCorrection,
|
||
|
},
|
||
|
32 => {
|
||
|
Name => 'TimeOffset',
|
||
|
Format => 'int64u',
|
||
|
ValueConv => '$val / 1e7',
|
||
|
PrintConv => '"$val s"',
|
||
|
},
|
||
|
48 => {
|
||
|
Name => 'StreamNumber',
|
||
|
Format => 'int16u',
|
||
|
PrintConv => '($val & 0x7f) . ($val & 0x8000 ? " (encrypted)" : "")',
|
||
|
},
|
||
|
54 => [
|
||
|
{
|
||
|
Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
|
||
|
Name => 'AudioCodecID',
|
||
|
Format => 'int16u',
|
||
|
PrintHex => 1,
|
||
|
SeparateTable => 'RIFF AudioEncoding',
|
||
|
PrintConv => \%Image::ExifTool::RIFF::audioEncoding,
|
||
|
},
|
||
|
{
|
||
|
Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/',
|
||
|
Name => 'ImageWidth',
|
||
|
Format => 'int32u',
|
||
|
},
|
||
|
],
|
||
|
56 => {
|
||
|
Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
|
||
|
Name => 'AudioChannels',
|
||
|
Format => 'int16u',
|
||
|
},
|
||
|
58 => [
|
||
|
{
|
||
|
Condition => '$self->{ASF_STREAM_TYPE} eq "Audio"',
|
||
|
Name => 'AudioSampleRate',
|
||
|
Format => 'int32u',
|
||
|
},
|
||
|
{
|
||
|
Condition => '$self->{ASF_STREAM_TYPE} =~ /^(Video|JFIF|Degradable JPEG)$/',
|
||
|
Name => 'ImageHeight',
|
||
|
Format => 'int32u',
|
||
|
},
|
||
|
],
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::HeaderExtension = (
|
||
|
PROCESS_PROC => \&ProcessASF,
|
||
|
'14E6A5CB-C672-4332-8399-A96952065B5A' => 'ExtendedStreamProps',
|
||
|
'A08649CF-4775-4670-8A16-6E35357566CD' => 'AdvancedMutualExcl',
|
||
|
'D1465A40-5A79-4338-B71B-E36B8FD6C249' => 'GroupMutualExclusion',
|
||
|
'D4FED15B-88D3-454F-81F0-ED5C45999E24' => 'StreamPrioritization',
|
||
|
'A69609E6-517B-11D2-B6AF-00C04FD908E9' => 'BandwidthSharing',
|
||
|
'7C4346A9-EFE0-4BFC-B229-393EDE415C85' => 'LanguageList',
|
||
|
'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA' => {
|
||
|
Name => 'Metadata',
|
||
|
SubDirectory => {
|
||
|
# have seen some tags same as ExtendedDescr, so use this table - PH
|
||
|
TagTable => 'Image::ExifTool::ASF::ExtendedDescr',
|
||
|
ProcessProc => \&ProcessMetadata,
|
||
|
},
|
||
|
},
|
||
|
'44231C94-9498-49D1-A141-1D134E457054' => {
|
||
|
Name => 'MetadataLibrary',
|
||
|
SubDirectory => {
|
||
|
# have seen some tags same as ExtendedDescr, so use this table - PH
|
||
|
TagTable => 'Image::ExifTool::ASF::ExtendedDescr',
|
||
|
ProcessProc => \&ProcessMetadata,
|
||
|
},
|
||
|
},
|
||
|
'D6E229DF-35DA-11D1-9034-00A0C90349BE' => 'IndexParameters',
|
||
|
'6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7' => 'TimecodeIndexParms',
|
||
|
'75B22630-668E-11CF-A6D9-00AA0062CE6C' => 'Compatibility',
|
||
|
'43058533-6981-49E6-9B74-AD12CB86D58C' => 'AdvancedContentEncryption',
|
||
|
'ABD3D211-A9BA-11cf-8EE6-00C00C205365' => 'Reserved1',
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::ASF::CodecList = (
|
||
|
PROCESS_PROC => \&ProcessCodecList,
|
||
|
VideoCodecName => {},
|
||
|
VideoCodecDescription => {},
|
||
|
AudioCodecName => {},
|
||
|
AudioCodecDescription => {},
|
||
|
OtherCodecName => {},
|
||
|
OtherCodecDescription => {},
|
||
|
);
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Generate GUID from 16 bytes of binary data
|
||
|
# Inputs: 0) data
|
||
|
# Returns: GUID
|
||
|
sub GetGUID($)
|
||
|
{
|
||
|
# must do some byte swapping
|
||
|
my $val = shift;
|
||
|
return $val unless length($val) == 16;
|
||
|
my $buff = unpack('H*',pack('NnnNN',unpack('VvvNN',$val)));
|
||
|
$buff =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/;
|
||
|
return uc($buff);
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process ASF content description
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessContentDescription($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $dirLen = $$dirInfo{DirLen};
|
||
|
return 0 if $dirLen < 10;
|
||
|
my @len = unpack('v5', $$dataPt);
|
||
|
my $pos = 10;
|
||
|
my $tag;
|
||
|
foreach $tag (0..4) {
|
||
|
my $len = shift @len;
|
||
|
next unless $len;
|
||
|
return 0 if $pos + $len > $dirLen;
|
||
|
my $val = $et->Decode(substr($$dataPt,$pos,$len),'UCS2','II');
|
||
|
$et->HandleTag($tagTablePtr, $tag, $val);
|
||
|
$pos += $len;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process ASF content branding
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessContentBranding($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $dirLen = $$dirInfo{DirLen};
|
||
|
return 0 if $dirLen < 40;
|
||
|
# decode banner image type
|
||
|
$et->HandleTag($tagTablePtr, 0, unpack('V', $$dataPt));
|
||
|
# decode banner image, banner URL and copyright URL
|
||
|
my $pos = 4;
|
||
|
my $tag;
|
||
|
foreach $tag (1..3) {
|
||
|
return 0 if $pos + 4 > $dirLen;
|
||
|
my $size = unpack("x${pos}V", $$dataPt);
|
||
|
$pos += 4;
|
||
|
next unless $size;
|
||
|
return 0 if $pos + $size > $dirLen;
|
||
|
my $val = substr($$dataPt, $pos, $size);
|
||
|
$et->HandleTag($tagTablePtr, $tag, $val);
|
||
|
$pos += $size;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Read ASF value
|
||
|
# Inputs: 0) ExifTool object ref, 1) data reference, 2) value offset,
|
||
|
# 3) format number, 4) size
|
||
|
# Returns: converted value
|
||
|
sub ReadASF($$$$$)
|
||
|
{
|
||
|
my ($et, $dataPt, $pos, $format, $size) = @_;
|
||
|
my @vals;
|
||
|
if ($format == 0) { # unicode string
|
||
|
$vals[0] = $et->Decode(substr($$dataPt,$pos,$size),'UCS2','II');
|
||
|
} elsif ($format == 2) { # 4-byte boolean
|
||
|
@vals = ReadValue($dataPt, $pos, 'int32u', undef, $size);
|
||
|
foreach (@vals) {
|
||
|
$_ = $_ ? 'True' : 'False';
|
||
|
}
|
||
|
} elsif ($format == 3) { # int32u
|
||
|
@vals = ReadValue($dataPt, $pos, 'int32u', undef, $size);
|
||
|
} elsif ($format == 4) { # int64u
|
||
|
@vals = ReadValue($dataPt, $pos, 'int64u', undef, $size);
|
||
|
} elsif ($format == 5) { # int16u
|
||
|
@vals = ReadValue($dataPt, $pos, 'int16u', undef, $size);
|
||
|
} else { # any other format (including 1, byte array): return raw data
|
||
|
$vals[0] = substr($$dataPt,$pos,$size);
|
||
|
}
|
||
|
return join ' ', @vals;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process extended content description
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessExtendedContentDescription($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $dirLen = $$dirInfo{DirLen};
|
||
|
return 0 if $dirLen < 2;
|
||
|
my $count = Get16u($dataPt, 0);
|
||
|
$et->VerboseDir($dirInfo, $count);
|
||
|
my $pos = 2;
|
||
|
my $i;
|
||
|
for ($i=0; $i<$count; ++$i) {
|
||
|
return 0 if $pos + 6 > $dirLen;
|
||
|
my $nameLen = unpack("x${pos}v", $$dataPt);
|
||
|
$pos += 2;
|
||
|
return 0 if $pos + $nameLen + 4 > $dirLen;
|
||
|
my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin');
|
||
|
$tag =~ s/^WM\///; # remove leading "WM/"
|
||
|
$pos += $nameLen;
|
||
|
my ($dType, $dLen) = unpack("x${pos}v2", $$dataPt);
|
||
|
$pos += 4;
|
||
|
return 0 if $pos + $dLen > $dirLen;
|
||
|
my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen);
|
||
|
$et->HandleTag($tagTablePtr, $tag, $val,
|
||
|
DataPt => $dataPt,
|
||
|
Start => $pos,
|
||
|
Size => $dLen,
|
||
|
);
|
||
|
$pos += $dLen;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process ASF metadata library (similar to ProcessExtendedContentDescription above)
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessMetadata($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $dirLen = $$dirInfo{DirLen};
|
||
|
return 0 if $dirLen < 2;
|
||
|
my $count = Get16u($dataPt, 0);
|
||
|
$et->VerboseDir($dirInfo, $count);
|
||
|
my $pos = 2;
|
||
|
my $i;
|
||
|
for ($i=0; $i<$count; ++$i) {
|
||
|
return 0 if $pos + 12 > $dirLen;
|
||
|
my ($index, $stream, $nameLen, $dType, $dLen) = unpack("x${pos}v4V", $$dataPt);
|
||
|
$pos += 12;
|
||
|
return 0 if $pos + $nameLen + $dLen > $dirLen;
|
||
|
my $tag = Image::ExifTool::Decode(undef,substr($$dataPt,$pos,$nameLen),'UCS2','II','Latin');
|
||
|
$tag =~ s/^WM\///; # remove leading "WM/"
|
||
|
$pos += $nameLen;
|
||
|
my $val = ReadASF($et,$dataPt,$pos,$dType,$dLen);
|
||
|
$et->HandleTag($tagTablePtr, $tag, $val,
|
||
|
DataPt => $dataPt,
|
||
|
Start => $pos,
|
||
|
Size => $dLen,
|
||
|
);
|
||
|
$pos += $dLen;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process WM/Picture preview
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessPicture($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $dirStart = $$dirInfo{DirStart};
|
||
|
my $dirLen = $$dirInfo{DirLen};
|
||
|
return 0 unless $dirLen > 9;
|
||
|
# extract picture type and length
|
||
|
my ($type, $picLen) = unpack("x${dirStart}CV", $$dataPt);
|
||
|
$et->VerboseDir('Picture');
|
||
|
$et->HandleTag($tagTablePtr, 0, $type);
|
||
|
# extract mime type and description strings (null-terminated unicode strings)
|
||
|
my $n = $dirLen - 5 - $picLen;
|
||
|
return 0 if $n & 0x01 or $n < 4;
|
||
|
my $str = substr($$dataPt, $dirStart+5, $n);
|
||
|
if ($str =~ /^((?:..)*?)\0\0((?:..)*?)\0\0/s) {
|
||
|
my ($mime, $desc) = ($1, $2);
|
||
|
$et->HandleTag($tagTablePtr, 1, $et->Decode($mime,'UCS2','II'));
|
||
|
$et->HandleTag($tagTablePtr, 2, $et->Decode($desc,'UCS2','II')) if length $desc;
|
||
|
}
|
||
|
$et->HandleTag($tagTablePtr, 3, substr($$dataPt, $dirStart+5+$n, $picLen));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process codec list
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessCodecList($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $dataPt = $$dirInfo{DataPt};
|
||
|
my $dirLen = $$dirInfo{DirLen};
|
||
|
return 0 if $dirLen < 20;
|
||
|
my $count = Get32u($dataPt, 16);
|
||
|
$et->VerboseDir($dirInfo, $count);
|
||
|
my $pos = 20;
|
||
|
my $i;
|
||
|
my %codecType = ( 1 => 'Video', 2 => 'Audio' );
|
||
|
for ($i=0; $i<$count; ++$i) {
|
||
|
return 0 if $pos + 8 > $dirLen;
|
||
|
my $type = ($codecType{Get16u($dataPt, $pos)} || 'Other') . 'Codec';
|
||
|
# stupid Windows programmers: these lengths are in characters (others are in bytes)
|
||
|
my $nameLen = Get16u($dataPt, $pos + 2) * 2;
|
||
|
$pos += 4;
|
||
|
return 0 if $pos + $nameLen + 2 > $dirLen;
|
||
|
my $name = $et->Decode(substr($$dataPt,$pos,$nameLen),'UCS2','II');
|
||
|
$et->HandleTag($tagTablePtr, "${type}Name", $name);
|
||
|
my $descLen = Get16u($dataPt, $pos + $nameLen) * 2;
|
||
|
$pos += $nameLen + 2;
|
||
|
return 0 if $pos + $descLen + 2 > $dirLen;
|
||
|
my $desc = $et->Decode(substr($$dataPt,$pos,$descLen),'UCS2','II');
|
||
|
$et->HandleTag($tagTablePtr, "${type}Description", $desc);
|
||
|
my $infoLen = Get16u($dataPt, $pos + $descLen);
|
||
|
$pos += $descLen + 2 + $infoLen;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Extract information from a ASF file
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
|
||
|
# Returns: 1 on success, 0 if this wasn't a valid ASF file
|
||
|
sub ProcessASF($$;$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $tagTablePtr) = @_;
|
||
|
my $raf = $$dirInfo{RAF};
|
||
|
my $verbose = $et->Options('Verbose');
|
||
|
my $rtnVal = 0;
|
||
|
my $pos = 0;
|
||
|
my ($buff, $err, @parentTable, @childEnd);
|
||
|
|
||
|
for (;;) {
|
||
|
last unless $raf->Read($buff, 24) == 24;
|
||
|
$pos += 24;
|
||
|
my $tag = GetGUID(substr($buff,0,16));
|
||
|
unless ($tagTablePtr) {
|
||
|
# verify this is a valid ASF file
|
||
|
last unless $tag eq '75B22630-668E-11CF-A6D9-00AA0062CE6C';
|
||
|
my $fileType = $$et{FILE_EXT};
|
||
|
$fileType = 'ASF' unless $fileType and $fileType =~ /^(ASF|WMV|WMA|DIVX)$/;
|
||
|
$et->SetFileType($fileType);
|
||
|
SetByteOrder('II');
|
||
|
$tagTablePtr = GetTagTable('Image::ExifTool::ASF::Main');
|
||
|
$rtnVal = 1;
|
||
|
}
|
||
|
my $size = Image::ExifTool::Get64u(\$buff, 16) - 24;
|
||
|
if ($size < 0) {
|
||
|
$err = 'Invalid ASF object size';
|
||
|
last;
|
||
|
}
|
||
|
if ($size > 0x7fffffff) {
|
||
|
if ($size > 0x7fffffff * 4294967296) {
|
||
|
$err = 'Invalid ASF object size';
|
||
|
} elsif ($et->Options('LargeFileSupport')) {
|
||
|
if ($raf->Seek($size, 1)) {
|
||
|
$et->VPrint(0, " Skipped large ASF object ($size bytes)\n");
|
||
|
$pos += $size;
|
||
|
next;
|
||
|
}
|
||
|
$err = 'Error seeking past large ASF object';
|
||
|
} else {
|
||
|
$err = 'Large ASF objects not supported (LargeFileSupport not set)';
|
||
|
}
|
||
|
last;
|
||
|
}
|
||
|
# go back to parent tag table if done with previous children
|
||
|
if (@childEnd and $pos >= $childEnd[-1]) {
|
||
|
pop @childEnd;
|
||
|
$tagTablePtr = pop @parentTable;
|
||
|
$$et{INDENT} = substr($$et{INDENT},0,-2);
|
||
|
}
|
||
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
|
||
|
$verbose and $et->VerboseInfo($tag, $tagInfo);
|
||
|
if ($tagInfo) {
|
||
|
my $subdir = $$tagInfo{SubDirectory};
|
||
|
if ($subdir) {
|
||
|
my $subTable = GetTagTable($$subdir{TagTable});
|
||
|
if ($$subTable{PROCESS_PROC} eq \&ProcessASF) {
|
||
|
if (defined $$subdir{Size}) {
|
||
|
my $s = $$subdir{Size};
|
||
|
if ($verbose > 2) {
|
||
|
$raf->Read($buff, $s) == $s or $err = 'Truncated file', last;
|
||
|
$et->VerboseDump(\$buff);
|
||
|
} elsif (not $raf->Seek($s, 1)) {
|
||
|
$err = 'Seek error';
|
||
|
last;
|
||
|
}
|
||
|
# continue processing linearly using subTable
|
||
|
push @parentTable, $tagTablePtr;
|
||
|
push @childEnd, $pos + $size;
|
||
|
$tagTablePtr = $subTable;
|
||
|
$pos += $$subdir{Size};
|
||
|
if ($verbose) {
|
||
|
$$et{INDENT} .= '| ';
|
||
|
$et->VerboseDir($$tagInfo{Name});
|
||
|
}
|
||
|
next;
|
||
|
}
|
||
|
} elsif ($raf->Read($buff, $size) == $size) {
|
||
|
my %subdirInfo = (
|
||
|
DataPt => \$buff,
|
||
|
DirStart => 0,
|
||
|
DirLen => $size,
|
||
|
DirName => $$tagInfo{Name},
|
||
|
);
|
||
|
$et->VerboseDump(\$buff) if $verbose > 2;
|
||
|
unless ($et->ProcessDirectory(\%subdirInfo, $subTable, $$subdir{ProcessProc})) {
|
||
|
$et->Warn("Error processing $$tagInfo{Name} directory");
|
||
|
}
|
||
|
$pos += $size;
|
||
|
next;
|
||
|
} else {
|
||
|
$err = 'Unexpected end of file';
|
||
|
last;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ($verbose > 2) {
|
||
|
$raf->Read($buff, $size) == $size or $err = 'Truncated file', last;
|
||
|
$et->VerboseDump(\$buff);
|
||
|
} elsif (not $raf->Seek($size, 1)) { # skip the block
|
||
|
$err = 'Seek error';
|
||
|
last;
|
||
|
}
|
||
|
$pos += $size;
|
||
|
}
|
||
|
$err and $et->Warn($err);
|
||
|
return $rtnVal;
|
||
|
}
|
||
|
|
||
|
1; # end
|
||
|
|
||
|
__END__
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
Image::ExifTool::ASF - Read ASF/WMA/WMV meta information
|
||
|
|
||
|
=head1 SYNOPSIS
|
||
|
|
||
|
This module is used by Image::ExifTool
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
This module contains routines required by Image::ExifTool to extract
|
||
|
information from Microsoft Advanced Systems Format (ASF) files, including
|
||
|
Windows Media Audio (WMA) and Windows Media Video (WMV) files.
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Copyright 2003-2018, Phil Harvey (phil at owl.phy.queensu.ca)
|
||
|
|
||
|
This library is free software; you can redistribute it and/or modify it
|
||
|
under the same terms as Perl itself.
|
||
|
|
||
|
=head1 REFERENCES
|
||
|
|
||
|
=over 4
|
||
|
|
||
|
=item L<http://www.microsoft.com/windows/windowsmedia/format/asfspec.aspx>
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 SEE ALSO
|
||
|
|
||
|
L<Image::ExifTool::TagNames/ASF Tags>,
|
||
|
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||
|
|
||
|
=cut
|
||
|
|