-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexificator.rb
executable file
·208 lines (166 loc) · 5.38 KB
/
exificator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/env ruby
require 'thor'
require 'fileutils'
require File.expand_path('lib/exiftool.rb', __dir__)
require File.expand_path('lib/prompt.rb', __dir__)
class Exificator < Thor
PUNCTUATION_REGEX = /['"'`~;:{}\[\]()<>,\/\|!@#\$%^&*'.\?]/
class_option :start, aliases: '-s', default: './', desc: "Starting directory to look for images."
class_option :regex, aliases: '-r', default: '.*(jpe?g|png|tiff?)', desc: "The regular expression to match file names. Ensures .* starts the regex so it matches the path part of the file path."
class_option :path, aliases: '-p', desc: "Path to a specific file. This will take presedence over start and regex options."
class_option :depth, aliases: '-d', desc: "Depth of the directories to look in. By default this command is fully recursive."
class_option :verbose, aliases: '-v', type: :boolean, desc: "Verbose output", default: true
# class_option :dry_run, type: :boolean, desc: "Does not modify images"
desc 'preview', 'Open the found images in Preview.app'
def preview
open(images.flatten.join("' '"))
end
desc 'list', 'Output a list of images found'
def list
puts images.sort.join("\n")
end
option :tag, aliases: '-t', default: ''
desc 'details', 'Output EXIF data for image(s)'
def details
images.each do |image|
puts "\nDetails for \"#{image}\"" if verbose?
detail(image)
end
end
option :show, type: :boolean, desc: 'Show the file'
desc 'process', 'Add EXIF descriptions and rename file to match'
def process
images.each.with_index do |image,i|
puts "#{i+1}/#{images.size}: Processing #{image}"
exiftool = ExifTool.new(image)
descriptions = exiftool.descriptions
unless descriptions == ''
puts "Current descriptions: "
puts descriptions
end
open(image) if show
new_description = Prompt.prompt("Enter new description:")
next if new_description.empty?
exiftool.descriptions = new_description
renameWithDescription(image, new_description)
end
end
option :format, desc: 'Extension of the format to convert to (e.g. jpg | tiff | png | gif). See sips command for more details', required: true
desc 'convert', 'Convert images to a specific format'
def convert
if images.size == 0
puts "No images found."
return
end
unless Prompt.confirm?("Are you sure you want to convert #{images.size} images to #{options[:format]}? (y/n)")
puts "Aborting convert!"
return
end
images.each.with_index do |image,i|
outfile = File.join(
File.dirname(image),
File.basename(image, '.*')+".#{format}"
)
puts "#{i+1}/#{images.size}: Converting #{image}"
`sips -s format #{format} "#{image}" -s formatOptions 100 --out "#{outfile}"`
end
end
option :prefix, type: :string, desc: 'Prefix to use. Defaults to last modified date in YYYYMMDDHHMMSS_ format.'
desc 'rename', 'Renames in format <prefix>_<description>.<ext> if there is a description.'
def rename
if images.size == 0
puts "No images found."
return
end
unless Prompt.confirm?("Are you sure you want to rename #{images.size} images in the format of #{options[:prefix]||'YYYYMMDDHHMMSS'}_<description>? (y/n)")
puts "Aborting rename!"
return
end
images.each.with_index do |image,i|
prefix = options[:prefix] || modified_at(image).strftime('%Y%m%d%H%M%S');
ext = File.extname(image)
dir = File.dirname(image)
description = ExifTool.new(image).get('Description')
descName = (description.empty? ? '' : '_'+description_to_name(description))
newPath = File.join(dir,"#{prefix}#{descName}#{ext}")
puts "#{i+1}/#{images.size}: Renaming #{image} to #{newPath}"
FileUtils.move(image, newPath)
end
end
private
def description_to_name(description)
description.strip.gsub(PUNCTUATION_REGEX,'').gsub(/\s+/,'_').gsub(/\_+/,'_')
end
def renameWithDescription(path, description)
name = description_to_name(description)
return if name.empty?
ext = File.extname(path).downcase
new_file = File.join(File.dirname(path),"#{name}#{ext}")
while File.exists?(new_file)
new_file = new_file.gsub(/(\.(\d+))?#{ext}/) { ".#{$2.to_i+1}#{ext}"}
end
FileUtils.move(path,new_file)
end
def start_dir
@start_dir ||= File.expand_path(options[:start])
end
def regex
@regex ||= lambda do
rx = options[:regex]
rx = ".*#{rx}" unless rx.start_with?('.*')
return rx
end.call
end
def path
@path ||= options[:path]
end
def tag
@tag ||= options[:tag]
end
def show
@show ||= options[:show]
end
def format
@format ||= options[:format]
end
def verbose?
options.key?(:verbose)
end
# Returns array of image paths
def images
return @images if @images
if path
@images = (File.file?(path) ? [path] : [])
return @images
end
@images = find
puts "Found #{@images.size} images" if verbose?
@images
end
def find
command = %Q(find -E "#{start_dir}" -type f -iregex "#{regex}")
command += " -maxdepth #{options[:depth]}" if options.key?(:depth)
puts "Finding files with: #{command}" if verbose?
`#{command}`.split("\n")
end
def detail(image)
et = ExifTool.new(image)
if options[:tag].strip.empty?
descriptions = ExifTool.new(image).descriptions
puts descriptions.empty? ? 'No descriptions were found' : descriptions
else
puts et.get(options[:tag], false)
end
end
def open(image_list)
`open -a Preview '#{image_list}'`
end
def modified_at(path)
return File.mtime(path)
end
end
begin
Exificator.start(ARGV)
rescue SystemExit, Interrupt
puts "\nGoodbye!"
end