exiftoolr 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in exiftoolr.gemspec
4
+ gemspec
@@ -0,0 +1,75 @@
1
+ # Ruby wrapper for ExifTool
2
+
3
+ This gem is the simplest thing that could possibly work that
4
+ reads the output of [exiftool](http://www.sno.phy.queensu.ca/~phil/exiftool)
5
+ and renders it into a ruby hash, with correctly typed values and symbolized keys.
6
+
7
+ ## Want constitutes "correct"?
8
+
9
+ * GPS latitude and longitude are rendered as signed floats,
10
+ where north and east are positive, and west and south are negative.
11
+ * Values like shutter speed and exposure time are rendered as Rationals,
12
+ which lets the caller show them as fractions (1/250) or as comparable.
13
+ * String values like "interop" and "serial number" are kept as strings
14
+ (which preserves zero prefixes)
15
+
16
+ ## Installation
17
+
18
+ You'll want to [install ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html), then
19
+
20
+ ```
21
+ gem install exiftoolr
22
+ ```
23
+
24
+ or add to your Gemfile:
25
+
26
+ ```
27
+ gem 'exiftoolr'
28
+ ```
29
+
30
+ and run ```bundle```.
31
+
32
+ ## Usage
33
+
34
+ ```ruby
35
+ require 'exiftoolr'
36
+ e = Exiftoolr.new("path/to/iPhone 4S.jpg")
37
+ e.to_hash
38
+ # => {:make => "Apple", :gps_longitude => -122.47566667, …
39
+ e.to_display_hash
40
+ # => {"Make" => "Apple", "GPS Longitude" => -122.47566667, …
41
+ ```
42
+
43
+ ### Multiple file support
44
+
45
+ Supply an array to the Exiftoolr initializer, then use ```.result_for```:
46
+
47
+ ```ruby
48
+ require 'exiftoolr'
49
+ e = Exiftoolr.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
+ * ```Exiftoolr::NoSuchFile``` is raised if the provided filename doesn't exist.
63
+ * ```Exiftoolr::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
+ Exiftoolr.new("Gemfile").errors?
68
+ #=> true
69
+ ```
70
+
71
+ ## Change history
72
+
73
+ ### 0.0.4
74
+
75
+ Added support for multiple file fetching (which is *much* faster for large directories)
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new
5
+ task :default => :test
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "exiftoolr/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "exiftoolr"
7
+ s.version = Exiftoolr::VERSION
8
+ s.authors = ["Matthew McEachen"]
9
+ s.email = ["matthew-github@mceachen.org"]
10
+ s.homepage = "https://github.com/mceachen/exif_tooler"
11
+ s.summary = %q{Multiget ExifTool wrapper for ruby}
12
+ s.description = %q{Multiget ExifTool wrapper for ruby}
13
+
14
+ s.rubyforge_project = "exiftoolr"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.requirements << "ExifTool (see http://www.sno.phy.queensu.ca/~phil/exiftool/)"
22
+
23
+ s.add_dependency "json"
24
+ s.add_development_dependency "rake"
25
+ s.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,64 @@
1
+ require "exiftoolr/version"
2
+ require "exiftoolr/result"
3
+
4
+ require 'json'
5
+ require 'shellwords'
6
+
7
+ class Exiftoolr
8
+ class NoSuchFile < StandardError; end
9
+ class NotAFile < StandardError; end
10
+ class ExiftoolNotInstalled < StandardError; end
11
+
12
+ def self.exiftool_installed?
13
+ `exiftool -ver 2> /dev/null`.to_f > 0
14
+ end
15
+
16
+ def self.expand_path(filename)
17
+ raise NoSuchFile, filename unless File.exist?(filename)
18
+ raise NotAFile, filename unless File.file?(filename)
19
+ File.expand_path(filename)
20
+ end
21
+
22
+ def initialize(filenames, exiftool_opts = "")
23
+ escaped_filenames = filenames.to_a.collect do |f|
24
+ Shellwords.escape(self.class.expand_path(f))
25
+ end.join(" ")
26
+ json = `exiftool #{exiftool_opts} -j −coordFormat "%.8f" -dateFormat "%Y-%m-%d %H:%M:%S" #{escaped_filenames} 2> /dev/null`
27
+ raise ExiftoolNotInstalled if json == ""
28
+ @file2result = { }
29
+ JSON.parse(json).each do |raw|
30
+ @file2result[r.source_file] = Result.new(raw)
31
+ end
32
+ end
33
+
34
+ def result_for(filename)
35
+ @file2result[self.class.expand_path(filename)]
36
+ end
37
+
38
+ def files_with_results
39
+ @file2result.values.collect{|r|r.source_file unless r.errors?}.compact
40
+ end
41
+
42
+ def to_hash
43
+ first.to_hash
44
+ end
45
+
46
+ def to_display_hash
47
+ first.to_display_hash
48
+ end
49
+
50
+ def symbol_display_hash
51
+ first.symbol_display_hash
52
+ end
53
+
54
+ def errors?
55
+ @file2result.values.any? { |ea| ea.errors? }
56
+ end
57
+
58
+ private
59
+
60
+ def first
61
+ raise InvalidArgument, "use #result_for when multiple filenames are used" if @file2result.size > 1
62
+ @file2result.values.first
63
+ end
64
+ end
@@ -0,0 +1,47 @@
1
+ require 'time'
2
+ require 'rational'
3
+
4
+ class Exiftoolr
5
+ class Result
6
+ attr_reader :to_hash, :to_display_hash, :symbol_display_hash
7
+
8
+ WORD_BOUNDARY_RES = [/([A-Z\d]+)([A-Z][a-z])/, /([a-z\d])([A-Z])/]
9
+ FRACTION_RE = /\d+\/\d+/
10
+
11
+ def initialize(raw_hash)
12
+ @raw_hash = raw_hash
13
+ @to_hash = { }
14
+ @to_display_hash = { }
15
+ @symbol_display_hash = { }
16
+
17
+ @raw_hash.each do |k, v|
18
+ display_key = WORD_BOUNDARY_RES.inject(k) { |key, regex| key.gsub(regex, '\1 \2') }
19
+ sym_key = display_key.downcase.gsub(' ', '_').to_sym
20
+ if sym_key == :gps_latitude || sym_key == :gps_longitude
21
+ value, direction = v.split(" ")
22
+ v = value.to_f
23
+ v *= -1 if direction == 'S' || direction == 'W'
24
+ elsif display_key =~ /\bdate\b/i
25
+ v = Time.parse(v)
26
+ elsif v =~ FRACTION_RE
27
+ v = Rational(*v.split('/').collect { |ea| ea.to_i })
28
+ end
29
+ @to_hash[sym_key] = v
30
+ @to_display_hash[display_key] = v
31
+ @symbol_display_hash[sym_key] = display_key
32
+ end
33
+ end
34
+
35
+ def [](key)
36
+ @to_hash[key]
37
+ end
38
+
39
+ def source_file
40
+ self[:source_file]
41
+ end
42
+
43
+ def errors?
44
+ self[:error] == "Unknown file type" || self[:warning] == "Unsupported file type"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ class Exiftoolr
2
+ VERSION = "0.0.4"
3
+ end
Binary file
@@ -0,0 +1,225 @@
1
+ ---
2
+ :color_temp_custom2: 4792
3
+ :sensor_left_border: 84
4
+ :flash_activity: 0
5
+ :measured_rggb: 674 1024 1024 670
6
+ :photo_effect: "Off"
7
+ :digital_zoom: None
8
+ :af_points_in_focus: 4
9
+ :color_temp_shade: 7000
10
+ :y_cb_cr_sub_sampling: YCbCr4:2:2 (2 1)
11
+ :metering_mode: Evaluative
12
+ :image_size: 3504x2336
13
+ :canon_model_id: EOS 20D
14
+ :bracket_mode: "Off"
15
+ :camera_type: EOS Mid-range
16
+ :exposure_mode: Auto
17
+ :tone_curve: Standard
18
+ :af_area_x_positions: 8 -555 571 -931 8 947 -555 571 8
19
+ :color_tone: Normal
20
+ :af_image_width: 3504
21
+ :max_aperture: 5.6
22
+ :aeb_bracket_value: 0
23
+ :shutter_curtain_sync: 1st-curtain sync
24
+ :aeb_sequence_auto_cancel: 0,-,+/Enabled
25
+ :scale_factor35efl: 1.6
26
+ :lens_af_stop_button: AF stop
27
+ :add_original_decision_data: "Off"
28
+ :thumbnail_image: (Binary data 11259 bytes)
29
+ :flash_sync_speed_av: Auto
30
+ :black_mask_right_border: 0
31
+ :base_iso: 200
32
+ :image_width: 3504
33
+ :focal_plane_x_resolution: 2886.363636
34
+ :exposure_program: Program AE
35
+ :safety_shift_in_av_or_tv: Disable
36
+ :measured_ev: 13.25
37
+ :continuous_drive: Single
38
+ :red_balance: 1.943842
39
+ :file_name: Canon 20D.jpg
40
+ :white_balance_blue: 0
41
+ :lens: 17.0 - 85.0 mm
42
+ :digital_gain: 0
43
+ :macro_mode: Normal
44
+ :wb_rggb_levels_flash: 2187 1023 1023 1333
45
+ :menu_button_display_position: Previous (top if power off)
46
+ :sensor_height: 2360
47
+ :file_size: 2.9 MB
48
+ :black_mask_top_border: 0
49
+ :ettlii: Evaluative
50
+ :af_area_width: 78
51
+ :f_number: 9.0
52
+ :directory: test
53
+ :black_mask_bottom_border: 0
54
+ :focal_plane_y_size: 15.37 mm
55
+ :toning_effect: None
56
+ :af_area_y_positions: 504 270 270 4 4 4 -262 -262 -496
57
+ :focal_plane_y_resolution: 2885.805763
58
+ :target_aperture: 9
59
+ :focus_mode: One-shot AF
60
+ :mirror_lockup: Disable
61
+ :color_temp_flash: 6309
62
+ :make: Canon
63
+ :set_function_when_shooting: Image replay
64
+ :wb_bracket_value_gm: 0
65
+ :exposure_time: !ruby/object:Rational
66
+ denominator: 250
67
+ numerator: 1
68
+ :thumbnail_offset: 9728
69
+ :canon_flash_mode: "Off"
70
+ :serial_number: 0330113864
71
+ :fov: 15.4 deg
72
+ :thumbnail_image_valid_area: 0 159 7 112
73
+ :af_point_selection_method: Normal
74
+ :color_space: sRGB
75
+ :original_decision_data_offset: 0
76
+ :lens_id: Unknown 17-85mm
77
+ :shutter_speed: !ruby/object:Rational
78
+ denominator: 250
79
+ numerator: 1
80
+ :light_value: 13.3
81
+ :superimposed_display: "On"
82
+ :exposure_compensation: 0
83
+ :orientation: Horizontal (normal)
84
+ :components_configuration: Y, Cb, Cr, -
85
+ :"shutter-ae_lock": AF/AE lock
86
+ :focal_length: 85.0 mm
87
+ :focal_length35efl: "85.0 mm (35 mm equivalent: 132.8 mm)"
88
+ :bracket_shot_number: 0
89
+ :model: Canon EOS 20D
90
+ :aperture_value: 9.0
91
+ :flash_guide_number: 0
92
+ :long_exposure_noise_reduction2: "Off"
93
+ :focal_plane_x_size: 23.04 mm
94
+ :sequence_number: 0
95
+ :long_exposure_noise_reduction: "Off"
96
+ :white_balance: Auto
97
+ :file_type: JPEG
98
+ :canon_exposure_mode: Program AE
99
+ :interop_version: "0100"
100
+ :canon_image_width: 3504
101
+ :color_temp_fluorescent: 3952
102
+ :canon_firmware_version: Firmware 1.0.2
103
+ :sharpness: "+1"
104
+ :color_temp_auto: 4662
105
+ :af_assist_beam: Emits
106
+ :iso: 200
107
+ :black_mask_left_border: 0
108
+ :color_temp_custom1: 6163
109
+ :focal_plane_resolution_unit: inches
110
+ :wb_rggb_levels_custom1: 2095 1023 1023 1316
111
+ :flash_bits: (none)
112
+ :sensor_right_border: 3587
113
+ :bracket_value: 0
114
+ :canon_image_type: Canon EOS 20D
115
+ :exif_version: "0221"
116
+ :exif_image_width: 3504
117
+ :encoding_process: Baseline DCT, Huffman coding
118
+ :contrast: "+1"
119
+ :file_modify_date: 2012-02-19 21:49:48 -08:00
120
+ :white_balance_red: 0
121
+ :auto_rotate: None
122
+ :scene_capture_type: Standard
123
+ :compression: JPEG (old-style)
124
+ :sensor_red_level: 0
125
+ :control_mode: Camera Local Control
126
+ :sensor_width: 3596
127
+ :shooting_mode: Program AE
128
+ :lens_type: Unknown (-1)
129
+ :sensor_blue_level: 0
130
+ :file_permissions: rw-r--r--
131
+ :num_af_points: 9
132
+ :focus_continuous: Single
133
+ :color_temp_tungsten: 3196
134
+ :thumbnail_length: 11259
135
+ :target_exposure_time: !ruby/object:Rational
136
+ denominator: 251
137
+ numerator: 1
138
+ :valid_af_points: 9
139
+ :long_focal: 85 mm
140
+ :wb_rggb_levels_daylight: 1995 1023 1023 1509
141
+ :interop_index: R98 - DCF basic file (sRGB)
142
+ :canon_image_height: 2336
143
+ :create_date: 2004-09-19 12:25:20 -07:00
144
+ :wb_bracket_mode: "Off"
145
+ :focal_units: 1/mm
146
+ :serial_number_format: Format 2
147
+ :short_focal: 17 mm
148
+ :sensor_top_border: 19
149
+ :measured_ev2: 13.5
150
+ :image_height: 2336
151
+ :color_temp_as_shot: 4662
152
+ :flash: Off, Did not fire
153
+ :iso_expansion: "On"
154
+ :record_mode: JPEG
155
+ :flashpix_version: "0100"
156
+ :wb_rggb_levels_tungsten: 1410 1023 1023 2408
157
+ :blue_balance: 1.638424
158
+ :sensor_bottom_border: 2354
159
+ :circle_of_confusion: 0.019 mm
160
+ :exif_byte_order: Little-endian (Intel, II)
161
+ :custom_rendered: Normal
162
+ :drive_mode: Single-frame Shooting
163
+ :color_components: 3
164
+ :x_resolution: 72
165
+ :exif_image_height: 2336
166
+ :aperture: 9.0
167
+ :easy_mode: Manual
168
+ :nd_filter: n/a
169
+ :exposure_level_increments: !ruby/object:Rational
170
+ denominator: 3
171
+ numerator: 1
172
+ :auto_exposure_bracketing: "Off"
173
+ :af_area_height: 78
174
+ :flash_firing: Fires
175
+ :wb_rggb_levels_custom2: 1912 1023 1023 1627
176
+ :self_timer2: 0
177
+ :lens35efl: "17.0 - 85.0 mm (35 mm equivalent: 26.6 - 132.8 mm)"
178
+ :mime_type: image/jpeg
179
+ :wb_rggb_levels_shade: 2292 1023 1023 1267
180
+ :canon_image_size: Large
181
+ :hyperfocal_distance: 41.74 m
182
+ :wb_rggb_levels_auto: 1973 1023 1023 1663
183
+ :zoom_source_width: 0
184
+ :auto_iso: 100
185
+ :vrd_offset: 0
186
+ :wb_shift_gm: 0
187
+ :wb_rggb_levels_fluorescent: 1797 1023 1023 2058
188
+ :color_temperature: 4800
189
+ :wb_rggb_levels: 1973 1015 1015 1663
190
+ :wb_shift_ab: 0
191
+ :user_comment: ""
192
+ :zoom_target_width: 0
193
+ :quality: Fine
194
+ :color_temp_cloudy: 6000
195
+ :af_image_height: 2336
196
+ :manual_flash_output: n/a
197
+ :flash_exposure_comp: 0
198
+ :wb_rggb_levels_cloudy: 2160 1023 1023 1373
199
+ :source_file: test/Canon 20D.jpg
200
+ :color_temp_daylight: 5200
201
+ :sharpness_frequency: n/a
202
+ :slow_shutter: None
203
+ :modify_date: 2004-09-19 12:25:20 -07:00
204
+ :bulb_duration: 0
205
+ :bits_per_sample: 8
206
+ :focus_range: Not Known
207
+ :saturation: "+1"
208
+ :focal_type: Zoom
209
+ :wb_rggb_levels_as_shot: 1973 1015 1015 1663
210
+ :min_aperture: 32
211
+ :exif_tool_version: 8.59
212
+ :y_resolution: 72
213
+ :date_time_original: 2004-09-19 12:25:20 -07:00
214
+ :shutter_speed_value: !ruby/object:Rational
215
+ denominator: 250
216
+ numerator: 1
217
+ :filter_effect: None
218
+ :resolution_unit: inches
219
+ :y_cb_cr_positioning: Co-sited
220
+ :picture_style: None
221
+ :file_number: 793-9390
222
+ :self_timer: "Off"
223
+ :wb_bracket_value_ab: 0
224
+ :optical_zoom_code: n/a
225
+ :owner_name: unknown
Binary file
@@ -0,0 +1,87 @@
1
+ ---
2
+ :exposure_compensation: 0
3
+ :model: DROIDX 6d460001fff80000015d7d330e01c011
4
+ :color_space: sRGB
5
+ :components_configuration: Cb, Y, Cr, Y
6
+ :exif_byte_order: Little-endian (Intel, II)
7
+ :sensing_method: One-chip color area
8
+ :create_date: 2010-12-08 01:15:37 -08:00
9
+ :bits_per_sample: 8
10
+ :date_time_original: 2010-12-08 01:15:37 -08:00
11
+ :source_file: test/Droid X.jpg
12
+ :thumbnail_length: 32586
13
+ :exif_image_width: 3264
14
+ :scene_type: Directly photographed
15
+ :contrast: Normal
16
+ :exposure_program: Program AE
17
+ :compression: JPEG (old-style)
18
+ :image_size: 3264x1840
19
+ :max_aperture_value: 2.8
20
+ :mime_type: image/jpeg
21
+ :x_resolution: 300
22
+ :interop_version: "0100"
23
+ :orientation: Rotate 90 CW
24
+ :software: 2.2
25
+ :thumbnail_offset: 2150
26
+ :shutter_speed: !ruby/object:Rational
27
+ denominator: 15
28
+ numerator: 1
29
+ :directory: test
30
+ :flashpix_version: "0100"
31
+ :y_cb_cr_positioning: Centered
32
+ :flash: Auto, Did not fire
33
+ :f_number: 2.8
34
+ :saturation: Normal
35
+ :file_modify_date: 2012-02-19 21:49:48 -08:00
36
+ :y_resolution: 300
37
+ :encoding_process: Baseline DCT, Huffman coding
38
+ :subject_distance: 0 m
39
+ :thumbnail_image: (Binary data 32586 bytes)
40
+ :white_balance: Auto
41
+ :metering_mode: Center-weighted average
42
+ :exposure_mode: Auto
43
+ :compressed_bits_per_pixel: 4
44
+ :circle_of_confusion: 0.004 mm
45
+ :color_components: 3
46
+ :image_height: 1840
47
+ :exif_tool_version: 8.59
48
+ :subject_distance_range: Unknown
49
+ :exposure_time: !ruby/object:Rational
50
+ denominator: 15
51
+ numerator: 1
52
+ :resolution_unit: inches
53
+ :scene_capture_type: Standard
54
+ :file_name: Droid X.jpg
55
+ :image_unique_id: ""
56
+ :hyperfocal_distance: 2.24 m
57
+ :focal_length35efl: "5.0 mm (35 mm equivalent: 38.0 mm)"
58
+ :light_source: Standard Light A
59
+ :y_cb_cr_sub_sampling: YCbCr4:2:0 (2 2)
60
+ :aperture: 2.8
61
+ :interop_index: R98 - DCF basic file (sRGB)
62
+ :fov: 50.7 deg
63
+ :brightness_value: -0.8203125
64
+ :file_permissions: rwxr-xr-x
65
+ :exif_version: "0220"
66
+ :digital_zoom_ratio: 0
67
+ :sharpness: Normal
68
+ :custom_rendered: Custom
69
+ :aperture_value: 2.8
70
+ :exif_image_height: 1840
71
+ :focal_length: 5.0 mm
72
+ :gain_control: High gain up
73
+ :light_value: 4.2
74
+ :modify_date: 2010-12-08 01:15:37 -08:00
75
+ :shutter_speed_value: !ruby/object:Rational
76
+ denominator: 15
77
+ numerator: 1
78
+ :make: Motorola
79
+ :exposure_index: 637
80
+ :file_size: 2023 kB
81
+ :focal_length_in35mm_format: 38 mm
82
+ :image_width: 3264
83
+ :user_comment: ""
84
+ :file_type: JPEG
85
+ :scale_factor35efl: 7.6
86
+ :iso: 637
87
+ :file_source: Digital Camera
Binary file
@@ -0,0 +1,327 @@
1
+ ---
2
+ :black_mask_bottom_border: 0
3
+ :red_trc: (Binary data 14 bytes)
4
+ :wb_rggb_levels_custom2: 1734 1023 1023 1901
5
+ :serial_number: 0720413239
6
+ :fov: 68.3 deg
7
+ :self_timer2: 0
8
+ :file_size: 3.8 MB
9
+ :wb_rggb_levels: 1822 1015 1015 1709
10
+ :wb_shift_ab: 0
11
+ :lens35efl: "17.0 - 40.0 mm (35 mm equivalent: 26.6 - 62.5 mm)"
12
+ :thumbnail_image_valid_area: 0 159 7 112
13
+ :"profile_description_ml-zh-cn": !binary |
14
+ 55u45py6IFJHQiDmj4/ov7Dmlofku7Y=
15
+
16
+ :current_iptc_digest: b443520a10119da99c2550175e6d0efb
17
+ :model: Canon EOS 20D
18
+ :af_point_selection_method: Normal
19
+ :aperture_value: 4.0
20
+ :coded_character_set: UTF8
21
+ :user_comment: ""
22
+ :color_space: sRGB
23
+ :focus_range: Not Known
24
+ :"profile_description_ml-ja-jp": !binary |
25
+ 44Kr44Oh44OpIFJHQiDjg5fjg63jg5XjgqHjgqTjg6s=
26
+
27
+ :zoom_target_width: 0
28
+ :device_model: ""
29
+ :flash_guide_number: 0
30
+ :focal_plane_x_size: 23.04 mm
31
+ :saturation: Normal
32
+ :quality: Fine
33
+ :"profile_description_ml-it-it": Profilo RGB Fotocamera
34
+ :focal_type: Zoom
35
+ :iso: 400
36
+ :sequence_number: 0
37
+ :profile_file_signature: acsp
38
+ :wb_rggb_levels_as_shot: 1822 1015 1015 1709
39
+ :black_mask_left_border: 0
40
+ :long_exposure_noise_reduction2: "Off"
41
+ :color_temp_custom2: 4301
42
+ :blue_trc: (Binary data 14 bytes)
43
+ :color_temp_custom1: 2694
44
+ :compression: JPEG (old-style)
45
+ :exif_tool_version: 8.59
46
+ :focal_plane_resolution_unit: inches
47
+ :sensor_left_border: 84
48
+ :sensor_red_level: 0
49
+ :wb_rggb_levels_custom1: 1096 1023 1023 2793
50
+ :"profile_description_ml-ko-kr": !binary |
51
+ 7Lm066mU6528IFJHQiDtlITroZztjIzsnbw=
52
+
53
+ :photo_effect: "Off"
54
+ :af_area_x_positions: 8 -555 571 -931 8 947 -555 571 8
55
+ :profile_version: 2.2.0
56
+ :control_mode: Camera Local Control
57
+ :flash_activity: 0
58
+ :connection_space_illuminant: 0.9642 1 0.82491
59
+ :sensor_width: 3596
60
+ :color_tone: Normal
61
+ :measured_rggb: 641 1024 1024 660
62
+ :create_date: 2005-11-26 14:20:34 -08:00
63
+ :shooting_mode: Depth-of-field AE
64
+ :application_record_version: 4
65
+ :af_image_width: 3504
66
+ :wb_bracket_mode: "Off"
67
+ :lens_type: Unknown (-1)
68
+ :focal_units: 1/mm
69
+ :make: Canon
70
+ :safety_shift_in_av_or_tv: Enable
71
+ :aeb_bracket_value: 0
72
+ :profile_id: 0
73
+ :software: Picasa
74
+ :serial_number_format: Format 2
75
+ :measured_ev: 14.12
76
+ :shutter_curtain_sync: 1st-curtain sync
77
+ :white_balance_blue: 0
78
+ :exif_byte_order: Little-endian (Intel, II)
79
+ :short_focal: 17 mm
80
+ :device_attributes: Reflective, Glossy, Positive, Color
81
+ :region_area_y:
82
+ - 0.248716
83
+ - 0.486301
84
+ - 0.243365
85
+ - 0.360873
86
+ - 0.466182
87
+ :custom_rendered: Normal
88
+ :sensor_top_border: 19
89
+ :focal_plane_y_size: 15.37 mm
90
+ :continuous_drive: Single
91
+ :profile_cmm_type: appl
92
+ :drive_mode: Single-frame Shooting
93
+ :toning_effect: None
94
+ :red_balance: 1.795074
95
+ :af_area_y_positions: 504 270 270 4 4 4 -262 -262 -496
96
+ :mime_type: image/jpeg
97
+ :color_components: 3
98
+ :region_area_x:
99
+ - 0.549658
100
+ - 0.858162
101
+ - 0.730736
102
+ - 0.453196
103
+ - 0.118579
104
+ :wb_rggb_levels_shade: 2215 1023 1023 1341
105
+ :x_resolution: 72
106
+ :cmm_flags: Not Embedded, Independent
107
+ :focus_mode: One-shot AF
108
+ :"profile_description_ml-fi-fi": Kameran RGB-profiili
109
+ :blue_matrix_column: 0.15662 0.08336 0.71953
110
+ :canon_image_size: Large
111
+ :original_decision_data_offset: 3968887
112
+ :focal_plane_y_resolution: 3959.322034
113
+ :hyperfocal_distance: 3.76 m
114
+ :lens_id: Unknown 17-40mm
115
+ :target_aperture: 4
116
+ :color_temp_cloudy: 6000
117
+ :wb_rggb_levels_auto: 1822 1023 1023 1709
118
+ :envelope_record_version: 4
119
+ :shutter_speed: !ruby/object:Rational
120
+ denominator: 4000
121
+ numerator: 1
122
+ :color_space_data: "RGB "
123
+ :af_image_height: 2336
124
+ :zoom_source_width: 0
125
+ :exposure_compensation: 0
126
+ :long_exposure_noise_reduction: "On"
127
+ :light_value: 14.0
128
+ :region_applied_to_dimensions_unit: pixel
129
+ :manual_flash_output: n/a
130
+ :y_resolution: 72
131
+ :white_balance: Auto
132
+ :flash_exposure_comp: !ruby/object:Rational
133
+ denominator: 3
134
+ numerator: 2
135
+ :superimposed_display: "On"
136
+ :date_time_original: 2005-11-26 14:20:34 -08:00
137
+ :wb_rggb_levels_cloudy: 2087 1023 1023 1459
138
+ :region_applied_to_dimensions_h: 2336
139
+ :interop_version: "0100"
140
+ :flash_bits: (none)
141
+ :shutter_speed_value: !ruby/object:Rational
142
+ denominator: 4000
143
+ numerator: 1
144
+ :source_file: test/faces.jpg
145
+ :file_type: JPEG
146
+ :file_name: faces.jpg
147
+ :iptc_digest: b443520a10119da99c2550175e6d0efb
148
+ :filter_effect: None
149
+ :sensor_right_border: 3587
150
+ :canon_exposure_mode: Depth-of-field AE
151
+ :"profile_description_ml-es-es": "Perfil RGB para C\xC3\xA1mara"
152
+ :profile_description_ml: Camera RGB Profile
153
+ :resolution_unit: inches
154
+ :region_area_w:
155
+ - 0.0667808
156
+ - 0.103881
157
+ - 0.0687785
158
+ - 0.0753424
159
+ - 0.11387
160
+ :bracket_value: 0
161
+ :digital_zoom: None
162
+ :y_cb_cr_positioning: Co-sited
163
+ :exif_image_width: 3504
164
+ :af_points_in_focus: 0,1,2,3,4,5,6,7,8
165
+ :color_temp_shade: 7000
166
+ :sensor_blue_level: 0
167
+ :canon_image_type: Canon EOS 20D
168
+ :region_applied_to_dimensions_w: 3504
169
+ :num_af_points: 9
170
+ :exif_version: "0221"
171
+ :y_cb_cr_sub_sampling: YCbCr4:2:2 (2 1)
172
+ :color_temp_tungsten: 3203
173
+ :aeb_sequence_auto_cancel: 0,-,+/Enabled
174
+ :metering_mode: Center-weighted average
175
+ :thumbnail_length: 12170
176
+ :green_matrix_column: 0.35332 0.67441 0.09042
177
+ :scale_factor35efl: 1.6
178
+ :image_size: 3504x2336
179
+ :measured_ev2: 14.125
180
+ :focus_continuous: Single
181
+ :primary_platform: Apple Computer Inc.
182
+ :lens_af_stop_button: ONE SHOT <-> AI SERVO
183
+ :media_white_point: 0.95047 1 1.0891
184
+ :image_height: 2336
185
+ :file_permissions: rw-r--r--
186
+ :lens: 17.0 - 40.0 mm
187
+ :add_original_decision_data: "On"
188
+ :"profile_description_ml-nl-nl": RGB-profiel Camera
189
+ :profile_class: Input Device Profile
190
+ :flash: Off, Did not fire
191
+ :digital_gain: 0
192
+ :thumbnail_image: (Binary data 12170 bytes)
193
+ :exif_image_height: 2336
194
+ :iso_expansion: "Off"
195
+ :red_matrix_column: 0.45427 0.24263 0.01482
196
+ :macro_mode: Normal
197
+ :aperture: 4.0
198
+ :color_temp_as_shot: 4599
199
+ :rendering_intent: Perceptual
200
+ :directory: test
201
+ :wb_rggb_levels_flash: 2112 1023 1023 1414
202
+ :mirror_lockup: Disable
203
+ :menu_button_display_position: Previous (top if power off)
204
+ :profile_copyright: Copyright 2003 Apple Computer Inc., all rights reserved.
205
+ :easy_mode: Manual
206
+ :auto_exposure_bracketing: "Off"
207
+ :color_temp_flash: 6303
208
+ :sensor_height: 2360
209
+ :auto_iso: 100
210
+ :nd_filter: n/a
211
+ :set_function_when_shooting: Change quality
212
+ :green_trc: (Binary data 14 bytes)
213
+ :vrd_offset: 0
214
+ :exposure_level_increments: !ruby/object:Rational
215
+ denominator: 3
216
+ numerator: 1
217
+ :wb_bracket_value_gm: 0
218
+ :region_area_h:
219
+ - 0.119007
220
+ - 0.1875
221
+ - 0.12286
222
+ - 0.101884
223
+ - 0.189212
224
+ :wb_rggb_levels_fluorescent: 1743 1023 1023 2205
225
+ :orientation: Horizontal (normal)
226
+ :exposure_time: !ruby/object:Rational
227
+ denominator: 4000
228
+ numerator: 1
229
+ :components_configuration: Y, Cb, Cr, -
230
+ :color_temp_daylight: 5200
231
+ :region_area_unit:
232
+ - normalized
233
+ - normalized
234
+ - normalized
235
+ - normalized
236
+ - normalized
237
+ :color_temperature: 4300
238
+ :thumbnail_offset: 4036
239
+ :sharpness_frequency: n/a
240
+ :wb_shift_gm: 0
241
+ :"profile_description_ml-da-dk": RGB-beskrivelse til Kamera
242
+ :region_name:
243
+ - Matthew McEachen
244
+ - Karen McEachen
245
+ - Ruth McEachen
246
+ - James McEachen
247
+ - Jamie McEachen
248
+ :focal_length: 17.0 mm
249
+ :device_manufacturer: appl
250
+ :slow_shutter: None
251
+ :canon_image_width: 3504
252
+ :focal_length35efl: "17.0 mm (35 mm equivalent: 26.6 mm)"
253
+ :warning: Invalid original decision data
254
+ :bulb_duration: 0
255
+ :color_temp_fluorescent: 3961
256
+ :bracket_shot_number: 0
257
+ :"profile_description_ml-pt-br": "Perfil RGB de C\xC3\xA2mera"
258
+ :picture_style: None
259
+ :bits_per_sample: 8
260
+ :region_type:
261
+ - Face
262
+ - Face
263
+ - Face
264
+ - Face
265
+ - Face
266
+ :canon_firmware_version: Firmware 2.0.3
267
+ :sharpness: 0
268
+ :file_number: 326-2647
269
+ :modify_date: 2012-02-15 22:58:07 -08:00
270
+ :"profile_description_ml-zh-tw": !binary |
271
+ 5pW45L2N55u45qmfIFJHQiDoibLlvanmj4/ov7A=
272
+
273
+ :self_timer: "Off"
274
+ :encoding_process: Baseline DCT, Huffman coding
275
+ :color_temp_auto: 4599
276
+ :profile_creator: appl
277
+ :af_assist_beam: Emits
278
+ :optical_zoom_code: n/a
279
+ :contrast: Normal
280
+ :white_balance_red: 0
281
+ :owner_name: unknown
282
+ :xmp_toolkit: XMP Core 5.1.2
283
+ :canon_model_id: EOS 20D
284
+ :wb_bracket_value_ab: 0
285
+ :target_exposure_time: !ruby/object:Rational
286
+ denominator: 4008
287
+ numerator: 1
288
+ :auto_rotate: None
289
+ :bracket_mode: "Off"
290
+ :valid_af_points: 9
291
+ :scene_capture_type: Standard
292
+ :exposure_mode: Auto
293
+ :tone_curve: Standard
294
+ :chromatic_adaptation: 1.04788 0.02292 -0.0502 0.02957 0.99049 -0.01706 -0.00923 0.01508 0.75165
295
+ :long_focal: 40 mm
296
+ :file_modify_date: 2012-02-19 21:49:48 -08:00
297
+ :flash_sync_speed_av: Auto
298
+ :"profile_description_ml-sv-se": "RGB-profil f\xC3\xB6r Kamera"
299
+ :profile_connection_space: "XYZ "
300
+ :wb_rggb_levels_daylight: 1933 1023 1023 1607
301
+ :black_mask_right_border: 0
302
+ :camera_type: EOS Mid-range
303
+ :"profile_description_ml-de-de": "RGB-Profil f\xC3\xBCr Kameras"
304
+ :record_mode: JPEG
305
+ :interop_index: R98 - DCF basic file (sRGB)
306
+ :profile_date_time: 2003-07-01 00:00:00 -07:00
307
+ :base_iso: 400
308
+ :focal_plane_x_resolution: 3959.322034
309
+ :flashpix_version: "0100"
310
+ :canon_image_height: 2336
311
+ :"profile_description_ml-no-no": RGB-kameraprofil
312
+ :profile_description: Camera RGB Profile
313
+ :wb_rggb_levels_tungsten: 1382 1023 1023 2574
314
+ :black_mask_top_border: 0
315
+ :exposure_program: Not Defined
316
+ :"profile_description_ml-fr-fu": "Profil RVB de l\xE2\x80\x99appareil-photo"
317
+ :blue_balance: 1.683744
318
+ :ettlii: Evaluative
319
+ :image_width: 3504
320
+ :"shutter-ae_lock": AF/AE lock
321
+ :af_area_height: 78
322
+ :sensor_bottom_border: 2354
323
+ :af_area_width: 78
324
+ :flash_firing: Fires
325
+ :circle_of_confusion: 0.019 mm
326
+ :canon_flash_mode: "Off"
327
+ :f_number: 4.0
Binary file
@@ -0,0 +1,80 @@
1
+ ---
2
+ :gps_latitude_ref: North
3
+ :model: iPhone 4S
4
+ :color_space: sRGB
5
+ :components_configuration: Y, Cb, Cr, -
6
+ :exif_byte_order: Big-endian (Motorola, MM)
7
+ :sensing_method: One-chip color area
8
+ :create_date: 2011-11-23 09:39:52 -08:00
9
+ :bits_per_sample: 8
10
+ :date_time_original: 2011-11-23 09:39:52 -08:00
11
+ :source_file: test/iPhone 4S.jpg
12
+ :gps_img_direction: 121.9838275
13
+ :thumbnail_length: 5987
14
+ :exif_image_width: 3264
15
+ :exposure_program: Program AE
16
+ :compression: JPEG (old-style)
17
+ :image_size: 3264x2448
18
+ :mime_type: image/jpeg
19
+ :gps_longitude: -122.47566667
20
+ :x_resolution: 72
21
+ :orientation: Horizontal (normal)
22
+ :software: 5.0.1
23
+ :thumbnail_offset: 914
24
+ :gps_altitude_ref: Above Sea Level
25
+ :shutter_speed: !ruby/object:Rational
26
+ denominator: 6135
27
+ numerator: 1
28
+ :directory: test
29
+ :flashpix_version: "0100"
30
+ :y_cb_cr_positioning: Centered
31
+ :flash: Auto, Did not fire
32
+ :f_number: 2.4
33
+ :gps_altitude: 14.2 m Above Sea Level
34
+ :file_modify_date: 2012-02-19 21:49:48 -08:00
35
+ :y_resolution: 72
36
+ :encoding_process: Baseline DCT, Huffman coding
37
+ :thumbnail_image: (Binary data 5987 bytes)
38
+ :exposure_mode: Auto
39
+ :white_balance: Auto
40
+ :circle_of_confusion: 0.004 mm
41
+ :metering_mode: Multi-segment
42
+ :gps_latitude: 37.50233333
43
+ :color_components: 3
44
+ :image_height: 2448
45
+ :exif_tool_version: 8.59
46
+ :exposure_time: !ruby/object:Rational
47
+ denominator: 6135
48
+ numerator: 1
49
+ :gps_longitude_ref: West
50
+ :resolution_unit: inches
51
+ :scene_capture_type: Standard
52
+ :gps_position: 37.50233333 N, 122.47566667 W
53
+ :file_name: iPhone 4S.jpg
54
+ :hyperfocal_distance: 2.08 m
55
+ :focal_length35efl: "4.3 mm (35 mm equivalent: 35.0 mm)"
56
+ :y_cb_cr_sub_sampling: YCbCr4:2:0 (2 2)
57
+ :aperture: 2.4
58
+ :fov: 54.4 deg
59
+ :brightness_value: 11.27442702
60
+ :file_permissions: rw-r--r--
61
+ :gps_img_direction_ref: True North
62
+ :exif_version: "0221"
63
+ :sharpness: Normal
64
+ :gps_time_stamp: "19:03:32"
65
+ :aperture_value: 2.4
66
+ :exif_image_height: 2448
67
+ :focal_length: 4.3 mm
68
+ :light_value: 15.8
69
+ :modify_date: 2011-11-23 09:39:52 -08:00
70
+ :shutter_speed_value: !ruby/object:Rational
71
+ denominator: 6135
72
+ numerator: 1
73
+ :make: Apple
74
+ :file_size: 1954 kB
75
+ :subject_area: 1631 1223 881 881
76
+ :focal_length_in35mm_format: 35 mm
77
+ :image_width: 3264
78
+ :file_type: JPEG
79
+ :scale_factor35efl: 8.2
80
+ :iso: 64
@@ -0,0 +1,62 @@
1
+ require "test/unit"
2
+ require "exiftoolr"
3
+ require "yaml"
4
+
5
+ class TestExiftoolr < Test::Unit::TestCase
6
+
7
+ DUMP_RESULTS = false
8
+
9
+ def test_missing
10
+ assert_raise Exiftoolr::NoSuchFile do
11
+ Exiftoolr.new("no/such/file")
12
+ end
13
+ end
14
+
15
+ def test_directory
16
+ assert_raise Exiftoolr::NotAFile do
17
+ Exiftoolr.new("lib")
18
+ end
19
+ end
20
+
21
+ def test_invalid_exif
22
+ assert Exiftoolr.new("Gemfile").errors?
23
+ end
24
+
25
+ def test_matches
26
+ Dir["**/*.jpg"].each do |filename|
27
+ e = Exiftoolr.new(filename)
28
+ validate_result(e, filename)
29
+ end
30
+ end
31
+
32
+ def validate_result(result, filename)
33
+ yaml_file = "#{filename}.yaml"
34
+ if File.exist?(yaml_file)
35
+ assert !result.errors?
36
+ else
37
+ assert result.errors?
38
+ return
39
+ end
40
+ exif = result.to_hash
41
+ File.open(yaml_file, 'w') { |out| YAML.dump(exif, out) } if DUMP_RESULTS
42
+ e = File.open(yaml_file) { |f| YAML::load(f) }
43
+ exif.keys.each do |k|
44
+ next if [:file_modify_date, :directory, :source_file].include? k
45
+ assert_equal e[k], exif[k], "Key '#{k}' was incorrect for #{filename}"
46
+ end
47
+ end
48
+
49
+ def test_multi_matches
50
+ filenames = Dir["**/*.jpg"].to_a
51
+ e = Exiftoolr.new(filenames)
52
+ filenames.each { |f| validate_result(e.result_for(f), f) }
53
+ end
54
+
55
+ def test_error_filtering
56
+ filenames = Dir["**/*.*"].to_a
57
+ e = Exiftoolr.new(filenames)
58
+ expected_files = Dir["**/*.jpg"].to_a.collect{|f|File.expand_path(f)}.sort
59
+ assert_equal expected_files, e.files_with_results.sort
60
+ filenames.each { |f| validate_result(e.result_for(f), f) }
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exiftoolr
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ version: 0.0.4
11
+ platform: ruby
12
+ authors:
13
+ - Matthew McEachen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-23 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ name: json
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ hash: 3
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :development
46
+ name: rake
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :development
60
+ name: rspec
61
+ version_requirements: *id003
62
+ description: Multiget ExifTool wrapper for ruby
63
+ email:
64
+ - matthew-github@mceachen.org
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files: []
70
+
71
+ files:
72
+ - .gitignore
73
+ - Gemfile
74
+ - README.md
75
+ - Rakefile
76
+ - exiftoolr.gemspec
77
+ - lib/exiftoolr.rb
78
+ - lib/exiftoolr/result.rb
79
+ - lib/exiftoolr/version.rb
80
+ - test/Canon 20D.jpg
81
+ - test/Canon 20D.jpg.yaml
82
+ - test/Droid X.jpg
83
+ - test/Droid X.jpg.yaml
84
+ - test/faces.jpg
85
+ - test/faces.jpg.yaml
86
+ - test/iPhone 4S.jpg
87
+ - test/iPhone 4S.jpg.yaml
88
+ - test/test_exiftoolr.rb
89
+ homepage: https://github.com/mceachen/exif_tooler
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options: []
94
+
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 3
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ requirements:
116
+ - ExifTool (see http://www.sno.phy.queensu.ca/~phil/exiftool/)
117
+ rubyforge_project: exiftoolr
118
+ rubygems_version: 1.8.17
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Multiget ExifTool wrapper for ruby
122
+ test_files:
123
+ - test/Canon 20D.jpg
124
+ - test/Canon 20D.jpg.yaml
125
+ - test/Droid X.jpg
126
+ - test/Droid X.jpg.yaml
127
+ - test/faces.jpg
128
+ - test/faces.jpg.yaml
129
+ - test/iPhone 4S.jpg
130
+ - test/iPhone 4S.jpg.yaml
131
+ - test/test_exiftoolr.rb