1 | #------------------------------------------------------------------------------
|
---|
2 | # File: Real.pm
|
---|
3 | #
|
---|
4 | # Description: Read Real audio/video meta information
|
---|
5 | #
|
---|
6 | # Revisions: 05/16/2006 - P. Harvey Created
|
---|
7 | #
|
---|
8 | # References: 1) http://www.getid3.org/
|
---|
9 | # 2) https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/htmfiles/rmff.htm
|
---|
10 | #------------------------------------------------------------------------------
|
---|
11 |
|
---|
12 | package Image::ExifTool::Real;
|
---|
13 |
|
---|
14 | use strict;
|
---|
15 | use vars qw($VERSION);
|
---|
16 | use Image::ExifTool qw(:DataAccess :Utils);
|
---|
17 | use Image::ExifTool::Canon;
|
---|
18 |
|
---|
19 | $VERSION = '1.04';
|
---|
20 |
|
---|
21 | sub ProcessRealMeta($$$);
|
---|
22 | sub ProcessRealProperties($$$);
|
---|
23 |
|
---|
24 | # Real property types (ref PH)
|
---|
25 | my %propertyType = (
|
---|
26 | 0 => 'int32u',
|
---|
27 | 2 => 'string',
|
---|
28 | );
|
---|
29 |
|
---|
30 | # Real Metadata property types
|
---|
31 | my %metadataFormat = (
|
---|
32 | 1 => 'string', # text
|
---|
33 | 2 => 'string', # text list
|
---|
34 | 3 => 'flag', # 1 or 4 byte integer
|
---|
35 | 4 => 'int32u', # 4-byte integer
|
---|
36 | 5 => 'undef', # binary data
|
---|
37 | 6 => 'string', # URL
|
---|
38 | 7 => 'string', # date
|
---|
39 | 8 => 'string', # file name
|
---|
40 | 9 => undef, # grouping
|
---|
41 | 10 => undef, # reference
|
---|
42 | );
|
---|
43 |
|
---|
44 | # Real Metadata property flag bit descriptions
|
---|
45 | my %metadataFlag = (
|
---|
46 | 0 => 'Read Only',
|
---|
47 | 1 => 'Private',
|
---|
48 | 2 => 'Type Descriptor',
|
---|
49 | );
|
---|
50 |
|
---|
51 |
|
---|
52 | # tags used in RealMedia (RM, RV and RMVB) files
|
---|
53 | %Image::ExifTool::Real::Media = (
|
---|
54 | GROUPS => { 2 => 'Video' },
|
---|
55 | NOTES => q{
|
---|
56 | These B<Tag ID>'s are Chunk ID's used in RealMedia and RealVideo (RM, RV and
|
---|
57 | RMVB) files.
|
---|
58 | },
|
---|
59 | CONT => { SubDirectory => { TagTable => 'Image::ExifTool::Real::ContentDescr' } },
|
---|
60 | MDPR => { SubDirectory => { TagTable => 'Image::ExifTool::Real::MediaProps' } },
|
---|
61 | PROP => { SubDirectory => { TagTable => 'Image::ExifTool::Real::Properties' } },
|
---|
62 | RJMD => { SubDirectory => { TagTable => 'Image::ExifTool::Real::Metadata' } },
|
---|
63 | );
|
---|
64 |
|
---|
65 | # pseudo-tags used in RealAudio (RA) files
|
---|
66 | %Image::ExifTool::Real::Audio = (
|
---|
67 | GROUPS => { 2 => 'Audio' },
|
---|
68 | NOTES => q{
|
---|
69 | Tags in the following table reference information extracted from various
|
---|
70 | versions of RealAudio (RA) files.
|
---|
71 | },
|
---|
72 | '.ra3' => { Name => 'RA3', SubDirectory => { TagTable => 'Image::ExifTool::Real::AudioV3' } },
|
---|
73 | '.ra4' => { Name => 'RA4', SubDirectory => { TagTable => 'Image::ExifTool::Real::AudioV4' } },
|
---|
74 | '.ra5' => { Name => 'RA5', SubDirectory => { TagTable => 'Image::ExifTool::Real::AudioV5' } },
|
---|
75 | );
|
---|
76 |
|
---|
77 | # pseudo-tags used in RealMedia Metafiles and RealMedia Plug-in Metafiles (RAM and RPM)
|
---|
78 | %Image::ExifTool::Real::Metafile = (
|
---|
79 | GROUPS => { 2 => 'Video' },
|
---|
80 | NOTES => q{
|
---|
81 | Tags representing information extracted from Real Audio Metafile and
|
---|
82 | RealMedia Plug-in Metafile (RAM and RPM) files.
|
---|
83 | },
|
---|
84 | txt => 'Text',
|
---|
85 | url => 'URL',
|
---|
86 | );
|
---|
87 |
|
---|
88 | %Image::ExifTool::Real::Properties = (
|
---|
89 | GROUPS => { 1 => 'Real-PROP', 2 => 'Video' },
|
---|
90 | PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData,
|
---|
91 | VARS => { ID_LABEL => 'Sequence' },
|
---|
92 | FORMAT => 'int32u',
|
---|
93 | 0 => { Name => 'MaxBitrate', PrintConv => 'ConvertBitrate($val)' },
|
---|
94 | 1 => { Name => 'AvgBitrate', PrintConv => 'ConvertBitrate($val)' },
|
---|
95 | 2 => 'MaxPacketSize',
|
---|
96 | 3 => 'AvgPacketSize',
|
---|
97 | 4 => 'NumPackets',
|
---|
98 | 5 => { Name => 'Duration', ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' },
|
---|
99 | 6 => { Name => 'Preroll', ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' },
|
---|
100 | 7 => { Name => 'IndexOffset', Unknown => 1 },
|
---|
101 | 8 => { Name => 'DataOffset', Unknown => 1 },
|
---|
102 | 9 => { Name => 'NumStreams', Format => 'int16u' },
|
---|
103 | 10 => {
|
---|
104 | Name => 'Flags',
|
---|
105 | Format => 'int16u',
|
---|
106 | PrintConv => { BITMASK => {
|
---|
107 | 0 => 'Allow Recording',
|
---|
108 | 1 => 'Perfect Play',
|
---|
109 | 2 => 'Live',
|
---|
110 | 3 => 'Allow Download', #PH (from rmeditor dump)
|
---|
111 | } },
|
---|
112 | },
|
---|
113 | );
|
---|
114 |
|
---|
115 | %Image::ExifTool::Real::MediaProps = (
|
---|
116 | GROUPS => { 1 => 'Real-MDPR', 2 => 'Video' },
|
---|
117 | PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData,
|
---|
118 | VARS => { ID_LABEL => 'Sequence' },
|
---|
119 | FORMAT => 'int32u',
|
---|
120 | PRIORITY => 0, # first stream takes priority
|
---|
121 | 0 => { Name => 'StreamNumber', Format => 'int16u' },
|
---|
122 | 1 => { Name => 'StreamMaxBitrate', PrintConv => 'ConvertBitrate($val)' },
|
---|
123 | 2 => { Name => 'StreamAvgBitrate', PrintConv => 'ConvertBitrate($val)' },
|
---|
124 | 3 => { Name => 'StreamMaxPacketSize' },
|
---|
125 | 4 => { Name => 'StreamAvgPacketSize' },
|
---|
126 | 5 => { Name => 'StreamStartTime' },
|
---|
127 | 6 => { Name => 'StreamPreroll', ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' },
|
---|
128 | 7 => { Name => 'StreamDuration',ValueConv => '$val / 1000', PrintConv => 'ConvertDuration($val)' },
|
---|
129 | 8 => { Name => 'StreamNameLen', Format => 'int8u', Unknown => 1 },
|
---|
130 | 9 => { Name => 'StreamName', Format => 'string[$val{8}]' },
|
---|
131 | 10 => { Name => 'StreamMimeLen', Format => 'int8u', Unknown => 1 },
|
---|
132 | 11 => {
|
---|
133 | Name => 'StreamMimeType',
|
---|
134 | Format => 'string[$val{10}]',
|
---|
135 | RawConv => '$self->{RealStreamMime} = $val',
|
---|
136 | },
|
---|
137 | 12 => { Name => 'FileInfoLen', Unknown => 1 },
|
---|
138 | 13 => {
|
---|
139 | Name => 'FileInfoLen2',
|
---|
140 | # if this condition fails, subsequent tags will not be processed
|
---|
141 | Condition => '$self->{RealStreamMime} eq "logical-fileinfo"',
|
---|
142 | Unknown => 1,
|
---|
143 | },
|
---|
144 | 14 => {
|
---|
145 | Name => 'FileInfoVersion',
|
---|
146 | Format => 'int16u',
|
---|
147 | },
|
---|
148 | 15 => {
|
---|
149 | Name => 'PhysicalStreams',
|
---|
150 | Format => 'int16u',
|
---|
151 | Unknown => 1,
|
---|
152 | },
|
---|
153 | 16 => {
|
---|
154 | Name => 'PhysicalStreamNumbers',
|
---|
155 | Format => 'int16u[$val{15}]',
|
---|
156 | Unknown => 1,
|
---|
157 | },
|
---|
158 | 17 => {
|
---|
159 | Name => 'DataOffsets',
|
---|
160 | Format => 'int32u[$val{15}]',
|
---|
161 | Unknown => 1,
|
---|
162 | },
|
---|
163 | 18 => {
|
---|
164 | Name => 'NumRules',
|
---|
165 | Format => 'int16u',
|
---|
166 | Unknown => 1,
|
---|
167 | },
|
---|
168 | 19 => {
|
---|
169 | Name => 'PhysicalStreamNumberMap',
|
---|
170 | Format => 'int16u[$val{18}]',
|
---|
171 | Unknown => 1,
|
---|
172 | },
|
---|
173 | 20 => {
|
---|
174 | Name => 'NumProperties',
|
---|
175 | Format => 'int16u',
|
---|
176 | Unknown => 1,
|
---|
177 | },
|
---|
178 | 21 => {
|
---|
179 | Name => 'FileInfoProperties',
|
---|
180 | Format => 'undef[$val{13}-$val{15}*6-$val{18}*2-12]',
|
---|
181 | SubDirectory => { TagTable => 'Image::ExifTool::Real::FileInfo' },
|
---|
182 | },
|
---|
183 | );
|
---|
184 |
|
---|
185 | # Observed FileInfo properties (ref PH)
|
---|
186 | %Image::ExifTool::Real::FileInfo = (
|
---|
187 | GROUPS => { 1 => 'Real-MDPR', 2 => 'Video' },
|
---|
188 | PROCESS_PROC => \&ProcessRealProperties,
|
---|
189 | NOTES => q{
|
---|
190 | The following tags have been observed in the FileInfo properties, but any
|
---|
191 | other existing information will also be extracted.
|
---|
192 | },
|
---|
193 | Indexable => { PrintConv => { 0 => 'False', 1 => 'True' } },
|
---|
194 | Keywords => { },
|
---|
195 | Description => { },
|
---|
196 | 'File ID' => { Name => 'FileID' },
|
---|
197 | 'Content Rating' => {
|
---|
198 | Name => 'ContentRating',
|
---|
199 | PrintConv => {
|
---|
200 | 0 => 'No Rating',
|
---|
201 | 1 => 'All Ages',
|
---|
202 | 2 => 'Older Children',
|
---|
203 | 3 => 'Younger Teens',
|
---|
204 | 4 => 'Older Teens',
|
---|
205 | 5 => 'Adult Supervision Recommended',
|
---|
206 | 6 => 'Adults Only',
|
---|
207 | },
|
---|
208 | },
|
---|
209 | Audiences => { },
|
---|
210 | audioMode => { Name => 'AudioMode' },
|
---|
211 | 'Creation Date' => {
|
---|
212 | Name => 'CreateDate',
|
---|
213 | Groups => { 2 => 'Time' },
|
---|
214 | ValueConv => q{
|
---|
215 | $val =~ m{(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)} ?
|
---|
216 | sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",$3,$2,$1,$4,$5,$6) : $val
|
---|
217 | },
|
---|
218 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
219 | },
|
---|
220 | 'Generated By' => { Name => 'Software' },
|
---|
221 | 'Modification Date' => {
|
---|
222 | Name => 'ModifyDate',
|
---|
223 | Groups => { 2 => 'Time' },
|
---|
224 | ValueConv => q{
|
---|
225 | $val =~ m{(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)} ?
|
---|
226 | sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",$3,$2,$1,$4,$5,$6) : $val
|
---|
227 | },
|
---|
228 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
229 | },
|
---|
230 | 'Target Audiences' => { Name => 'TargetAudiences' },
|
---|
231 | 'Audio Format' => { Name => 'AudioFormat' },
|
---|
232 | 'Video Quality' => { Name => 'VideoQuality' },
|
---|
233 | videoMode => { Name => 'VideoMode' },
|
---|
234 | );
|
---|
235 |
|
---|
236 | %Image::ExifTool::Real::ContentDescr = (
|
---|
237 | GROUPS => { 1 => 'Real-CONT', 2 => 'Video' },
|
---|
238 | PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData,
|
---|
239 | VARS => { ID_LABEL => 'Sequence' },
|
---|
240 | FORMAT => 'int16u',
|
---|
241 | 0 => { Name => 'TitleLen', Unknown => 1 },
|
---|
242 | 1 => { Name => 'Title', Format => 'string[$val{0}]' },
|
---|
243 | 2 => { Name => 'AuthorLen', Unknown => 1 },
|
---|
244 | 3 => { Name => 'Author', Format => 'string[$val{2}]', Groups => { 2 => 'Author' } },
|
---|
245 | 4 => { Name => 'CopyrightLen', Unknown => 1 },
|
---|
246 | 5 => { Name => 'Copyright', Format => 'string[$val{4}]', Groups => { 2 => 'Author' } },
|
---|
247 | 6 => { Name => 'CommentLen', Unknown => 1 },
|
---|
248 | 7 => { Name => 'Comment', Format => 'string[$val{6}]' },
|
---|
249 | );
|
---|
250 |
|
---|
251 | # Real RJMD meta information (ref PH)
|
---|
252 | %Image::ExifTool::Real::Metadata = (
|
---|
253 | GROUPS => { 1 => 'Real-RJMD', 2 => 'Video' },
|
---|
254 | PROCESS_PROC => \&ProcessRealMeta,
|
---|
255 | NOTES => q{
|
---|
256 | The tags below represent information which has been observed in the Real
|
---|
257 | Metadata format, but ExifTool will extract any information it finds in this
|
---|
258 | format. (As far as I can tell from the referenced documentation, string
|
---|
259 | values should be plain text, but this is not the case for the only sample
|
---|
260 | file I have been able to obtain containing this information. These tags
|
---|
261 | could also be split into separate sub-directories, but this will wait until
|
---|
262 | I have better documentation or a more complete set of samples.)
|
---|
263 | },
|
---|
264 | 'Album/Name' => 'AlbumName',
|
---|
265 | 'Track/Category' => 'TrackCategory',
|
---|
266 | 'Track/Comments' => 'TrackComments',
|
---|
267 | 'Track/Lyrics' => 'TrackLyrics',
|
---|
268 | );
|
---|
269 |
|
---|
270 | %Image::ExifTool::Real::AudioV3 = (
|
---|
271 | GROUPS => { 1 => 'Real-RA3', 2 => 'Audio' },
|
---|
272 | PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData,
|
---|
273 | VARS => { ID_LABEL => 'Sequence' },
|
---|
274 | FORMAT => 'int8u',
|
---|
275 | 0 => { Name => 'Channels', Format => 'int16u' },
|
---|
276 | 1 => { Name => 'Unknown', Format => 'int16u[3]', Unknown => 1 },
|
---|
277 | 2 => { Name => 'BytesPerMinute', Format => 'int16u' },
|
---|
278 | 3 => { Name => 'AudioBytes', Format => 'int32u' },
|
---|
279 | 4 => { Name => 'TitleLen', Unknown => 1 },
|
---|
280 | 5 => { Name => 'Title', Format => 'string[$val{4}]' },
|
---|
281 | 6 => { Name => 'ArtistLen', Unknown => 1 },
|
---|
282 | 7 => { Name => 'Artist', Format => 'string[$val{6}]', Groups => { 2 => 'Author' } },
|
---|
283 | 8 => { Name => 'CopyrightLen', Unknown => 1 },
|
---|
284 | 9 => { Name => 'Copyright', Format => 'string[$val{8}]', Groups => { 2 => 'Author' } },
|
---|
285 | 10 => { Name => 'CommentLen', Unknown => 1 },
|
---|
286 | 11 => { Name => 'Comment', Format => 'string[$val{10}]' },
|
---|
287 | );
|
---|
288 |
|
---|
289 | %Image::ExifTool::Real::AudioV4 = (
|
---|
290 | GROUPS => { 1 => 'Real-RA4', 2 => 'Audio' },
|
---|
291 | PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData,
|
---|
292 | VARS => { ID_LABEL => 'Sequence' },
|
---|
293 | FORMAT => 'int16u',
|
---|
294 | 0 => { Name => 'FourCC1', Format => 'undef[4]', Unknown => 1 },
|
---|
295 | 1 => { Name => 'AudioFileSize', Format => 'int32u', Unknown => 1 },
|
---|
296 | 2 => { Name => 'Version2', Unknown => 1 },
|
---|
297 | 3 => { Name => 'HeaderSize', Format => 'int32u', Unknown => 1 },
|
---|
298 | 4 => { Name => 'CodecFlavorID', Unknown => 1 },
|
---|
299 | 5 => { Name => 'CodedFrameSize', Format => 'int32u', Unknown => 1 },
|
---|
300 | 6 => { Name => 'AudioBytes', Format => 'int32u' },
|
---|
301 | 7 => { Name => 'BytesPerMinute', Format => 'int32u' },
|
---|
302 | 8 => { Name => 'Unknown', Format => 'int32u', Unknown => 1 },
|
---|
303 | 9 => { Name => 'SubPacketH', Unknown => 1 },
|
---|
304 | 10 => 'AudioFrameSize',
|
---|
305 | 11 => { Name => 'SubPacketSize', Unknown => 1 },
|
---|
306 | 12 => { Name => 'Unknown', Unknown => 1 },
|
---|
307 | 13 => 'SampleRate',
|
---|
308 | 14 => { Name => 'Unknown', Unknown => 1 },
|
---|
309 | 15 => 'BitsPerSample',
|
---|
310 | 16 => 'Channels',
|
---|
311 | 17 => { Name => 'FourCC2Len', Format => 'int8u', Unknown => 1 },
|
---|
312 | 18 => { Name => 'FourCC2', Format => 'undef[4]', Unknown => 1 },
|
---|
313 | 19 => { Name => 'FourCC3Len', Format => 'int8u', Unknown => 1 },
|
---|
314 | 20 => { Name => 'FourCC3', Format => 'undef[4]', Unknown => 1 },
|
---|
315 | 21 => { Name => 'Unknown', Format => 'int8u', Unknown => 1 },
|
---|
316 | 22 => { Name => 'Unknown', Unknown => 1 },
|
---|
317 | 23 => { Name => 'TitleLen', Format => 'int8u', Unknown => 1 },
|
---|
318 | 24 => { Name => 'Title', Format => 'string[$val{23}]' },
|
---|
319 | 25 => { Name => 'ArtistLen', Format => 'int8u', Unknown => 1 },
|
---|
320 | 26 => { Name => 'Artist', Format => 'string[$val{25}]', Groups => { 2 => 'Author' } },
|
---|
321 | 27 => { Name => 'CopyrightLen', Format => 'int8u', Unknown => 1 },
|
---|
322 | 28 => { Name => 'Copyright', Format => 'string[$val{27}]', Groups => { 2 => 'Author' } },
|
---|
323 | 29 => { Name => 'CommentLen', Format => 'int8u', Unknown => 1 },
|
---|
324 | 30 => { Name => 'Comment', Format => 'string[$val{29}]' },
|
---|
325 | );
|
---|
326 |
|
---|
327 | %Image::ExifTool::Real::AudioV5 = (
|
---|
328 | GROUPS => { 1 => 'Real-RA5', 2 => 'Audio' },
|
---|
329 | PROCESS_PROC => \&Image::ExifTool::Canon::ProcessSerialData,
|
---|
330 | VARS => { ID_LABEL => 'Sequence' },
|
---|
331 | FORMAT => 'int16u',
|
---|
332 | 0 => { Name => 'FourCC1', Format => 'undef[4]', Unknown => 1 },
|
---|
333 | 1 => { Name => 'AudioFileSize', Format => 'int32u', Unknown => 1 },
|
---|
334 | 2 => { Name => 'Version2', Unknown => 1 },
|
---|
335 | 3 => { Name => 'HeaderSize', Format => 'int32u', Unknown => 1 },
|
---|
336 | 4 => { Name => 'CodecFlavorID', Unknown => 1 },
|
---|
337 | 5 => { Name => 'CodedFrameSize', Format => 'int32u', Unknown => 1 },
|
---|
338 | 6 => { Name => 'AudioBytes', Format => 'int32u' },
|
---|
339 | 7 => { Name => 'BytesPerMinute', Format => 'int32u' },
|
---|
340 | 8 => { Name => 'Unknown', Format => 'int32u', Unknown => 1 },
|
---|
341 | 9 => { Name => 'SubPacketH', Unknown => 1 },
|
---|
342 | 10 => { Name => 'FrameSize', Unknown => 1 },
|
---|
343 | 11 => { Name => 'SubPacketSize', Unknown => 1 },
|
---|
344 | 12 => 'SampleRate',
|
---|
345 | 13 => { Name => 'SampleRate2', Unknown => 1 },
|
---|
346 | 14 => { Name => 'BitsPerSample', Format => 'int32u' },
|
---|
347 | 15 => 'Channels',
|
---|
348 | 16 => { Name => 'Genr', Format => 'int32u', Unknown => 1 },
|
---|
349 | 17 => { Name => 'FourCC3', Format => 'undef[4]', Unknown => 1 },
|
---|
350 | );
|
---|
351 |
|
---|
352 | #------------------------------------------------------------------------------
|
---|
353 | # Process Real NameValueProperties
|
---|
354 | # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table ref
|
---|
355 | # Returns: 1 on success
|
---|
356 | sub ProcessRealProperties($$$)
|
---|
357 | {
|
---|
358 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
359 | my $dataPt = $$dirInfo{DataPt};
|
---|
360 | my $dirLen = $$dirInfo{DirLen};
|
---|
361 | my $pos = $$dirInfo{DirStart};
|
---|
362 | my $verbose = $exifTool->Options('Verbose');
|
---|
363 |
|
---|
364 | $verbose and $exifTool->VerboseDir('RealProperties', undef, $dirLen);
|
---|
365 |
|
---|
366 | while ($pos + 6 <= $dirLen) {
|
---|
367 |
|
---|
368 | # get property size and version
|
---|
369 | my ($size, $vers) = unpack("x${pos}Nn", $$dataPt);
|
---|
370 | last if $size < 6;
|
---|
371 | unless ($vers == 0) {
|
---|
372 | $pos += $size;
|
---|
373 | next;
|
---|
374 | }
|
---|
375 | $pos += 6;
|
---|
376 |
|
---|
377 | my $tagLen = unpack("x${pos}C", $$dataPt);
|
---|
378 | ++$pos;
|
---|
379 |
|
---|
380 | last if $pos + $tagLen > $dirLen;
|
---|
381 | my $tag = substr($$dataPt, $pos, $tagLen);
|
---|
382 | $pos += $tagLen;
|
---|
383 |
|
---|
384 | last if $pos + 6 > $dirLen;
|
---|
385 | my ($type, $valLen) = unpack("x${pos}Nn", $$dataPt);
|
---|
386 | $pos += 6;
|
---|
387 |
|
---|
388 | last if $pos + $valLen > $dirLen;
|
---|
389 | my $format = $propertyType{$type} || 'undef';
|
---|
390 | my $count = int($valLen / Image::ExifTool::FormatSize($format));
|
---|
391 | my $val = ReadValue($dataPt, $pos, $format, $count, $dirLen-$pos);
|
---|
392 |
|
---|
393 | my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
394 | unless ($tagInfo) {
|
---|
395 | my $tagName;
|
---|
396 | ($tagName = $tag) =~ s/\s+//g;
|
---|
397 | next unless $tagName =~ /^\w+$/; # ignore crazy names
|
---|
398 | $tagInfo = { Name => ucfirst($tagName) };
|
---|
399 | Image::ExifTool::AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
---|
400 | }
|
---|
401 | if ($verbose) {
|
---|
402 | $exifTool->VerboseInfo($tag, $tagInfo,
|
---|
403 | Table => $tagTablePtr,
|
---|
404 | Value => $val,
|
---|
405 | DataPt => $dataPt,
|
---|
406 | Size => $valLen,
|
---|
407 | Start => $pos,
|
---|
408 | Addr => $pos + $$dirInfo{DataPos},
|
---|
409 | Format => $format,
|
---|
410 | Count => $count,
|
---|
411 | );
|
---|
412 | }
|
---|
413 | $exifTool->FoundTag($tagInfo, $val);
|
---|
414 | $pos += $valLen;
|
---|
415 | }
|
---|
416 | return 1;
|
---|
417 | }
|
---|
418 |
|
---|
419 | #------------------------------------------------------------------------------
|
---|
420 | # Process Real metadata properties
|
---|
421 | # Inputs: 0) ExifTool object reference, 1) dirInfo ref, 2) tag table ref
|
---|
422 | # Returns: 1 on success
|
---|
423 | sub ProcessRealMeta($$$)
|
---|
424 | {
|
---|
425 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
426 | my $dataPt = $$dirInfo{DataPt};
|
---|
427 | my $dataPos = $$dirInfo{DataPos};
|
---|
428 | my $pos = $$dirInfo{DirStart};
|
---|
429 | my $dirEnd = $pos + $$dirInfo{DirLen};
|
---|
430 | my $verbose = $exifTool->Options('Verbose');
|
---|
431 | my $prefix = $$dirInfo{Prefix} || '';
|
---|
432 | $prefix and $prefix .= '/';
|
---|
433 |
|
---|
434 | $verbose and $exifTool->VerboseDir('RealMetadata', undef, $$dirInfo{DirLen});
|
---|
435 |
|
---|
436 | for (;;) {
|
---|
437 | last if $pos + 28 > $dirEnd;
|
---|
438 | # extract fixed-position metadata structure members
|
---|
439 | my ($size, $type, $flags, $valuePos, $subPropPos, $numSubProps, $nameLen)
|
---|
440 | = unpack("x${pos}N7", $$dataPt);
|
---|
441 | # make pointers relative to data start
|
---|
442 | $valuePos += $pos;
|
---|
443 | $subPropPos += $pos;
|
---|
444 | # validate what we have read so far
|
---|
445 | last if $pos + $size > $dirEnd;
|
---|
446 | last if $pos + 28 + $nameLen > $dirEnd;
|
---|
447 | last if $valuePos < $pos + 28 + $nameLen;
|
---|
448 | last if $valuePos + 4 > $dirEnd;
|
---|
449 | my $tag = substr($$dataPt, $pos + 28, $nameLen);
|
---|
450 | $tag =~ s/\0.*//s; # truncate at null
|
---|
451 | $tag = $prefix . $tag;
|
---|
452 | my $valueLen = unpack("x${valuePos}N", $$dataPt);
|
---|
453 | $valuePos += 4; # point at value itself
|
---|
454 | last if $valuePos + $valueLen > $dirEnd;
|
---|
455 |
|
---|
456 | my $format = $metadataFormat{$type};
|
---|
457 | my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
458 | unless ($tagInfo) {
|
---|
459 | my $tagName = $tag;
|
---|
460 | $tagName =~ tr/A-Za-z0-9//dc;
|
---|
461 | $tagInfo = { Name => ucfirst($tagName) };
|
---|
462 | Image::ExifTool::AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
---|
463 | }
|
---|
464 | if ($verbose) {
|
---|
465 | $format = 'undef' unless defined $format;
|
---|
466 | $flags = Image::ExifTool::DecodeBits($flags, \%metadataFlag);
|
---|
467 | }
|
---|
468 | if ($valueLen and $format) {
|
---|
469 | # (a flag can be 1 or 4 bytes)
|
---|
470 | if ($format eq 'flag') {
|
---|
471 | $format = ($valueLen == 4) ? 'int32u' : 'int8u';
|
---|
472 | } elsif ($type == 7 and $tagInfo) {
|
---|
473 | # add PrintConv and ValueConv for "date" type
|
---|
474 | $$tagInfo{ValueConv} or $$tagInfo{ValueConv} = q{
|
---|
475 | $val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ ?
|
---|
476 | sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d",$1,$2,$3,$4,$5,$6) :
|
---|
477 | $val;
|
---|
478 | };
|
---|
479 | $$tagInfo{PrintConv} or $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
|
---|
480 | }
|
---|
481 | my $count = int($valueLen / Image::ExifTool::FormatSize($format));
|
---|
482 | my $val = ReadValue($dataPt, $valuePos, $format, $count, $dirEnd-$valuePos);
|
---|
483 | $exifTool->HandleTag($tagTablePtr, $tag, $val,
|
---|
484 | DataPt => $dataPt,
|
---|
485 | DataPos => $dataPos,
|
---|
486 | Start => $valuePos,
|
---|
487 | Size => $valueLen,
|
---|
488 | Format => "type=$type, flags=$flags",
|
---|
489 | );
|
---|
490 | }
|
---|
491 | # extract sub-properties
|
---|
492 | if ($numSubProps) {
|
---|
493 | my $dirStart = $valuePos + $valueLen + $numSubProps * 8;
|
---|
494 | my %dirInfo = (
|
---|
495 | DataPt => $dataPt,
|
---|
496 | DataPos => $dataPos,
|
---|
497 | DirStart => $dirStart,
|
---|
498 | DirLen => $pos + $size - $dirStart,
|
---|
499 | Prefix => $tag,
|
---|
500 | );
|
---|
501 | $exifTool->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
---|
502 | }
|
---|
503 | $pos += $size; # step to next Metadata structure
|
---|
504 | }
|
---|
505 | unless ($pos == $dirEnd) {
|
---|
506 | $exifTool->Warn('Format error in Real Metadata');
|
---|
507 | return 0;
|
---|
508 | }
|
---|
509 | return 1;
|
---|
510 | }
|
---|
511 |
|
---|
512 | #------------------------------------------------------------------------------
|
---|
513 | # Read information frame a Real file
|
---|
514 | # Inputs: 0) ExifTool object reference, 1) Directory information reference
|
---|
515 | # Returns: 1 on success, 0 if this wasn't a valid Real file
|
---|
516 | sub ProcessReal($$)
|
---|
517 | {
|
---|
518 | my ($exifTool, $dirInfo) = @_;
|
---|
519 | my $raf = $$dirInfo{RAF};
|
---|
520 | my ($buff, $tag, $vers, $extra, @mimeTypes, %dirCount);
|
---|
521 |
|
---|
522 | $raf->Read($buff, 8) == 8 or return 0;
|
---|
523 | $buff =~ m{^(\.RMF|\.ra\xfd|pnm://|rtsp://|http://)} or return 0;
|
---|
524 |
|
---|
525 | my ($type, $tagTablePtr);
|
---|
526 | if ($1 eq '.RMF') {
|
---|
527 | $tagTablePtr = GetTagTable('Image::ExifTool::Real::Media');
|
---|
528 | $type = 'RM';
|
---|
529 | } elsif ($1 eq ".ra\xfd") {
|
---|
530 | $tagTablePtr = GetTagTable('Image::ExifTool::Real::Audio');
|
---|
531 | $type = 'RA';
|
---|
532 | } else {
|
---|
533 | $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metafile');
|
---|
534 | my $ext = $exifTool->{FILE_EXT};
|
---|
535 | $type = ($ext and $ext eq 'RPM') ? 'RPM' : 'RAM';
|
---|
536 | require Image::ExifTool::PostScript;
|
---|
537 | local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf) || "\n";
|
---|
538 | $raf->Seek(0,0);
|
---|
539 | while ($raf->ReadLine($buff)) {
|
---|
540 | last if length $buff > 256;
|
---|
541 | next unless $buff ;
|
---|
542 | chomp $buff;
|
---|
543 | if ($type) {
|
---|
544 | # must be a Real file type if protocol is http
|
---|
545 | return 0 if $buff =~ /^http/ and $buff !~ /\.(ra|rm|rv|rmvb|smil)$/i;
|
---|
546 | $exifTool->SetFileType($type);
|
---|
547 | undef $type;
|
---|
548 | }
|
---|
549 | # save URL or Text from RAM file
|
---|
550 | my $tag = $buff =~ m{^[a-z]{3,4}://} ? 'url' : 'txt';
|
---|
551 | $exifTool->HandleTag($tagTablePtr, $tag, $buff);
|
---|
552 | }
|
---|
553 | return 1;
|
---|
554 | }
|
---|
555 |
|
---|
556 | $exifTool->SetFileType($type);
|
---|
557 | SetByteOrder('MM');
|
---|
558 | my $verbose = $exifTool->Options('Verbose');
|
---|
559 | #
|
---|
560 | # Process RealAudio file
|
---|
561 | #
|
---|
562 | if ($type eq 'RA') {
|
---|
563 | ($vers, $extra) = unpack('x4nn', $buff);
|
---|
564 | $tag = ".ra$vers";
|
---|
565 | my $fpos = $raf->Tell();
|
---|
566 | unless ($raf->Read($buff, 512)) {
|
---|
567 | $exifTool->Warn('Error reading audio header');
|
---|
568 | return 1;
|
---|
569 | }
|
---|
570 | my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
571 | if ($verbose > 2) {
|
---|
572 | $exifTool->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos);
|
---|
573 | }
|
---|
574 | if ($tagInfo) {
|
---|
575 | my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
|
---|
576 | my %dirInfo = (
|
---|
577 | DataPt => \$buff,
|
---|
578 | DataPos => $fpos,
|
---|
579 | DirLen => length $buff,
|
---|
580 | DirStart => 0,
|
---|
581 | );
|
---|
582 | $exifTool->ProcessDirectory(\%dirInfo, $subTablePtr);
|
---|
583 | } else {
|
---|
584 | $exifTool->Warn('Unsupported RealAudio version');
|
---|
585 | }
|
---|
586 | return 1;
|
---|
587 | }
|
---|
588 | #
|
---|
589 | # Process RealMedia file
|
---|
590 | #
|
---|
591 | # skip the rest of the RM header
|
---|
592 | my $size = unpack('x4N', $buff);
|
---|
593 | unless ($raf->Seek($size - 8, 1)) {
|
---|
594 | $exifTool->Warn('Error seeking in file');
|
---|
595 | return 0;
|
---|
596 | }
|
---|
597 |
|
---|
598 | # Process RealMedia chunks
|
---|
599 | for (;;) {
|
---|
600 | $raf->Read($buff, 10) == 10 or last;
|
---|
601 | ($tag, $size, $vers) = unpack('a4Nn', $buff);
|
---|
602 | last if $tag eq "\0\0\0\0";
|
---|
603 | if ($verbose) {
|
---|
604 | $exifTool->VPrint(0, "$tag chunk ($size bytes):\n");
|
---|
605 | } else {
|
---|
606 | last if $tag eq 'DATA'; # stop normal parsing at DATA tag
|
---|
607 | }
|
---|
608 | if ($size & 0x80000000) {
|
---|
609 | $exifTool->Warn('Bad chunk header');
|
---|
610 | last;
|
---|
611 | }
|
---|
612 | my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
613 | if ($tagInfo and $$tagInfo{SubDirectory}) {
|
---|
614 | my $fpos = $raf->Tell();
|
---|
615 | unless ($raf->Read($buff, $size-10) == $size-10) {
|
---|
616 | $exifTool->Warn("Error reading $tag chunk");
|
---|
617 | last;
|
---|
618 | }
|
---|
619 | if ($verbose > 2) {
|
---|
620 | $exifTool->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos);
|
---|
621 | }
|
---|
622 | my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
|
---|
623 | my %dirInfo = (
|
---|
624 | DataPt => \$buff,
|
---|
625 | DataPos => $fpos,
|
---|
626 | DirLen => length $buff,
|
---|
627 | DirStart => 0,
|
---|
628 | );
|
---|
629 | if ($dirCount{$tag}) {
|
---|
630 | $exifTool->{SET_GROUP1} = '+' . ++$dirCount{$tag};
|
---|
631 | } else {
|
---|
632 | $dirCount{$tag} = 1;
|
---|
633 | }
|
---|
634 | $exifTool->ProcessDirectory(\%dirInfo, $subTablePtr);
|
---|
635 | delete $exifTool->{SET_GROUP1};
|
---|
636 | # keep track of stream MIME types
|
---|
637 | my $mime = $exifTool->{RealStreamMime};
|
---|
638 | if ($mime) {
|
---|
639 | delete $exifTool->{RealStreamMime};
|
---|
640 | $mime =~ s/\0.*//s;
|
---|
641 | push @mimeTypes, $mime unless $mime =~ /^logical-/;
|
---|
642 | }
|
---|
643 | } else {
|
---|
644 | unless ($raf->Seek($size-10, 1)) {
|
---|
645 | $exifTool->Warn('Error seeking in file');
|
---|
646 | last;
|
---|
647 | }
|
---|
648 | }
|
---|
649 | }
|
---|
650 | # override MIMEType with stream MIME type if we only have one stream
|
---|
651 | if (@mimeTypes == 1 and length $mimeTypes[0]) {
|
---|
652 | $exifTool->{VALUE}->{MIMEType} = $mimeTypes[0];
|
---|
653 | $exifTool->VPrint(0, " MIMEType = $mimeTypes[0]\n");
|
---|
654 | }
|
---|
655 | #
|
---|
656 | # Process footer containing Real metadata and ID3 information
|
---|
657 | #
|
---|
658 | if ($raf->Seek(-140, 2) and $raf->Read($buff, 12) == 12 and $buff =~ /^RMJE/) {
|
---|
659 | my $metaSize = unpack('x8N', $buff);
|
---|
660 | if ($raf->Seek(-$metaSize-12, 1) and
|
---|
661 | $raf->Read($buff, $metaSize) == $metaSize and
|
---|
662 | $buff =~ /^RJMD/)
|
---|
663 | {
|
---|
664 | my %dirInfo = (
|
---|
665 | DataPt => \$buff,
|
---|
666 | DataPos => $raf->Tell() - $metaSize,
|
---|
667 | DirStart => 8,
|
---|
668 | DirLen => length($buff) - 8,
|
---|
669 | );
|
---|
670 | my $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metadata');
|
---|
671 | $exifTool->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
---|
672 | } else {
|
---|
673 | $exifTool->Warn('Bad metadata footer');
|
---|
674 | }
|
---|
675 | if ($raf->Seek(-128, 2) and $raf->Read($buff, 128) == 128 and $buff =~ /^TAG/) {
|
---|
676 | $exifTool->VPrint(0, "ID3v1:\n");
|
---|
677 | my %dirInfo = (
|
---|
678 | DataPt => \$buff,
|
---|
679 | DirStart => 0,
|
---|
680 | DirLen => length($buff),
|
---|
681 | );
|
---|
682 | my $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1');
|
---|
683 | $exifTool->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
---|
684 | }
|
---|
685 | }
|
---|
686 | return 1;
|
---|
687 | }
|
---|
688 |
|
---|
689 | 1; # end
|
---|
690 |
|
---|
691 | __END__
|
---|
692 |
|
---|
693 | =head1 NAME
|
---|
694 |
|
---|
695 | Image::ExifTool::Real - Read Real audio/video meta information
|
---|
696 |
|
---|
697 | =head1 SYNOPSIS
|
---|
698 |
|
---|
699 | This module is used by Image::ExifTool
|
---|
700 |
|
---|
701 | =head1 DESCRIPTION
|
---|
702 |
|
---|
703 | This module contains the routines required by Image::ExifTool to read meta
|
---|
704 | information in RealAudio (RA), RealMedia (RM, RV and RMVB) and RealMedia
|
---|
705 | Metafile (RAM and RPM) files.
|
---|
706 |
|
---|
707 | =head1 NOTES
|
---|
708 |
|
---|
709 | There must be a bug in the software that wrote the Metadata used in the test
|
---|
710 | file t/images/Real.rm because the TrackLyricsDataSize word is written
|
---|
711 | little-endian, but the Real format is big-endian.
|
---|
712 |
|
---|
713 | =head1 AUTHOR
|
---|
714 |
|
---|
715 | Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
|
---|
716 |
|
---|
717 | This library is free software; you can redistribute it and/or modify it
|
---|
718 | under the same terms as Perl itself.
|
---|
719 |
|
---|
720 | =head1 REFERENCES
|
---|
721 |
|
---|
722 | =over 4
|
---|
723 |
|
---|
724 | =item L<http://www.getid3.org/>
|
---|
725 |
|
---|
726 | =item L<https://common.helixcommunity.org/nonav/2003/HCS_SDK_r5/htmfiles/rmff.htm>
|
---|
727 |
|
---|
728 | =back
|
---|
729 |
|
---|
730 | =head1 SEE ALSO
|
---|
731 |
|
---|
732 | L<Image::ExifTool::TagNames/Real Tags>,
|
---|
733 | L<Image::ExifTool(3pm)|Image::ExifTool>
|
---|
734 |
|
---|
735 | =cut
|
---|
736 |
|
---|