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   3634 use strict;
  1         4  
  1         27  
14 1     1   5 use vars qw($VERSION);
  1         1  
  1         32  
15 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         194  
16 1     1   384 use Image::ExifTool::Import;
  1         2  
  1         739  
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 69 my ($et, $meta, $parent) = @_;
107 44 50       71 ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return;
108 44         51 my ($key, $val, $name, $tagTablePtr);
109 44         157 foreach $key (sort keys %$meta) {
110 130         248 my $tag = $parent . ucfirst($key);
111 130 100       249 foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) {
  9         18  
112 136 100       249 ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next;
113 95 100       168 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
114 95 100       161 unless ($$tagTablePtr{$tag}) {
115 62         142 ($name = $tag) =~ s/[^-_a-zA-Z0-9](.?)/\U$1/g;
116 62         77 $name =~ s/ParametersVendorContentComLytroTags//;
117 62         68 my %tagInfo;
118 62 100       197 $tagInfo{Groups} = { 2 => 'Image' } unless $name =~ s/^Devices//;
119 62 100       116 $tagInfo{List} = 1 if ref $$meta{$key} eq 'ARRAY';
120 62         103 $tagInfo{Name} = $name;
121 62 100       100 my $str = $tag eq $name ? '' : " as $name";
122 62         180 $et->VPrint(0, " [adding $tag$str]\n");
123 62         135 AddTagToTable($tagTablePtr, $tag, \%tagInfo);
124             }
125 95         170 $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         2 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     3 return 0 unless $raf->Read($buff, 16) == 16 and $buff =~ /^\x89LFP\x0d\x0a\x1a\x0a/;
143 1         6 $et->SetFileType(); # set the FileType tag
144 1         4 SetByteOrder('MM');
145 1         3 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         8 my $size = Get32u(\$buff, 12);
149 3 50       7 $size & 0x80000000 and $et->Warn('Invalid LFP segment size'), last;
150 3 50       7 $raf->Read($id, 80) == 80 or $et->Warn('Truncated LFP segment'), last; # ignore the sha1
151 3 50       6 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       6 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         8 $et->VerboseDump(\$buff, Addr=>$raf->Tell()-$size);
160 3 50       18 if ($buff =~ /^\{\s+"/) { # JSON metadata?
    0          
161 3         9 pos($buff) = 0;
162 3         12 $et->HandleTag($tagTablePtr, 'JSONMetadata', $buff);
163 3         11 my $meta = Image::ExifTool::Import::ReadJSONObject(undef, \$buff);
164 3         10 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         10 my $pad = 16 - ($size % 16);
171 3 50       11 $raf->Seek($pad, 1) if $pad != 16;
172             }
173 1         3 return 1;
174             }
175              
176             1; # end
177              
178             __END__