File Coverage

blib/lib/Image/ExifTool/Red.pm
Criterion Covered Total %
statement 46 55 83.6
branch 11 26 42.3
condition 4 15 26.6
subroutine 4 4 100.0
pod 0 1 0.0
total 65 101 64.3


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