Skip to content

Commit 3992435

Browse files
committed
Add contrib/fujifilm_auto_settings script
1 parent 35cbff0 commit 3992435

File tree

2 files changed

+287
-0
lines changed

2 files changed

+287
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dbmaint|Yes|LMW|find and remove database entries for missing film rolls and imag
4848
[exportLUT](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it
4949
[ext_editor](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors
5050
[face_recognition](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition
51+
[fujifilm_auto_settings](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_auto_settings)|No|LMW|Apply fujifilm film emulations on import. [See here](https://bastibe.de/2022-05-04-customizing-darktable-for-fujifilm-cameras.html) for more information.
5152
[fujifilm_dynamic_range](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting
5253
[fujifilm_ratings](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
5354
[geoJSON_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ...

Diff for: contrib/fujifilm_auto_settings.lua

+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
--[[ fujifilm_auto_settings-0.2
2+
3+
Apply Fujifilm film simulations, in-camera crop mode, and dynamic range.
4+
5+
Copyright (C) 2022 Bastian Bechtold <[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 2 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 along
18+
with this program; if not, write to the Free Software Foundation, Inc.,
19+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20+
--]]
21+
22+
--[[About this Plugin
23+
Automatically applies styles that load Fujifilm film simulation LUTs,
24+
copy crop ratios from the JPG, and correct exposure according to the
25+
chosen dynamic range setting in camera.
26+
27+
Dependencies:
28+
- exiftool (https://www.sno.phy.queensu.ca/~phil/exiftool/)
29+
- Fuji LUTs (https://blog.sowerby.me/fuji-film-simulation-profiles/)
30+
31+
Based on fujifim_dynamic_range by Dan Torop.
32+
33+
Film Simulations
34+
----------------
35+
36+
Fujifilm cameras are famous for their film simulations, such as Provia
37+
or Velvia or Classic Chrome. Indeed it is my experience that they rely
38+
on these film simulations for accurate colors.
39+
40+
Darktable however does not know about or implement these film
41+
simulations. But they are available to download from Stuart Sowerby as
42+
3DL LUTs. (PNG LUTs are also available, but they show a strange
43+
posterization artifact when loaded in Darktable, which the 3DLs do
44+
not).
45+
46+
In order to use this plugin, you must prepare a number of styles:
47+
- provia
48+
- astia
49+
- velvia
50+
- classic_chrome
51+
- pro_neg_standard
52+
- pro_neg_high
53+
- eterna
54+
- acros_green
55+
- acros_red
56+
- acros_yellow
57+
- acros
58+
- mono_green
59+
- mono_red
60+
- mono_yellow
61+
- mono
62+
- sepia
63+
64+
These styles should apply the according film simulation in a method of
65+
your choosing.
66+
67+
This plugin checks the image's "Film Mode" exif parameter, and applies
68+
the appropriate style. If no matching style exists, no action is taken
69+
and no harm is done.
70+
71+
Crop Factor
72+
-----------
73+
74+
Fujifilm cameras allow in-camera cropping to one of three aspect
75+
ratios: 2:3 (default), 16:9, and 1:1.
76+
77+
This plugin checks the image's "Raw Image Aspect Ratio" exif
78+
parameter, and applies the appropriate style.
79+
80+
To use, prepare another four styles:
81+
- square_crop_portrait
82+
- square_crop_landscape
83+
- sixteen_by_nine_crop_portrait
84+
- sixteen_by_nine_crop_landscape
85+
86+
These styles should apply a square crop and a 16:9 crop to
87+
portrait/landscape images. If no matching style exists, no action is
88+
taken and no harm is done.
89+
90+
Dynamic Range
91+
-------------
92+
93+
Fujifilm cameras have a built-in dynamic range compensation, which
94+
(optionally automatically) reduce exposure by one or two stops, and
95+
compensate by raising the tone curve by one or two stops. These modes
96+
are called DR200 and DR400, respectively.
97+
98+
The plugin reads the raw file's "Auto Dynamic Range" or "Development
99+
Dynamic Range" parameter, and applies one of two styles:
100+
- DR200
101+
- DR400
102+
103+
These styles should raise exposure by one and two stops, respectively,
104+
and expand highlight latitude to make room for additional highlights.
105+
I like to implement them with the tone equalizer in eigf mode, raising
106+
exposure by one/two stops over the lower half of the sliders, then
107+
ramping to zero at 0 EV. If no matching styles exist, no action is
108+
taken and no harm is done.
109+
110+
These tags have been checked on a Fujifilm X-T3 and X-Pro2. Other
111+
cameras may behave in other ways.
112+
113+
--]]
114+
115+
local dt = require("darktable")
116+
local du = require("lib/dtutils")
117+
local df = require("lib/dtutils.file")
118+
119+
du.check_min_api_version("7.0.0", "fujifilm_auto_settings")
120+
121+
-- return data structure for script_manager
122+
123+
local script_data = {}
124+
125+
script_data.destroy = nil -- function to destory the script
126+
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them completely yet, otherwise leave as nil
127+
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
128+
129+
local function exiftool_get(exiftool_command, RAF_filename, flag)
130+
local command = exiftool_command .. " " .. flag .. " -t " .. RAF_filename
131+
dt.print_log(command)
132+
local output = io.popen(command)
133+
local exiftool_result = output:read("*all")
134+
output:close()
135+
if #exiftool_result == 0 then
136+
dt.print_error("[fujifilm_auto_settings] no output returned by exiftool")
137+
return
138+
end
139+
local exiftool_result = string.match(exiftool_result, "\t(.*)")
140+
if not exiftool_result then
141+
dt.print_error("[fujifilm_auto_settings] could not parse exiftool output")
142+
return
143+
end
144+
exiftool_result = exiftool_result:match("^%s*(.-)%s*$") -- strip whitespace
145+
return exiftool_result
146+
end
147+
148+
local function apply_style(image, style_name)
149+
for _, s in ipairs(dt.styles) do
150+
if s.name == style_name then
151+
dt.styles.apply(s, image)
152+
return
153+
end
154+
end
155+
dt.print_error("[fujifilm_auto_settings] could not find style " .. style_name)
156+
end
157+
158+
local function apply_tag(image, tag_name)
159+
local tagnum = dt.tags.find(tag_name)
160+
if tagnum == nil then
161+
-- create tag if it doesn't exist
162+
tagnum = dt.tags.create(tag_name)
163+
dt.print_log("[fujifilm_auto_settings] creating tag " .. tag_name)
164+
end
165+
dt.tags.attach(tagnum, image)
166+
end
167+
168+
local function detect_auto_settings(event, image)
169+
if image.exif_maker ~= "FUJIFILM" then
170+
dt.print_log("[fujifilm_auto_settings] ignoring non-Fujifilm image")
171+
return
172+
end
173+
-- it would be nice to check image.is_raw but this appears to not yet be set
174+
if not string.match(image.filename, "%.RAF$") then
175+
dt.print_log("[fujifilm_auto_settings] ignoring non-raw image")
176+
return
177+
end
178+
local exiftool_command = df.check_if_bin_exists("exiftool")
179+
if not exiftool_command then
180+
dt.print_error("[fujifilm_auto_settings] exiftool not found")
181+
return
182+
end
183+
local RAF_filename = df.sanitize_filename(tostring(image))
184+
185+
-- dynamic range mode
186+
-- if in DR Auto, the value is saved to Auto Dynamic Range, with a % suffix:
187+
local auto_dynamic_range = exiftool_get(exiftool_command, RAF_filename, "-AutoDynamicRange")
188+
-- if manually chosen DR, the value is saved to Development Dynamic Range:
189+
if auto_dynamic_range == nil then
190+
auto_dynamic_range = exiftool_get(exiftool_command, RAF_filename, "-DevelopmentDynamicRange") .. "%"
191+
end
192+
if auto_dynamic_range == "100%" then
193+
apply_tag(image, "DR100")
194+
-- default; no need to change style
195+
elseif auto_dynamic_range == "200%" then
196+
apply_style(image, "DR200")
197+
apply_tag(image, "DR200")
198+
dt.print_log("[fujifilm_auto_settings] DR200")
199+
elseif auto_dynamic_range == "400%" then
200+
apply_style(image, "DR400")
201+
apply_tag(image, "DR400")
202+
dt.print_log("[fujifilm_auto_settings] DR400")
203+
end
204+
205+
-- cropmode
206+
local raw_aspect_ratio = exiftool_get(exiftool_command, RAF_filename, "-RawImageAspectRatio")
207+
if raw_aspect_ratio == "3:2" then
208+
apply_tag(image, "3:2")
209+
-- default; no need to apply style
210+
elseif raw_aspect_ratio == "1:1" then
211+
if image.width > image.height then
212+
apply_style(image, "square_crop_landscape")
213+
else
214+
apply_style(image, "square_crop_portrait")
215+
end
216+
apply_tag(image, "1:1")
217+
dt.print_log("[fujifilm_auto_settings] square crop")
218+
elseif raw_aspect_ratio == "16:9" then
219+
if image.width > image.height then
220+
apply_style(image, "sixteen_by_nine_crop_landscape")
221+
else
222+
apply_style(image, "sixteen_by_nine_crop_portrait")
223+
end
224+
apply_tag(image, "16:9")
225+
dt.print_log("[fujifilm_auto_settings] 16:9 crop")
226+
end
227+
228+
-- filmmode
229+
local raw_filmmode = exiftool_get(exiftool_command, RAF_filename, "-FilmMode")
230+
local style_map = {
231+
["Provia"] = "provia",
232+
["Astia"] = "astia",
233+
["Classic Chrome"] = "classic_chrome",
234+
["Eterna"] = "eterna",
235+
["Acros+G"] = "acros_green",
236+
["Acros+R"] = "acros_red",
237+
["Acros+Ye"] = "acros_yellow",
238+
["Acros"] = "acros",
239+
["Mono+G"] = "mono_green",
240+
["Mono+R"] = "mono_red",
241+
["Mono+Ye"] = "mono_yellow",
242+
["Mono"] = "mono",
243+
["Pro Neg Hi"] = "pro_neg_high",
244+
["Pro Neg Std"] = "pro_neg_standard",
245+
["Sepia"] = "sepia",
246+
["Velvia"] = "velvia",
247+
}
248+
for key, value in pairs(style_map) do
249+
if string.find(raw_filmmode, key) then
250+
apply_style(image, value)
251+
apply_tag(image, key)
252+
dt.print_log("[fujifilm_auto_settings] film simulation " .. key)
253+
end
254+
end
255+
end
256+
257+
local function detect_auto_settings_multi(event, shortcut)
258+
local images = dt.gui.selection()
259+
if #images == 0 then
260+
dt.print(_("Please select an image"))
261+
else
262+
for _, image in ipairs(images) do
263+
detect_auto_settings(event, image)
264+
end
265+
end
266+
end
267+
268+
local function destroy()
269+
dt.destroy_event("fujifilm_auto_settings", "post-import-image")
270+
dt.destroy_event("fujifilm_auto_settings", "shortcut")
271+
end
272+
273+
if not df.check_if_bin_exists("exiftool") then
274+
dt.print_log("Please install exiftool to use fujifilm_auto_settings")
275+
error("[fujifilm_auto_settings] exiftool not found")
276+
end
277+
278+
dt.register_event("fujifilm_auto_settings", "post-import-image", detect_auto_settings)
279+
280+
dt.register_event("fujifilm_auto_settings", "shortcut", detect_auto_settings_multi, "fujifilm_auto_settings")
281+
282+
dt.print_log("[fujifilm_auto_settings] loaded")
283+
284+
script_data.destroy = destroy
285+
286+
return script_data

0 commit comments

Comments
 (0)