phtools 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +43 -0
  3. data/.rubocop_todo.yml +373 -0
  4. data/Guardfile +6 -3
  5. data/History.md +4 -0
  6. data/TODO.md +2 -0
  7. data/exe/phbackup +2 -1
  8. data/exe/phfixdto +2 -1
  9. data/exe/phfixfmd +2 -1
  10. data/exe/phgettags +2 -1
  11. data/exe/phls +2 -1
  12. data/exe/phmove +6 -3
  13. data/exe/phrename +2 -1
  14. data/exe/phtools +3 -1
  15. data/lib/phbackup.rb +2 -2
  16. data/lib/phevent.rb +2 -1
  17. data/lib/phfixdto.rb +4 -3
  18. data/lib/phfixfmd.rb +2 -2
  19. data/lib/phgettags.rb +7 -7
  20. data/lib/phls.rb +5 -6
  21. data/lib/phmove.rb +12 -21
  22. data/lib/phrename.rb +7 -6
  23. data/lib/phtagset.rb +2 -1
  24. data/lib/phtools/error.rb +2 -1
  25. data/lib/phtools/exif_tagger/error.rb +1 -0
  26. data/lib/phtools/exif_tagger/tag_collection.rb +17 -14
  27. data/lib/phtools/exif_tagger/tag_writer.rb +8 -7
  28. data/lib/phtools/exif_tagger/tags/_tag.rb +92 -37
  29. data/lib/phtools/exif_tagger/tags/_tag_array_of_strings.rb +35 -0
  30. data/lib/phtools/exif_tagger/tags/_tag_date.rb +25 -27
  31. data/lib/phtools/exif_tagger/tags/_tag_hash_of_strings.rb +39 -0
  32. data/lib/phtools/exif_tagger/tags/_tag_string.rb +32 -0
  33. data/lib/phtools/exif_tagger/tags/city.rb +7 -21
  34. data/lib/phtools/exif_tagger/tags/coded_character_set.rb +6 -21
  35. data/lib/phtools/exif_tagger/tags/collections.rb +20 -26
  36. data/lib/phtools/exif_tagger/tags/copyright.rb +6 -22
  37. data/lib/phtools/exif_tagger/tags/country.rb +6 -23
  38. data/lib/phtools/exif_tagger/tags/country_code.rb +5 -21
  39. data/lib/phtools/exif_tagger/tags/create_date.rb +17 -13
  40. data/lib/phtools/exif_tagger/tags/creator.rb +14 -25
  41. data/lib/phtools/exif_tagger/tags/date_time_original.rb +18 -13
  42. data/lib/phtools/exif_tagger/tags/gps_created.rb +21 -44
  43. data/lib/phtools/exif_tagger/tags/image_unique_id.rb +12 -27
  44. data/lib/phtools/exif_tagger/tags/keywords.rb +15 -27
  45. data/lib/phtools/exif_tagger/tags/location.rb +6 -23
  46. data/lib/phtools/exif_tagger/tags/modify_date.rb +8 -8
  47. data/lib/phtools/exif_tagger/tags/state.rb +6 -23
  48. data/lib/phtools/exif_tagger/tags/world_region.rb +6 -22
  49. data/lib/phtools/exif_tagger/tags.rb +2 -1
  50. data/lib/phtools/exif_tagger.rb +1 -0
  51. data/lib/phtools/ph_file.rb +35 -14
  52. data/lib/phtools/runner.rb +4 -4
  53. data/lib/phtools/utils.rb +1 -0
  54. data/lib/phtools/version.rb +2 -1
  55. data/lib/phtools.rb +11 -9
  56. metadata +7 -2
@@ -1,46 +1,34 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
5
- require_relative '_tag'
6
+ require_relative '_tag_array_of_strings'
6
7
 
7
8
  module ExifTagger
8
9
  module Tag
9
10
  # MWG:Keywords, string[0,64]+, List of Strings
10
- # = IPTC:Keywords, XMP-dc:Subject
11
- class Keywords < Tag
11
+ # IPTC:Keywords, XMP-dc:Subject
12
+ # exiftool types:
13
+ # Keywords = Array ["aaa", "bbb"] OR String "aaa"
14
+ # Subject = Array ["aaa", "bbb"] OR String "aaa"
15
+
16
+ class Keywords < TagArrayOfStrings
12
17
  MAX_BYTESIZE = 64
13
- EXIFTOOL_TAGS = %w(Keywords Subject)
18
+ EXIFTOOL_TAGS = %w(Keywords Subject).freeze
14
19
 
15
- def initialize(value_raw = [])
16
- super(Array(value_raw).flatten.map { |i| i.to_s })
17
- end
20
+ private
18
21
 
19
- def check_for_warnings(original_values: {})
22
+ def validate_vs_previous
20
23
  @warnings = []
21
24
  @warnings.freeze
22
25
  end
23
26
 
24
- private
25
-
26
- def validate
27
- @value.each do |v|
28
- bsize = v.bytesize
29
- if bsize > MAX_BYTESIZE
30
- @errors << %{#{tag_name}: '#{v}' } +
31
- %{is #{bsize - MAX_BYTESIZE} bytes longer than allowed #{MAX_BYTESIZE}}
32
- @value_invalid << v
33
- end
34
- end
35
- @value = @value - @value_invalid
36
- end
37
-
38
27
  def generate_write_script_lines
39
- @write_script_lines = []
40
- unless @value.empty?
41
- @value.each do |o|
42
- @write_script_lines << %Q(-MWG:Keywords-=#{o})
43
- @write_script_lines << %Q(-MWG:Keywords+=#{o})
28
+ @value.each do |o|
29
+ unless Tag.empty?(o)
30
+ @write_script_lines << %(-MWG:Keywords-=#{o})
31
+ @write_script_lines << %(-MWG:Keywords+=#{o})
44
32
  end
45
33
  end
46
34
  end
@@ -1,39 +1,22 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
5
- require_relative '_tag'
6
+ require_relative '_tag_string'
6
7
 
7
8
  module ExifTagger
8
9
  module Tag
9
10
  # MWG:Location, String[0,32]
10
- # = IPTC:Sub-location + XMP-iptcCore:Location
11
- # + XMP-iptcExt:LocationShownSublocation
12
- class Location < Tag
11
+ # IPTC:Sub-location, XMP-iptcCore:Location, XMP-iptcExt:LocationShownSublocation
12
+ class Location < TagString
13
13
  MAX_BYTESIZE = 32
14
- EXIFTOOL_TAGS = %w(Sub-location Location LocationShownSublocation)
15
-
16
- def initialize(value_raw = [])
17
- super(value_raw.to_s)
18
- end
14
+ EXIFTOOL_TAGS = %w(Sub-location Location LocationShownSublocation).freeze
19
15
 
20
16
  private
21
17
 
22
- def validate
23
- bsize = @value.bytesize
24
- if bsize > MAX_BYTESIZE
25
- @errors << %{#{tag_name}: '#{@value}' } +
26
- %{is #{bsize - MAX_BYTESIZE} bytes longer than allowed #{MAX_BYTESIZE}}
27
- @value_invalid << @value
28
- @value = ''
29
- end
30
- end
31
-
32
18
  def generate_write_script_lines
33
- @write_script_lines = []
34
- unless @value.empty?
35
- @write_script_lines << %Q(-MWG:Location=#{@value})
36
- end
19
+ @write_script_lines << %(-MWG:Location=#{@value})
37
20
  end
38
21
  end
39
22
  end
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
@@ -8,18 +9,17 @@ module ExifTagger
8
9
  module Tag
9
10
  # -EXIF:ModifyDate=now
10
11
  class ModifyDate < TagDate
11
- EXIFTOOL_TAGS = %w(ModifyDate)
12
+ MAX_BYTESIZE = 32
13
+ EXIFTOOL_TAGS = %w(ModifyDate).freeze
12
14
 
13
15
  private
14
16
 
15
17
  def generate_write_script_lines
16
- @write_script_lines = []
17
- case
18
- when @value.kind_of?(String) && !@value.empty?
19
- @write_script_lines << %Q(-EXIF:ModifyDate=#{@value})
20
- when @value.kind_of?(DateTime) || @value.kind_of?(Time)
21
- @write_script_lines << %Q(-EXIF:ModifyDate=#{@value.strftime('%F %T')})
22
- end
18
+ @write_script_lines << if @value.is_a?(DateTime)
19
+ %(-EXIF:ModifyDate=#{@value.strftime('%F %T')})
20
+ else
21
+ %(-EXIF:ModifyDate=#{@value})
22
+ end
23
23
  end
24
24
  end
25
25
  end
@@ -1,39 +1,22 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
5
- require_relative '_tag'
6
+ require_relative '_tag_string'
6
7
 
7
8
  module ExifTagger
8
9
  module Tag
9
10
  # MWG:State, String[0,32]
10
- # = IPTC:Province-State + XMP-photoshop:State
11
- # + XMP-iptcExt:LocationShownProvinceState
12
- class State < Tag
11
+ # IPTC:Province-State, XMP-photoshop:State, XMP-iptcExt:LocationShownProvinceState
12
+ class State < TagString
13
13
  MAX_BYTESIZE = 32
14
- EXIFTOOL_TAGS = %w(Province-State State LocationShownProvinceState)
15
-
16
- def initialize(value_raw = [])
17
- super(value_raw.to_s)
18
- end
14
+ EXIFTOOL_TAGS = %w(Province-State State LocationShownProvinceState).freeze
19
15
 
20
16
  private
21
17
 
22
- def validate
23
- bsize = @value.bytesize
24
- if bsize > MAX_BYTESIZE
25
- @errors << %{#{tag_name}: '#{@value}' } +
26
- %{is #{bsize - MAX_BYTESIZE} bytes longer than allowed #{MAX_BYTESIZE}}
27
- @value_invalid << @value
28
- @value = ''
29
- end
30
- end
31
-
32
18
  def generate_write_script_lines
33
- @write_script_lines = []
34
- unless @value.empty?
35
- @write_script_lines << %Q(-MWG:State=#{@value})
36
- end
19
+ @write_script_lines << %(-MWG:State=#{@value})
37
20
  end
38
21
  end
39
22
  end
@@ -1,37 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
5
- require_relative '_tag'
6
+ require_relative '_tag_string'
6
7
 
7
8
  module ExifTagger
8
9
  module Tag
9
- # -XMP-iptcExt:LocationShownWorldRegion, String
10
- class WorldRegion < Tag
10
+ # XMP-iptcExt:LocationShownWorldRegion, String
11
+ class WorldRegion < TagString
11
12
  MAX_BYTESIZE = 64 # No limit in XMP spec
12
- EXIFTOOL_TAGS = %w(LocationShownWorldRegion)
13
-
14
- def initialize(value_raw = [])
15
- super(value_raw.to_s)
16
- end
13
+ EXIFTOOL_TAGS = %w(LocationShownWorldRegion).freeze
17
14
 
18
15
  private
19
16
 
20
- def validate
21
- bsize = @value.bytesize
22
- if bsize > MAX_BYTESIZE
23
- @errors << %{#{tag_name}: '#{@value}' } +
24
- %{is #{bsize - MAX_BYTESIZE} bytes longer than allowed #{MAX_BYTESIZE}}
25
- @value_invalid << @value
26
- @value = ''
27
- end
28
- end
29
-
30
17
  def generate_write_script_lines
31
- @write_script_lines = []
32
- unless @value.empty?
33
- @write_script_lines << %Q(-XMP-iptcExt:LocationShownWorldRegion=#{@value})
34
- end
18
+ @write_script_lines << %(-XMP:LocationShownWorldRegion=#{@value})
35
19
  end
36
20
  end
37
21
  end
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
@@ -7,5 +8,5 @@ Dir.glob(File.join(__dir__, 'tags', '*.rb')).each { |f| require_relative f }
7
8
 
8
9
  # ExifTagger helper methods
9
10
  module ExifTagger
10
- TAGS_SUPPORTED = (Tag.constants - [:Tag, :TagDate]).map { |i| i.to_s.underscore.to_sym }
11
+ TAGS_SUPPORTED = (Tag.constants - [:Tag, :TagArrayOfStrings, :TagHashOfStrings, :TagString, :TagDate]).map { |i| i.to_s.underscore.to_sym }
11
12
  end
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
@@ -6,14 +7,13 @@ require 'phtools/error'
6
7
  require 'date'
7
8
  require 'fileutils'
8
9
 
9
- # Foto tools
10
10
  module PhTools
11
11
  # media type constants
12
- FILE_TYPE_IMAGE_NORMAL = %w{jpg jpeg tif tiff png}
13
- FILE_TYPE_IMAGE_RAW = %w{orf arw dng}
12
+ FILE_TYPE_IMAGE_NORMAL = %w(jpg jpeg tif tiff png).freeze
13
+ FILE_TYPE_IMAGE_RAW = %w(orf arw dng).freeze
14
14
  FILE_TYPE_IMAGE = FILE_TYPE_IMAGE_NORMAL + FILE_TYPE_IMAGE_RAW
15
- FILE_TYPE_VIDEO = %w{avi mp4 mpg mts dv mov mkv m2t m2ts}
16
- FILE_TYPE_AUDIO = %w{wav}
15
+ FILE_TYPE_VIDEO = %w(avi mp4 mpg mts dv mov mkv m2t m2ts 3gp).freeze
16
+ FILE_TYPE_AUDIO = %w(wav).freeze
17
17
 
18
18
  # phtools file name operations
19
19
  class PhFile
@@ -26,7 +26,7 @@ module PhTools
26
26
  ZERO_DATE = DateTime.new(0)
27
27
 
28
28
  def self.validate_file!(filename, file_type)
29
- fail PhTools::Error, 'does not exist' unless
29
+ fail PhTools::Error, 'does not exist' unless
30
30
  filename && File.exist?(filename)
31
31
  fail PhTools::Error, 'not a file' if File.directory?(filename)
32
32
  fail PhTools::Error, 'no permission to write' unless
@@ -49,7 +49,7 @@ module PhTools
49
49
  [true, '']
50
50
  end
51
51
 
52
- attr_reader :filename, :dirname, :extname, :basename, :basename_part,
52
+ attr_reader :filename, :dirname, :extname, :type, :basename, :basename_part,
53
53
  :basename_clean, :date_time, :author
54
54
 
55
55
  def initialize(filename)
@@ -57,7 +57,7 @@ module PhTools
57
57
  end
58
58
 
59
59
  def to_s
60
- "#{@filename}"
60
+ @filename.to_s
61
61
  end
62
62
 
63
63
  def <=>(other)
@@ -67,13 +67,33 @@ module PhTools
67
67
  def basename_standard(basename_clean: @basename_clean,
68
68
  date_time: @date_time,
69
69
  author: @author)
70
- %Q{#{date_time.strftime('%Y%m%d-%H%M%S')}_#{(author.upcase + "XXXXXX")[0, NICKNAME_SIZE]} #{basename_clean}}
70
+ %(#{date_time.strftime('%Y%m%d-%H%M%S')}_#{(author.upcase + 'XXXXXX')[0, NICKNAME_SIZE]} #{basename_clean})
71
71
  end
72
72
 
73
73
  def basename_is_standard?
74
74
  @basename == basename_standard
75
75
  end
76
76
 
77
+ def image?
78
+ FILE_TYPE_IMAGE.include?(@type)
79
+ end
80
+
81
+ def image_normal?
82
+ FILE_TYPE_IMAGE_NORMAL.include?(@type)
83
+ end
84
+
85
+ def image_raw?
86
+ FILE_TYPE_IMAGE_RAW.include?(@type)
87
+ end
88
+
89
+ def video?
90
+ FILE_TYPE_VIDEO.include?(@type)
91
+ end
92
+
93
+ def audio?
94
+ FILE_TYPE_AUDIO.include?(@type)
95
+ end
96
+
77
97
  def standardize(dirname: @dirname, basename_clean: @basename_clean,
78
98
  extname: @extname, date_time: @date_time,
79
99
  author: @author)
@@ -100,7 +120,7 @@ module PhTools
100
120
  end
101
121
 
102
122
  def cleanse!(dirname: @dirname, basename_clean: @basename_clean,
103
- extname: @extname)
123
+ extname: @extname)
104
124
  filename = cleanse(dirname: dirname, basename_clean: basename_clean,
105
125
  extname: extname)
106
126
  set_state(filename)
@@ -119,8 +139,8 @@ module PhTools
119
139
  def date_time_to_time
120
140
  Time.new(@date_time.year, @date_time.month, @date_time.day,
121
141
  @date_time.hour, @date_time.min, @date_time.sec)
122
- # no use of @date_time.zone - assuming file's timezone always
123
- # equals to photografer's computer timezone
142
+ # no use of @date_time.zone - assuming file's timezone always
143
+ # equals to photografer's computer timezone
124
144
  end
125
145
 
126
146
  private
@@ -130,6 +150,7 @@ module PhTools
130
150
  @extname = File.extname(filename)
131
151
  @basename = File.basename(filename, @extname)
132
152
  @filename = File.join(@dirname, @basename + @extname)
153
+ @type = @extname.empty? ? '' : @extname.slice(1..-1).downcase
133
154
  parse_basename
134
155
  @basename_clean = @basename_part[:clean]
135
156
  @date_time = reveal_date_time
@@ -166,8 +187,8 @@ module PhTools
166
187
  end
167
188
 
168
189
  def parse_basename
169
- default = { prefix: '', clean: '', date: '',
170
- time: '', author: '', id: '', flags: '' }
190
+ default = { prefix: '', clean: '', date: '', time: '', author: '', id: '', flags: '' }
191
+
171
192
  case @basename
172
193
  # check YYYYmmdd-HHMMSS_AUT[ID]{FLAGS}cleanname
173
194
  when /^(?<prefix>(?<date>\d{8})-(?<time>\d{6})_(?<author>\w{#{NICKNAME_MIN_SIZE},#{NICKNAME_MAX_SIZE}})\[(?<id>.*)\]\{(?<flags>.*)\})(?<clean>.*)/
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
@@ -9,7 +10,6 @@ require 'phtools/error.rb'
9
10
  require 'phtools/ph_file.rb'
10
11
  require 'docopt'
11
12
 
12
- # Foto Tools
13
13
  module PhTools
14
14
  # Main class processing input stream
15
15
  class Runner
@@ -37,7 +37,7 @@ module PhTools
37
37
  exit 1
38
38
  ensure
39
39
  if PhTools.debug
40
- STDERR.puts "Runner Instance Variables: "
40
+ STDERR.puts 'Runner Instance Variables: '
41
41
  STDERR.puts context
42
42
  end
43
43
  end
@@ -84,8 +84,8 @@ module PhTools
84
84
  end
85
85
 
86
86
  def context
87
- self.instance_variables.map do |item|
88
- { item => self.instance_variable_get(item) }
87
+ instance_variables.map do |item|
88
+ { item => instance_variable_get(item) }
89
89
  end
90
90
  end
91
91
  end
data/lib/phtools/utils.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
 
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module PhTools
3
- VERSION = '0.10.0'.freeze
4
+ VERSION = '0.11.1'
4
5
  end
data/lib/phtools.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # encoding: UTF-8
3
4
  # (c) ANB Andrew Bizyaev
4
5
  require 'phtools/version'
@@ -18,17 +19,18 @@ module PhTools
18
19
  phtools v#{VERSION} is a bundle of small CLI tools for arranging, renaming, tagging
19
20
  of the photo and video files. Helps to keep your photo-video assets in order.
20
21
  Please run phtools in a terminal via CLI commands:
21
- phls\t(#{Phls::about}),
22
- phmove\t(#{Phmove::about}),
23
- phbackup\t(#{Phbackup::about}),
24
- phrename\t(#{Phrename::about}),
25
- phevent\t(#{Phevent::about}),
26
- phfixdto\t(#{Phfixdto::about}),
27
- phfixfmd\t(#{Phfixfmd::about}),
28
- phgettags\t(#{Phgettags::about}),
29
- phtagset\t(#{Phtagset::about}).
22
+ phls\t(#{Phls.about}),
23
+ phmove\t(#{Phmove.about}),
24
+ phbackup\t(#{Phbackup.about}),
25
+ phrename\t(#{Phrename.about}),
26
+ phevent\t(#{Phevent.about}),
27
+ phfixdto\t(#{Phfixdto.about}),
28
+ phfixfmd\t(#{Phfixfmd.about}),
29
+ phgettags\t(#{Phgettags.about}),
30
+ phtagset\t(#{Phtagset.about}).
30
31
  For more information run these commands with -h option.
31
32
  General info about phtools usage see at https://github.com/AndrewBiz/phtools.git
32
33
  TEXT
34
+ about
33
35
  end
34
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phtools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Bizyaev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-28 00:00:00.000000000 Z
11
+ date: 2017-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -252,6 +252,8 @@ extra_rdoc_files: []
252
252
  files:
253
253
  - ".gitignore"
254
254
  - ".rspec"
255
+ - ".rubocop.yml"
256
+ - ".rubocop_todo.yml"
255
257
  - ".travis.yml"
256
258
  - Gemfile
257
259
  - Guardfile
@@ -288,7 +290,10 @@ files:
288
290
  - lib/phtools/exif_tagger/tag_writer.rb
289
291
  - lib/phtools/exif_tagger/tags.rb
290
292
  - lib/phtools/exif_tagger/tags/_tag.rb
293
+ - lib/phtools/exif_tagger/tags/_tag_array_of_strings.rb
291
294
  - lib/phtools/exif_tagger/tags/_tag_date.rb
295
+ - lib/phtools/exif_tagger/tags/_tag_hash_of_strings.rb
296
+ - lib/phtools/exif_tagger/tags/_tag_string.rb
292
297
  - lib/phtools/exif_tagger/tags/city.rb
293
298
  - lib/phtools/exif_tagger/tags/coded_character_set.rb
294
299
  - lib/phtools/exif_tagger/tags/collections.rb