phtools 0.10.0 → 0.11.1

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