phtools 0.8.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/TODO.md +4 -3
- data/exe/phfixdto +42 -0
- data/exe/phfixfmd +1 -1
- data/exe/phgettags +39 -0
- data/exe/phls +9 -5
- data/exe/phrename +1 -1
- data/lib/phfixdto.rb +62 -0
- data/lib/phfixfmd.rb +1 -2
- data/lib/phgettags.rb +58 -0
- data/lib/phtools/exif_tagger/error.rb +11 -0
- data/lib/phtools/exif_tagger/tag_collection.rb +103 -0
- data/lib/phtools/exif_tagger/tag_writer.rb +79 -0
- data/lib/phtools/exif_tagger/tags/_tag.rb +111 -0
- data/lib/phtools/exif_tagger/tags/_tag_date.rb +45 -0
- data/lib/phtools/exif_tagger/tags/city.rb +39 -0
- data/lib/phtools/exif_tagger/tags/coded_character_set.rb +38 -0
- data/lib/phtools/exif_tagger/tags/collections.rb +45 -0
- data/lib/phtools/exif_tagger/tags/copyright.rb +39 -0
- data/lib/phtools/exif_tagger/tags/country.rb +40 -0
- data/lib/phtools/exif_tagger/tags/country_code.rb +38 -0
- data/lib/phtools/exif_tagger/tags/create_date.rb +32 -0
- data/lib/phtools/exif_tagger/tags/creator.rb +44 -0
- data/lib/phtools/exif_tagger/tags/date_time_original.rb +32 -0
- data/lib/phtools/exif_tagger/tags/gps_created.rb +78 -0
- data/lib/phtools/exif_tagger/tags/image_unique_id.rb +47 -0
- data/lib/phtools/exif_tagger/tags/keywords.rb +49 -0
- data/lib/phtools/exif_tagger/tags/location.rb +40 -0
- data/lib/phtools/exif_tagger/tags/modify_date.rb +26 -0
- data/lib/phtools/exif_tagger/tags/state.rb +40 -0
- data/lib/phtools/exif_tagger/tags/world_region.rb +38 -0
- data/lib/phtools/exif_tagger/tags.rb +11 -0
- data/lib/phtools/exif_tagger.rb +8 -0
- data/lib/phtools/version.rb +1 -1
- data/lib/phtools.rb +4 -4
- metadata +31 -4
- data/lib/phfixdate.rb +0 -13
- data/lib/phmtags.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11a9513d5b974ef96b53c7d9f7b4a30299fecb37
|
4
|
+
data.tar.gz: d5156efe4cb97fd0c352168c9a8be41db697eb85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95b747d7110174f61b325c8cdd58ff6b79d21ca3ae494ea96c9bc0bdc1c6c43ba8621d9d81f0d297ee5b67a74d9486d38ea5405f5df4c1723bc5c2b26b14948b
|
7
|
+
data.tar.gz: 15c18ab1c5cc87c2e98f8008b0768452efc2db051daae2054ed01fcfc3d91785055c89a58643b7cd82de89bb07eb30abf0043b991a29d7fc278e7d0801db3f00
|
data/TODO.md
CHANGED
@@ -23,14 +23,15 @@
|
|
23
23
|
- [ ] phrename: make new usage mode: `phrename -t TAG`. Useful if user wants to re-set date-time using TAG keeping author-nickname unchanged
|
24
24
|
|
25
25
|
### phgettags
|
26
|
-
- [
|
26
|
+
- [x] create phgettags (based on ftmtags)
|
27
27
|
|
28
28
|
### phfixfmd
|
29
29
|
- [x] create phfixfmd - fix FileModifyDate to get equal to date-time-in-the-name
|
30
30
|
|
31
31
|
### phfixdto
|
32
|
-
- [
|
33
|
-
- [
|
32
|
+
- [x] create phfixdto - fix MWG:DateTimeOriginal (DTO) := date-time-in-name
|
33
|
+
- [x] phfixdto: make -N --no_run option = no running exiftool script, only preparation
|
34
|
+
- [ ] phfixdto: smartly check if CreateDate set (including MWG subfields). If any - then update CreateDate
|
34
35
|
|
35
36
|
### phevent
|
36
37
|
- [ ] create phevent (based on ftevent)
|
data/exe/phfixdto
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
module PhTools
|
6
|
+
tool_name = File.basename(__FILE__)
|
7
|
+
require "#{tool_name}"
|
8
|
+
|
9
|
+
file_type = FILE_TYPE_IMAGE + FILE_TYPE_VIDEO + FILE_TYPE_AUDIO
|
10
|
+
usage = <<DOCOPT
|
11
|
+
***************************************************
|
12
|
+
phtools - *Keep Your Photos In Order* (c) ANB
|
13
|
+
***************************************************
|
14
|
+
#{tool_name} updates the input file's DateTimeOriginal (CreateDate) tags with
|
15
|
+
the date-time encoded in the filename (date-time-in-the-name). The file should
|
16
|
+
be renamed to phtools Standard Name before using this command (use phrename for
|
17
|
+
this).
|
18
|
+
This program uses external utility ExifTool created by Phil Harvey
|
19
|
+
(http://www.sno.phy.queensu.ca/~phil/exiftool/).
|
20
|
+
#{tool_name} acts as a 'sink' program meaning it expects the input files
|
21
|
+
to be passed to STDIN, does its job on the files and produces no useful
|
22
|
+
(for other pipe-optimized programs) output.
|
23
|
+
In other words this command is intended to be used with other programs
|
24
|
+
connected via input pipes as a last command in the pipe chain, e.g.:
|
25
|
+
phls | phrename - a anb | #{tool_name}
|
26
|
+
|
27
|
+
Usage:
|
28
|
+
#{tool_name} [-N] [-D]
|
29
|
+
#{tool_name} -h | --help
|
30
|
+
#{tool_name} -v | --version
|
31
|
+
|
32
|
+
Options:
|
33
|
+
-N --no_run Dry-run mode means no run exiftool command, only generate script
|
34
|
+
file 'exif_tagger_dto.txt'. User can check the script and
|
35
|
+
manually run the command: `exiftool -@ exif_tagger_dto.txt`
|
36
|
+
-D --debug Turn on debugging (verbose) mode
|
37
|
+
-h --help Show this screen.
|
38
|
+
-v --version Show version.
|
39
|
+
DOCOPT
|
40
|
+
|
41
|
+
PhTools.const_get(tool_name.capitalize).new(usage, file_type).run!
|
42
|
+
end
|
data/exe/phfixfmd
CHANGED
@@ -11,7 +11,7 @@ module PhTools
|
|
11
11
|
***************************************************
|
12
12
|
phtools - *Keep Your Photos In Order* (c) ANB
|
13
13
|
***************************************************
|
14
|
-
#{tool_name} changes an input file modify-date according to the date encoded
|
14
|
+
#{tool_name} changes an input file's modify-date according to the date encoded
|
15
15
|
in the filename (date-time-in-the-name). Therefore the file should be renamed
|
16
16
|
to phtools Standard Name before using this command (use phrename for this).
|
17
17
|
#{tool_name} acts as a 'filter' program meaning it expects the input files
|
data/exe/phgettags
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
module PhTools
|
6
|
+
tool_name = File.basename(__FILE__)
|
7
|
+
require "#{tool_name}"
|
8
|
+
|
9
|
+
file_type = FILE_TYPE_IMAGE + FILE_TYPE_VIDEO + FILE_TYPE_AUDIO
|
10
|
+
usage = <<DOCOPT
|
11
|
+
***************************************************
|
12
|
+
phtools - *Keep Your Photos In Order* (c) ANB
|
13
|
+
***************************************************
|
14
|
+
#{tool_name} extracts the list of tags stored inside the given file.
|
15
|
+
|
16
|
+
#{tool_name} acts as a 'sink' program meaning it expects the input files
|
17
|
+
to be passed to STDIN, does its job on the input files and produces no useful
|
18
|
+
(for other pipe-optimized programs) output.
|
19
|
+
In other words this command is intended to be used with other programs
|
20
|
+
connected via input pipes as a last command in the pipe chain, e.g.:
|
21
|
+
phls | #{tool_name}
|
22
|
+
|
23
|
+
This program uses external utility ExifTool created by Phil Harvey
|
24
|
+
(http://www.sno.phy.queensu.ca/~phil/exiftool/).
|
25
|
+
|
26
|
+
Usage:
|
27
|
+
#{tool_name} [-f] [-D]
|
28
|
+
#{tool_name} -h | --help
|
29
|
+
#{tool_name} -v | --version
|
30
|
+
|
31
|
+
Options:
|
32
|
+
-f --full_dump Print all tags
|
33
|
+
-D --debug Turn on debugging (verbose) mode
|
34
|
+
-h --help Show this screen.
|
35
|
+
-v --version Show version.
|
36
|
+
DOCOPT
|
37
|
+
|
38
|
+
PhTools.const_get(tool_name.capitalize).new(usage, file_type).run!
|
39
|
+
end
|
data/exe/phls
CHANGED
@@ -11,16 +11,20 @@ module PhTools
|
|
11
11
|
***************************************************
|
12
12
|
phtools - *Keep Your Photos In Order* (c) ANB
|
13
13
|
***************************************************
|
14
|
-
#{tool_name} scans given directories and generates list of files to standard
|
15
|
-
In short it acts like a smart 'ls' command (or 'dir' in Windows).
|
14
|
+
#{tool_name} scans given directories and generates list of files to standard
|
15
|
+
output. In short it acts like a smart 'ls' command (or 'dir' in Windows).
|
16
16
|
Set DIRs to be scanned as a parameters. If no DIRs are set - current dir (.)
|
17
17
|
will be scanned. Set FILEMASKs as a parameters - and only files matching the
|
18
|
-
masks will be processed. If no FILEMASK is set *.* will be used by-default.
|
18
|
+
masks will be processed. If no FILEMASK is set '*.*' will be used by-default.
|
19
19
|
To avoid unnessesary mask extraction by OS - put it in ''.
|
20
20
|
Note, #{tool_name} works only with phtools-friendly file types: #{file_type * ','}
|
21
|
-
#{tool_name} is a starting point for all other phtools to be connected via pipes.
|
22
21
|
|
23
|
-
|
22
|
+
#{tool_name} acts as a 'source' program meaning it does not require any input
|
23
|
+
from STDIN, it generates list of files based on input parameters and send it
|
24
|
+
to STDOUT.
|
25
|
+
In other words this command is intended to be used with other programs
|
26
|
+
connected via pipes as a 1st command in the pipe chain, e.g.:
|
27
|
+
#{tool_name} abc '*aaa*' | phrename -a anb => scans 'abc' folder and
|
24
28
|
sends all found phtools friendly files filtered with *aaa* to phrename command.
|
25
29
|
|
26
30
|
Usage:
|
data/exe/phrename
CHANGED
@@ -54,7 +54,7 @@ Options:
|
|
54
54
|
-t TAG --tag_date=TAG Force program to use TAG as a Date-Time creation
|
55
55
|
info instead of standard phtools tags.
|
56
56
|
You can retreive all existing tags using command:
|
57
|
-
`phls filename|
|
57
|
+
`phls filename|phgettags -f` OR
|
58
58
|
`exiftool -s filename`
|
59
59
|
|
60
60
|
-s DELTA --shift_time=DELTA DELTA (in seconds) will be added to Date-Time value
|
data/lib/phfixdto.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
require 'mini_exiftool'
|
6
|
+
require 'phtools/runner'
|
7
|
+
require 'phtools/exif_tagger'
|
8
|
+
|
9
|
+
module PhTools
|
10
|
+
# phfixdto logic
|
11
|
+
class Phfixdto < Runner
|
12
|
+
def self.about
|
13
|
+
'fixes DateTimeOriginal tag to be equal to date-time-in-the-name'
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def process_before
|
19
|
+
@writer = ExifTagger::TagWriter.new(
|
20
|
+
script_name: 'exif_tagger_dto.txt',
|
21
|
+
memo: "#{DateTime.now}: Script generated by #{@tool_name} version #{PhTools::VERSION})",
|
22
|
+
output: STDERR)
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_file(phfile)
|
26
|
+
fail PhTools::Error, 'wrong date-time-in-the-name' unless phfile.date_time_ok?
|
27
|
+
|
28
|
+
STDERR.puts %( ...#{phfile}...)
|
29
|
+
begin
|
30
|
+
@tags_original = MiniExiftool.new(phfile.filename,
|
31
|
+
replace_invalid_chars: true,
|
32
|
+
composite: true,
|
33
|
+
timestamps: DateTime)
|
34
|
+
rescue
|
35
|
+
raise PhTools::Error, "EXIF tags reading - #{e.message}"
|
36
|
+
end
|
37
|
+
|
38
|
+
@tags_to_write = ExifTagger::TagCollection.new
|
39
|
+
|
40
|
+
@tags_to_write[:date_time_original] = phfile.date_time_to_time
|
41
|
+
@tags_to_write.item(:date_time_original).force_write = true
|
42
|
+
|
43
|
+
unless @tags_original[:create_date].nil?
|
44
|
+
@tags_to_write[:create_date] = phfile.date_time_to_time
|
45
|
+
@tags_to_write.item(:create_date).force_write = true
|
46
|
+
end
|
47
|
+
@tags_to_write.check_for_warnings(original_values: @tags_original)
|
48
|
+
fail PhTools::Error, @tags_to_write.error_message unless @tags_to_write.valid?
|
49
|
+
|
50
|
+
@writer.add_to_script(phfile: phfile, tags: @tags_to_write)
|
51
|
+
|
52
|
+
return ''
|
53
|
+
|
54
|
+
rescue => e
|
55
|
+
raise PhTools::Error, e.message
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_after
|
59
|
+
@writer.run! unless @options_cli['--no_run']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/phfixfmd.rb
CHANGED
@@ -14,8 +14,7 @@ module PhTools
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def process_file(phfile)
|
17
|
-
fail PhTools::Error, 'wrong date-time
|
18
|
-
phfile.date_time_ok?
|
17
|
+
fail PhTools::Error, 'wrong date-time-in-the-name' unless phfile.date_time_ok?
|
19
18
|
begin
|
20
19
|
File.utime(Time.now, phfile.date_time_to_time, phfile.filename)
|
21
20
|
rescue
|
data/lib/phgettags.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
require 'phtools/runner'
|
6
|
+
require 'mini_exiftool'
|
7
|
+
require 'phtools/exif_tagger'
|
8
|
+
|
9
|
+
module PhTools
|
10
|
+
class Phgettags < Runner
|
11
|
+
|
12
|
+
def self.about
|
13
|
+
"extracts the tags stored inside the file"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def process_file(phfile)
|
19
|
+
begin
|
20
|
+
tags = MiniExiftool.new(phfile.filename,
|
21
|
+
numerical: false,
|
22
|
+
coord_format: '%d %d %.4f',
|
23
|
+
replace_invalid_chars: true,
|
24
|
+
composite: true,
|
25
|
+
timestamps: DateTime)
|
26
|
+
rescue
|
27
|
+
raise PhTools::Error, "EXIF tags reading - #{e.message}"
|
28
|
+
end
|
29
|
+
|
30
|
+
puts "******** FILE #{phfile} ********"
|
31
|
+
|
32
|
+
if not @options_cli['--full_dump']
|
33
|
+
puts format('%-29s %s', "FileModifyDate", "#{tags.FileModifyDate}")
|
34
|
+
ExifTagger::TAGS_SUPPORTED.each do |tag|
|
35
|
+
puts "#{tag.to_s.camelize}"
|
36
|
+
ExifTagger::Tag.const_get(tag.to_s.camelize).const_get('EXIFTOOL_TAGS').each do |t|
|
37
|
+
v = tags[t]
|
38
|
+
v = 'EMPTY' if v.respond_to?(:empty?) && v.empty?
|
39
|
+
if v.nil?
|
40
|
+
puts format(' %-27s %s', t, 'NIL')
|
41
|
+
else
|
42
|
+
puts format(' %-27s %-10s %s', t, "(#{v.class})", v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else #full_dump
|
47
|
+
tags.to_hash.each do |t,v|
|
48
|
+
puts format('%-29s %-10s %s', t, "(#{v.class})", v)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return ''
|
53
|
+
|
54
|
+
rescue => e
|
55
|
+
raise PhTools::Error, "exif tags operating: #{e.message}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
module ExifTagger
|
6
|
+
class Error < StandardError; end
|
7
|
+
class UnknownTag < Error; end
|
8
|
+
class WriteTag < Error; end
|
9
|
+
class CreatorsDirectory < Error; end
|
10
|
+
class PlacesDirectory < Error; end
|
11
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
module ExifTagger
|
6
|
+
# EXIF tags collection
|
7
|
+
class TagCollection
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(init_values = nil)
|
11
|
+
@collection = []
|
12
|
+
return if init_values.nil?
|
13
|
+
case
|
14
|
+
when init_values.is_a?(Hash)
|
15
|
+
init_values.each do |k, v|
|
16
|
+
self[k] = v
|
17
|
+
end
|
18
|
+
when init_values.is_a?(ExifTagger::TagCollection)
|
19
|
+
init_values.each do |item|
|
20
|
+
self[item.tag_id] = item.value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@collection.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
str = ''
|
31
|
+
@collection.each { |i| str << i.to_s }
|
32
|
+
str
|
33
|
+
end
|
34
|
+
|
35
|
+
def []=(tag, value)
|
36
|
+
return if value.nil?
|
37
|
+
delete(tag)
|
38
|
+
@collection << produce_tag(tag, value.dup)
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](tag)
|
42
|
+
ind = @collection.find_index(tag)
|
43
|
+
ind.nil? ? nil : @collection[ind].value
|
44
|
+
end
|
45
|
+
|
46
|
+
def item(tag)
|
47
|
+
ind = @collection.find_index(tag)
|
48
|
+
ind.nil? ? nil : @collection[ind]
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete(tag)
|
52
|
+
@collection.delete(tag)
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid?
|
56
|
+
ok = true
|
57
|
+
@collection.each { |i| ok &&= i.valid? }
|
58
|
+
ok
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_warnings?
|
62
|
+
warn = false
|
63
|
+
@collection.each { |i| warn ||= i.warnings.empty? }
|
64
|
+
warn
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_for_warnings(original_values: {})
|
68
|
+
@collection.each do |i|
|
69
|
+
i.check_for_warnings(original_values: original_values)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def error_message
|
74
|
+
str = ''
|
75
|
+
unless valid?
|
76
|
+
str = "Tags are NOT VALID:\n"
|
77
|
+
@collection.each do |item|
|
78
|
+
item.errors.each { |e| str << ' ' + e + "\n" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
str
|
82
|
+
end
|
83
|
+
|
84
|
+
def warning_message
|
85
|
+
str = ''
|
86
|
+
if with_warnings?
|
87
|
+
@collection.each do |item|
|
88
|
+
item.warnings.each { |e| str << ' WARNING: ' + e + "\n" }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
str
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def produce_tag(tag, value)
|
97
|
+
tag_class = ExifTagger::Tag.const_get(tag.to_s.camelize)
|
98
|
+
tag_class.new(value)
|
99
|
+
rescue => e
|
100
|
+
raise ExifTagger::UnknownTag, "Tag #{tag} - #{e.message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
# Exif tagger
|
8
|
+
module ExifTagger
|
9
|
+
# batch EXIF tags setter
|
10
|
+
class TagWriter
|
11
|
+
DEFAULT_OPTIONS = %w(-v0 -FileModifyDate<DateTimeOriginal -overwrite_original -ignoreMinorErrors)
|
12
|
+
attr_reader :script_name, :added_files_count
|
13
|
+
|
14
|
+
def initialize(script_name: 'exif_tagger.txt',
|
15
|
+
memo: 'Generated by phtools',
|
16
|
+
output: STDOUT, err: STDERR)
|
17
|
+
@script_name = script_name
|
18
|
+
create_script(memo)
|
19
|
+
@added_files_count = 0
|
20
|
+
@output = output
|
21
|
+
@err = err
|
22
|
+
@output.puts "*** Preparing exiftool script '#{@script_name}' ..."
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_to_script(phfile: '', tags: {}, options: DEFAULT_OPTIONS)
|
26
|
+
@script.puts "# **(#{@added_files_count + 1})** Processing file: #{phfile} *****"
|
27
|
+
# tags
|
28
|
+
tags.each do |k|
|
29
|
+
@script.puts tags.item(k).to_write_script
|
30
|
+
end
|
31
|
+
# file to be altered
|
32
|
+
@script.puts %Q{#{phfile}}
|
33
|
+
# General options
|
34
|
+
options.each { |o| @script.puts "#{o}" }
|
35
|
+
@script.puts %Q{-execute}
|
36
|
+
@script.puts
|
37
|
+
@added_files_count += 1
|
38
|
+
|
39
|
+
rescue => e
|
40
|
+
raise WriteTag, "adding item to exiftool script - #{e.message}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def close_script
|
44
|
+
@script.close
|
45
|
+
@output.puts "*** Finished preparation of the script '#{script_name}'"
|
46
|
+
rescue => e
|
47
|
+
raise WriteTag, "closing exiftool script - #{e.message}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def command
|
51
|
+
"exiftool -@ #{@script_name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def run!
|
55
|
+
close_script
|
56
|
+
if @added_files_count > 0
|
57
|
+
@output.puts "*** Running #{command} ..."
|
58
|
+
ok = system(command, out: @output, err: @err)
|
59
|
+
fail if ok.nil?
|
60
|
+
@output.puts "*** Finished #{command}"
|
61
|
+
else
|
62
|
+
@output.puts "*** Nothing to update, skip running #{command} ..."
|
63
|
+
end
|
64
|
+
rescue
|
65
|
+
raise WriteTag, "running #{command}"
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def create_script(memo)
|
71
|
+
@script = File.open(@script_name, 'w+:utf-8')
|
72
|
+
@script.puts '# exiftool script for batch tag operations'
|
73
|
+
@script.puts "# #{memo}"
|
74
|
+
@script.puts "# usage: exiftool -@ #{@script_name}"
|
75
|
+
rescue => e
|
76
|
+
raise ExifTagger::WriteTag, "creating exiftool script - #{e.message}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
|
7
|
+
module ExifTagger
|
8
|
+
module Tag
|
9
|
+
# Parent class for all tags
|
10
|
+
class Tag
|
11
|
+
include Comparable
|
12
|
+
|
13
|
+
EXIFTOOL_TAGS = []
|
14
|
+
attr_reader :errors, :value, :value_invalid, :warnings, :write_script_lines
|
15
|
+
attr_accessor :info, :force_write
|
16
|
+
|
17
|
+
def initialize(value_norm = '')
|
18
|
+
@value = value_norm
|
19
|
+
@errors = []
|
20
|
+
@value_invalid = []
|
21
|
+
@warnings = []
|
22
|
+
@write_script_lines = []
|
23
|
+
@info = ''
|
24
|
+
@force_write = false
|
25
|
+
validate
|
26
|
+
@value.freeze
|
27
|
+
@errors.freeze
|
28
|
+
@value_invalid.freeze
|
29
|
+
@warnings.freeze
|
30
|
+
end
|
31
|
+
|
32
|
+
def tag_id
|
33
|
+
self.class.to_s.demodulize.underscore.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def tag_name
|
37
|
+
self.class.to_s.demodulize
|
38
|
+
end
|
39
|
+
|
40
|
+
def <=>(other)
|
41
|
+
if other.respond_to? :tag_id
|
42
|
+
tag_id <=> other.tag_id
|
43
|
+
else
|
44
|
+
tag_id <=> other.to_s.to_sym
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"#{tag_id} = #{@value}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid?
|
53
|
+
@errors.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_for_warnings(original_values: {})
|
57
|
+
@warnings = []
|
58
|
+
self.class::EXIFTOOL_TAGS.each do |tag|
|
59
|
+
v = original_values[tag]
|
60
|
+
unless v.nil?
|
61
|
+
case
|
62
|
+
when v.kind_of?(String)
|
63
|
+
@warnings << "#{tag_name} has original value: #{tag}='#{v}'" unless v.empty?
|
64
|
+
when v.kind_of?(Array)
|
65
|
+
@warnings << "#{tag_name} has original value: #{tag}=#{v}" unless v.join.empty?
|
66
|
+
else
|
67
|
+
@warnings << "#{tag_name} has original value: #{tag}=#{v}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
@warnings.freeze
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_write_script
|
75
|
+
str = ''
|
76
|
+
generate_write_script_lines
|
77
|
+
unless @write_script_lines.empty?
|
78
|
+
str << print_info
|
79
|
+
str << print_warnings
|
80
|
+
str << print_lines
|
81
|
+
end
|
82
|
+
str
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def print_info
|
88
|
+
@info.empty? ? '' : "# INFO: #{@info}\n"
|
89
|
+
end
|
90
|
+
|
91
|
+
def print_warnings
|
92
|
+
str = ''
|
93
|
+
@warnings.each do |w|
|
94
|
+
str << "# WARNING: #{w}\n"
|
95
|
+
end
|
96
|
+
str
|
97
|
+
end
|
98
|
+
|
99
|
+
def print_lines
|
100
|
+
str = ''
|
101
|
+
@write_script_lines.each do |l|
|
102
|
+
unless @warnings.empty?
|
103
|
+
str << '# ' unless @force_write
|
104
|
+
end
|
105
|
+
str << "#{l}\n"
|
106
|
+
end
|
107
|
+
str
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
require_relative '_tag'
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
module ExifTagger
|
9
|
+
module Tag
|
10
|
+
class TagDate < Tag
|
11
|
+
MAX_BYTESIZE = 32 # no limit set in EXIF spec for Date
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def validate
|
16
|
+
case
|
17
|
+
when @value.kind_of?(String)
|
18
|
+
bsize = @value.bytesize
|
19
|
+
if bsize > MAX_BYTESIZE
|
20
|
+
@errors << %(#{tag_name}: '#{@value}' ) +
|
21
|
+
%(is #{bsize - MAX_BYTESIZE} bytes longer than allowed #{MAX_BYTESIZE})
|
22
|
+
@value_invalid << @value
|
23
|
+
@value = ''
|
24
|
+
end
|
25
|
+
when @value.kind_of?(DateTime)
|
26
|
+
if @value == DateTime.new(0)
|
27
|
+
@errors << %(#{tag_name}: '#{@value}' zero Date)
|
28
|
+
@value_invalid << @value
|
29
|
+
@value = ''
|
30
|
+
end
|
31
|
+
when @value.kind_of?(Time)
|
32
|
+
if @value == Time.new(0)
|
33
|
+
@errors << %(#{tag_name}: '#{@value}' zero Date)
|
34
|
+
@value_invalid << @value
|
35
|
+
@value = ''
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@errors << %(#{tag_name}: '#{@value}' is of wrong type (#{@value.class}))
|
39
|
+
@value_invalid << @value
|
40
|
+
@value = ''
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
require_relative '_tag'
|
6
|
+
|
7
|
+
module ExifTagger
|
8
|
+
module Tag
|
9
|
+
# MWG:City, String[0,32]
|
10
|
+
# = IPTC:City XMP-photoshop:City XMP-iptcExt:LocationShownCity
|
11
|
+
class City < Tag
|
12
|
+
MAX_BYTESIZE = 32
|
13
|
+
EXIFTOOL_TAGS = %w(City LocationShownCity)
|
14
|
+
|
15
|
+
def initialize(value_raw = '')
|
16
|
+
super(value_raw.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate
|
22
|
+
bsize = @value.bytesize
|
23
|
+
if bsize > MAX_BYTESIZE
|
24
|
+
@errors << %(#{tag_name}: '#{@value}' ) +
|
25
|
+
%(is #{bsize - MAX_BYTESIZE} bytes longer than allowed #{MAX_BYTESIZE})
|
26
|
+
@value_invalid << @value
|
27
|
+
@value = ''
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_write_script_lines
|
32
|
+
@write_script_lines = []
|
33
|
+
unless @value.empty?
|
34
|
+
@write_script_lines << %Q(-MWG:City=#{@value})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|