filmrolls 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.md +1 -3
- data/README.md +6 -8
- data/Rakefile +42 -0
- data/bin/filmrolls +2 -42
- data/filmrolls.gemspec +76 -0
- data/lib/filmrolls/cli.rb +131 -0
- data/lib/filmrolls/exiftool.rb +13 -0
- data/lib/filmrolls/negative.rb +77 -0
- data/lib/filmrolls/version.rb +3 -0
- data/lib/filmrolls/xmlformat.rb +72 -0
- data/lib/filmrolls.rb +2 -2
- data/man/filmrolls.1 +47 -0
- data/test/data/filmrolls.xml +39 -0
- data/test/data/with-exif.tiff +0 -0
- data/test/data/without-exif.tiff +0 -0
- data/test/test_helper.rb +4 -7
- data/test/{test_filmrolls.rb → test_xmlformat.rb} +3 -3
- metadata +82 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3b26c01d199b8c0d15625d83aeaa6d90865808820bd57bc114919429f2aaff58
|
4
|
+
data.tar.gz: 3369d49d9416dbbde432869b871cce83f6fc559c7a3287cbe867a7bb4b512043
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e30f40902c26e0fc230905c701b9e93b8f1b9ee4b349ec6102367f5967854b328b58650639db1f832804425886c424f48f5557e81a36088ef0c247689c1a610e
|
7
|
+
data.tar.gz: b791d3eacc96426b5a3bcf7de02af2acbf57d1389d7b890f6538596781b1cef0cf9f8bc52077f3c6f51052f3891b49c0172d85b6c44288e0f3e816a6ddace43a
|
data/LICENSE.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# ISC License
|
2
2
|
|
3
|
-
|
4
|
-
Copyright (c) 2016, Simon Sigurdhsson
|
3
|
+
Copyright (c) 2017, Simon Sigurdhsson
|
5
4
|
|
6
5
|
Permission to use, copy, modify, and/or distribute this software for any
|
7
6
|
purpose with or without fee is hereby granted, provided that the above
|
@@ -14,4 +13,3 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
14
13
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
15
14
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
16
15
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
17
|
-
```
|
data/README.md
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
# Film Rolls EXIF tagger
|
2
2
|
|
3
|
-
[![Build Status](https://img.shields.io/travis/urdh/
|
3
|
+
[![Build Status](https://img.shields.io/travis/urdh/filmrolls/master.svg)][build-status]
|
4
4
|
[![Code Quality](https://img.shields.io/codacy/grade/ad6e6ba15806436ea9cb80c41fc75fb0/master.svg)][codacy]
|
5
5
|
[![Code Coverage](https://img.shields.io/codacy/coverage/ad6e6ba15806436ea9cb80c41fc75fb0/master.svg)][codacy]
|
6
6
|
[![Gem](https://img.shields.io/gem/v/filmrolls.svg)][release]
|
7
|
-
[![License](https://img.shields.io/github/license/urdh/
|
7
|
+
[![License](https://img.shields.io/github/license/urdh/filmrolls.svg)](LICENSE.md)
|
8
8
|
[![Semantic Versioning](https://img.shields.io/badge/semver-2.0.0-blue.svg)][semver]
|
9
9
|
|
10
10
|
This is a utility designed to read the XML files used by the [Film Rolls iOS app][film-rolls], and enable batch EXIF tagging of scanned negatives in TIFF format based on the information in these XML files.
|
11
11
|
|
12
|
-
The gem is released under the [ISC license]
|
13
|
-
Eventually there will be some sort of [changelog]
|
12
|
+
The gem is released under the [ISC license](LICENSE.md).
|
13
|
+
Eventually there will be some sort of [changelog](CHANGELOG.md) as well.
|
14
14
|
|
15
15
|
[film-rolls]: https://itunes.apple.com/se/app/film-rolls-app-for-film-photographers/id675626559
|
16
16
|
[semver]: http://semver.org/spec/v2.0.0.html
|
17
17
|
|
18
|
-
[build-status]: https://travis-ci.org/urdh/
|
19
|
-
[codacy]: https://www.codacy.com/app/Sigurdhsson/
|
18
|
+
[build-status]: https://travis-ci.org/urdh/filmrolls
|
19
|
+
[codacy]: https://www.codacy.com/app/Sigurdhsson/filmrolls
|
20
20
|
[release]: https://rubygems.org/gems/filmrolls
|
21
|
-
[license]: https://github.com/urdh/roll2exif/blob/master/LICENSE.md
|
22
|
-
[changelog]: https://github.com/urdh/roll2exif/blob/master/CHANGELOG.md
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rdoc/task'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
|
5
|
+
def gemspec
|
6
|
+
require 'rubygems/specification'
|
7
|
+
@gemspec ||= Gem::Specification.load(
|
8
|
+
File.expand_path(File.dirname(__FILE__) + '/filmrolls.gemspec')
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
rule(/\.[0-9]$/ => [proc { |name| "#{name}.ronn" }]) do |task|
|
13
|
+
require 'ronn'
|
14
|
+
appname, section = File.basename(task.name).split('.')
|
15
|
+
File.open(task.name, 'w') do |man|
|
16
|
+
man.write Ronn.new(
|
17
|
+
task.source,
|
18
|
+
name: appname,
|
19
|
+
section: section,
|
20
|
+
tagline: gemspec.summary,
|
21
|
+
manual: "#{gemspec.name} v#{gemspec.version}"
|
22
|
+
).to_roff
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Build all manual pages'
|
27
|
+
task ronn: gemspec.files.select { |f| f[/\.[0-9]$/] }
|
28
|
+
|
29
|
+
Gem::PackageTask.new(gemspec) {}
|
30
|
+
|
31
|
+
RDoc::Task.new(:rdoc) do |r|
|
32
|
+
r.options = gemspec.rdoc_options
|
33
|
+
r.rdoc_files.include(gemspec.require_paths)
|
34
|
+
r.rdoc_files.include(gemspec.extra_rdoc_files)
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::TestTask.new(:test) do |t|
|
38
|
+
t.libs = gemspec.require_paths + %w[test]
|
39
|
+
t.test_files = gemspec.test_files
|
40
|
+
end
|
41
|
+
|
42
|
+
task default: :test
|
data/bin/filmrolls
CHANGED
@@ -1,45 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'rubygems'
|
3
2
|
require 'commander/import'
|
4
|
-
require 'filmrolls'
|
3
|
+
require 'filmrolls/cli'
|
5
4
|
|
6
|
-
|
7
|
-
GEMSPEC = Gem::Specification.find_by_name('filmrolls')
|
8
|
-
SPECFILE = GEMSPEC.loaded_from
|
9
|
-
rescue Gem::LoadError
|
10
|
-
SPECFILE = File.expand_path(File.dirname(__FILE__) + '/../filmrolls.gemspec')
|
11
|
-
GEMSPEC = Gem::Specification.load(SPECFILE)
|
12
|
-
end
|
13
|
-
|
14
|
-
program :version, GEMSPEC.version.to_s
|
15
|
-
program :description, GEMSPEC.summary
|
16
|
-
|
17
|
-
command :list do |c|
|
18
|
-
c.syntax = 'filmrolls list [options] xml-file'
|
19
|
-
c.summary = 'List film rolls'
|
20
|
-
c.description = 'List ID and additional data for all film rolls in xml-file.'
|
21
|
-
|
22
|
-
c.action do |args, _options|
|
23
|
-
filename = args.shift
|
24
|
-
abort 'Missing xml-file argument.' if filename.nil?
|
25
|
-
|
26
|
-
begin
|
27
|
-
xml = File.read(filename)
|
28
|
-
rescue SystemCallError
|
29
|
-
abort "Could not read XML input from `#{filename}`."
|
30
|
-
end
|
31
|
-
|
32
|
-
rolls =
|
33
|
-
Filmrolls::FilmRolls.load(xml)[:rolls].map do |roll|
|
34
|
-
roll = roll.to_h
|
35
|
-
roll[:frames] = roll[:frames].length
|
36
|
-
end
|
37
|
-
|
38
|
-
unless rolls.empty?
|
39
|
-
require 'terminal-table'
|
40
|
-
head = rolls.first.keys.map(&:capitalize)
|
41
|
-
rows = rolls.map(&:values)
|
42
|
-
say Terminal::Table.new(headings: head, rows: rows)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
5
|
+
Filmrolls::Cli.run
|
data/filmrolls.gemspec
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'filmrolls/version'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'filmrolls'
|
7
|
+
s.version = Filmrolls::VERSION
|
8
|
+
s.summary = 'Tag TIFF files with EXIF data extracted from XML data'
|
9
|
+
|
10
|
+
s.author = 'Simon Sigurdhsson'
|
11
|
+
s.email = 'Sigurdhsson@gmail.com'
|
12
|
+
s.homepage = 'https://github.com/urdh/filmrolls'
|
13
|
+
s.license = 'ISC'
|
14
|
+
|
15
|
+
s.description = <<-EOF
|
16
|
+
This is a utility designed to read the XML files used by the Film Rolls
|
17
|
+
iOS app, and enable batch EXIF tagging of scanned negatives in TIFF format
|
18
|
+
based on the information in these XML files.
|
19
|
+
EOF
|
20
|
+
|
21
|
+
s.files = %w[
|
22
|
+
filmrolls.gemspec
|
23
|
+
Rakefile
|
24
|
+
README.md
|
25
|
+
LICENSE.md
|
26
|
+
bin/filmrolls
|
27
|
+
man/filmrolls.1
|
28
|
+
lib/filmrolls.rb
|
29
|
+
lib/filmrolls/cli.rb
|
30
|
+
lib/filmrolls/exiftool.rb
|
31
|
+
lib/filmrolls/negative.rb
|
32
|
+
lib/filmrolls/version.rb
|
33
|
+
lib/filmrolls/xmlformat.rb
|
34
|
+
test/test_helper.rb
|
35
|
+
test/test_exiftool.rb
|
36
|
+
test/test_xmlformat.rb
|
37
|
+
test/data/filmrolls.xml
|
38
|
+
test/data/with-exif.tiff
|
39
|
+
test/data/without-exif.tiff
|
40
|
+
]
|
41
|
+
# TODO: how to generate the man page and inclue in the gem?
|
42
|
+
|
43
|
+
s.bindir = 'bin'
|
44
|
+
s.executables = %w[
|
45
|
+
filmrolls
|
46
|
+
]
|
47
|
+
|
48
|
+
s.require_paths = 'lib'
|
49
|
+
s.test_files = %w[
|
50
|
+
test/test_exiftool.rb
|
51
|
+
test/test_xmlformat.rb
|
52
|
+
]
|
53
|
+
|
54
|
+
s.extra_rdoc_files = %w[README.md LICENSE.md bin/filmrolls]
|
55
|
+
s.rdoc_options = [
|
56
|
+
'--line-numbers', '--inline-source',
|
57
|
+
'--title', 'Film Rolls EXIF tagger',
|
58
|
+
'--main', 'README.md'
|
59
|
+
]
|
60
|
+
|
61
|
+
s.add_dependency 'mini_exiftool', ['~> 2.8']
|
62
|
+
s.add_dependency 'exiftool_vendored', ['~> 11']
|
63
|
+
s.add_dependency 'nokogiri', ['~> 1.10']
|
64
|
+
s.add_dependency 'geokit', ['~> 1.11']
|
65
|
+
s.add_dependency 'commander', ['~> 4.4']
|
66
|
+
s.add_dependency 'terminal-table', ['~> 1.8']
|
67
|
+
|
68
|
+
s.add_development_dependency 'rake', ['~> 12']
|
69
|
+
s.add_development_dependency 'rdoc', ['~> 6']
|
70
|
+
s.add_development_dependency 'bundler', ['~> 2']
|
71
|
+
s.add_development_dependency 'simplecov', ['~> 0.14']
|
72
|
+
s.add_development_dependency 'codacy-coverage', ['~> 2']
|
73
|
+
s.add_development_dependency 'minitest', ['~> 5.8']
|
74
|
+
s.add_development_dependency 'minitest-reporters', ['~> 1.3']
|
75
|
+
s.add_development_dependency 'ronn', ['~> 0.7']
|
76
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'filmrolls'
|
2
|
+
require 'commander'
|
3
|
+
|
4
|
+
module Filmrolls
|
5
|
+
module Cli
|
6
|
+
include Commander::Methods
|
7
|
+
|
8
|
+
def self.run
|
9
|
+
program :version, Filmrolls::VERSION
|
10
|
+
program :description, 'Tag TIFF files with EXIF data extracted from XML.'
|
11
|
+
program :help_formatter, :compact
|
12
|
+
|
13
|
+
global_option '-r', '--rolls FILE', 'Film Rolls XML file (default: stdin)' do |r|
|
14
|
+
$rolls_file = r
|
15
|
+
end
|
16
|
+
|
17
|
+
command 'list-rolls' do |c|
|
18
|
+
c.syntax = 'filmrolls list-rolls [--rolls FILE]'
|
19
|
+
c.summary = 'List film rolls'
|
20
|
+
c.description = 'List ID and additional data for all film rolls ' \
|
21
|
+
'in input.'
|
22
|
+
|
23
|
+
c.action do |_args, _options|
|
24
|
+
rolls = get_rolls($rolls_file).map do |roll|
|
25
|
+
roll.merge(
|
26
|
+
frames: roll[:frames].length,
|
27
|
+
unload: roll[:unload].to_date,
|
28
|
+
load: roll[:load].to_date
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
unless rolls.empty?
|
33
|
+
require 'terminal-table'
|
34
|
+
head = rolls.first.keys.map(&:capitalize)
|
35
|
+
rows = rolls.map(&:values)
|
36
|
+
say Terminal::Table.new(headings: head, rows: rows)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
command 'list-frames' do |c|
|
42
|
+
c.syntax = 'filmrolls list-frames [--rolls FILE] --id ID'
|
43
|
+
c.summary = 'List frames'
|
44
|
+
c.description = 'List frames from film roll with ID in input.'
|
45
|
+
c.option '-i', '--id ID', 'Use data from roll with id ID'
|
46
|
+
|
47
|
+
c.action do |_args, options|
|
48
|
+
abort "A film roll ID must be supplied" if options.id.nil?
|
49
|
+
|
50
|
+
roll = get_rolls($rolls_file).detect do |r|
|
51
|
+
r[:id] == options.id
|
52
|
+
end
|
53
|
+
|
54
|
+
abort "Could not find film roll with ID #{options.id}" if roll.nil?
|
55
|
+
|
56
|
+
unless roll.nil?
|
57
|
+
require 'terminal-table'
|
58
|
+
head = roll[:frames].first.keys.map(&:capitalize)
|
59
|
+
rows = roll[:frames].map(&:values)
|
60
|
+
say Terminal::Table.new(headings: head, rows: rows)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
command :tag do |c|
|
66
|
+
c.syntax = 'filmrolls tag [--dry-run] [--rolls FILE] --id ID IMAGE...'
|
67
|
+
c.summary = 'Write EXIF tags'
|
68
|
+
c.description = 'Write EXIF tags to a set of images using data from ' \
|
69
|
+
'film roll with ID in input.'
|
70
|
+
c.option '-i', '--id ID', 'Use data from roll with id ID'
|
71
|
+
c.option '-n', '--dry-run', "Don't actually modify any files"
|
72
|
+
|
73
|
+
c.action do |args, options|
|
74
|
+
abort "A film roll ID must be supplied" if options.id.nil?
|
75
|
+
|
76
|
+
roll = get_rolls($rolls_file).detect do |r|
|
77
|
+
r[:id] == options.id
|
78
|
+
end
|
79
|
+
|
80
|
+
abort "Could not find film roll with ID #{options.id}" if roll.nil?
|
81
|
+
|
82
|
+
unless args.length == roll[:frames].length
|
83
|
+
abort "Expected #{roll[:frames].length} images, got #{args.length}"
|
84
|
+
end
|
85
|
+
|
86
|
+
roll[:frames].zip(args).each do |frame, file|
|
87
|
+
log 'Path', file
|
88
|
+
negative = Filmrolls::Negative.new(file)
|
89
|
+
log 'Date', frame[:date]
|
90
|
+
negative.date = frame[:date]
|
91
|
+
log 'Camera', roll[:camera]
|
92
|
+
negative.camera = roll[:camera]
|
93
|
+
log 'Lens', frame[:lens]
|
94
|
+
negative.lens = frame[:lens]
|
95
|
+
log 'Film', roll[:film]
|
96
|
+
negative.film = roll[:film]
|
97
|
+
log 'ISO', roll[:speed]
|
98
|
+
negative.speed = roll[:speed]
|
99
|
+
if frame[:shutter_speed] != 0 and frame[:aperture] != 0
|
100
|
+
log 'Shutter speed', "#{frame[:shutter_speed]}s"
|
101
|
+
negative.shutter_speed = frame[:shutter_speed]
|
102
|
+
log 'Aperture', "ƒ/#{frame[:aperture]}"
|
103
|
+
negative.aperture = frame[:aperture]
|
104
|
+
end
|
105
|
+
if frame[:compensation] != 0
|
106
|
+
log 'Compensation', frame[:compensation]
|
107
|
+
negative.compensation = frame[:compensation]
|
108
|
+
end
|
109
|
+
if frame[:position] != Geokit::LatLng.new(0.0, 0.0)
|
110
|
+
log 'Position', frame[:position]
|
111
|
+
negative.position = frame[:position]
|
112
|
+
end
|
113
|
+
negative.save! unless options.dry_run
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class << self
|
120
|
+
private
|
121
|
+
|
122
|
+
def get_rolls(file)
|
123
|
+
begin
|
124
|
+
Filmrolls::XMLFormat.load(file.nil? ? $stdin.read : File.read(file))[:rolls]
|
125
|
+
rescue SystemCallError => err
|
126
|
+
abort "Could not read input XML: #{err.message}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'exiftool_vendored'
|
2
|
+
require 'mini_exiftool'
|
3
|
+
|
4
|
+
module Filmrolls
|
5
|
+
class Exiftool < MiniExiftool
|
6
|
+
self.command = ExiftoolVendored.path_to_exiftool
|
7
|
+
|
8
|
+
def initialize(filename_or_io = nil, opts = {})
|
9
|
+
opts[:timestamps] = DateTime unless opts.key? :timestamps
|
10
|
+
super(filename_or_io, opts)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'filmrolls/exiftool'
|
2
|
+
|
3
|
+
module Filmrolls
|
4
|
+
class Negative
|
5
|
+
def initialize(path)
|
6
|
+
@file = Exiftool.new(path, :coord_format => "%.6f degrees")
|
7
|
+
end
|
8
|
+
|
9
|
+
def merge(*args)
|
10
|
+
args.each do |key, value|
|
11
|
+
method = "#{key}="
|
12
|
+
@self.public_send(method, value) if @self.respond_to? method
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
require 'yaml'
|
18
|
+
@file.to_yaml
|
19
|
+
end
|
20
|
+
|
21
|
+
def changed?
|
22
|
+
@file.changed?
|
23
|
+
end
|
24
|
+
|
25
|
+
def save!
|
26
|
+
@file.save!
|
27
|
+
end
|
28
|
+
|
29
|
+
def aperture=(val)
|
30
|
+
@file[:ApertureValue] = val
|
31
|
+
end
|
32
|
+
|
33
|
+
def speed=(val)
|
34
|
+
@file[:ISO] = val
|
35
|
+
end
|
36
|
+
|
37
|
+
def shutter_speed=(val)
|
38
|
+
@file[:ShutterSpeedValue] = val
|
39
|
+
end
|
40
|
+
|
41
|
+
def compensation=(val)
|
42
|
+
@file[:ExposureCompensation] = val
|
43
|
+
end
|
44
|
+
|
45
|
+
def position=(val)
|
46
|
+
@file[:GPSLatitude] = "#{val.lat} degrees"
|
47
|
+
@file[:GPSLatitudeRef] = 1 # Positive number interpreted as East
|
48
|
+
@file[:GPSLongitude] = "#{val.lng} degrees"
|
49
|
+
@file[:GPSLongitudeRef] = 1 # Positive number interpreted as North
|
50
|
+
end
|
51
|
+
|
52
|
+
def film=(val)
|
53
|
+
@file[:UserComment] = val
|
54
|
+
end
|
55
|
+
|
56
|
+
def date=(val)
|
57
|
+
@file[:DateTimeOriginal] = val
|
58
|
+
end
|
59
|
+
|
60
|
+
def lens=(val)
|
61
|
+
# TODO: this is hacky as f
|
62
|
+
make = val.strip.split(/\s+/)[0]
|
63
|
+
model = val.strip[(make.length)..-1]
|
64
|
+
@file[:Lens] = val
|
65
|
+
@file[:LensMake] = make
|
66
|
+
@file[:LensModel] = model
|
67
|
+
end
|
68
|
+
|
69
|
+
def camera=(val)
|
70
|
+
make = val.strip.split(/\s+/)[0]
|
71
|
+
model = val.strip[(make.length)..-1]
|
72
|
+
@file[:LocalizedCameraModel] = val
|
73
|
+
@file[:Make] = make
|
74
|
+
@file[:Model] = model
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'date'
|
3
|
+
require 'geokit'
|
4
|
+
|
5
|
+
module Filmrolls
|
6
|
+
module XMLFormat
|
7
|
+
class Parser
|
8
|
+
def self.load(io)
|
9
|
+
doc = Nokogiri::XML(io)
|
10
|
+
doc.remove_namespaces!
|
11
|
+
{
|
12
|
+
cameras:
|
13
|
+
doc.xpath('/data/cameras/camera').map { |n| load_camera(n) },
|
14
|
+
lenses:
|
15
|
+
doc.xpath('/data/lenses/lens').map { |n| load_lens(n) },
|
16
|
+
accessories:
|
17
|
+
doc.xpath('/data/accessories/accessory').map { |n| load_accessory(n) },
|
18
|
+
rolls:
|
19
|
+
doc.xpath('/data/filmRolls/filmRoll').map { |n| load_filmroll(n) }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
private
|
25
|
+
|
26
|
+
def load_camera(node)
|
27
|
+
node.text
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_lens(node)
|
31
|
+
node.text
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_accessory(node)
|
35
|
+
node.text
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_filmroll(node)
|
39
|
+
{
|
40
|
+
id: node.at_xpath('./note').text,
|
41
|
+
film: node.at_xpath('./title').text,
|
42
|
+
speed: node.at_xpath('./speed').text.to_i,
|
43
|
+
camera: node.at_xpath('./camera').text,
|
44
|
+
load: DateTime.iso8601(node.at_xpath('./load').text),
|
45
|
+
unload: DateTime.iso8601(node.at_xpath('./unload').text),
|
46
|
+
frames: node.xpath('./frames/frame').map { |n| load_frame(n) }
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_frame(node)
|
51
|
+
{
|
52
|
+
lens: node.at_xpath('./lens').text,
|
53
|
+
aperture: node.at_xpath('./aperture').text.to_f,
|
54
|
+
shutter_speed: node.at_xpath('./shutterSpeed').text.to_r,
|
55
|
+
compensation: node.at_xpath('./compensation').text.to_f,
|
56
|
+
accessory: node.at_xpath('./accessory').text,
|
57
|
+
date: DateTime.iso8601(node.at_xpath('./date').text),
|
58
|
+
note: node.at_xpath('./note').text,
|
59
|
+
position: Geokit::LatLng.new(
|
60
|
+
node.at_xpath('./latitude').text.to_f,
|
61
|
+
node.at_xpath('./longitude').text.to_f
|
62
|
+
)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.load(io)
|
69
|
+
Parser.load(io)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/filmrolls.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
require 'filmrolls/version'
|
2
|
-
require 'filmrolls/
|
3
|
-
require 'filmrolls/
|
2
|
+
require 'filmrolls/negative'
|
3
|
+
require 'filmrolls/xmlformat'
|
data/man/filmrolls.1
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
.\" generated with Ronn/v0.7.3
|
2
|
+
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
3
|
+
.
|
4
|
+
.TH "FILMROLLS" "1" "May 2019" "" "filmrolls v0.1.0"
|
5
|
+
.
|
6
|
+
.SH "NAME"
|
7
|
+
\fBfilmrolls\fR \- Tag TIFF files with EXIF data extracted from XML data
|
8
|
+
.
|
9
|
+
.SH "SYNOPSIS"
|
10
|
+
\fBfilmrolls list\-rolls [\-\-rolls FILE]\fR \fBfilmrolls list\-frames [\-\-rolls FILE] \-\-id ID\fR \fBfilmrolls tag [\-\-rolls FILE] \-\-id ID FILES\.\.\.\fR
|
11
|
+
.
|
12
|
+
.SH "DESCRIPTION"
|
13
|
+
Reads XML data from the \fIFilm Rolls\fR iOS app and displays or applies this data as EXIF tags to a given set of files\.
|
14
|
+
.
|
15
|
+
.SH "OPTIONS"
|
16
|
+
.
|
17
|
+
.TP
|
18
|
+
\fB\-r\fR, \fB\-\-rolls\fR
|
19
|
+
Input XML file\. If this argument is not present, XML is read from standard input\.
|
20
|
+
.
|
21
|
+
.TP
|
22
|
+
\fB\-i\fR, \fB\-\-id\fR
|
23
|
+
A specific film roll to work with, keyed by the \fINotes\fR field of the \fIFilm Rolls\fR content\.
|
24
|
+
.
|
25
|
+
.TP
|
26
|
+
\fB\-n\fR, \fB\-\-dry\-run\fR
|
27
|
+
Don\'t actually modify any files\.
|
28
|
+
.
|
29
|
+
.SH "COMMANDS"
|
30
|
+
.
|
31
|
+
.TP
|
32
|
+
\fBlist\-rolls\fR
|
33
|
+
List all film rolls in the input XML, along with some common metadata\.
|
34
|
+
.
|
35
|
+
.TP
|
36
|
+
\fBlist\-frames\fR
|
37
|
+
List all frames of the given roll in the input XML, along with all the relevant metadata\.
|
38
|
+
.
|
39
|
+
.TP
|
40
|
+
\fBtag\fR
|
41
|
+
Tag the given input files with EXIF data based on the given roll in the input XML\. All file formats supported by exiftool(1) are supported\. The number of files to tag must match the number of frames in the given roll\.
|
42
|
+
.
|
43
|
+
.IP
|
44
|
+
\fBNote:\fR No attempt to validate the file format of the target files will be made, and the command will exit after the first failure\. Use the \fB\-\-dry\-run\fR flag to verify the command\-line arguments before performing real actions\.
|
45
|
+
.
|
46
|
+
.SH "SEE ALSO"
|
47
|
+
exiftool(1)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<data xmlns="http://www.w3schools.com"
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
4
|
+
xsi:schemaLocation="http://www.w3schools.com export.xsd">
|
5
|
+
<cameras>
|
6
|
+
<camera>Yashica Electro 35 GT</camera>
|
7
|
+
<camera>Voigtländer Bessa R2M</camera>
|
8
|
+
</cameras>
|
9
|
+
<lenses>
|
10
|
+
<lens>Yashinon 45mm f/1.7</lens>
|
11
|
+
<lens>Color Skopar 35/2.5 Pancake II</lens>
|
12
|
+
</lenses>
|
13
|
+
<accessories>
|
14
|
+
</accessories>
|
15
|
+
<filmRolls>
|
16
|
+
<filmRoll>
|
17
|
+
<title>Ilford Delta 100</title>
|
18
|
+
<speed>100</speed>
|
19
|
+
<camera>Voigtländer Bessa R2M</camera>
|
20
|
+
<load>2016-03-28T15:16:36Z</load>
|
21
|
+
<unload>2016-05-21T14:13:15Z</unload>
|
22
|
+
<note>A0012</note>
|
23
|
+
<frames>
|
24
|
+
<frame>
|
25
|
+
<lens>Color Skopar 35/2.5 Pancake II</lens>
|
26
|
+
<aperture>5.6</aperture>
|
27
|
+
<shutterSpeed>1/500</shutterSpeed>
|
28
|
+
<compensation></compensation>
|
29
|
+
<accessory></accessory>
|
30
|
+
<number>1</number>
|
31
|
+
<date>2016-05-13T14:12:40Z</date>
|
32
|
+
<latitude>57.700767</latitude>
|
33
|
+
<longitude>11.953715</longitude>
|
34
|
+
<note></note>
|
35
|
+
</frame>
|
36
|
+
</frames>
|
37
|
+
</filmRoll>
|
38
|
+
</filmRolls>
|
39
|
+
</data>
|
Binary file
|
Binary file
|
data/test/test_helper.rb
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
if ENV['CI']
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require 'minitest/reporters'
|
6
|
-
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
7
|
-
end
|
1
|
+
require 'simplecov' if ENV['CI'] || ENV['COVERAGE']
|
2
|
+
|
3
|
+
require 'minitest/reporters'
|
4
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require 'minitest/autorun'
|
3
|
-
require 'filmrolls/
|
3
|
+
require 'filmrolls/xmlformat'
|
4
4
|
|
5
|
-
describe 'Filmrolls::
|
5
|
+
describe 'Filmrolls::XMLFormat.load' do
|
6
6
|
let(:data) do
|
7
|
-
@output ||= Filmrolls::
|
7
|
+
@output ||= Filmrolls::XMLFormat.load(File.read('test/data/filmrolls.xml'))
|
8
8
|
end
|
9
9
|
|
10
10
|
describe 'the parser output' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filmrolls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Sigurdhsson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mini_exiftool
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '11'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '11'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: nokogiri
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
47
|
+
version: '1.10'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
54
|
+
version: '1.10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: geokit
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,34 +108,62 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '12'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rdoc
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '6'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: bundler
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - "~>"
|
116
130
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
131
|
+
version: '2'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.14'
|
118
146
|
type: :development
|
119
147
|
prerelease: false
|
120
148
|
version_requirements: !ruby/object:Gem::Requirement
|
121
149
|
requirements:
|
122
150
|
- - "~>"
|
123
151
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
152
|
+
version: '0.14'
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: codacy-coverage
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
128
156
|
requirements:
|
129
157
|
- - "~>"
|
130
158
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
159
|
+
version: '2'
|
132
160
|
type: :development
|
133
161
|
prerelease: false
|
134
162
|
version_requirements: !ruby/object:Gem::Requirement
|
135
163
|
requirements:
|
136
164
|
- - "~>"
|
137
165
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
166
|
+
version: '2'
|
139
167
|
- !ruby/object:Gem::Dependency
|
140
168
|
name: minitest
|
141
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,14 +184,28 @@ dependencies:
|
|
156
184
|
requirements:
|
157
185
|
- - "~>"
|
158
186
|
- !ruby/object:Gem::Version
|
159
|
-
version: '1.
|
187
|
+
version: '1.3'
|
160
188
|
type: :development
|
161
189
|
prerelease: false
|
162
190
|
version_requirements: !ruby/object:Gem::Requirement
|
163
191
|
requirements:
|
164
192
|
- - "~>"
|
165
193
|
- !ruby/object:Gem::Version
|
166
|
-
version: '1.
|
194
|
+
version: '1.3'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: ronn
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0.7'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0.7'
|
167
209
|
description: |2
|
168
210
|
This is a utility designed to read the XML files used by the Film Rolls
|
169
211
|
iOS app, and enable batch EXIF tagging of scanned negatives in TIFF format
|
@@ -172,21 +214,41 @@ email: Sigurdhsson@gmail.com
|
|
172
214
|
executables:
|
173
215
|
- filmrolls
|
174
216
|
extensions: []
|
175
|
-
extra_rdoc_files:
|
217
|
+
extra_rdoc_files:
|
218
|
+
- README.md
|
219
|
+
- LICENSE.md
|
220
|
+
- bin/filmrolls
|
176
221
|
files:
|
177
222
|
- LICENSE.md
|
178
223
|
- README.md
|
224
|
+
- Rakefile
|
179
225
|
- bin/filmrolls
|
226
|
+
- filmrolls.gemspec
|
180
227
|
- lib/filmrolls.rb
|
228
|
+
- lib/filmrolls/cli.rb
|
229
|
+
- lib/filmrolls/exiftool.rb
|
230
|
+
- lib/filmrolls/negative.rb
|
231
|
+
- lib/filmrolls/version.rb
|
232
|
+
- lib/filmrolls/xmlformat.rb
|
233
|
+
- man/filmrolls.1
|
234
|
+
- test/data/filmrolls.xml
|
235
|
+
- test/data/with-exif.tiff
|
236
|
+
- test/data/without-exif.tiff
|
181
237
|
- test/test_exiftool.rb
|
182
|
-
- test/test_filmrolls.rb
|
183
238
|
- test/test_helper.rb
|
184
|
-
|
239
|
+
- test/test_xmlformat.rb
|
240
|
+
homepage: https://github.com/urdh/filmrolls
|
185
241
|
licenses:
|
186
242
|
- ISC
|
187
243
|
metadata: {}
|
188
244
|
post_install_message:
|
189
|
-
rdoc_options:
|
245
|
+
rdoc_options:
|
246
|
+
- "--line-numbers"
|
247
|
+
- "--inline-source"
|
248
|
+
- "--title"
|
249
|
+
- Film Rolls EXIF tagger
|
250
|
+
- "--main"
|
251
|
+
- README.md
|
190
252
|
require_paths:
|
191
253
|
- lib
|
192
254
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -200,9 +262,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
200
262
|
- !ruby/object:Gem::Version
|
201
263
|
version: '0'
|
202
264
|
requirements: []
|
203
|
-
|
204
|
-
rubygems_version: 2.6.11
|
265
|
+
rubygems_version: 3.0.3
|
205
266
|
signing_key:
|
206
267
|
specification_version: 4
|
207
268
|
summary: Tag TIFF files with EXIF data extracted from XML data
|
208
|
-
test_files:
|
269
|
+
test_files:
|
270
|
+
- test/test_exiftool.rb
|
271
|
+
- test/test_xmlformat.rb
|