multi_exiftool 0.0.1 → 0.1.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.
Files changed (40) hide show
  1. data/CHANGELOG +1 -1
  2. data/LICENSE +21 -0
  3. data/Manifest +16 -23
  4. data/README +77 -16
  5. data/Rakefile +17 -38
  6. data/examples/01_simple_reading.rb +13 -0
  7. data/examples/02_simple_writing.rb +19 -0
  8. data/examples/03_reading_using_groups.rb +28 -0
  9. data/lib/multi_exiftool.rb +6 -38
  10. data/lib/multi_exiftool/executable.rb +67 -0
  11. data/lib/multi_exiftool/reader.rb +60 -0
  12. data/lib/multi_exiftool/values.rb +60 -0
  13. data/lib/multi_exiftool/writer.rb +55 -0
  14. data/multi_exiftool.gemspec +25 -16
  15. data/test/helper.rb +27 -0
  16. data/test/test_reader.rb +135 -0
  17. data/test/test_values.rb +75 -0
  18. data/test/test_values_using_groups.rb +61 -0
  19. data/test/test_writer.rb +102 -0
  20. metadata +49 -47
  21. data/COPYING +0 -165
  22. data/data/fixtures/read_non_existing_file.stderr +0 -1
  23. data/data/fixtures/read_non_existing_file.stdout +0 -0
  24. data/data/fixtures/read_one_file.stderr +0 -0
  25. data/data/fixtures/read_one_file.stdout +0 -92
  26. data/data/fixtures/read_two_files.stderr +0 -0
  27. data/data/fixtures/read_two_files.stdout +0 -212
  28. data/data/regression/read_command.rb +0 -12
  29. data/data/regression/read_command.rb.out +0 -16
  30. data/data/regression/write_command.rb +0 -16
  31. data/data/regression/write_command.rb.out +0 -40
  32. data/lib/multi_exiftool/command_generator.rb +0 -68
  33. data/lib/multi_exiftool/parser.rb +0 -43
  34. data/lib/multi_exiftool/read_object.rb +0 -57
  35. data/script/colorize.rb +0 -6
  36. data/script/generate_fixture.rb +0 -27
  37. data/test/test_command_generator.rb +0 -89
  38. data/test/test_helper.rb +0 -44
  39. data/test/test_parser.rb +0 -51
  40. data/test/test_read_object.rb +0 -36
@@ -0,0 +1,60 @@
1
+ # coding: utf-8
2
+ require 'date'
3
+ module MultiExiftool
4
+
5
+ # Representing (tag, value) pairs of metadata.
6
+ # Access via bracket-methods or dynamic method-interpreting via
7
+ # method_missing.
8
+ class Values
9
+
10
+ def initialize values
11
+ @values = {}
12
+ values.map do |tag,val|
13
+ val = val.kind_of?(Hash) ? Values.new(val) : val
14
+ @values[Values.unify_tag(tag)] = val
15
+ end
16
+ end
17
+
18
+ def [](tag)
19
+ parse_value(@values[Values.unify_tag(tag)])
20
+ end
21
+
22
+ def self.unify_tag tag
23
+ tag.gsub(/[-_]/, '').downcase
24
+ end
25
+
26
+ private
27
+
28
+ def method_missing tag, *args, &block
29
+ res = self[Values.unify_tag(tag.to_s)]
30
+ if res && block_given?
31
+ if block.arity > 0
32
+ yield res
33
+ else
34
+ res.instance_eval &block
35
+ end
36
+ end
37
+ res
38
+ end
39
+
40
+ def parse_value val
41
+ return val unless val.kind_of?(String)
42
+ case val
43
+ when /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)([-+]\d\d:\d\d)?$/
44
+ arr = $~.captures[0,6].map {|cap| cap.to_i}
45
+ arr << $7 if $7
46
+ if arr.size == 7
47
+ DateTime.new(*arr).to_time
48
+ else
49
+ Time.local(*arr)
50
+ end
51
+ when %r(^(\d+)/(\d+)$)
52
+ Rational($1, $2)
53
+ else
54
+ val
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,55 @@
1
+ # coding: utf-8
2
+ require_relative 'executable'
3
+
4
+ module MultiExiftool
5
+
6
+ # Handle writing of metadata via exiftool.
7
+ # Composing the command for the command-line executing it and parsing
8
+ # possible errors.
9
+ class Writer
10
+
11
+ attr_accessor :overwrite_original
12
+ attr_writer :values
13
+
14
+ include Executable
15
+
16
+ def values
17
+ Array(@values)
18
+ end
19
+
20
+ # Options to use with the exiftool command.
21
+ def options
22
+ opts = super
23
+ opts[:overwrite_original] = true if @overwrite_original
24
+ opts
25
+ end
26
+
27
+ # Getting the command for the command-line which would be executed
28
+ # when calling #write. It could be useful for logging, debugging or
29
+ # maybe even for creating a batch-file with exiftool command to be
30
+ # processed.
31
+ def command
32
+ cmd = [exiftool_command]
33
+ cmd << options_args
34
+ cmd << values_args
35
+ cmd << escaped_filenames
36
+ cmd.flatten.join(' ')
37
+ end
38
+
39
+ alias write execute # :nodoc:
40
+
41
+ private
42
+
43
+ def values_args
44
+ raise MultiExiftool::Error.new('No values.') if values.empty?
45
+ @values.map {|tag, val| "-#{tag}=#{escape(val.to_s)}"}
46
+ end
47
+
48
+ def parse_results
49
+ @errors = @stderr.readlines
50
+ @errors.empty?
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -2,34 +2,43 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{multi_exiftool}
5
- s.version = "0.0.1"
5
+ s.version = "0.1.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Jan Friedrich"]
9
- s.date = %q{2008-11-25}
10
- s.description = %q{}
11
- s.email = %q{janfri.rubyforge@gmail.com}
12
- s.extra_rdoc_files = ["lib/multi_exiftool.rb", "lib/multi_exiftool/parser.rb", "lib/multi_exiftool/command_generator.rb", "lib/multi_exiftool/read_object.rb", "README", "CHANGELOG", "COPYING"]
13
- s.files = ["Rakefile", "Manifest", "test/test_command_generator.rb", "test/test_helper.rb", "test/test_parser.rb", "test/test_read_object.rb", "lib/multi_exiftool.rb", "lib/multi_exiftool/parser.rb", "lib/multi_exiftool/command_generator.rb", "lib/multi_exiftool/read_object.rb", "README", "data/fixtures/read_non_existing_file.stderr", "data/fixtures/read_non_existing_file.stdout", "data/fixtures/read_two_files.stderr", "data/fixtures/read_one_file.stderr", "data/fixtures/read_one_file.stdout", "data/fixtures/read_two_files.stdout", "data/regression/read_command.rb", "data/regression/read_command.rb.out", "data/regression/write_command.rb", "data/regression/write_command.rb.out", "script/generate_fixture.rb", "script/colorize.rb", "CHANGELOG", "COPYING", "multi_exiftool.gemspec"]
14
- s.has_rdoc = true
15
- s.homepage = %q{http://multiexiftool.rubyforge.org}
9
+ s.date = %q{2010-01-27}
10
+ s.description = %q{This library is wrapper for the Exiftool command-line application (http://www.sno.phy.queensu.ca/~phil/exiftool) written by Phil Harvey. It is designed for dealing with multiple files at once by creating commands to call exiftool with various arguments, call it and parsing the results.}
11
+ s.email = %q{janfri26@gmail.com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "lib/multi_exiftool.rb", "lib/multi_exiftool/executable.rb", "lib/multi_exiftool/reader.rb", "lib/multi_exiftool/values.rb", "lib/multi_exiftool/writer.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "examples/01_simple_reading.rb", "examples/02_simple_writing.rb", "examples/03_reading_using_groups.rb", "lib/multi_exiftool.rb", "lib/multi_exiftool/executable.rb", "lib/multi_exiftool/reader.rb", "lib/multi_exiftool/values.rb", "lib/multi_exiftool/writer.rb", "test/helper.rb", "test/test_reader.rb", "test/test_values.rb", "test/test_values_using_groups.rb", "test/test_writer.rb", "multi_exiftool.gemspec"]
14
+ s.homepage = %q{http://rubyforge.org/projects/multiexiftool}
15
+ s.post_install_message = %q{
16
+ +-----------------------------------------------------------------------+
17
+ | Please ensure you have installed exiftool version 7.65 or higher and |
18
+ | it's found in your PATH (Try "exiftool -ver" on your commandline). |
19
+ | For more details see |
20
+ | http://www.sno.phy.queensu.ca/~phil/exiftool/install.html |
21
+ +-----------------------------------------------------------------------+
22
+ }
16
23
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Multi_exiftool", "--main", "README"]
17
24
  s.require_paths = ["lib"]
18
- s.rubyforge_project = %q{multiexiftool}
19
- s.rubygems_version = %q{1.3.1}
20
- s.summary = %q{}
21
- s.test_files = ["test/test_read_object.rb", "test/test_helper.rb", "test/test_command_generator.rb", "test/test_parser.rb"]
25
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9.1")
26
+ s.requirements = ["exiftool, version 7.65 or higher"]
27
+ s.rubyforge_project = %q{multi_exiftool}
28
+ s.rubygems_version = %q{1.3.5}
29
+ s.summary = %q{This library is wrapper for the Exiftool command-line application (http://www.sno.phy.queensu.ca/~phil/exiftool).}
30
+ s.test_files = ["test/test_reader.rb", "test/test_values.rb", "test/test_values_using_groups.rb", "test/test_writer.rb"]
22
31
 
23
32
  if s.respond_to? :specification_version then
24
33
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
- s.specification_version = 2
34
+ s.specification_version = 3
26
35
 
27
36
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
- s.add_development_dependency(%q<yact>, [">= 0"])
37
+ s.add_development_dependency(%q<contest>, [">= 0"])
29
38
  else
30
- s.add_dependency(%q<yact>, [">= 0"])
39
+ s.add_dependency(%q<contest>, [">= 0"])
31
40
  end
32
41
  else
33
- s.add_dependency(%q<yact>, [">= 0"])
42
+ s.add_dependency(%q<contest>, [">= 0"])
34
43
  end
35
44
  end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ require_relative '../lib/multi_exiftool'
3
+ require 'test/unit'
4
+ require 'contest'
5
+ require 'open3'
6
+ require 'stringio'
7
+
8
+ module TestHelper
9
+
10
+ def mocking_open3(command, outstr, errstr)
11
+ open3_eigenclass = class << Open3; self; end
12
+ open3_eigenclass.module_exec(command, outstr, errstr) do |cmd, out, err|
13
+ define_method :popen3 do |arg|
14
+ if arg == cmd
15
+ return [nil, StringIO.new(out), StringIO.new(err)]
16
+ else
17
+ raise ArgumentError.new("Expected call of Open3.popen3 with argument #{cmd.inspect} but was #{arg.inspect}.")
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ class Test::Unit::TestCase
26
+ include TestHelper
27
+ end
@@ -0,0 +1,135 @@
1
+ # coding: utf-8
2
+ require_relative 'helper'
3
+
4
+ class TestReader < Test::Unit::TestCase
5
+
6
+ setup do
7
+ @reader = MultiExiftool::Reader.new
8
+ end
9
+
10
+ context 'command method' do
11
+
12
+ test 'simple case' do
13
+ @reader.filenames = %w(a.jpg b.tif c.bmp)
14
+ command = 'exiftool -J a.jpg b.tif c.bmp'
15
+ assert_equal command, @reader.command
16
+ end
17
+
18
+ test 'no filenames' do
19
+ assert_raises MultiExiftool::Error do
20
+ @reader.command
21
+ end
22
+ @reader.filenames = []
23
+ assert_raises MultiExiftool::Error do
24
+ @reader.command
25
+ end
26
+ end
27
+
28
+ test 'filenames with spaces' do
29
+ @reader.filenames = ['one file with spaces.jpg', 'another file with spaces.tif']
30
+ command = 'exiftool -J one\ file\ with\ spaces.jpg another\ file\ with\ spaces.tif'
31
+ assert_equal command, @reader.command
32
+ end
33
+
34
+ test 'tags' do
35
+ @reader.filenames = %w(a.jpg b.tif c.bmp)
36
+ @reader.tags = %w(author fnumber)
37
+ command = 'exiftool -J -author -fnumber a.jpg b.tif c.bmp'
38
+ assert_equal command, @reader.command
39
+ end
40
+
41
+ test 'options with boolean argument' do
42
+ @reader.filenames = %w(a.jpg b.tif c.bmp)
43
+ @reader.options = {:e => true}
44
+ command = 'exiftool -J -e a.jpg b.tif c.bmp'
45
+ assert_equal command, @reader.command
46
+ end
47
+
48
+ test 'options with value argument' do
49
+ @reader.filenames = %w(a.jpg b.tif c.bmp)
50
+ @reader.options = {:lang => 'de'}
51
+ command = 'exiftool -J -lang de a.jpg b.tif c.bmp'
52
+ assert_equal command, @reader.command
53
+ end
54
+
55
+ test 'numerical flag' do
56
+ @reader.filenames = %w(a.jpg b.tif c.bmp)
57
+ @reader.numerical = true
58
+ command = 'exiftool -J -n a.jpg b.tif c.bmp'
59
+ assert_equal command, @reader.command
60
+ end
61
+
62
+ test 'group flag' do
63
+ @reader.filenames = %w(a.jpg)
64
+ @reader.group = 0
65
+ command = 'exiftool -J -g0 a.jpg'
66
+ assert_equal command, @reader.command
67
+ @reader.group = 1
68
+ command = 'exiftool -J -g1 a.jpg'
69
+ assert_equal command, @reader.command
70
+ end
71
+
72
+ end
73
+
74
+ context 'read method' do
75
+
76
+ test 'try to read a non-existing file' do
77
+ mocking_open3('exiftool -J non_existing_file', '', 'File non_existing_file not found.')
78
+ @reader.filenames = %w(non_existing_file)
79
+ res = @reader.read
80
+ assert_equal [], res
81
+ assert_equal ['File non_existing_file not found.'], @reader.errors
82
+ end
83
+
84
+ test 'successful reading with one tag' do
85
+ json = <<-EOS
86
+ [{
87
+ "SourceFile": "a.jpg",
88
+ "FNumber": 11.0
89
+ },
90
+ {
91
+ "SourceFile": "b.tif",
92
+ "FNumber": 9.0
93
+ },
94
+ {
95
+ "SourceFile": "c.bmp",
96
+ "FNumber": 8.0
97
+ }]
98
+ EOS
99
+ json.gsub!(/^ {8}/, '')
100
+ mocking_open3('exiftool -J -fnumber a.jpg b.tif c.bmp', json, '')
101
+ @reader.filenames = %w(a.jpg b.tif c.bmp)
102
+ @reader.tags = %w(fnumber)
103
+ res = @reader.read
104
+ assert_kind_of Array, res
105
+ assert_equal [11.0, 9.0, 8.0], res.map {|e| e['FNumber']}
106
+ assert_equal [], @reader.errors
107
+ end
108
+
109
+ test 'successful reading of hierarichal data' do
110
+ json = <<-EOS
111
+ [{
112
+ "SourceFile": "a.jpg",
113
+ "EXIF": {
114
+ "FNumber": 7.1
115
+ },
116
+ "MakerNotes": {
117
+ "FNumber": 7.0
118
+ }
119
+ }]
120
+ EOS
121
+ json.gsub!(/^ {8}/, '')
122
+ mocking_open3('exiftool -J -g0 -fnumber a.jpg', json, '')
123
+ @reader.filenames = %w(a.jpg)
124
+ @reader.tags = %w(fnumber)
125
+ @reader.group = 0
126
+ res = @reader.read.first
127
+ assert_equal 'a.jpg', res.source_file
128
+ assert_equal 7.1, res.exif.fnumber
129
+ assert_equal 7.0, res.maker_notes.fnumber
130
+ assert_equal [], @reader.errors
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,75 @@
1
+ # coding: utf-8
2
+ require_relative 'helper'
3
+ require 'date'
4
+
5
+ class TestValues < Test::Unit::TestCase
6
+
7
+ context 'value access' do
8
+
9
+ setup do
10
+ hash = {'FNumber' => 8, 'Author' => 'janfri'}
11
+ @values = MultiExiftool::Values.new(hash)
12
+ end
13
+
14
+ test 'original spelling of tag name' do
15
+ assert_equal 8, @values['FNumber']
16
+ end
17
+
18
+ test 'variant spellings of tag names' do
19
+ assert_equal 8, @values['fnumber']
20
+ assert_equal 8, @values['f_number']
21
+ assert_equal 8, @values['f-number']
22
+ end
23
+
24
+ test 'tag access via methods' do
25
+ assert_equal 8, @values.fnumber
26
+ assert_equal 8, @values.f_number
27
+ end
28
+
29
+ end
30
+
31
+ context 'parsing of values' do
32
+
33
+ context 'timestamps' do
34
+
35
+ setup do
36
+ hash = {
37
+ 'TimestampWithoutZone' => '2009:08:25 12:35:42',
38
+ 'TimestampWithPositiveZone' => '2009:08:26 20:22:24+05:00',
39
+ 'TimestampWithNegativeZone' => '2009:08:26 20:22:24-07:00'
40
+ }
41
+ @values = MultiExiftool::Values.new(hash)
42
+ end
43
+
44
+ test 'local Time object' do
45
+ time = Time.local(2009, 8, 25, 12, 35, 42)
46
+ assert_equal time, @values['TimestampWithoutZone']
47
+ end
48
+
49
+ test 'Time object with given zone' do
50
+ time = DateTime.new(2009,8,26,20,22,24,'+0500').to_time
51
+ assert_equal time, @values['TimestampWithPositiveZone']
52
+ time = DateTime.new(2009,8,26,20,22,24,'-0700').to_time
53
+ assert_equal time, @values['TimestampWithNegativeZone']
54
+ end
55
+
56
+ end
57
+
58
+ context 'other values' do
59
+
60
+ setup do
61
+ hash = {
62
+ 'ShutterSpeed' => '1/200'
63
+ }
64
+ @values = MultiExiftool::Values.new(hash)
65
+ end
66
+
67
+ test 'rational values' do
68
+ assert_equal Rational(1, 200), @values['ShutterSpeed']
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,61 @@
1
+ # coding: utf-8
2
+ require_relative 'helper'
3
+
4
+ class TestValuesUsingGroups < Test::Unit::TestCase
5
+
6
+ setup do
7
+ hash = {'EXIF' => {'FNumber' => 8, 'Author' => 'janfri'}}
8
+ @values = MultiExiftool::Values.new(hash)
9
+ end
10
+
11
+ test 'bracket access' do
12
+ assert_equal 8, @values['EXIF']['FNumber']
13
+ assert_equal 'janfri', @values['EXIF']['Author']
14
+ end
15
+
16
+ test 'method access' do
17
+ assert_equal 8, @values.exif.fnumber
18
+ assert_equal 'janfri', @values.exif.author
19
+ end
20
+
21
+ test 'mixed access' do
22
+ assert_equal 8, @values.exif['FNumber']
23
+ assert_equal 'janfri', @values.exif['Author']
24
+ assert_equal 8, @values['EXIF'].fnumber
25
+ assert_equal 'janfri', @values['EXIF'].author
26
+ end
27
+
28
+ test 'block access without block parameter' do
29
+ $ok = false
30
+ $block_self = nil
31
+ res = @values.exif do
32
+ $ok = true
33
+ $block_self = self
34
+ end
35
+ assert $ok, "Block for exif wasn't executed."
36
+ assert_equal @values.exif, $block_self
37
+ assert_equal @values.exif, res
38
+ @values.iptc do
39
+ assert false, "This block should not be executed because IPTC isn't aviable."
40
+ end
41
+ end
42
+
43
+ test 'block access with block parameter' do
44
+ $ok = false
45
+ $self = self
46
+ $block_param = nil
47
+ res = @values.exif do |e|
48
+ $ok = true
49
+ $self = self
50
+ $block_param = e
51
+ end
52
+ assert $ok, "Block for exif wasn't executed."
53
+ assert_equal @values.exif, $block_param
54
+ assert_equal self, $self
55
+ assert_equal @values.exif, res
56
+ @values.iptc do
57
+ assert false, "This block should not be executed because IPTC isn't aviable."
58
+ end
59
+ end
60
+
61
+ end