Skip to content

Commit 3cf0fea

Browse files
committed
[contrib/rename] Rename files including moving them into new or other
film rolls.
1 parent 8f6c2c8 commit 3cf0fea

File tree

1 file changed

+315
-0
lines changed

1 file changed

+315
-0
lines changed

contrib/rename.lua

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
--[[
2+
3+
rename.lua - rename image file(s)
4+
5+
Copyright (C) 2020, 2021 Bill Ferguson <[email protected]>.
6+
7+
This program is free software: you can redistribute it and/or modify
8+
it under the terms of the GNU General Public License as published by
9+
the Free Software Foundation; either version 3 of the License, or
10+
(at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
You should have received a copy of the GNU General Public License
18+
along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
]]
20+
--[[
21+
rename - rename an image file or files
22+
23+
This shortcut resets the GPS information to that contained within
24+
the image file. If no GPS info is in the image file, the GPS data
25+
is cleared.
26+
27+
USAGE
28+
* require this script from your luarc file or start it from script_manager
29+
* select an image or images
30+
* enter a renaming pattern
31+
* click the button to rename the files
32+
33+
BUGS, COMMENTS, SUGGESTIONS
34+
* Send to Bill Ferguson, [email protected]
35+
36+
CHANGES
37+
38+
TODO
39+
* Add pattern builder
40+
* Add new name preview
41+
]]
42+
43+
local dt = require "darktable"
44+
local du = require "lib/dtutils"
45+
local df = require "lib/dtutils.file"
46+
47+
du.check_min_api_version("7.0.0", "rename")
48+
49+
local gettext = dt.gettext
50+
51+
-- Tell gettext where to find the .mo file translating messages for a particular domain
52+
gettext.bindtextdomain("rename",dt.configuration.config_dir.."/lua/locale/")
53+
54+
local function _(msgid)
55+
return gettext.dgettext("rename", msgid)
56+
end
57+
58+
-- namespace variable
59+
local rename = {
60+
presets = {},
61+
substitutes = {},
62+
placeholders = {"ROLL_NAME","FILE_FOLDER","FILE_NAME","FILE_EXTENSION","ID","VERSION","SEQUENCE","YEAR","MONTH","DAY",
63+
"HOUR","MINUTE","SECOND","EXIF_YEAR","EXIF_MONTH","EXIF_DAY","EXIF_HOUR","EXIF_MINUTE","EXIF_SECOND",
64+
"STARS","LABELS","MAKER","MODEL","TITLE","CREATOR","PUBLISHER","RIGHTS","USERNAME","PICTURES_FOLDER",
65+
"HOME","DESKTOP","EXIF_ISO","EXIF_EXPOSURE","EXIF_EXPOSURE_BIAS","EXIF_APERTURE","EXIF_FOCUS_DISTANCE",
66+
"EXIF_FOCAL_LENGTH","LONGITUDE","LATITUDE","ELEVATION","LENS","DESCRIPTION","EXIF_CROP"},
67+
widgets = {},
68+
}
69+
rename.module_installed = false
70+
rename.event_registered = false
71+
72+
-- script_manager integration
73+
local script_data = {}
74+
75+
script_data.destroy = nil -- function to destory the script
76+
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
77+
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
78+
79+
80+
-- - - - - - - - - - - - - - - - - - - - - - - -
81+
-- C O N S T A N T S
82+
-- - - - - - - - - - - - - - - - - - - - - - - -
83+
84+
local MODULE_NAME = "rename"
85+
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
86+
local USER = os.getenv("USERNAME")
87+
local HOME = os.getenv(dt.configuration.running_os == "windows" and "HOMEPATH" or "HOME")
88+
local PICTURES = HOME .. PS .. dt.configuration.running_os == "windows" and "My Pictures" or "Pictures"
89+
local DESKTOP = HOME .. PS .. "Desktop"
90+
91+
-- - - - - - - - - - - - - - - - - - - - - - - -
92+
-- F U N C T I O N S
93+
-- - - - - - - - - - - - - - - - - - - - - - - -
94+
95+
local function build_substitution_list(image, sequence, datetime, username, pic_folder, home, desktop)
96+
-- build the argument substitution list from each image
97+
-- local datetime = os.date("*t")
98+
local colorlabels = {}
99+
if image.red then table.insert(colorlabels, "red") end
100+
if image.yellow then table.insert(colorlabels, "yellow") end
101+
if image.green then table.insert(colorlabels, "green") end
102+
if image.blue then table.insert(colorlabels, "blue") end
103+
if image.purple then table.insert(colorlabels, "purple") end
104+
local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, ",")
105+
local eyear,emon,eday,ehour,emin,esec = string.match(image.exif_datetime_taken, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d-)$")
106+
local replacements = {image.film,image.path,df.get_filename(image.filename),string.upper(df.get_filetype(image.filename)),image.id,image.duplicate_index,
107+
sequence,datetime.year,string.format("%02d", datetime.month),string.format("%02d", datetime.day),string.format("%02d", datetime.hour),
108+
string.format("%02d", datetime.min),string.format("%02d", datetime.sec),eyear,emon,eday,ehour,emin,esec,image.rating,labels,
109+
image.exif_maker,image.exif_model,image.title,image.creator,image.publisher,image.rights,username,pic_folder,home,desktop,
110+
image.exif_iso,image.exif_exposure,image.exif_exposure_bias,image.exif_aperture,image.exif_focus_distance,image.exif_focal_length,
111+
image.longitude,image.latitude,image.elevation,image.exif_lens,image.description,image.exif_crop}
112+
113+
for i=1,#rename.placeholders,1 do rename.substitutes[rename.placeholders[i]] = replacements[i] end
114+
end
115+
116+
local function substitute_list(str)
117+
-- replace the substitution variables in a string
118+
for match in string.gmatch(str, "%$%(.-%)") do
119+
local var = string.match(match, "%$%((.-)%)")
120+
if rename.substitutes[var] then
121+
str = string.gsub(str, "%$%("..var.."%)", rename.substitutes[var])
122+
else
123+
dt.print_error(_("unrecognized variable " .. var))
124+
dt.print(_("unknown variable " .. var .. ", aborting..."))
125+
return -1
126+
end
127+
end
128+
return str
129+
end
130+
131+
local function clear_substitute_list()
132+
for i=1,#rename.placeholders,1 do rename.substitutes[rename.placeholders[i]] = nil end
133+
end
134+
135+
local function stop_job(job)
136+
job.valid = false
137+
end
138+
139+
local function install_module()
140+
if not rename.module_installed then
141+
dt.register_lib(
142+
_("rename"),
143+
("rename"),
144+
true,
145+
true,
146+
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",700}},
147+
dt.new_widget("box"){
148+
orientation = "vertical",
149+
rename.widgets.pattern,
150+
rename.widgets.button,
151+
},
152+
nil,
153+
nil
154+
)
155+
rename.module_installed = true
156+
end
157+
end
158+
159+
local function destroy()
160+
dt.gui.libs["rename"].visible = false
161+
end
162+
163+
local function restart()
164+
dt.gui.libs["rename"].visible = true
165+
end
166+
167+
-- - - - - - - - - - - - - - - - - - - - - - - -
168+
-- M A I N
169+
-- - - - - - - - - - - - - - - - - - - - - - - -
170+
171+
local function do_rename(images)
172+
if #images > 0 then
173+
local pattern = rename.widgets.pattern.text
174+
dt.preferences.write(MODULE_NAME, "pattern", "string", pattern)
175+
dt.print_log(_("pattern is " .. pattern))
176+
if string.len(pattern) > 0 then
177+
local datetime = os.date("*t")
178+
179+
local job = dt.gui.create_job(_("renaming images"), true, stop_job)
180+
for i, image in ipairs(images) do
181+
if job.valid then
182+
job.percent = i / #images
183+
build_substitution_list(image, i, datetime, USER, PICTURES, HOME, DESKTOP)
184+
local new_name = substitute_list(pattern)
185+
if new_name == -1 then
186+
dt.print(_("unable to do variable substitution, exiting..."))
187+
stop_job(job)
188+
return
189+
end
190+
clear_substitute_list()
191+
local args = {}
192+
local path = string.sub(df.get_path(new_name), 1, -2)
193+
if string.len(path) == 0 then
194+
path = image.path
195+
end
196+
local filename = df.get_filename(new_name)
197+
local filmname = image.path
198+
if path ~= image.path then
199+
if not df.check_if_file_exists(df.sanitize_filename(path)) then
200+
df.mkdir(df.sanitize_filename(path))
201+
end
202+
filmname = path
203+
end
204+
args[1] = dt.films.new(filmname)
205+
args[2] = image
206+
if filename ~= image.filename then
207+
args[3] = filename
208+
end
209+
dt.database.move_image(table.unpack(args))
210+
end
211+
end
212+
stop_job(job)
213+
local collect_rules = dt.gui.libs.collect.filter()
214+
dt.gui.libs.collect.filter(collect_rules)
215+
dt.print(_("renamed " .. #images .. " images"))
216+
else -- pattern length
217+
dt.print_error("no pattern supplied, returning...")
218+
dt.print(_("please enter the new name or pattern"))
219+
end
220+
else -- image count
221+
dt.print_error("no images selected, returning...")
222+
dt.print(_("please select some images and try again"))
223+
end
224+
end
225+
226+
local function reset_callback()
227+
rename.widgets.pattern.text = ""
228+
end
229+
230+
-- - - - - - - - - - - - - - - - - - - - - - - -
231+
-- W I D G E T S
232+
-- - - - - - - - - - - - - - - - - - - - - - - -
233+
234+
rename.widgets.pattern = dt.new_widget("entry"){
235+
tooltip = _("$(ROLL_NAME) - film roll name\n") ..
236+
_("$(FILE_FOLDER) - image file folder\n") ..
237+
_("$(FILE_NAME) - image file name\n") ..
238+
_("$(FILE_EXTENSION) - image file extension\n") ..
239+
_("$(ID) - image id\n") ..
240+
_("$(VERSION) - version number\n") ..
241+
_("$(SEQUENCE) - sequence number of selection\n") ..
242+
_("$(YEAR) - current year\n") ..
243+
_("$(MONTH) - current month\n") ..
244+
_("$(DAY) - current day\n") ..
245+
_("$(HOUR) - current hour\n") ..
246+
_("$(MINUTE) - current minute\n") ..
247+
_("$(SECOND) - current second\n") ..
248+
_("$(EXIF_YEAR) - EXIF year\n") ..
249+
_("$(EXIF_MONTH) - EXIF month\n") ..
250+
_("$(EXIF_DAY) - EXIF day\n") ..
251+
_("$(EXIF_HOUR) - EXIF hour\n") ..
252+
_("$(EXIF_MINUTE) - EXIF minute\n") ..
253+
_("$(EXIF_SECOND) - EXIF seconds\n") ..
254+
_("$(EXIF_ISO) - EXIF ISO\n") ..
255+
_("$(EXIF_EXPOSURE) - EXIF exposure\n") ..
256+
_("$(EXIF_EXPOSURE_BIAS) - EXIF exposure bias\n") ..
257+
_("$(EXIF_APERTURE) - EXIF aperture\n") ..
258+
_("$(EXIF_FOCAL_LENGTH) - EXIF focal length\n") ..
259+
_("$(EXIF_FOCUS_DISTANCE) - EXIF focus distance\n") ..
260+
_("$(EXIF_CROP) - EXIF crop\n") ..
261+
_("$(LONGITUDE) - longitude\n") ..
262+
_("$(LATITUDE) - latitude\n") ..
263+
_("$(ELEVATION) - elevation\n") ..
264+
_("$(STARS) - star rating\n") ..
265+
_("$(LABELS) - color labels\n") ..
266+
_("$(MAKER) - camera maker\n") ..
267+
_("$(MODEL) - camera model\n") ..
268+
_("$(LENS) - lens\n") ..
269+
_("$(TITLE) - title from metadata\n") ..
270+
_("$(DESCRIPTION) - description from metadata\n") ..
271+
_("$(CREATOR) - creator from metadata\n") ..
272+
_("$(PUBLISHER) - publisher from metadata\n") ..
273+
_("$(RIGHTS) - rights from metadata\n") ..
274+
_("$(USERNAME) - username\n") ..
275+
_("$(PICTURES_FOLDER) - pictures folder\n") ..
276+
_("$(HOME) - user's home directory\n") ..
277+
_("$(DESKTOP) - desktop directory"),
278+
placeholder = _("enter pattern $(FILE_FOLDER)/$(FILE_NAME)"),
279+
text = ""
280+
}
281+
282+
local pattern_pref = dt.preferences.read(MODULE_NAME, "pattern", "string")
283+
if pattern_pref then
284+
rename.widgets.pattern.text = pattern_pref
285+
end
286+
287+
rename.widgets.button = dt.new_widget("button"){
288+
label = _("rename"),
289+
clicked_callback = function(this)
290+
do_rename(dt.gui.action_images)
291+
end
292+
}
293+
294+
if dt.gui.current_view().id == "lighttable" then
295+
install_module()
296+
else
297+
if not rename.event_registered then
298+
dt.register_event(
299+
"rename", "view-changed",
300+
function(event, old_view, new_view)
301+
if new_view.name == "lighttable" and old_view.name == "darkroom" then
302+
install_module()
303+
end
304+
end
305+
)
306+
rename.event_registered = true
307+
end
308+
end
309+
310+
script_data.destroy = destroy
311+
script_data.restart = restart
312+
script_data.destroy_method = "hide"
313+
script_data.show = restart
314+
315+
return script_data

0 commit comments

Comments
 (0)