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   6139 use strict;
  1         3  
  1         50  
14 1     1   5 use vars qw($VERSION);
  1         2  
  1         51  
15 1     1   3 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         307  
16 1     1   519 use Image::ExifTool::Import;
  1         2  
  1         1100  
17              
18             $VERSION = '1.04';
19              
20             sub ExtractTags($$$);
21              
22             # Lytro LFP tags (ref PH)
23             %Image::ExifTool::Lytro::Main = (
24             GROUPS => { 2 => 'Camera' },
25             VARS => { ID_FMT => 'none' },
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 75 my ($et, $meta, $parent) = @_;
107 44 50       83 ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return;
108 44         54 my ($key, $val, $name, $tagTablePtr);
109 44         109 foreach $key (Image::ExifTool::OrderedKeys($meta)) {
110 130         250 my $tag = $parent . ucfirst($key);
111 130 100       323 foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) {
  9         21  
112 136 100       293 ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next;
113 95 100       182 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
114 95 100       204 unless ($$tagTablePtr{$tag}) {
115 62         166 ($name = $tag) =~ s/[^-_a-zA-Z0-9](.?)/\U$1/g;
116 62         88 $name =~ s/ParametersVendorContentComLytroTags//;
117 62         69 my %tagInfo;
118 62 100       216 $tagInfo{Groups} = { 2 => 'Image' } unless $name =~ s/^Devices//;
119 62 100       137 $tagInfo{List} = 1 if ref $$meta{$key} eq 'ARRAY';
120 62         120 $tagInfo{Name} = $name;
121 62 100       105 my $str = $tag eq $name ? '' : " as $name";
122 62         194 $et->VPrint(0, " [adding $tag$str]\n");
123 62         128 AddTagToTable($tagTablePtr, $tag, \%tagInfo);
124             }
125 95         228 $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 3 my ($et, $dirInfo) = @_;
137 1         3 my $raf = $$dirInfo{RAF};
138 1         5 my $verbose = $et->Options('Verbose');
139 1         3 my ($buff, $id);
140              
141             # validate the Lytro file header
142 1 50 33     4 return 0 unless $raf->Read($buff, 16) == 16 and $buff =~ /^\x89LFP\x0d\x0a\x1a\x0a/;
143 1         8 $et->SetFileType(); # set the FileType tag
144 1         6 SetByteOrder('MM');
145 1         4 my $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
146 1         6 while ($raf->Read($buff, 16) == 16) {
147 3 50       14 $buff =~ /^\x89LF/ or $et->Warn('LFP format error'), last;
148 3         40 my $size = Get32u(\$buff, 12);
149 3 50       8 $size & 0x80000000 and $et->Warn('Invalid LFP segment size'), last;
150 3 50       8 $raf->Read($id, 80) == 80 or $et->Warn('Truncated LFP segment'), last; # ignore the sha1
151 3 50       9 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       8 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       26 $raf->Read($buff,$size) == $size or $et->Warn('Truncated LFP data'), last;
159 3         18 $et->VerboseDump(\$buff, Addr=>$raf->Tell()-$size);
160 3 50       19 if ($buff =~ /^\{\s+"/) { # JSON metadata?
    0          
161 3         12 pos($buff) = 0;
162 3         14 $et->HandleTag($tagTablePtr, 'JSONMetadata', $buff);
163 3         17 my $meta = Image::ExifTool::Import::ReadJSONObject(undef, \$buff);
164 3         13 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         11 my $pad = 16 - ($size % 16);
171 3 50       21 $raf->Seek($pad, 1) if $pad != 16;
172             }
173 1         21 return 1;
174             }
175              
176             1; # end
177              
178             __END__