File Coverage

blib/lib/Image/ExifTool/Real.pm
Criterion Covered Total %
statement 173 203 85.2
branch 69 122 56.5
condition 19 45 42.2
subroutine 7 7 100.0
pod 0 3 0.0
total 268 380 70.5


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   4454 use strict;
  1         2  
  1         33  
15 1     1   5 use vars qw($VERSION);
  1         2  
  1         41  
16 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         222  
17 1     1   1817 use Image::ExifTool::Canon;
  1         99  
  1         3263  
18              
19             $VERSION = '1.06';
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 6 my ($et, $dirInfo, $tagTablePtr) = @_;
359 1         2 my $dataPt = $$dirInfo{DataPt};
360 1         3 my $dirLen = $$dirInfo{DirLen};
361 1         2 my $pos = $$dirInfo{DirStart};
362 1         5 my $verbose = $et->Options('Verbose');
363              
364 1 50       9 $verbose and $et->VerboseDir('RealProperties', undef, $dirLen);
365              
366 1         13 while ($pos + 6 <= $dirLen) {
367              
368             # get property size and version
369 9         36 my ($size, $vers) = unpack("x${pos}Nn", $$dataPt);
370 9 50       22 last if $size < 6;
371 9 50       16 unless ($vers == 0) {
372 0         0 $pos += $size;
373 0         0 next;
374             }
375 9         15 $pos += 6;
376              
377 9         20 my $tagLen = unpack("x${pos}C", $$dataPt);
378 9         15 ++$pos;
379              
380 9 50       18 last if $pos + $tagLen > $dirLen;
381 9         21 my $tag = substr($$dataPt, $pos, $tagLen);
382 9         12 $pos += $tagLen;
383              
384 9 50       20 last if $pos + 6 > $dirLen;
385 9         24 my ($type, $valLen) = unpack("x${pos}Nn", $$dataPt);
386 9         13 $pos += 6;
387              
388 9 50       19 last if $pos + $valLen > $dirLen;
389 9   50     21 my $format = $propertyType{$type} || 'undef';
390 9         21 my $count = int($valLen / Image::ExifTool::FormatSize($format));
391 9         21 my $val = ReadValue($dataPt, $pos, $format, $count, $dirLen-$pos);
392              
393 9         41 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
394 9 50       31 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         26 $et->FoundTag($tagInfo, $val);
414 9         29 $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 17 my ($et, $dirInfo, $tagTablePtr) = @_;
426 7         14 my $dataPt = $$dirInfo{DataPt};
427 7         10 my $dataPos = $$dirInfo{DataPos};
428 7         13 my $pos = $$dirInfo{DirStart};
429 7         12 my $dirEnd = $pos + $$dirInfo{DirLen};
430 7         21 my $verbose = $et->Options('Verbose');
431 7   100     30 my $prefix = $$dirInfo{Prefix} || '';
432 7 100       18 $prefix and $prefix .= '/';
433              
434 7 50       15 $verbose and $et->VerboseDir('RealMetadata', undef, $$dirInfo{DirLen});
435              
436 7         10 for (;;) {
437 21 100       50 last if $pos + 28 > $dirEnd;
438             # extract fixed-position metadata structure members
439 14         67 my ($size, $type, $flags, $valuePos, $subPropPos, $numSubProps, $nameLen)
440             = unpack("x${pos}N7", $$dataPt);
441             # make pointers relative to data start
442 14         28 $valuePos += $pos;
443 14         18 $subPropPos += $pos;
444             # validate what we have read so far
445 14 50       28 last if $pos + $size > $dirEnd;
446 14 50       28 last if $pos + 28 + $nameLen > $dirEnd;
447 14 50       29 last if $valuePos < $pos + 28 + $nameLen;
448 14 50       28 last if $valuePos + 4 > $dirEnd;
449 14         30 my $tag = substr($$dataPt, $pos + 28, $nameLen);
450 14         76 $tag =~ s/\0.*//s; # truncate at null
451 14         31 $tag = $prefix . $tag;
452 14         39 my $valueLen = unpack("x${valuePos}N", $$dataPt);
453 14         23 $valuePos += 4; # point at value itself
454 14 50       32 last if $valuePos + $valueLen > $dirEnd;
455              
456 14         30 my $format = $metadataFormat{$type};
457 14         38 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
458 14 100       48 unless ($tagInfo) {
459 10         18 my $tagName = $tag;
460 10         21 $tagName =~ tr/A-Za-z0-9//dc;
461 10         29 $tagInfo = { Name => ucfirst($tagName) };
462 10         28 AddTagToTable($tagTablePtr, $tag, $tagInfo);
463             }
464 14 50       27 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     48 if ($valueLen and $format) {
469             # (a flag can be 1 or 4 bytes)
470 9 50 33     35 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         37 $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       30 if ($numSubProps) {
493 6         13 my $dirStart = $valuePos + $valueLen + $numSubProps * 8;
494 6         26 my %dirInfo = (
495             DataPt => $dataPt,
496             DataPos => $dataPos,
497             DirStart => $dirStart,
498             DirLen => $pos + $size - $dirStart,
499             Prefix => $tag,
500             );
501 6         59 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
502             }
503 14         32 $pos += $size; # step to next Metadata structure
504             }
505 7 50       16 unless ($pos == $dirEnd) {
506 0         0 $et->Warn('Format error in Real Metadata');
507 0         0 return 0;
508             }
509 7         18 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 10 my ($et, $dirInfo) = @_;
519 3         6 my $raf = $$dirInfo{RAF};
520 3         7 my ($buff, $tag, $vers, $extra, @mimeTypes, %dirCount);
521              
522 3 50       11 $raf->Read($buff, 8) == 8 or return 0;
523 3 50       38 $buff =~ m{^(\.RMF|\.ra\xfd|pnm://|rtsp://|http://)} or return 0;
524              
525 3   33     24 my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3;
526 3         6 my ($type, $tagTablePtr);
527 3 100       25 if ($1 eq '.RMF') {
    100          
528 1         4 $tagTablePtr = GetTagTable('Image::ExifTool::Real::Media');
529 1         2 $type = 'RM';
530             } elsif ($1 eq ".ra\xfd") {
531 1         5 $tagTablePtr = GetTagTable('Image::ExifTool::Real::Audio');
532 1         3 $type = 'RA';
533             } else {
534 1         6 $tagTablePtr = GetTagTable('Image::ExifTool::Real::Metafile');
535 1         4 my $ext = $$et{FILE_EXT};
536 1 50 33     12 $type = ($ext and $ext eq 'RPM') ? 'RPM' : 'RAM';
537 1         657 require Image::ExifTool::PostScript;
538 1   50     6 local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf) || "\n";
539 1         5 $raf->Seek(0,0);
540 1         6 while ($raf->ReadLine($buff)) {
541 1 50       4 last if length $buff > 256;
542 1 50       4 next unless $buff ;
543 1         3 chomp $buff;
544 1 50       3 if ($type) {
545             # must be a Real file type if protocol is http
546 1 50 33     7 return 0 if $buff =~ /^http/ and $buff !~ /\.(ra|rm|rv|rmvb|smil)$/i;
547 1         7 $et->SetFileType($type);
548 1 50       9 return 1 if $fast3;
549 1         2 undef $type;
550             }
551             # save URL or Text from RAM file
552 1 50       12 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         11 $et->SetFileType($type);
559 2 50       6 return 1 if $fast3;
560 2         8 SetByteOrder('MM');
561 2         16 my $verbose = $et->Options('Verbose');
562             #
563             # Process RealAudio file
564             #
565 2 100       9 if ($type eq 'RA') {
566 1         7 ($vers, $extra) = unpack('x4nn', $buff);
567 1         4 $tag = ".ra$vers";
568 1         5 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         6 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
574 1 50       21 if ($verbose > 2) {
575 0         0 $et->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos);
576             }
577 1 50       3 if ($tagInfo) {
578 1         6 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         4 $et->ProcessDirectory(\%dirInfo, $subTablePtr);
586             } else {
587 0         0 $et->Warn('Unsupported RealAudio version');
588             }
589 1         9 return 1;
590             }
591             #
592             # Process RealMedia file
593             #
594             # skip the rest of the RM header
595 1         4 my $size = unpack('x4N', $buff);
596 1 50       6 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         3 for (;;) {
603 6 100       22 $raf->Read($buff, 10) == 10 or last;
604 5         23 ($tag, $size, $vers) = unpack('a4Nn', $buff);
605 5 50       15 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       11 last if $tag eq 'DATA'; # stop normal parsing at DATA tag
610             }
611 5 50       12 if ($size & 0x80000000) {
612 0         0 $et->Warn('Bad chunk header');
613 0         0 last;
614             }
615 5         16 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
616 5 100 66     33 if ($tagInfo and $$tagInfo{SubDirectory}) {
617 4         10 my $fpos = $raf->Tell();
618 4 50       14 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       21 if ($verbose > 2) {
623 0         0 $et->VerboseInfo($tag, $tagInfo, DataPt => \$buff, DataPos => $fpos);
624             }
625 4         13 my $subTablePtr = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
626 4         19 my %dirInfo = (
627             DataPt => \$buff,
628             DataPos => $fpos,
629             DirLen => length $buff,
630             DirStart => 0,
631             );
632 4 100       12 if ($dirCount{$tag}) {
633 1         4 $$et{SET_GROUP1} = '+' . ++$dirCount{$tag};
634             } else {
635 3         7 $dirCount{$tag} = 1;
636             }
637 4         18 $et->ProcessDirectory(\%dirInfo, $subTablePtr);
638 4         10 delete $$et{SET_GROUP1};
639             # keep track of stream MIME types
640 4         9 my $mime = $$et{RealStreamMime};
641 4 100       16 if ($mime) {
642 2         4 delete $$et{RealStreamMime};
643 2         8 $mime =~ s/\0.*//s;
644 2 100       11 push @mimeTypes, $mime unless $mime =~ /^logical-/;
645             }
646             } else {
647 1 50       9 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     10 if (@mimeTypes == 1 and length $mimeTypes[0]) {
655 1         5 $$et{VALUE}{MIMEType} = $mimeTypes[0];
656 1         8 $et->VPrint(0, " MIMEType = $mimeTypes[0]\n");
657             }
658             #
659             # Process footer containing Real metadata and ID3 information
660             #
661 1 50 33     4 if ($raf->Seek(-140, 2) and $raf->Read($buff, 12) == 12 and $buff =~ /^RMJE/) {
      33        
662 1         7 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         7 my %dirInfo = (
668             DataPt => \$buff,
669             DataPos => $raf->Tell() - $metaSize,
670             DirStart => 8,
671             DirLen => length($buff) - 8,
672             );
673 1         5 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     11 if ($raf->Seek(-128, 2) and $raf->Read($buff, 128) == 128 and $buff =~ /^TAG/) {
      33        
679 1         5 $et->VPrint(0, "ID3v1:\n");
680 1         11 my %dirInfo = (
681             DataPt => \$buff,
682             DirStart => 0,
683             DirLen => length($buff),
684             );
685 1         4 my $tagTablePtr = GetTagTable('Image::ExifTool::ID3::v1');
686 1         5 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
687             }
688             }
689 1         7 return 1;
690             }
691              
692             1; # end
693              
694             __END__