File Coverage

blib/lib/Image/ExifTool/Real.pm
Criterion Covered Total %
statement 173 203 85.2
branch 69 122 56.5
condition 20 48 41.6
subroutine 7 7 100.0
pod 0 3 0.0
total 269 383 70.2


line stmt bran cond sub pod time code
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 1     1   4462 use strict;
  1         3  
  1         31  
15 1     1   6 use vars qw($VERSION);
  1         2  
  1         38  
16 1     1   9 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         223  
17 1     1   2082 use Image::ExifTool::Canon;
  1         113  
  1         3630  
18              
19             $VERSION = '1.07';
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'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 1     1 0 4 my ($et, $dirInfo, $tagTablePtr) = @_;
359 1         3 my $dataPt = $$dirInfo{DataPt};
360 1         2 my $dirLen = $$dirInfo{DirLen};
361 1         3 my $pos = $$dirInfo{DirStart};
362 1         3 my $verbose = $et->Options('Verbose');
363              
364 1 50       4 $verbose and $et->VerboseDir('RealProperties', undef, $dirLen);
365              
366 1         16 while ($pos + 6 <= $dirLen) {
367              
368             # get property size and version
369 9         28 my ($size, $vers) = unpack("x${pos}Nn", $$dataPt);
370 9 50       20 last if $size < 6;
371 9 50       20 unless ($vers == 0) {
372 0         0 $pos += $size;
373 0         0 next;
374             }
375 9         12 $pos += 6;
376              
377 9         21 my $tagLen = unpack("x${pos}C", $$dataPt);
378 9         16 ++$pos;
379              
380 9 50       19 last if $pos + $tagLen > $dirLen;
381 9         18 my $tag = substr($$dataPt, $pos, $tagLen);
382 9         13 $pos += $tagLen;
383              
384 9 50       16 last if $pos + 6 > $dirLen;
385 9         25 my ($type, $valLen) = unpack("x${pos}Nn", $$dataPt);
386 9         10 $pos += 6;
387              
388 9 50       20 last if $pos + $valLen > $dirLen;
389 9   50     32 my $format = $propertyType{$type} || 'undef';
390 9         19 my $count = int($valLen / Image::ExifTool::FormatSize($format));
391 9         21 my $val = ReadValue($dataPt, $pos, $format, $count, $dirLen-$pos);
392              
393 9         29 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
394 9 50       22 unless ($tagInfo) {
395 0         0 my $tagName;
396 0         0 ($tagName = $tag) =~ s/\s+//g;
397 0 0       0 next unless $tagName =~ /^\w+$/; # ignore crazy names
398 0         0 $tagInfo = { Name => ucfirst($tagName) };
399 0         0 AddTagToTable($tagTablePtr, $tag, $tagInfo);
400             }
401 9 50       16 if ($verbose) {
402             $et->VerboseInfo($tag, $tagInfo,
403             Table => $tagTablePtr,
404             Value => $val,
405             DataPt => $dataPt,
406             Size => $valLen,
407             Start => $pos,
408             Addr => $pos + $$dirInfo{DataPos},
409 0         0 Format => $format,
410             Count => $count,
411             );
412             }
413 9         27 $et->FoundTag($tagInfo, $val);
414 9         24 $pos += $valLen;
415             }
416 1         3 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 7     7 0 15 my ($et, $dirInfo, $tagTablePtr) = @_;
426 7         14 my $dataPt = $$dirInfo{DataPt};
427 7         12 my $dataPos = $$dirInfo{DataPos};
428 7         12 my $pos = $$dirInfo{DirStart};
429 7         14 my $dirEnd = $pos + $$dirInfo{DirLen};
430 7         22 my $verbose = $et->Options('Verbose');
431 7   100     39 my $prefix = $$dirInfo{Prefix} || '';
432 7 100       28 $prefix and $prefix .= '/';
433              
434 7 50       20 $verbose and $et->VerboseDir('RealMetadata', undef, $$dirInfo{DirLen});
435              
436 7         11 for (;;) {
437 21 100       44 last if $pos + 28 > $dirEnd;
438             # extract fixed-position metadata structure members
439 14         63 my ($size, $type, $flags, $valuePos, $subPropPos, $numSubProps, $nameLen)
440             = unpack("x${pos}N7", $$dataPt);
441             # make pointers relative to data start
442 14         25 $valuePos += $pos;
443 14         19 $subPropPos += $pos;
444             # validate what we have read so far
445 14 50       30 last if $pos + $size > $dirEnd;
446 14 50       27 last if $pos + 28 + $nameLen > $dirEnd;
447 14 50       30 last if $valuePos < $pos + 28 + $nameLen;
448 14 50       24 last if $valuePos + 4 > $dirEnd;
449 14         44 my $tag = substr($$dataPt, $pos + 28, $nameLen);
450 14         80 $tag =~ s/\0.*//s; # truncate at null
451 14         29 $tag = $prefix . $tag;
452 14         68 my $valueLen = unpack("x${valuePos}N", $$dataPt);
453 14         24 $valuePos += 4; # point at value itself
454 14 50       28 last if $valuePos + $valueLen > $dirEnd;
455              
456 14         30 my $format = $metadataFormat{$type};
457 14         40 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
458 14 100       37 unless ($tagInfo) {
459 10         19 my $tagName = $tag;
460 10         25 $tagName =~ tr/A-Za-z0-9//dc;
461 10         29 $tagInfo = { Name => ucfirst($tagName) };
462 10         26 AddTagToTable($tagTablePtr, $tag, $tagInfo);
463             }
464 14 50       30 if ($verbose) {
465 0 0       0 $format = 'undef' unless defined $format;
466 0         0 $flags = Image::ExifTool::DecodeBits($flags, \%metadataFlag);
467             }
468 14 100 66     52 if ($valueLen and $format) {
469             # (a flag can be 1 or 4 bytes)
470 9 50 33     32 if ($format eq 'flag') {
    50          
471 0 0       0 $format = ($valueLen == 4) ? 'int32u' : 'int8u';
472             } elsif ($type == 7 and $tagInfo) {
473             # add PrintConv and ValueConv for "date" type
474 0 0       0 $$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 0 0       0 $$tagInfo{PrintConv} or $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
480             }
481 9         20 my $count = int($valueLen / Image::ExifTool::FormatSize($format));
482 9         24 my $val = ReadValue($dataPt, $valuePos, $format, $count, $dirEnd-$valuePos);
483 9         51 $et->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 14 100       26 if ($numSubProps) {
493 6         14 my $dirStart = $valuePos + $valueLen + $numSubProps * 8;
494 6         28 my %dirInfo = (
495             DataPt => $dataPt,
496             DataPos => $dataPos,
497             DirStart => $dirStart,
498             DirLen => $pos + $size - $dirStart,
499             Prefix => $tag,
500             );
501 6         24 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
502             }
503 14         29 $pos += $size; # step to next Metadata structure
504             }
505 7 50       14 unless ($pos == $dirEnd) {
506 0         0 $et->Warn('Format error in Real Metadata');
507 0         0 return 0;
508             }
509 7         16 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 3     3 0 11 my ($et, $dirInfo) = @_;
519 3         8 my $raf = $$dirInfo{RAF};
520 3         8 my ($buff, $tag, $vers, $extra, @mimeTypes, %dirCount);
521              
522 3 50       13 $raf->Read($buff, 8) == 8 or return 0;
523 3 50       29 $buff =~ m{^(\.RMF|\.ra\xfd|pnm://|rtsp://|http://)} or return 0;
524              
525 3   33     13 my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3;
526 3         10 my ($type, $tagTablePtr);
527 3 100       17 if ($1 eq '.RMF') {
    100          
528 1         14 $tagTablePtr = GetTagTable('Image::ExifTool::Real::Media');
529 1         7 $type = 'RM';
530             } elsif ($1 eq ".ra\xfd") {
531 1         5 $tagTablePtr = GetTagTable('Image::ExifTool::Real::Audio');
532 1         2 $type = 'RA';
533             } else {
534 1         5 $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metafile');
535 1         4 my $ext = $$et{FILE_EXT};
536 1 50 33     7 $type = ($ext and $ext eq 'RPM') ? 'RPM' : 'RAM';
537 1         698 require Image::ExifTool::PostScript;
538 1   50     6 local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf) || "\n";
539 1         5 $raf->Seek(0,0);
540 1         8 while ($raf->ReadLine($buff)) {
541 1 50       5 last if length $buff > 256;
542 1 50       4 next unless $buff ;
543 1         5 chomp $buff;
544 1 50       4 if ($type) {
545             # must be a Real file type if protocol is http
546 1 50 33     6 return 0 if $buff =~ /^http/ and $buff !~ /\.(ra|rm|rv|rmvb|smil)$/i;
547 1         7 $et->SetFileType($type);
548 1 50       4 return 1 if $fast3;
549 1         6 undef $type;
550             }
551             # save URL or Text from RAM file
552 1 50       14 my $tag = $buff =~ m{^[a-z]{3,4}://} ? 'url' : 'txt';
553 1         6 $et->HandleTag($tagTablePtr, $tag, $buff);
554             }
555 1         7 return 1;
556             }
557              
558 2         25 $et->SetFileType($type);
559 2 50       8 return 1 if $fast3;
560 2         10 SetByteOrder('MM');
561 2         7 my $verbose = $et->Options('Verbose');
562             #
563             # Process RealAudio file
564             #
565 2 100       13 if ($type eq 'RA') {
566 1         5 ($vers, $extra) = unpack('x4nn', $buff);
567 1         4 $tag = ".ra$vers";
568 1         3 my $fpos = $raf->Tell();
569 1 50       5 unless ($raf->Read($buff, 512)) {
570 0         0 $et->Warn('Error reading audio header');
571 0         0 return 1;
572             }
573 1         5 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
574 1 50       5 if ($verbose > 2) {
575 0         0 $et->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos);
576             }
577 1 50       6 if ($tagInfo) {
578 1         5 my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
579 1         9 my %dirInfo = (
580             DataPt => \$buff,
581             DataPos => $fpos,
582             DirLen => length $buff,
583             DirStart => 0,
584             );
585 1         6 $et->ProcessDirectory(\%dirInfo, $subTablePtr);
586             } else {
587 0         0 $et->Warn('Unsupported RealAudio version');
588             }
589 1         7 return 1;
590             }
591             #
592             # Process RealMedia file
593             #
594             # skip the rest of the RM header
595 1         7 my $size = unpack('x4N', $buff);
596 1 50       5 unless ($raf->Seek($size - 8, 1)) {
597 0         0 $et->Warn('Error seeking in file');
598 0         0 return 0;
599             }
600              
601             # Process RealMedia chunks
602 1         4 for (;;) {
603 6 100       24 $raf->Read($buff, 10) == 10 or last;
604 5         23 ($tag, $size, $vers) = unpack('a4Nn', $buff);
605 5 50       24 last if $tag eq "\0\0\0\0";
606 5 50       11 if ($verbose) {
607 0         0 $et->VPrint(0, "$tag chunk ($size bytes):\n");
608             } else {
609 5 50       16 last if $tag eq 'DATA'; # stop normal parsing at DATA tag
610             }
611 5 50 33     22 if ($size & 0x80000000 or $size < 10) {
612 0         0 $et->Warn('Bad chunk header');
613 0         0 last;
614             }
615 5         14 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
616 5 100 66     29 if ($tagInfo and $$tagInfo{SubDirectory}) {
617 4         22 my $fpos = $raf->Tell();
618 4 50       24 unless ($raf->Read($buff, $size-10) == $size-10) {
619 0         0 $et->Warn("Error reading $tag chunk");
620 0         0 last;
621             }
622 4 50       14 if ($verbose > 2) {
623 0         0 $et->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos);
624             }
625 4         18 my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
626 4         31 my %dirInfo = (
627             DataPt => \$buff,
628             DataPos => $fpos,
629             DirLen => length $buff,
630             DirStart => 0,
631             );
632 4 100       22 if ($dirCount{$tag}) {
633 1         17 $$et{SET_GROUP1} = '+' . ++$dirCount{$tag};
634             } else {
635 3         10 $dirCount{$tag} = 1;
636             }
637 4         22 $et->ProcessDirectory(\%dirInfo, $subTablePtr);
638 4         9 delete $$et{SET_GROUP1};
639             # keep track of stream MIME types
640 4         10 my $mime = $$et{RealStreamMime};
641 4 100       12 if ($mime) {
642 2         6 delete $$et{RealStreamMime};
643 2         6 $mime =~ s/\0.*//s;
644 2 100       12 push @mimeTypes, $mime unless $mime =~ /^logical-/;
645             }
646             } else {
647 1 50       8 unless ($raf->Seek($size-10, 1)) {
648 0         0 $et->Warn('Error seeking in file');
649 0         0 last;
650             }
651             }
652             }
653             # override MIMEType with stream MIME type if we only have one stream
654 1 50 33     29 if (@mimeTypes == 1 and length $mimeTypes[0]) {
655 1         5 $$et{VALUE}{MIMEType} = $mimeTypes[0];
656 1         9 $et->VPrint(0, " MIMEType = $mimeTypes[0]\n");
657             }
658             #
659             # Process footer containing Real metadata and ID3 information
660             #
661 1 50 33     6 if ($raf->Seek(-140, 2) and $raf->Read($buff, 12) == 12 and $buff =~ /^RMJE/) {
      33        
662 1         4 my $metaSize = unpack('x8N', $buff);
663 1 50 33     6 if ($raf->Seek(-$metaSize-12, 1) and
      33        
664             $raf->Read($buff, $metaSize) == $metaSize and
665             $buff =~ /^RJMD/)
666             {
667 1         4 my %dirInfo = (
668             DataPt => \$buff,
669             DataPos => $raf->Tell() - $metaSize,
670             DirStart => 8,
671             DirLen => length($buff) - 8,
672             );
673 1         3 my $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metadata');
674 1         6 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
675             } else {
676 0         0 $et->Warn('Bad metadata footer');
677             }
678 1 50 33     7 if ($raf->Seek(-128, 2) and $raf->Read($buff, 128) == 128 and $buff =~ /^TAG/) {
      33        
679 1         7 $et->VPrint(0, "ID3v1:\n");
680 1         10 my %dirInfo = (
681             DataPt => \$buff,
682             DirStart => 0,
683             DirLen => length($buff),
684             );
685 1         14 my $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1');
686 1         5 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
687             }
688             }
689 1         11 return 1;
690             }
691              
692             1; # end
693              
694             __END__