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   4372 use strict;
  1         3  
  1         32  
14 1     1   6 use vars qw($VERSION);
  1         3  
  1         39  
15 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         250  
16 1     1   439 use Image::ExifTool::Import;
  1         2  
  1         965  
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 85 my ($et, $meta, $parent) = @_;
107 44 50       104 ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return;
108 44         69 my ($key, $val, $name, $tagTablePtr);
109 44         194 foreach $key (sort keys %$meta) {
110 130         305 my $tag = $parent . ucfirst($key);
111 130 100       308 foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) {
  9         21  
112 136 100       297 ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next;
113 95 100       221 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
114 95 100       200 unless ($$tagTablePtr{$tag}) {
115 62         187 ($name = $tag) =~ s/[^-_a-zA-Z0-9](.?)/\U$1/g;
116 62         139 $name =~ s/ParametersVendorContentComLytroTags//;
117 62         81 my %tagInfo;
118 62 100       278 $tagInfo{Groups} = { 2 => 'Image' } unless $name =~ s/^Devices//;
119 62 100       146 $tagInfo{List} = 1 if ref $$meta{$key} eq 'ARRAY';
120 62         119 $tagInfo{Name} = $name;
121 62 100       133 my $str = $tag eq $name ? '' : " as $name";
122 62         225 $et->VPrint(0, " [adding $tag$str]\n");
123 62         163 AddTagToTable($tagTablePtr, $tag, \%tagInfo);
124             }
125 95         248 $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         4 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         7 $et->SetFileType(); # set the FileType tag
144 1         5 SetByteOrder('MM');
145 1         11 my $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
146 1         5 while ($raf->Read($buff, 16) == 16) {
147 3 50       17 $buff =~ /^\x89LF/ or $et->Warn('LFP format error'), last;
148 3         11 my $size = Get32u(\$buff, 12);
149 3 50       9 $size & 0x80000000 and $et->Warn('Invalid LFP segment size'), last;
150 3 50       9 $raf->Read($id, 80) == 80 or $et->Warn('Truncated LFP segment'), last; # ignore the sha1
151 3 50       8 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       9 $raf->Read($buff,$size) == $size or $et->Warn('Truncated LFP data'), last;
159 3         11 $et->VerboseDump(\$buff, Addr=>$raf->Tell()-$size);
160 3 50       17 if ($buff =~ /^\{\s+"/) { # JSON metadata?
    0          
161 3         11 pos($buff) = 0;
162 3         14 $et->HandleTag($tagTablePtr, 'JSONMetadata', $buff);
163 3         13 my $meta = Image::ExifTool::Import::ReadJSONObject(undef, \$buff);
164 3         12 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         12 my $pad = 16 - ($size % 16);
171 3 50       16 $raf->Seek($pad, 1) if $pad != 16;
172             }
173 1         4 return 1;
174             }
175              
176             1; # end
177              
178             __END__