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   4330 use strict;
  1         2  
  1         47  
14 1     1   4 use vars qw($VERSION);
  1         1  
  1         34  
15 1     1   3 use Image::ExifTool qw(:DataAccess :Utils);
  1         1  
  1         174  
16 1     1   410 use Image::ExifTool::Import;
  1         15  
  1         856  
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 64 my ($et, $meta, $parent) = @_;
107 44 50       57 ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return;
108 44         44 my ($key, $val, $name, $tagTablePtr);
109 44         65 foreach $key (Image::ExifTool::OrderedKeys($meta)) {
110 130         162 my $tag = $parent . ucfirst($key);
111 130 100       245 foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) {
  9         15  
112 136 100       221 ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next;
113 95 100       134 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
114 95 100       152 unless ($$tagTablePtr{$tag}) {
115 62         129 ($name = $tag) =~ s/[^-_a-zA-Z0-9](.?)/\U$1/g;
116 62         67 $name =~ s/ParametersVendorContentComLytroTags//;
117 62         59 my %tagInfo;
118 62 100       158 $tagInfo{Groups} = { 2 => 'Image' } unless $name =~ s/^Devices//;
119 62 100       105 $tagInfo{List} = 1 if ref $$meta{$key} eq 'ARRAY';
120 62         84 $tagInfo{Name} = $name;
121 62 100       83 my $str = $tag eq $name ? '' : " as $name";
122 62         151 $et->VPrint(0, " [adding $tag$str]\n");
123 62         90 AddTagToTable($tagTablePtr, $tag, \%tagInfo);
124             }
125 95         155 $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 2 my ($et, $dirInfo) = @_;
137 1         3 my $raf = $$dirInfo{RAF};
138 1         3 my $verbose = $et->Options('Verbose');
139 1         2 my ($buff, $id);
140              
141             # validate the Lytro file header
142 1 50 33     2 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         14 my $tagTablePtr = GetTagTable('Image::ExifTool::Lytro::Main');
146 1         4 while ($raf->Read($buff, 16) == 16) {
147 3 50       9 $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       5 $raf->Read($id, 80) == 80 or $et->Warn('Truncated LFP segment'), last; # ignore the sha1
151 3 50       14 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       5 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         7 $et->VerboseDump(\$buff, Addr=>$raf->Tell()-$size);
160 3 50       19 if ($buff =~ /^\{\s+"/) { # JSON metadata?
    0          
161 3         8 pos($buff) = 0;
162 3         8 $et->HandleTag($tagTablePtr, 'JSONMetadata', $buff);
163 3         11 my $meta = Image::ExifTool::Import::ReadJSONObject(undef, \$buff);
164 3         7 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         7 my $pad = 16 - ($size % 16);
171 3 50       14 $raf->Seek($pad, 1) if $pad != 16;
172             }
173 1         4 return 1;
174             }
175              
176             1; # end
177              
178             __END__