From 95968c3d9595ce71e468208e6178558f7da42c63 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 4 Mar 2019 12:20:55 -0500 Subject: [PATCH 1/3] Add AutoGrouper Adds AutoGrouper script which allows the user to select multiple images (or an entire collection) and automatically group them based on timestamp. The separation between groups is user defined via a slider. --- contrib/AutoGrouper.lua | 155 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 contrib/AutoGrouper.lua diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua new file mode 100644 index 0000000..2a1bd24 --- /dev/null +++ b/contrib/AutoGrouper.lua @@ -0,0 +1,155 @@ +--[[AutoGrouper plugin for darktable + + copyright (c) 2019 Kevin Ertel + + darktable 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. + + darktable 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 darktable. If not, see . +]] + +--[[About this Plugin +This plugin adds the module "Auto Group" to darktable's lighttable view + +----REQUIRED SOFTWARE---- +None + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in + +Set a gap amount in second which will be used to determine when images should no +longer be added to a group. If an image is more then the specified amount of time +from the last image in the group it will not be added. Images without timestamps +in exif data will be ignored. + +There are two buttons. One allows the grouping to be performed only on the currently +selected images, the other button performs grouping on the entire active collection +]] + +local dt = require "darktable" +local mod = 'autogroup' +local gettext = dt.gettext +-- Tell gettext where to find the .mo file translating messages for a particular domain +gettext.bindtextdomain("AutoGrouper",dt.configuration.config_dir.."/lua/locale/") +local function _(msgid) + return gettext.dgettext("AutoGrouper", msgid) +end + +local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive) + if test >= low and test <= high then + return true + else + return false + end +end + +local function CompTime(first, second) + first_time = first.exif_datetime_taken + if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end + first_time = tonumber(string.gsub(first_time, '[^0-9]*','')) + second_time = second.exif_datetime_taken + if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end + second_time = tonumber(string.gsub(second_time, '[^0-9]*','')) + return first_time < second_time +end + +local function SeperateTime(str) + local cleaned = string.gsub(str, '[^%d]',':') + cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss + local year = string.sub(cleaned,1,4) + local month = string.sub(cleaned,6,7) + local day = string.sub(cleaned,9,10) + local hour = string.sub(cleaned,12,13) + local min = string.sub(cleaned,15,16) + local sec = string.sub(cleaned,18,19) + return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} +end + +local function GetTimeDiff(curr_image, prev_image) + local curr_time = SeperateTime(curr_image.exif_datetime_taken) + local prev_time = SeperateTime(prev_image.exif_datetime_taken) + return os.time(curr_time)-os.time(prev_time) +end + +local function main(on_collection) + local images = {} + if on_collection then + local col_images = dt.collection + for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection + table.insert(images,i,image) + end + else + images = dt.gui.selection() + end + dt.preferences.write(mod, 'active_gap', 'integer', GUI.gap.value) + if #images < 2 then return end + table.sort(images, function(first, second) return CompTime(first,second) end) --sort images into + + for i, image in ipairs(images) do + if i == 1 then + prev_image = image + elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then + local curr_image = image + if GetTimeDiff(curr_image, prev_image) < GUI.gap.value then + images[i]:group_with(images[i-1]) + end + prev_image = curr_image + end + end +end + +-- GUI -- +GUI = { + gap = {}, + selected = {}, + collection = {} +} +temp = dt.preferences.read(mod, 'active_gap', 'integer') +if not InRange(temp, 1, 3600) then temp = 3 end +GUI.gap = dt.new_widget('slider'){ + label = _('group gap [sec.]'), + tooltip = _('minimum gap, in seconds, between groups'), + soft_min = 1, + soft_max = 30, + hard_min = 1, + hard_max = 3600, + step = 1, + digits = 0, + value = temp, + reset_callback = function(self) + self.value = 3 + end +} +GUI.selected = dt.new_widget("button"){ + label = _('auto group: selected'), + tooltip =_('auto group selected images'), + clicked_callback = function() main(false) end +} +GUI.collection = dt.new_widget("button"){ + label = _('auto group: collection'), + tooltip =_('auto group the entire collection'), + clicked_callback = function() main(true) end +} +dt.register_lib( + "AutoGroup_Lib", -- Module name + _("Auto Group"), -- name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers + dt.new_widget("box"){ + orientation = "vertical", + GUI.gap, + GUI.selected, + GUI.collection + } +) From 9e97b8f5a1fcddc074d279fe4e47e67168d3c581 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 4 Mar 2019 12:47:52 -0500 Subject: [PATCH 2/3] Added comments, made the gap inclusive Added comments throughout Change the gap comparison to be inclusive ( from '<' to '<=') --- contrib/AutoGrouper.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 2a1bd24..3125363 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -53,7 +53,7 @@ local function InRange(test, low, high) --tests if test value is within range of end end -local function CompTime(first, second) +local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second first_time = first.exif_datetime_taken if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end first_time = tonumber(string.gsub(first_time, '[^0-9]*','')) @@ -63,7 +63,7 @@ local function CompTime(first, second) return first_time < second_time end -local function SeperateTime(str) +local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations local cleaned = string.gsub(str, '[^%d]',':') cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss local year = string.sub(cleaned,1,4) @@ -75,7 +75,7 @@ local function SeperateTime(str) return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} end -local function GetTimeDiff(curr_image, prev_image) +local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image local curr_time = SeperateTime(curr_image.exif_datetime_taken) local prev_time = SeperateTime(prev_image.exif_datetime_taken) return os.time(curr_time)-os.time(prev_time) @@ -93,14 +93,14 @@ local function main(on_collection) end dt.preferences.write(mod, 'active_gap', 'integer', GUI.gap.value) if #images < 2 then return end - table.sort(images, function(first, second) return CompTime(first,second) end) --sort images into + table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp for i, image in ipairs(images) do if i == 1 then prev_image = image - elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then + elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group local curr_image = image - if GetTimeDiff(curr_image, prev_image) < GUI.gap.value then + if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then images[i]:group_with(images[i-1]) end prev_image = curr_image From 2c3536afc93867d05a4ca6bfd0703a5964865753 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 6 May 2019 10:49:30 -0400 Subject: [PATCH 3/3] Changes requestd via PR 1) mod changed to MOD 2) autogroup changed to autogrouper 3) prints "please select at least 2 images" if user attempts to run with fewer than 2 images selected 4) change visible module name to "auto group" (lower case) 5) changed soft max on slider to 60s - I also increase the hard max up to a full day, I saw no reason not to --- contrib/AutoGrouper.lua | 169 ++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 3125363..0d92915 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -37,7 +37,7 @@ selected images, the other button performs grouping on the entire active collect ]] local dt = require "darktable" -local mod = 'autogroup' +local MOD = 'autogrouper' local gettext = dt.gettext -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("AutoGrouper",dt.configuration.config_dir.."/lua/locale/") @@ -46,110 +46,113 @@ local function _(msgid) end local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive) - if test >= low and test <= high then - return true - else - return false - end + if test >= low and test <= high then + return true + else + return false + end end local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second - first_time = first.exif_datetime_taken - if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end - first_time = tonumber(string.gsub(first_time, '[^0-9]*','')) - second_time = second.exif_datetime_taken - if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end - second_time = tonumber(string.gsub(second_time, '[^0-9]*','')) - return first_time < second_time + first_time = first.exif_datetime_taken + if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end + first_time = tonumber(string.gsub(first_time, '[^0-9]*','')) + second_time = second.exif_datetime_taken + if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end + second_time = tonumber(string.gsub(second_time, '[^0-9]*','')) + return first_time < second_time end local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations - local cleaned = string.gsub(str, '[^%d]',':') - cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss - local year = string.sub(cleaned,1,4) - local month = string.sub(cleaned,6,7) - local day = string.sub(cleaned,9,10) - local hour = string.sub(cleaned,12,13) - local min = string.sub(cleaned,15,16) - local sec = string.sub(cleaned,18,19) - return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} + local cleaned = string.gsub(str, '[^%d]',':') + cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss + local year = string.sub(cleaned,1,4) + local month = string.sub(cleaned,6,7) + local day = string.sub(cleaned,9,10) + local hour = string.sub(cleaned,12,13) + local min = string.sub(cleaned,15,16) + local sec = string.sub(cleaned,18,19) + return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} end local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image - local curr_time = SeperateTime(curr_image.exif_datetime_taken) - local prev_time = SeperateTime(prev_image.exif_datetime_taken) - return os.time(curr_time)-os.time(prev_time) + local curr_time = SeperateTime(curr_image.exif_datetime_taken) + local prev_time = SeperateTime(prev_image.exif_datetime_taken) + return os.time(curr_time)-os.time(prev_time) end local function main(on_collection) - local images = {} - if on_collection then - local col_images = dt.collection - for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection - table.insert(images,i,image) - end - else - images = dt.gui.selection() - end - dt.preferences.write(mod, 'active_gap', 'integer', GUI.gap.value) - if #images < 2 then return end - table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp - - for i, image in ipairs(images) do - if i == 1 then - prev_image = image - elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group - local curr_image = image - if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then - images[i]:group_with(images[i-1]) - end - prev_image = curr_image - end - end + local images = {} + if on_collection then + local col_images = dt.collection + for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection + table.insert(images,i,image) + end + else + images = dt.gui.selection() + end + dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value) + if #images < 2 then + dt.print('please select at least 2 images') + return + end + table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp + + for i, image in ipairs(images) do + if i == 1 then + prev_image = image + elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group + local curr_image = image + if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then + images[i]:group_with(images[i-1]) + end + prev_image = curr_image + end + end end -- GUI -- GUI = { - gap = {}, - selected = {}, - collection = {} + gap = {}, + selected = {}, + collection = {} } -temp = dt.preferences.read(mod, 'active_gap', 'integer') -if not InRange(temp, 1, 3600) then temp = 3 end +temp = dt.preferences.read(MOD, 'active_gap', 'integer') +if not InRange(temp, 1, 86400) then temp = 3 end GUI.gap = dt.new_widget('slider'){ - label = _('group gap [sec.]'), - tooltip = _('minimum gap, in seconds, between groups'), - soft_min = 1, - soft_max = 30, - hard_min = 1, - hard_max = 3600, - step = 1, - digits = 0, - value = temp, - reset_callback = function(self) - self.value = 3 - end + label = _('group gap [sec.]'), + tooltip = _('minimum gap, in seconds, between groups'), + soft_min = 1, + soft_max = 60, + hard_min = 1, + hard_max = 86400, + step = 1, + digits = 0, + value = temp, + reset_callback = function(self) + self.value = 3 + end } GUI.selected = dt.new_widget("button"){ - label = _('auto group: selected'), - tooltip =_('auto group selected images'), - clicked_callback = function() main(false) end + label = _('auto group: selected'), + tooltip =_('auto group selected images'), + clicked_callback = function() main(false) end } GUI.collection = dt.new_widget("button"){ - label = _('auto group: collection'), - tooltip =_('auto group the entire collection'), - clicked_callback = function() main(true) end + label = _('auto group: collection'), + tooltip =_('auto group the entire collection'), + clicked_callback = function() main(true) end } dt.register_lib( - "AutoGroup_Lib", -- Module name - _("Auto Group"), -- name - true, -- expandable - true, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers - dt.new_widget("box"){ - orientation = "vertical", - GUI.gap, - GUI.selected, - GUI.collection - } + 'AutoGroup_Lib', -- Module name + _('auto group'), -- name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers + dt.new_widget("box"){ + orientation = "vertical", + GUI.gap, + GUI.selected, + GUI.collection + } )