exiftool 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 97cc49e13b847ed687dca2430f6e3974d1327998
4
+ data.tar.gz: 7a6c7e780ad4962983b491c517d2f4a8a890264b
5
+ SHA512:
6
+ metadata.gz: 614fee303365b7d799ebf3d29109502194ed89f35685f4dfabe45a729fd184a23e3d657bc4c91270e5c9ffdda7873f613956c4fa511bfa067f52996500f93f28
7
+ data.tar.gz: 97ad66d4cab65ea08313ae3b24222852ab9477113deb276eecb88dbda568d59833ddcd164485a5d68857cbca6393729443ddd792d9924d136785c4d1873136f9
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ script: bundle exec rake
3
+ before_install:
4
+ - sudo apt-get install libimage-exiftool-perl
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - rbx-19mode
9
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in exiftoolr.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # Ruby wrapper for ExifTool
2
+
3
+ [![Build Status](https://secure.travis-ci.org/mceachen/exiftool.png?branch=master)](http://travis-ci.org/mceachen/exiftool)
4
+ [![Gem Version](https://badge.fury.io/rb/exiftool.png)](http://rubygems.org/gems/exiftool)
5
+ [![Code Climate](https://codeclimate.com/github/mceachen/exiftool.png)](https://codeclimate.com/github/mceachen/exiftool)
6
+
7
+ This gem is the simplest thing that could possibly work that
8
+ reads the output of [exiftool](http://www.sno.phy.queensu.ca/~phil/exiftool)
9
+ and renders it into a ruby hash, with correctly typed values and symbolized keys.
10
+
11
+ Rubies 1.9 and later are supported.
12
+
13
+ ## Features
14
+
15
+ * GPS latitude and longitude values are parsed as signed floats,
16
+ where north and east are positive, and west and south are negative.
17
+ * Values like shutter speed and exposure time are rendered as Rationals,
18
+ which lets the caller show them as fractions (1/250) or as comparable numeric instances.
19
+ * String values like "interop" and "serial number" are kept as strings
20
+ (which preserves zero prefixes)
21
+ * Timestamps are attempted to be interpreted with correct timezones and sub-second resolution, if
22
+ the header contains that data.
23
+ Please note that EXIF headers don't always include a timezone offset, so we just adopt the system
24
+ timezone, which may, of course, be wrong.
25
+ * No method_missing madness
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'Exiftool'
31
+ e = Exiftool.new("path/to/iPhone 4S.jpg")
32
+ e.to_hash
33
+ # => {:make => "Apple", :gps_longitude => -122.47566667, …
34
+ e.to_display_hash
35
+ # => {"Make" => "Apple", "GPS Longitude" => -122.47566667, …
36
+ ```
37
+
38
+ ### Multiple file support
39
+
40
+ This gem supports Exiftool's multiget, which lets you fetch metadata for many files at once.
41
+
42
+ This can be dramatically more efficient (like, 60x faster) than spinning up the ```exiftool```
43
+ process for each file.
44
+
45
+ Supply an array to the Exiftool initializer, then use ```.result_for```:
46
+
47
+ ```ruby
48
+ require 'Exiftool'
49
+ e = Exiftool.new(Dir["**/*.jpg"])
50
+ result = e.result_for("path/to/iPhone 4S.jpg")
51
+ result.to_hash
52
+ # => {:make => "Apple", :gps_longitude => -122.47566667, …
53
+ result[:gps_longitude]
54
+ # => -122.47566667
55
+
56
+ e.files_with_results
57
+ # => ["path/to/iPhone 4S.jpg", "path/to/Droid X.jpg", …
58
+ ```
59
+
60
+ ### When things go wrong
61
+
62
+ * ```Exiftool::NoSuchFile``` is raised if the provided filename doesn't exist.
63
+ * ```Exiftool::ExiftoolNotInstalled``` is raised if ```exiftool``` isn't in your ```PATH```.
64
+ * If ExifTool has a problem reading EXIF data, no exception is raised, but ```#errors?``` will return true:
65
+
66
+ ```ruby
67
+ Exiftool.new("Gemfile").errors?
68
+ #=> true
69
+ ```
70
+
71
+ ## Installation
72
+
73
+ First [install ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html).
74
+
75
+ Then, add this your Gemfile:
76
+
77
+ gem 'exiftool'
78
+
79
+ and then run ```bundle```.
80
+
81
+ ## Change history
82
+
83
+ ### 0.2.0
84
+
85
+ * Renamed from exiftoolr to exiftool
86
+
87
+ ### 0.1.0
88
+
89
+ * Better timestamp parsing—now both sub-second and timezone offsets are handled correctly
90
+ * Switched to minitest-spec
91
+ * Ruby 1.8.7 is no longer supported, hence the minor version uptick.
92
+
93
+ ### 0.0.9
94
+
95
+ * Explicitly added MIT licensing to the gemspec.
96
+
97
+ ### 0.0.8
98
+
99
+ * Extracted methods in parsing to make the code complexity lower. FOUR DOT OH GPA
100
+
101
+ ### 0.0.7
102
+
103
+ * Added warning values for EXIF headers that are corrupt
104
+ * Made initialize gracefully accept an empty array, or an array of Pathname instances
105
+ * Added support for ruby 1.9.3 and exiftool v8.15 (Ubuntu Natty) and v8.85 (current stable version)
106
+
107
+ ### 0.0.5
108
+
109
+ Fixed homepage URL in gemspec
110
+
111
+ ### 0.0.4
112
+
113
+ Added support for multiple file fetching (which is *much* faster for large directories)
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'yard'
4
+ YARD::Rake::YardocTask.new do |t|
5
+ t.files = ['lib/**/*.rb', 'README.md']
6
+ end
7
+
8
+ require 'rake/testtask'
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.libs.push "lib"
12
+ t.libs.push "test"
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
16
+
17
+ task :default => :test
data/exiftool.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'exiftool/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'exiftool'
8
+ spec.version = Exiftool::VERSION
9
+ spec.authors = ['Matthew McEachen']
10
+ spec.email = %w(matthew-github@mceachen.org)
11
+ spec.homepage = 'https://github.com/mceachen/exiftoolr'
12
+ spec.summary = %q{Multiget ExifTool wrapper for ruby}
13
+ spec.description = %q{Multiget ExifTool wrapper for ruby}
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.requirements << 'ExifTool (see http://www.sno.phy.queensu.ca/~phil/exiftool/)'
22
+
23
+ spec.add_dependency 'json'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'bundler'
26
+ spec.add_development_dependency 'yard'
27
+ spec.add_development_dependency 'minitest'
28
+ spec.add_development_dependency 'minitest-great_expectations'
29
+ spec.add_development_dependency 'minitest-reporters' unless ENV['CI']
30
+ end
data/lib/exiftool.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'json'
2
+ require 'shellwords'
3
+ require 'exiftool/result'
4
+
5
+ class Exiftool
6
+ class NoSuchFile < StandardError ; end
7
+ class NotAFile < StandardError ; end
8
+ class ExiftoolNotInstalled < StandardError ; end
9
+
10
+ def self.exiftool_installed?
11
+ exiftool_version > 0
12
+ end
13
+
14
+ def self.exiftool_version
15
+ @@exiftool_version ||= `exiftool -ver 2> /dev/null`.to_f
16
+ end
17
+
18
+ def self.expand_path(filename)
19
+ raise(NoSuchFile, filename) unless File.exist?(filename)
20
+ raise(NotAFile, filename) unless File.file?(filename)
21
+ File.expand_path(filename)
22
+ end
23
+
24
+ def initialize(filenames, exiftool_opts = '')
25
+ @file2result = {}
26
+ filenames = [filenames] if filenames.is_a?(String)
27
+ unless filenames.empty?
28
+ escaped_filenames = filenames.collect do |f|
29
+ Shellwords.escape(self.class.expand_path(f.to_s))
30
+ end.join(" ")
31
+ # I'd like to use -dateformat, but it doesn't support timezone offsets properly,
32
+ # nor sub-second timestamps.
33
+ cmd = "exiftool #{exiftool_opts} -j -coordFormat \"%.8f\" #{escaped_filenames} 2> /dev/null"
34
+ json = `#{cmd}`
35
+ raise ExiftoolNotInstalled if json == ""
36
+ JSON.parse(json).each do |raw|
37
+ result = Result.new(raw)
38
+ @file2result[result.source_file] = result
39
+ end
40
+ end
41
+ end
42
+
43
+ def result_for(filename)
44
+ @file2result[self.class.expand_path(filename)]
45
+ end
46
+
47
+ def files_with_results
48
+ @file2result.values.collect { |r| r.source_file unless r.errors? }.compact
49
+ end
50
+
51
+ def to_hash
52
+ first.to_hash
53
+ end
54
+
55
+ def to_display_hash
56
+ first.to_display_hash
57
+ end
58
+
59
+ def symbol_display_hash
60
+ first.symbol_display_hash
61
+ end
62
+
63
+ def errors?
64
+ @file2result.values.any? { |ea| ea.errors? }
65
+ end
66
+
67
+ private
68
+
69
+ def first
70
+ raise InvalidArgument, 'use #result_for when multiple filenames are used' if @file2result.size > 1
71
+ @file2result.values.first
72
+ end
73
+ end
@@ -0,0 +1,62 @@
1
+ require 'time'
2
+ require 'rational'
3
+
4
+ class Exiftool
5
+ class Parser
6
+
7
+ WORD_BOUNDARY_RES = [/([A-Z\d]+)([A-Z][a-z])/, /([a-z\d])([A-Z])/]
8
+ FRACTION_RE = /^(\d+)\/(\d+)$/
9
+
10
+ attr_reader :key, :display_key, :sym_key, :raw_value
11
+
12
+ def initialize(key, raw_value)
13
+ @key = key
14
+ @display_key = WORD_BOUNDARY_RES.inject(key) { |k, regex| k.gsub(regex, '\1 \2') }
15
+ @sym_key = display_key.downcase.gsub(' ', '_').to_sym
16
+ @raw_value = raw_value
17
+ end
18
+
19
+ def value
20
+ for_lat_long ||
21
+ for_date ||
22
+ for_fraction ||
23
+ raw_value
24
+ rescue StandardError => e
25
+ "Warning: Parsing '#{raw_value}' for attribute '#{key}' raised #{e.message}"
26
+ end
27
+
28
+ private
29
+
30
+ def for_lat_long
31
+ if sym_key == :gps_latitude || sym_key == :gps_longitude
32
+ value, direction = raw_value.split(" ")
33
+ if value =~ /\A\d+\.?\d*\z/
34
+ value.to_f * (['S', 'W'].include?(direction) ? -1 : 1)
35
+ end
36
+ end
37
+ end
38
+
39
+ def for_date
40
+ if raw_value.is_a?(String) && display_key =~ /\bdate\b/i
41
+ try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S%z') } ||
42
+ try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S') } ||
43
+ try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S.%L%z') } ||
44
+ try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S.%L') } ||
45
+ try_parse { Time.parse(raw_value) }
46
+ end
47
+ end
48
+
49
+ def try_parse
50
+ yield
51
+ rescue ArgumentError
52
+ nil
53
+ end
54
+
55
+ def for_fraction
56
+ if raw_value.is_a?(String)
57
+ scan = raw_value.scan(FRACTION_RE).first
58
+ v = Rational(*scan.map { |ea| ea.to_i }) unless scan.nil?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,32 @@
1
+ require 'exiftool/parser'
2
+
3
+ class Exiftool
4
+ class Result
5
+ attr_reader :to_hash, :to_display_hash, :symbol_display_hash
6
+
7
+ def initialize(raw_hash)
8
+ @raw_hash = raw_hash
9
+ @to_hash = {}
10
+ @to_display_hash = {}
11
+ @symbol_display_hash = {}
12
+ @raw_hash.each do |key, raw_value|
13
+ p = Parser.new(key, raw_value)
14
+ @to_hash[p.sym_key] = p.value
15
+ @to_display_hash[p.display_key] = p.value
16
+ @symbol_display_hash[p.sym_key] = p.display_key
17
+ end
18
+ end
19
+
20
+ def [](key)
21
+ @to_hash[key]
22
+ end
23
+
24
+ def source_file
25
+ self[:source_file]
26
+ end
27
+
28
+ def errors?
29
+ self[:error] == 'Unknown file type' || self[:warning] == 'Unsupported file type'
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ class Exiftool
2
+ VERSION = Gem::Version.new('0.2.0')
3
+ end
Binary file
@@ -0,0 +1,225 @@
1
+ ---
2
+ :source_file: /Users/mrm/Dropbox/code/exiftoolr/test/Canon 20D.jpg
3
+ :exif_tool_version: 9.31
4
+ :file_name: Canon 20D.jpg
5
+ :directory: /Users/mrm/Dropbox/code/exiftoolr/test
6
+ :file_size: 2.9 MB
7
+ :file_modify_date: 2012-02-23 05:22:17.000000000 +00:00
8
+ :file_access_date: 2013-07-14 20:25:41.000000000 +00:00
9
+ :file_inode_change_date: 2013-02-16 19:21:59.000000000 +00:00
10
+ :file_permissions: rw-r--r--
11
+ :file_type: JPEG
12
+ :mime_type: image/jpeg
13
+ :exif_byte_order: Little-endian (Intel, II)
14
+ :make: Canon
15
+ :model: Canon EOS 20D
16
+ :orientation: Horizontal (normal)
17
+ :x_resolution: 72
18
+ :y_resolution: 72
19
+ :resolution_unit: inches
20
+ :modify_date: 2004-09-19 12:25:20.000000000 +00:00
21
+ :y_cb_cr_positioning: Co-sited
22
+ :exposure_time: !ruby/object:Rational
23
+ denominator: 250
24
+ numerator: 1
25
+ :f_number: 9.0
26
+ :exposure_program: Program AE
27
+ :iso: 200
28
+ :exif_version: '0221'
29
+ :date_time_original: 2004-09-19 12:25:20.000000000 +00:00
30
+ :create_date: 2004-09-19 12:25:20.000000000 +00:00
31
+ :components_configuration: Y, Cb, Cr, -
32
+ :shutter_speed_value: !ruby/object:Rational
33
+ denominator: 250
34
+ numerator: 1
35
+ :aperture_value: 9.0
36
+ :flash: Off, Did not fire
37
+ :focal_length: 85.0 mm
38
+ :macro_mode: Normal
39
+ :self_timer: 'Off'
40
+ :quality: Fine
41
+ :canon_flash_mode: 'Off'
42
+ :continuous_drive: Single
43
+ :focus_mode: One-shot AF
44
+ :record_mode: JPEG
45
+ :canon_image_size: Large
46
+ :easy_mode: Manual
47
+ :digital_zoom: None
48
+ :contrast: '+1'
49
+ :saturation: '+1'
50
+ :sharpness: '+1'
51
+ :metering_mode: Evaluative
52
+ :focus_range: Not Known
53
+ :canon_exposure_mode: Program AE
54
+ :lens_type: Unknown (-1)
55
+ :max_focal_length: 85 mm
56
+ :min_focal_length: 17 mm
57
+ :focal_units: 1/mm
58
+ :max_aperture: 5.6
59
+ :min_aperture: 32
60
+ :flash_activity: 0
61
+ :flash_bits: (none)
62
+ :focus_continuous: Single
63
+ :zoom_source_width: 0
64
+ :zoom_target_width: 0
65
+ :photo_effect: 'Off'
66
+ :manual_flash_output: n/a
67
+ :focal_type: Zoom
68
+ :focal_plane_x_size: 23.04 mm
69
+ :focal_plane_y_size: 15.37 mm
70
+ :auto_iso: 100
71
+ :base_iso: 200
72
+ :measured_ev: 13.25
73
+ :target_aperture: 9
74
+ :target_exposure_time: !ruby/object:Rational
75
+ denominator: 251
76
+ numerator: 1
77
+ :exposure_compensation: 0
78
+ :white_balance: Auto
79
+ :slow_shutter: None
80
+ :sequence_number: 0
81
+ :optical_zoom_code: n/a
82
+ :flash_guide_number: 0
83
+ :flash_exposure_comp: 0
84
+ :auto_exposure_bracketing: 'Off'
85
+ :aeb_bracket_value: 0
86
+ :control_mode: Camera Local Control
87
+ :measured_ev2: 13.5
88
+ :bulb_duration: 0
89
+ :camera_type: EOS Mid-range
90
+ :auto_rotate: None
91
+ :nd_filter: n/a
92
+ :self_timer2: 0
93
+ :canon_image_type: Canon EOS 20D
94
+ :canon_firmware_version: Firmware 1.0.2
95
+ :owner_name: unknown
96
+ :serial_number: 0330113864
97
+ :set_function_when_shooting: Image replay
98
+ :long_exposure_noise_reduction: 'Off'
99
+ :flash_sync_speed_av: Auto
100
+ :shutter-ae_lock: AF/AE lock
101
+ :af_assist_beam: Emits
102
+ :exposure_level_increments: 1/3 Stop
103
+ :flash_firing: Fires
104
+ :iso_expansion: 'On'
105
+ :aeb_sequence_auto_cancel: 0,-,+/Enabled
106
+ :superimposed_display: 'On'
107
+ :menu_button_display_position: Previous (top if power off)
108
+ :mirror_lockup: Disable
109
+ :af_point_selection_method: Normal
110
+ :ettlii: Evaluative
111
+ :shutter_curtain_sync: 1st-curtain sync
112
+ :safety_shift_in_av_or_tv: Disable
113
+ :lens_af_stop_button: AF stop
114
+ :add_original_decision_data: 'Off'
115
+ :canon_model_id: EOS 20D
116
+ :num_af_points: 9
117
+ :valid_af_points: 9
118
+ :canon_image_width: 3504
119
+ :canon_image_height: 2336
120
+ :af_image_width: 3504
121
+ :af_image_height: 2336
122
+ :af_area_width: 78
123
+ :af_area_height: 78
124
+ :af_area_x_positions: 8 -555 571 -931 8 947 -555 571 8
125
+ :af_area_y_positions: 504 270 270 4 4 4 -262 -262 -496
126
+ :af_points_in_focus: 4
127
+ :thumbnail_image_valid_area: 0 159 7 112
128
+ :serial_number_format: Format 2
129
+ :original_decision_data_offset: 0
130
+ :file_number: 793-9390
131
+ :bracket_mode: 'Off'
132
+ :bracket_value: 0
133
+ :bracket_shot_number: 0
134
+ :long_exposure_noise_reduction2: 'Off'
135
+ :wb_bracket_mode: 'Off'
136
+ :wb_bracket_value_ab: 0
137
+ :wb_bracket_value_gm: 0
138
+ :filter_effect: None
139
+ :toning_effect: None
140
+ :tone_curve: Standard
141
+ :sharpness_frequency: n/a
142
+ :sensor_red_level: 0
143
+ :sensor_blue_level: 0
144
+ :white_balance_red: 0
145
+ :white_balance_blue: 0
146
+ :color_temperature: 4800
147
+ :picture_style: None
148
+ :digital_gain: 0
149
+ :wb_shift_ab: 0
150
+ :wb_shift_gm: 0
151
+ :measured_rggb: 674 1024 1024 670
152
+ :vrd_offset: 0
153
+ :sensor_width: 3596
154
+ :sensor_height: 2360
155
+ :sensor_left_border: 84
156
+ :sensor_top_border: 19
157
+ :sensor_right_border: 3587
158
+ :sensor_bottom_border: 2354
159
+ :black_mask_left_border: 0
160
+ :black_mask_top_border: 0
161
+ :black_mask_right_border: 0
162
+ :black_mask_bottom_border: 0
163
+ :wb_rggb_levels_as_shot: 1973 1015 1015 1663
164
+ :color_temp_as_shot: 4662
165
+ :wb_rggb_levels_auto: 1973 1023 1023 1663
166
+ :color_temp_auto: 4662
167
+ :wb_rggb_levels_daylight: 1995 1023 1023 1509
168
+ :color_temp_daylight: 5200
169
+ :wb_rggb_levels_shade: 2292 1023 1023 1267
170
+ :color_temp_shade: 7000
171
+ :wb_rggb_levels_cloudy: 2160 1023 1023 1373
172
+ :color_temp_cloudy: 6000
173
+ :wb_rggb_levels_tungsten: 1410 1023 1023 2408
174
+ :color_temp_tungsten: 3196
175
+ :wb_rggb_levels_fluorescent: 1797 1023 1023 2058
176
+ :color_temp_fluorescent: 3952
177
+ :wb_rggb_levels_flash: 2187 1023 1023 1333
178
+ :color_temp_flash: 6309
179
+ :wb_rggb_levels_custom1: 2095 1023 1023 1316
180
+ :color_temp_custom1: 6163
181
+ :wb_rggb_levels_custom2: 1912 1023 1023 1627
182
+ :color_temp_custom2: 4792
183
+ :color_tone: Normal
184
+ :user_comment: ''
185
+ :flashpix_version: '0100'
186
+ :color_space: sRGB
187
+ :exif_image_width: 3504
188
+ :exif_image_height: 2336
189
+ :interop_index: R98 - DCF basic file (sRGB)
190
+ :interop_version: '0100'
191
+ :focal_plane_x_resolution: 2886.363636
192
+ :focal_plane_y_resolution: 2885.805763
193
+ :focal_plane_resolution_unit: inches
194
+ :custom_rendered: Normal
195
+ :exposure_mode: Auto
196
+ :scene_capture_type: Standard
197
+ :compression: JPEG (old-style)
198
+ :thumbnail_offset: 9728
199
+ :thumbnail_length: 11259
200
+ :image_width: 3504
201
+ :image_height: 2336
202
+ :encoding_process: Baseline DCT, Huffman coding
203
+ :bits_per_sample: 8
204
+ :color_components: 3
205
+ :y_cb_cr_sub_sampling: YCbCr4:2:2 (2 1)
206
+ :aperture: 9.0
207
+ :drive_mode: Single-frame Shooting
208
+ :image_size: 3504x2336
209
+ :lens: 17.0 - 85.0 mm
210
+ :lens_id: Unknown 17-85mm
211
+ :scale_factor35efl: 1.0
212
+ :shooting_mode: Program AE
213
+ :shutter_speed: !ruby/object:Rational
214
+ denominator: 250
215
+ numerator: 1
216
+ :thumbnail_image: (Binary data 11259 bytes)
217
+ :wb_rggb_levels: 1973 1015 1015 1663
218
+ :blue_balance: 1.638424
219
+ :circle_of_confusion: 0.030 mm
220
+ :fov: 23.7 deg
221
+ :focal_length35efl: '85.0 mm (35 mm equivalent: 85.6 mm)'
222
+ :hyperfocal_distance: 26.91 m
223
+ :lens35efl: '17.0 - 85.0 mm (35 mm equivalent: 17.1 - 85.6 mm)'
224
+ :light_value: 13.3
225
+ :red_balance: 1.943842