exiftoolr 0.1.0 → 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 +4 -4
- data/README.md +8 -66
- data/exiftoolr.gemspec +2 -2
- data/lib/exiftoolr.rb +2 -72
- data/test/exiftoolr_test.rb +1 -1
- metadata +16 -5
- data/lib/exiftoolr/parser.rb +0 -63
- data/lib/exiftoolr/result.rb +0 -32
- data/lib/exiftoolr/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86c98c6b3dee0ab28c3ca8f2d5a5707abceb771a
|
4
|
+
data.tar.gz: 0e3a10c593d43ad8a1eac4cad6e2771f3adb82d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bfcc98ac2e68a9d958d1879170ab6adae73e026239f5978c1db7d0fd887f25796dda5b30cb3d94f270deb4b1f056aee62166295978eebeee6672fe8588da130
|
7
|
+
data.tar.gz: 232d3ac4d5d4f0993d6801a7ce16ff6681185be71bc2ca6a9306cfe35e95666e419e667da6b7c95c2264e8e30f2152b7a53be1ac9d9f4b88066fa44add6846dc
|
data/README.md
CHANGED
@@ -8,77 +8,19 @@ This gem is the simplest thing that could possibly work that
|
|
8
8
|
reads the output of [exiftool](http://www.sno.phy.queensu.ca/~phil/exiftool)
|
9
9
|
and renders it into a ruby hash, with correctly typed values and symbolized keys.
|
10
10
|
|
11
|
-
|
11
|
+
Note that this gem was renamed from 'exiftoolr' to the less-ungainly 'exiftool'
|
12
|
+
as of version 0.2.0. This gem simply adds a "class alias" from Exiftoolr to Exiftool,
|
13
|
+
and is dependant on that new gem.
|
12
14
|
|
13
|
-
|
15
|
+
Consumers should switch to the new gem's namespace as soon as possible.
|
14
16
|
|
15
|
-
|
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.
|
17
|
+
Go to [exiftool](https://github.com/mceachen/exiftool) for more information!
|
25
18
|
|
26
|
-
##
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
require 'exiftoolr'
|
30
|
-
e = Exiftoolr.new("path/to/iPhone 4S.jpg")
|
31
|
-
e.to_hash
|
32
|
-
# => {:make => "Apple", :gps_longitude => -122.47566667, …
|
33
|
-
e.to_display_hash
|
34
|
-
# => {"Make" => "Apple", "GPS Longitude" => -122.47566667, …
|
35
|
-
```
|
36
|
-
|
37
|
-
### Multiple file support
|
38
|
-
|
39
|
-
This gem supports Exiftool's multiget, which lets you fetch metadata for many files at once.
|
40
|
-
|
41
|
-
This can be dramatically more efficient (like, 60x faster) than spinning up the ```exiftool```
|
42
|
-
process for each file.
|
43
|
-
|
44
|
-
Supply an array to the Exiftoolr initializer, then use ```.result_for```:
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
require 'exiftoolr'
|
48
|
-
e = Exiftoolr.new(Dir["**/*.jpg"])
|
49
|
-
result = e.result_for("path/to/iPhone 4S.jpg")
|
50
|
-
result.to_hash
|
51
|
-
# => {:make => "Apple", :gps_longitude => -122.47566667, …
|
52
|
-
result[:gps_longitude]
|
53
|
-
# => -122.47566667
|
54
|
-
|
55
|
-
e.files_with_results
|
56
|
-
# => ["path/to/iPhone 4S.jpg", "path/to/Droid X.jpg", …
|
57
|
-
```
|
58
|
-
|
59
|
-
### When things go wrong
|
60
|
-
|
61
|
-
* ```Exiftoolr::NoSuchFile``` is raised if the provided filename doesn't exist.
|
62
|
-
* ```Exiftoolr::ExiftoolNotInstalled``` is raised if ```exiftool``` isn't in your ```PATH```.
|
63
|
-
* If ExifTool has a problem reading EXIF data, no exception is raised, but ```#errors?``` will return true:
|
64
|
-
|
65
|
-
```ruby
|
66
|
-
Exiftoolr.new("Gemfile").errors?
|
67
|
-
#=> true
|
68
|
-
```
|
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 'exiftoolr'
|
19
|
+
## Change history
|
78
20
|
|
79
|
-
|
21
|
+
### 0.2.0
|
80
22
|
|
81
|
-
|
23
|
+
* Renamed to 'exiftool' (but kept backward compatibility)
|
82
24
|
|
83
25
|
### 0.1.0
|
84
26
|
|
data/exiftoolr.gemspec
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'exiftoolr/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = 'exiftoolr'
|
8
|
-
spec.version =
|
7
|
+
spec.version = Gem::Version.new('0.2.0')
|
9
8
|
spec.authors = ['Matthew McEachen']
|
10
9
|
spec.email = %w(matthew-github@mceachen.org)
|
11
10
|
spec.homepage = 'https://github.com/mceachen/exiftoolr'
|
@@ -21,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
21
20
|
spec.requirements << 'ExifTool (see http://www.sno.phy.queensu.ca/~phil/exiftool/)'
|
22
21
|
|
23
22
|
spec.add_dependency 'json'
|
23
|
+
spec.add_dependency 'exiftool'
|
24
24
|
spec.add_development_dependency 'rake'
|
25
25
|
spec.add_development_dependency 'bundler'
|
26
26
|
spec.add_development_dependency 'yard'
|
data/lib/exiftoolr.rb
CHANGED
@@ -1,73 +1,3 @@
|
|
1
|
-
require '
|
2
|
-
require 'shellwords'
|
3
|
-
require 'exiftoolr/result'
|
1
|
+
require 'exiftool'
|
4
2
|
|
5
|
-
|
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
|
3
|
+
Exiftoolr = Exiftool
|
data/test/exiftoolr_test.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: exiftoolr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: exiftool
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,9 +136,6 @@ files:
|
|
122
136
|
- Rakefile
|
123
137
|
- exiftoolr.gemspec
|
124
138
|
- lib/exiftoolr.rb
|
125
|
-
- lib/exiftoolr/parser.rb
|
126
|
-
- lib/exiftoolr/result.rb
|
127
|
-
- lib/exiftoolr/version.rb
|
128
139
|
- test/Canon 20D.jpg
|
129
140
|
- test/Canon 20D.jpg.yaml
|
130
141
|
- test/Droid X.jpg
|
data/lib/exiftoolr/parser.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
require 'time'
|
2
|
-
require 'date'
|
3
|
-
require 'rational'
|
4
|
-
|
5
|
-
class Exiftoolr
|
6
|
-
class Parser
|
7
|
-
|
8
|
-
WORD_BOUNDARY_RES = [/([A-Z\d]+)([A-Z][a-z])/, /([a-z\d])([A-Z])/]
|
9
|
-
FRACTION_RE = /^(\d+)\/(\d+)$/
|
10
|
-
|
11
|
-
attr_reader :key, :display_key, :sym_key, :raw_value
|
12
|
-
|
13
|
-
def initialize(key, raw_value)
|
14
|
-
@key = key
|
15
|
-
@display_key = WORD_BOUNDARY_RES.inject(key) { |k, regex| k.gsub(regex, '\1 \2') }
|
16
|
-
@sym_key = display_key.downcase.gsub(' ', '_').to_sym
|
17
|
-
@raw_value = raw_value
|
18
|
-
end
|
19
|
-
|
20
|
-
def value
|
21
|
-
for_lat_long ||
|
22
|
-
for_date ||
|
23
|
-
for_fraction ||
|
24
|
-
raw_value
|
25
|
-
rescue StandardError => e
|
26
|
-
"Warning: Parsing '#{raw_value}' for attribute '#{key}' raised #{e.message}"
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def for_lat_long
|
32
|
-
if sym_key == :gps_latitude || sym_key == :gps_longitude
|
33
|
-
value, direction = raw_value.split(" ")
|
34
|
-
if value =~ /\A\d+\.?\d*\z/
|
35
|
-
value.to_f * (['S', 'W'].include?(direction) ? -1 : 1)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def for_date
|
41
|
-
if raw_value.is_a?(String) && display_key =~ /\bdate\b/i
|
42
|
-
try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S%z') } ||
|
43
|
-
try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S') } ||
|
44
|
-
try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S.%L%z') } ||
|
45
|
-
try_parse { Time.strptime(raw_value, '%Y:%m:%d %H:%M:%S.%L') } ||
|
46
|
-
try_parse { Time.parse(raw_value) }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def try_parse
|
51
|
-
yield
|
52
|
-
rescue ArgumentError
|
53
|
-
nil
|
54
|
-
end
|
55
|
-
|
56
|
-
def for_fraction
|
57
|
-
if raw_value.is_a?(String)
|
58
|
-
scan = raw_value.scan(FRACTION_RE).first
|
59
|
-
v = Rational(*scan.map { |ea| ea.to_i }) unless scan.nil?
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
data/lib/exiftoolr/result.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'exiftoolr/parser'
|
2
|
-
|
3
|
-
class Exiftoolr
|
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
|
data/lib/exiftoolr/version.rb
DELETED