File Coverage

blib/lib/Image/ExifTool/Red.pm
Criterion Covered Total %
statement 44 53 83.0
branch 11 26 42.3
condition 4 15 26.6
subroutine 4 4 100.0
pod 0 1 0.0
total 63 99 63.6


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Red.pm
3             #
4             # Description: Read Redcode R3D video files
5             #
6             # Revisions: 2018-01-25 - P. Harvey Created
7             #
8             # References: 1) http://www.wikiwand.com/en/REDCODE
9             #------------------------------------------------------------------------------
10              
11             package Image::ExifTool::Red;
12              
13 1     1   4490 use strict;
  1         2  
  1         34  
14 1     1   6 use vars qw($VERSION);
  1         2  
  1         39  
15 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         1125  
16              
17             $VERSION = '1.01';
18              
19             sub ProcessR3D($$);
20              
21             # RED format codes (ref PH)
22             my %redFormat = (
23             0 => 'int8u',
24             1 => 'string',
25             2 => 'float',
26             3 => 'int8u', # (how is this different than 0?)
27             4 => 'int16u',
28             5 => 'int8s', # (not sure about this)
29             6 => 'int32s',
30             7 => 'undef', # (mixed-format structure?)
31             8 => 'int32u', # (NC)
32             9 => 'undef', # ? (seen 256 bytes, all zero)
33             );
34              
35             # error strings
36             my $errTrunc = 'Truncated R3D file';
37              
38             # RED directory tags (ref PH)
39             %Image::ExifTool::Red::Main = (
40             GROUPS => { 2 => 'Camera' },
41             NOTES => 'Tags extracted from Redcode R3D video files.',
42             VARS => { ALPHA_FIRST => 1 },
43              
44             RED1 => { Name => 'Red1Header', SubDirectory => { TagTable => 'Image::ExifTool::Red::RED1' } },
45             RED2 => { Name => 'Red2Header', SubDirectory => { TagTable => 'Image::ExifTool::Red::RED2' } },
46              
47             # (upper 4 bits of tag ID are the format code)
48             # ---- format 1 ----
49             0x1000 => 'StartEdgeCode', #1
50             0x1001 => { Name => 'StartTimecode', Groups => { 2 => 'Time' } }, #1
51             0x1002 => { #1
52             Name => 'OtherDate1',
53             Groups => { 2 => 'Time' },
54             # format is "YYYY_MM_DD[_TZ?]"
55             ValueConv => '$val =~ s/(\d{4})_(\d{2})_/$1:$2:/; $val =~ tr/_/ /; $val',
56             },
57             0x1003 => { #1
58             Name => 'OtherDate2',
59             Groups => { 2 => 'Time' },
60             ValueConv => '$val =~ s/(\d{4})_(\d{2})_/$1:$2:/; $val =~ tr/_/ /; $val',
61             },
62             0x1004 => { #1
63             Name => 'OtherDate3',
64             Groups => { 2 => 'Time' },
65             ValueConv => '$val =~ s/(\d{4})_(\d{2})_/$1:$2:/; $val =~ tr/_/ /; $val',
66             },
67             0x1005 => { #1
68             Name => 'DateTimeOriginal',
69             Description => 'Date/Time Original',
70             Groups => { 2 => 'Time' },
71             ValueConv => '$val =~ s/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/$1:$2:$3 $4:$5:/; $val',
72             PrintConv => '$self->ConvertDateTime($val)',
73             },
74             0x1006 => 'SerialNumber', #1
75             0x1019 => 'CameraType', #1
76             0x101a => { Name => 'ReelNumber', Groups => { 2 => 'Video' } }, #1
77             0x101b => { Name => 'Take', Groups => { 2 => 'Video' } },
78             0x1023 => { #1
79             Name => 'DateCreated',
80             Groups => { 2 => 'Time' },
81             ValueConv => '$val =~ s/(\d{4})(\d{2})/$1:$2:/; $val',
82             },
83             0x1024 => { #1
84             Name => 'TimeCreated',
85             Groups => { 2 => 'Time' },
86             ValueConv => '$val =~ s/(\d{2})(\d{2})/$1:$2:/; $val',
87             },
88             0x1025 => 'FirmwareVersion', #1
89             0x1029 => { Name => 'ReelTimecode', Groups => { 2 => 'Time' } }, #1
90             0x102a => 'StorageType', #1
91             0x1030 => { #1
92             Name => 'StorageFormatDate',
93             Groups => { 2 => 'Time' },
94             ValueConv => '$val =~ s/(\d{4})(\d{2})/$1:$2:/; $val',
95             },
96             0x1031 => { #1
97             Name => 'StorageFormatTime',
98             Groups => { 2 => 'Time' },
99             ValueConv => '$val =~ s/(\d{2})(\d{2})/$1:$2:/; $val',
100             },
101             0x1032 => 'StorageSerialNumber', #1
102             0x1033 => 'StorageModel', #1
103             0x1036 => 'AspectRatio', #1
104             # 0x1041 - seen 'NA'
105             0x1042 => 'Revision', # ? (seen "TODO, rev EPIC-1.0" and "MYSTERIUM X, rev EPIC-1.0")
106             # 0x1051 - seen 'C', 'L'
107             0x1056 => 'OriginalFileName',
108             0x106e => 'LensMake',
109             0x106f => 'LensNumber', # (last 2 hex digits are LensType)
110             0x1070 => 'LensModel',
111             0x1071 => {
112             Name => 'Model',
113             Description => 'Camera Model Name',
114             },
115             0x107c => { Name => 'CameraOperator', Groups => { 2 => 'Author' } },
116             0x1086 => {
117             Name => 'VideoFormat',
118             Groups => { 2 => 'Video' },
119             },
120             0x1096 => 'Filter', # optical low-pass filter
121             0x10a0 => 'Brain',
122             0x10a1 => 'Sensor',
123             # ---- format 2 ----
124             0x200d => 'ColorTemperature',
125             # 0x200e - (sometimes this is frame rate)
126             # 0x2015 - seen '1 1 1' (RGBGain or RGBGamma?)
127             0x204b => 'RGBCurves', # (blackx/y,toex/y,midx/y,kneex/y,whitex/y)
128             0x2066 => {
129             Name => 'OriginalFrameRate',
130             Groups => { 2 => 'Video' },
131             PrintConv => 'int($val * 1000 + 0.5) / 1000',
132             },
133             # ---- format 4 ----
134             0x4037 => { Name => 'CropArea' }, # (NC)
135             0x403b => 'ISO',
136             # 0x404e - related to CropArea (or "0 0 0 0")
137             0x406a => { Name => 'FNumber', ValueConv => '$val / 10' },
138             0x406b => 'FocalLength',
139             # 0x4084 - related to ISO?
140             # 0x4087 - related to ISO?
141             # ---- format 6 ----
142             0x606c => { Name => 'FocusDistance', ValueConv => '$val/1000', PrintConv => '"$val m"' },
143             );
144              
145             # RED1 file header (ref PH)
146             %Image::ExifTool::Red::RED1 = (
147             GROUPS => { 2 => 'Video' },
148             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
149             NOTES => 'Redcode version 1 header.',
150             # 0x00 - int32u: length of header
151             # 0x04 - string: "RED1"
152             # 0x0a - string: "R1"
153             0x07 => { Name => 'RedcodeVersion', Format => 'string[1]' }, #1
154             # 0x0e - looks funny; my sample has a value of 43392 here
155             # 0x0e => { Name => 'AudioSampleRate', Format => 'int16u' }, #1
156             0x36 => { Name => 'ImageWidth', Format => 'int16u' }, #1
157             0x3a => { Name => 'ImageHeight', Format => 'int16u' }, #PH (ref 1 gave 0x3c)
158             0x3e => { #PH (ref 1 gave 0x42 for denom)
159             Name => 'FrameRate',
160             Format => 'rational32u',
161             PrintConv => 'int($val * 1000 + 0.5) / 1000',
162             },
163             0x43 => { Name => 'OriginalFileName', Format => 'string[32]' }, #1
164             );
165              
166             # RED2 file header (ref PH)
167             %Image::ExifTool::Red::RED2 = (
168             GROUPS => { 2 => 'Video' },
169             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
170             NOTES => 'Redcode version 2 header.',
171             # 0x00 - int32u: length of header
172             # 0x04 - string: "RED2"
173             0x07 => { Name => 'RedcodeVersion', Format => 'string[1]' },
174             # 0x08 - seen 0x05
175             # 0x09 - seen 0x0d,0x0f,0x10
176             # 0x0a - string: "R2"
177             # 0x0c - seen 0x04,0x05,0x07,0x08,0x0b,0x0c
178             # 0x0d - seen 0x01,0x08 (and 0x09 in block 1)
179             # 0x0e - int16u: seen 3072
180             # 0x10 - looks like some sort of 32-byte hash or something (same in other blocks)
181             # 0x30-0x3f - mostly 0x00's with a couple of 0x01's
182             # 0x40 - int8u: count of 0x18-byte "rdi" records
183             # 0x41-0x43 - seen "\0\0\x01"
184             # ---- rdi record: (0x18 bytes long) ----
185             # 0x44 - string: "rdi#" (where number is index of "rdi" record, starting at \x01)
186             0x4c => { Name => 'ImageWidth', Format => 'int32u' },
187             0x50 => { Name => 'ImageHeight', Format => 'int32u' },
188             # 0x54 - seen 0x11,0x13,0x15 (and 0x03 in "rdi\x02" record)
189             # 0x55 - seen 0x02
190             0x56 => {
191             Name => 'FrameRate',
192             Format => 'int16u[3]',
193             ValueConv => 'my @a = split " ",$val; ($a[1] * 0x10000 + $a[2]) / $a[0]',
194             PrintConv => 'int($val * 1000 + 0.5) / 1000',
195             },
196             # (immediately following last "rdi" record is a
197             # Red directory beginning with int16u size)
198             );
199              
200             #------------------------------------------------------------------------------
201             # Process metadata from a Redcode R3D video (ref PH)
202             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
203             # Returns: 1 on success, 0 if this wasn't a valid R3D file
204             sub ProcessR3D($$)
205             {
206 1     1 0 4 my ($et, $dirInfo) = @_;
207 1         3 my $raf = $$dirInfo{RAF};
208 1         3 my ($buff, $buf2, $pos, $dirLen, $dirEnd);
209 1         36 my $verbose = $et->Options('Verbose');
210              
211             # R3D file structure:
212             # - each block starts with int32u block size followed by 4-byte block type
213             # - first block type is either "RED1" (version 1) or "RED2" (version 2)
214             # - blocks begin on even 0x1000 byte boundaries for version 2 files
215              
216             # validate the file header
217 1 50 33     5 return 0 unless $raf->Read($buff, 8) == 8 and $buff =~ /^\0\0..RED(1|2)/s;
218 1         3 my $ver = $1;
219 1         8 my $size = unpack('N', $buff);
220 1 50       3 return 0 if $size < 8;
221              
222 1         6 $et->SetFileType();
223 1         7 SetByteOrder('MM');
224 1         8 my $tagTablePtr = GetTagTable('Image::ExifTool::Red::Main');
225 1         2 my $dataPos = 0;
226              
227             # read the first block of the file
228 1 50       5 $raf->Read($buf2, $size - 8) == $size - 8 or return $et->Warn($errTrunc);
229 1         3 $buff .= $buf2;
230              
231             # extract tags from the header
232 1         7 $et->HandleTag($tagTablePtr, "RED$ver", undef, DataPt => \$buff);
233              
234             # read the second block from a version 1 file because
235             # the first block doesn't contain a Red directory
236 1 50       5 if ($ver eq '1') {
237             # (read more than we need)
238 0 0       0 $raf->Read($buff, 0x10000) or return $et->Warn($errTrunc);
239 0         0 $dataPos += $size;
240 0         0 $pos = 0x22; # directory starts at offset 0x22
241             } else {
242             # calculate position of Red directory start
243 1 50       3 length($buff) < 0x41 and return $et->Warn($errTrunc);
244 1         5 my $n = Get8u(\$buff, 0x40); # number of "rdi" records
245 1         4 $pos = 0x44 + $n * 0x18;
246             }
247 1 50       4 if ($pos + 8 > length $buff) {
248 0         0 $dirLen = 0; # find directory the hard way
249             } else {
250 1         5 $dirLen = Get16u(\$buff, $pos); # get length of Red directory
251 1         4 $pos += 2; # skip length word
252             }
253             # do sanity check on the directory size (in case our assumptions were wrong)
254 1 50 33     10 if ($dirLen < 300 or $dirLen >= 2048 or $pos + $dirLen > length $buff) {
      33        
255             # tag 0x1000 with length 0x000f should be near the directory start
256 0 0       0 $buff =~ /\0\x0f\x10\0/g or return $et->Warn("Can't find Red directory");
257 0         0 $pos = pos($buff) - 4;
258 0         0 $dirEnd = length $buff;
259 0         0 undef $dirLen;
260 0         0 $et->Warn('This R3D file is different. Please submit a sample for testing');
261             } else {
262 1         2 $dirEnd = $pos + $dirLen;
263             }
264 1 50       3 $$et{INDENT} .= '| ', $et->VerboseDir('Red', undef, $dirLen) if $verbose;
265              
266             # process the first Red directory
267 1         3 while ($pos + 4 <= $dirEnd) {
268 83         173 my $len = Get16u(\$buff, $pos);
269 83 50 33     265 last if $len < 4 or $pos + $len > $dirEnd;
270 83         184 my $tag = Get16u(\$buff, $pos + 2);
271 83         166 my $fmt = $redFormat{$tag >> 12}; # format is top 4 bits of tag ID (ref PH)
272 83 50 0     150 $fmt or $dirLen && $et->Warn('Unknown format code'), last;
273 83         249 $et->HandleTag($tagTablePtr, $tag, undef,
274             DataPt => \$buff,
275             DataPos => $dataPos,
276             Start => $pos + 4,
277             Size => $len - 4,
278             Format => $fmt,
279             );
280 83         195 $pos += $len;
281             }
282 1 50       4 $$et{INDENT} = substr($$et{INDENT}, 0, -2) if $verbose;
283              
284 1         3 return 1;
285             }
286              
287             1; # end
288              
289             __END__