File Coverage

blib/lib/Image/ExifTool/Lytro.pm
Criterion Covered Total %
statement 57 61 93.4
branch 24 38 63.1
condition 1 3 33.3
subroutine 6 6 100.0
pod 0 2 0.0
total 88 110 80.0


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Lytro.pm
3             #
4             # Description: Read Lytro LFP files
5             #
6             # Revisions: 2014-07-17 - P. Harvey Created
7             #
8             # References: 1) http://optics.miloush.net/lytro/TheFileFormat.aspx
9             #------------------------------------------------------------------------------
10              
11             package Image::ExifTool::Lytro;
12              
13 1     1   4433 use strict;
  1         3  
  1         32  
14 1     1   5 use vars qw($VERSION);
  1         2  
  1         39  
15 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         243  
16 1     1   458 use Image::ExifTool::Import;
  1         3  
  1         951  
17              
18             $VERSION = '1.03';
19              
20             sub ExtractTags($$$);
21              
22             # Lytro LFP tags (ref PH)
23             %Image::ExifTool::Lytro::Main = (
24             GROUPS => { 2 => 'Camera' },
25             VARS => { NO_ID => 1 },
26             NOTES => q{
27             Tag definitions for Lytro Light Field Picture (LFP) files. ExifTool
28             extracts the full JSON metadata blocks, as well as breaking them down into
29             individual tags. All available tags are extracted from the JSON metadata,
30             even if they don't appear in the table below.
31             },
32             JSONMetadata => {
33             Notes => 'the full JSON-format metadata blocks',
34             Binary => 1,
35             List => 1,
36             },
37             EmbeddedImage => {
38             Notes => 'JPEG image embedded in LFP files written by Lytro Desktop',
39             Groups => { 2 => 'Preview' },
40             Binary => 1,
41             },
42             Type => { Name => 'CameraType' },
43             CameraMake => { Name => 'Make' },
44             CameraModel => { Name => 'Model', Description => 'Camera Model Name' },
45             CameraSerialNumber => { Name => 'SerialNumber'},
46             CameraFirmware => { Name => 'FirmwareVersion'},
47             DevicesAccelerometerSampleArrayTime => { Name => 'AccelerometerTime'},
48             DevicesAccelerometerSampleArrayX => { Name => 'AccelerometerX'},
49             DevicesAccelerometerSampleArrayY => { Name => 'AccelerometerY'},
50             DevicesAccelerometerSampleArrayZ => { Name => 'AccelerometerZ'},
51             DevicesClockZuluTime => {
52             Name => 'DateTimeOriginal',
53             Description => 'Date/Time Original',
54             Groups => { 2 => 'Time' },
55             ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)',
56             PrintConv => '$self->ConvertDateTime($val)',
57             },
58             DevicesLensFNumber => {
59             Name => 'FNumber',
60             PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
61             },
62             DevicesLensFocalLength => {
63             Name => 'FocalLength',
64             ValueConv => '$val * 1000', # convert from metres to mm
65             PrintConv => 'sprintf("%.1f mm",$val)',
66             },
67             DevicesLensTemperature => {
68             Name => 'LensTemperature',
69             PrintConv => 'sprintf("%.1f C",$val)',
70             },
71             DevicesSocTemperature => {
72             Name => 'SocTemperature',
73             PrintConv => 'sprintf("%.1f C",$val)',
74             },
75             DevicesShutterFrameExposureDuration => {
76             Name => 'FrameExposureTime',
77             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
78             },
79             DevicesShutterPixelExposureDuration => {
80             Name => 'ExposureTime',
81             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
82             },
83             DevicesSensorPixelPitch => {
84             Name => 'FocalPlaneXResolution',
85             Notes => 'Y resolution is the same as X resolution',
86             ValueConv => '25.4 / $val / 1000', # convert from metres to pixels/inch
87             },
88             DevicesSensorSensorSerial => { Name => 'SensorSerialNumber'},
89             DevicesSensorIso => { Name => 'ISO' },
90             ImageLimitExposureBias => { Groups => { 2 => 'Image' }, PrintConv => 'sprintf("%+.1f", $val)' },
91             ImageModulationExposureBias => { Groups => { 2 => 'Image' }, PrintConv => 'sprintf("%+.1f", $val)' },
92             ImageOrientation => {
93             Name => 'Orientation',
94             Groups => { 2 => 'Image' },
95             PrintConv => {
96             1 => 'Horizontal (normal)',
97             },
98             },
99             );
100              
101             #------------------------------------------------------------------------------
102             # Extract tags from a parsed JSON hash
103             # Inputs: 0) ExifTool ref, 1) tag hash ref, 2) base tag name
104             sub ExtractTags($$$)
105             {
106 44     44 0 126 my ($et, $meta, $parent) = @_;
107 44 50       102 ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return;
108 44         66 my ($key, $val, $name, $tagTablePtr);
109 44         184 foreach $key (sort keys %$meta) {
110 130         307 my $tag = $parent . ucfirst($key);
111 130 100       292 foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) {
  9         22  
112 136 100       299 ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next;
113 95 100       1028 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
114 95 100       198 unless ($$tagTablePtr{$tag}) {
115 62         188 ($name = $tag) =~ s/[^-_a-zA-Z0-9](.?)/\U$1/g;
116 62         101 $name =~ s/ParametersVendorContentComLytroTags//;
117 62         91 my %tagInfo;
118 62 100       241 $tagInfo{Groups} = { 2 => 'Image' } unless $name =~ s/^Devices//;
119 62 100       141 $tagInfo{List} = 1 if ref $$meta{$key} eq 'ARRAY';
120 62         112 $tagInfo{Name} = $name;
121 62 100       125 my $str = $tag eq $name ? '' : " as $name";
122 62         252 $et->VPrint(0, " [adding $tag$str]\n");
123 62         151 AddTagToTable($tagTablePtr, $tag, \%tagInfo);
124             }
125 95         211 $et->HandleTag($tagTablePtr, $tag, $val);
126             }
127             }
128             }
129              
130             #------------------------------------------------------------------------------
131             # Process segments from a Lytro LFP image
132             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
133             # Returns: 1 on success, 0 if this wasn't a valid Lytro image
134             sub ProcessLFP($$)
135             {
136 1     1 0 4 my ($et, $dirInfo) = @_;
137 1         4 my $raf = $$dirInfo{RAF};
138 1         5 my $verbose = $et->Options('Verbose');
139 1         12 my ($buff, $id);
140              
141             # validate the Lytro file header
142 1 50 33     7 return 0 unless $raf->Read($buff, 16) == 16 and $buff =~ /^\x89LFP\x0d\x0a\x1a\x0a/;
143 1         7 $et->SetFileType(); # set the FileType tag
144 1         9 SetByteOrder('MM');
145 1         13 my $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
146 1         18 while ($raf->Read($buff, 16) == 16) {
147 3 50       16 $buff =~ /^\x89LF/ or $et->Warn('LFP format error'), last;
148 3         14 my $size = Get32u(\$buff, 12);
149 3 50       9 $size & 0x80000000 and $et->Warn('Invalid LFP segment size'), last;
150 3 50       14 $raf->Read($id, 80) == 80 or $et->Warn('Truncated LFP segment'), last; # ignore the sha1
151 3 50       17 if ($verbose) {
152 0         0 $id =~ s/\0.*//s;
153 0         0 $et->VPrint(0, substr($buff,1,3), " segment ($size bytes, $id)\n");
154             }
155 3 50       12 if ($size > 20000000) {
156 0 0       0 $raf->Seek($size, 1) or $et->Warn('Seek error in LFP file'), last;
157             } else {
158 3 50       8 $raf->Read($buff,$size) == $size or $et->Warn('Truncated LFP data'), last;
159 3         9 $et->VerboseDump(\$buff, Addr=>$raf->Tell()-$size);
160 3 50       18 if ($buff =~ /^\{\s+"/) { # JSON metadata?
    0          
161 3         13 pos($buff) = 0;
162 3         14 $et->HandleTag($tagTablePtr, 'JSONMetadata', $buff);
163 3         18 my $meta = Image::ExifTool::Import::ReadJSONObject(undef, \$buff);
164 3         9 ExtractTags($et, $meta, '');
165             } elsif ($buff =~ /^\xff\xd8\xff/) { # embedded JPEG image?
166 0         0 $et->HandleTag($tagTablePtr, 'EmbeddedImage', $buff);
167             }
168             }
169             # skip padding if necessary
170 3         13 my $pad = 16 - ($size % 16);
171 3 50       12 $raf->Seek($pad, 1) if $pad != 16;
172             }
173 1         5 return 1;
174             }
175              
176             1; # end
177              
178             __END__