phtools 0.8.1 → 0.10.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.
- 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
|