exiftoolr 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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