diff --git a/official/use_paired_jpg_as_mipmap.lua b/official/use_paired_jpg_as_mipmap.lua new file mode 100644 index 00000000..aabc90ca --- /dev/null +++ b/official/use_paired_jpg_as_mipmap.lua @@ -0,0 +1,337 @@ +--[[ + + use_paired_jpg_as_mipmap.lua - Use the JPG from the RAW JPG pair as the full size mipmap + + Copyright (C) 2026 Bill Ferguson . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +]] +--[[ + use_paired_jpg_as_mipmap - Use the JPG from the RAW JPG pair as the full size mipmap + + use_paired_jpg_as_mipmap looks for RAW+JPEG image pairs as images are imported. After + import the JPEG image is copied to the mipmap cache as the full resolution mipmap. Requests + for smaller mipmap sizes can be satisfied by down sampling the full resolution mipmap. User's can + decide whether or not to keep the paired JPEG files. The default is to keep them. If the + user doesn't want them, they are deleted after being copied to the mipmap cache. + + ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT + + None + + USAGE + + - Add the script to the lua-scripts + - Enable the script + - Turn off the keep_jpgs preference if the JPEGS are not wanted + + BUGS, COMMENTS, SUGGESTIONS + Bill Ferguson + + CHANGES +]] + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +-- local ds = require "lib/dtutils.string" +-- local dtsys = require "lib/dtutils.system" +local log = require "lib/dtutils.log" +-- local debug = require "darktable.debug" + + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A P I C H E C K +-- - - - - - - - - - - - - - - - - - - - - - - - + +du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- I 1 8 N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local gettext = dt.gettext.gettext + +local function _(msgid) + return gettext(msgid) +end + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- S C R I P T M A N A G E R I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local script_data = {} + +script_data.destroy = nil -- function to destory the script +script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet +script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again +script_data.show = nil -- only required for libs since the destroy_method only hides them + +script_data.metadata = { + name = _("use paired jpg as mipmap"), -- visible name of script + purpose = _("Use the JPG from the RAW JPG pair as the full size mipmap"), -- purpose of script + author = "Bill Ferguson ", -- your name and optionally e-mail address + help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/use_paired_jpg_as_mipmap/" -- URL to help/documentation +} + + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- C O N S T A N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local MODULE = "use_paired_jpg_as_mipmap" +local DEFAULT_LOG_LEVEL = log.info +local TMP_DIR = dt.configuration.tmp_dir + +-- path separator +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + +-- command separator +local CS = dt.configuration.running_os == "windows" and "&" or ";" + +-- max mipmap size - 5.4.x = 8, after is 10 +local MAX_MIPMAP_SIZE = (dt.configuration.version > "5.4.1") and "10" or "8" + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- L O G L E V E L +-- - - - - - - - - - - - - - - - - - - - - - - - + +log.log_level(DEFAULT_LOG_LEVEL) + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- N A M E S P A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +local use_paired_jpg_as_mipmap = {} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- G L O B A L V A R I A B L E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +use_paired_jpg_as_mipmap.log_level = DEFAULT_LOG_LEVEL +use_paired_jpg_as_mipmap.imported_images = {} +use_paired_jpg_as_mipmap.image_count = 0 +use_paired_jpg_as_mipmap.mipmap_dir = nil +use_paired_jpg_as_mipmap.keep_jpgs = true + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- P R E F E R E N C E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +dt.preferences.register(MODULE, "keep_jpgs", "bool", script_data.metadata.name .. " - " .. _("keep JPEG files"), + _("don't delete JPEGs after copying them to mipmap folder"), true) + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A L I A S E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local namespace = use_paired_jpg_as_mipmap +local pmj = use_paired_jpg_as_mipmap + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- F U N C T I O N S +-- - - - - - - - - - - - - - - - - - - - - - - - + +------------------- +-- helper functions +------------------- + +local function set_log_level(level) + if not level then + level = namespace.log_level + end + + local old_log_level = log.log_level() + log.log_level(level) + + return old_log_level +end + +local function restore_log_level(level) + if not level then + level = namespace.log_level + end + log.log_level(level) +end + +local function reset_log_level() + log.log_level(DEFAULT_LOG_LEVEL) + namespace.log_level = DEFAULT_LOG_LEVEL +end + +local function pref_read(name, pref_type) + local old_log_level = set_log_level(namespace.log_level) + + log.msg(log.debug, MODULE .. ": name is " .. name .. " and type is " .. pref_type) + + local val = dt.preferences.read(MODULE, name, pref_type) + + log.msg(log.debug, MODULE .. ": read value " .. tostring(val)) + + restore_log_level(old_log_level) + return val +end + +local function pref_write(name, pref_type, value) + local old_log_level = set_log_level(namespace.log_level) + + log.msg(log.debug, MODULE .. ": writing value " .. tostring(value) .. " for name " .. name) + + dt.preferences.write(MODULE, name, pref_type, value) + + restore_log_level(old_log_level) +end + +local function refresh_collection() + local rules = dt.gui.libs.collect.filter() + dt.gui.libs.collect.filter(rules) +end + +local function get_mipmap_dir() + local old_log_level = set_log_level(pmj.log_level) + local mipmap_dir = nil + local cachedir = dt.configuration.cache_dir + + local cmd = "cd " .. cachedir .. "; ls -d mip*" + + if dt.configuration.running_os == "windows" then + cmd = "forfiles /P \"" .. cachedir .. "\" /M mip* /C \"cmd /c echo @file\"" + end + + local p = io.popen(cmd) + if p then + for line in p:lines() do + if line:len() > 4 then + mipmap_dir = cachedir .. PS .. line + log.msg(log.info, MODULE .. ": mipmap_dir is " .. mipmap_dir) + end + end + p:close() + end + + restore_log_level(old_log_level) + return mipmap_dir +end + +local function stop_job(job) + job.valid = false +end + +local function process_images(images, count) + local old_log_level = set_log_level(pmj.log_level) + + -- if dt.collection is equal to count there are no + -- RAW+JPEG pairs + + if count == #dt.collection then + log.msg(log.screen, _("No RAW+JPEG pairs to process")) + return + end + + local job = dt.gui.create_job(_("Generating cache from jpgs"), true, stop_job) + local processed_count = 0 + local mipmap_dir = get_mipmap_dir() .. PS .. MAX_MIPMAP_SIZE .. PS + + local rc = df.mkdir(mipmap_dir) + log.msg(log.debug, MODULE .. ": rc from mkdir is " .. rc) + + for k,v in pairs(images) do + if job.valid then + if v["raw"] and v["jpg"] then + local raw_image_id = v["raw"].id + local fname = v["jpg"].path .. PS .. v["jpg"].filename + df.file_copy(fname, mipmap_dir .. raw_image_id .. ".jpg") + if not pmj.keep_jpgs then + log.msg(log.debug, MODULE .. ": removing jpg") + v["jpg"]:delete() + local success, msg = os.remove(v["jpg"].path .. PS .. v["jpg"].filename) + if not success then + log.msg(log.warn, MODULE .. ": unable to remove jpg - reason: " .. msg) + end + success, msg = os.remove(v["jpg"].path .. PS .. v["jpg"].filename .. ".xmp") + if not success then + log.msg(log.warn, MODULE .. ": unable to remove jpg xmp - reason: " .. msg) + end + end + -- v["raw"]:generate_cache(true, 6, 6) + processed_count = processed_count + 1 + end + job.percent = processed_count / count + end + dt.control.sleep(5) + end + stop_job(job) + if not pmj.keep_jpgs then + refresh_collection() + end + dt.util.message(MODULE, "broadcast", "finished") +end + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- M A I N P R O G R A M +-- - - - - - - - - - - - - - - - - - - - - - - - + +use_paired_jpg_as_mipmap.keep_jpgs = pref_read("keep_jpgs", "bool") + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- U S E R I N T E R F A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- D A R K T A B L E I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - + +local function destroy() + dt.destroy_event(MODULE, "post-import-film") + dt.destroy_event(MODULE, "post-import-image") +end + +script_data.destroy = destroy + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- E V E N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +dt.register_event(MODULE, "post-import-film", + function(event, film) + process_images(pmj.imported_images, pmj.image_count) + pmj.imported_images = {} + pmj.image_count = 0 + end +) + +dt.register_event(MODULE, "post-import-image", + function(event, image) + local basename = df.get_basename(image.filename) + local extension = string.lower(df.get_filetype(image.filename)) + + if not string.match(extension, "jpg") then + extension = "raw" + end + + if not pmj.imported_images[basename] then + pmj.imported_images[basename] = {} + end + + pmj.imported_images[basename][extension] = image + + if not string.match(extension, "jpg") then + pmj.image_count = pmj.image_count + 1 + end + end +) + +return script_data