multi_exiftool 0.0.1 → 0.1.0

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