761 Commits

Author SHA1 Message Date
wpferguson
d109612045 Merge pull request #683 from andriiryzhkov/fix_lua_win
Fix stray "File Not Found" stderr from dir on Windows startup
2026-06-05 10:59:10 -04:00
Andrii Ryzhkov
0f47f46b35 Fix stray "File Not Found" stderr from dir on Windows startup 2026-06-05 16:10:49 +02:00
wpferguson
1a44df7d3c Merge pull request #677 from andriiryzhkov/fix_raw_denoise
Fix wrong colors in ai_raw_denoise.lua
2026-06-01 12:42:09 -04:00
wpferguson
aa62473cbf Merge pull request #681 from darktable-org/bug_fix_and_internal_metadata
Bug fix and internal metadata
2026-06-01 12:38:10 -04:00
Bill Ferguson
f7faad0962 official/extract_burst_roll_images - added check for internal metadata
so that can be used instead of spawning exiv2 or exiftool to detect
RawBurstRollImages.

Added select button to select burst roll images.

Added action button to extract burst roll images from selected burst
roll image container
2026-05-30 15:06:37 -04:00
Bill Ferguson
53549f16b5 official/auto_straighten - Added check for Nikon cameras where
rotation is opposite of Canon and thus requires the sign to be
changed.
2026-05-30 15:04:47 -04:00
Bill Ferguson
7531a29235 contrib/image_time - Use internal metadata if available instead of
resorting to calling exiv2 or exiftool
2026-05-30 15:03:40 -04:00
Andrii Ryzhkov
a4dce23ef8 Fix wrong colors on non-RGGB sensors via RGGB origin shift and daylight WB round-trip 2026-05-16 17:04:28 +02:00
wpferguson
8abc350d68 Merge pull request #676 from andriiryzhkov/ai_scripts
Add ai_denoise and ai_raw_denoise example scripts for the darktable.ai Lua API
2026-05-15 14:58:08 -04:00
Andrii Ryzhkov
078011d6b4 Add ai_denoise and ai_raw_denoise example scripts for the darktable.ai Lua API 2026-05-14 16:32:34 +02:00
wpferguson
2a16599f91 Merge pull request #675 from wpferguson/script_manager_match_fix
script_manager - fix system scripts detection
2026-05-11 12:32:38 -04:00
Bill Ferguson
14646cf8e6 tools/script_manager - sanitized system directory string for lua pattern
matching so system lua scripts detection works when lua special characters
are in the path (such as -)
2026-05-11 12:24:30 -04:00
wpferguson
5823d015ac Merge pull request #674 from wpferguson/fix_translations_and_bug
Fix translations and bug
2026-04-29 14:47:12 -04:00
Bill Ferguson
30e6b9459a official scripts - clean up translations 2026-04-28 22:36:45 -04:00
Bill Ferguson
d3e15065d2 official/extract_burst_roll_images - fix string length 2026-04-28 22:35:54 -04:00
wpferguson
fe722f8409 Merge pull request #670 from wpferguson/fix_pdf_PDF
fix official/selection_to_pdf translatable strings
2026-04-20 19:32:26 -04:00
Bill Ferguson
b91baebdeb official/selection_to_pdf - change pdf to PDF in translatable strings 2026-04-20 19:27:56 -04:00
wpferguson
5ebadd83ea Merge pull request #669 from wpferguson/sm_logging
tools/script_manager - changed log msg from debug to info
2026-04-19 16:00:05 -04:00
Bill Ferguson
01c3b7d2f6 tools/script_manager - changed log msg from debug to info 2026-04-19 15:58:35 -04:00
wpferguson
bfee6b12dc Merge pull request #668 from wpferguson/script_manager_fix
tools/script_manager - fixed gui safety test for gui installation
2026-04-19 14:56:04 -04:00
Bill Ferguson
503a59d590 tools/script_manager - fixed gui safety test for gui installation 2026-04-19 14:54:25 -04:00
wpferguson
78176e32e8 Merge pull request #631 from wpferguson/bundled_script_manager
Updated script_manager for bundled scripts

Part of https://github.com/darktable-org/darktable/pull/19899
2026-04-19 14:38:24 -04:00
wpferguson
4d8d076d8e Merge pull request #667 from bkhl/master
Missing argument in group_persistence.lua
2026-04-18 15:29:10 -04:00
Björn Lindström
72f31d6208 Missing argument in group_persistence.lua
`add_to_group` was still failing as the leader was not provided
as second argument.
2026-04-18 20:59:58 +02:00
wpferguson
784a6ea85d Merge pull request #666 from bkhl/group_persistence-function-order
Order functions in dependency order in group_persistance.lua
2026-04-18 14:49:59 -04:00
Björn Lindström
bbfd9d12e3 Order functions in dependency order in group_persistance.lua
The call to `add_to_group` in `build_group` failed as `add_to_group`
was a local function defined later. This reorders the functions to avoid
that.
2026-04-18 20:38:52 +02:00
wpferguson
a55de740e5 Merge pull request #663 from deekayhd/issue_661
fix incomplete removal of datetime_taken in PR #653
2026-04-11 11:11:20 -04:00
deekayhd
dc31b221ef fix username as nil 2026-04-11 14:36:30 +02:00
deekayhd
34b88490c2 fix assignment of empty variables derived from exit_datetime_taken 2026-04-11 14:30:29 +02:00
deekayhd
d9b77bb3a2 fix incomplete removal of datetime_taken in PR #653 2026-04-10 19:56:00 +02:00
wpferguson
cc198d6ca1 Merge pull request #662 from wpferguson/fix_transfer_hierachy
contrib/transfer_hierarchy bug fix
2026-04-09 12:21:31 -04:00
Bill Ferguson
84582cbf29 contrib/transfer_hierarchy - fixed unused variable being used as
boolean function
2026-04-09 12:18:54 -04:00
wpferguson
9e7a18e290 Merge pull request #660 from wpferguson/readd_toggle_group_view
re-add contrib/toggle_group_view
2026-04-06 21:23:41 -04:00
Bill Ferguson
8ff81fcfa4 contrib/toggle_group_view - re-added toggle_group_view after getting
button sensitivity figured out.

This script adds a way to toggle a selected or hovered group to it's
own collection and back again
2026-04-06 21:20:32 -04:00
wpferguson
b649e7a840 Merge pull request #659 from wpferguson/fix_siv_typo
contrib/selected_image_visible - corrected typo
2026-04-05 15:32:32 -04:00
Bill Ferguson
efa06ee34c contrib/selected_image_visible - corrected typo 2026-04-05 15:31:38 -04:00
wpferguson
77959422e0 Merge pull request #658 from wpferguson/remove_toggle_group_view
contrib/toggle_group_view - removed because there are too many problems
2026-04-04 20:10:29 -04:00
Bill Ferguson
0bff346b05 contrib/toggle_group_view - removed because there are too many problems
needing fixed.
2026-04-04 20:08:14 -04:00
wpferguson
d05c494147 Merge pull request #657 from wpferguson/fix_event_destroy
contrib/toggle_group_view - fixed event destroy call
2026-04-04 19:53:27 -04:00
Bill Ferguson
9cbbae9621 contrib/toggle_group_view - fixed event destroy call 2026-04-04 19:52:13 -04:00
wpferguson
fb2aade198 Merge pull request #656 from wpferguson/fix_button_function_call
contrib/selected_image_visible - corrected action button function call
2026-04-04 19:48:07 -04:00
Bill Ferguson
7ffd8bdc27 contrib/selected_image_visible - corrected action button function call 2026-04-04 19:44:53 -04:00
wpferguson
b689dbc7ee Merge pull request #655 from wpferguson/update_toogle_group_view
contrib/toggle_group_view
2026-04-04 17:20:49 -04:00
Bill Ferguson
027135a257 contrib/toggle_group_view - updated to current version from old
version I mistakenly used.
2026-04-04 17:19:23 -04:00
wpferguson
643057deaf Merge pull request #652 from wpferguson/selected_image_visible
contrib/selected_image_visible
2026-04-04 15:56:12 -04:00
Bill Ferguson
a00f71f533 contrib/selected_image_visible - Added action button to make
selected non visible image visible
2026-04-04 15:54:00 -04:00
wpferguson
36d6db24e7 Merge pull request #654 from wpferguson/fix_s2c_destroy
official/selection2collection - fixed 2 bugs with destroy function
2026-04-04 15:23:26 -04:00
Bill Ferguson
f62a5a817f official/selection2collection - fixed 2 bugs with destroy function 2026-04-04 15:21:41 -04:00
wpferguson
44b40b305b Merge pull request #651 from wpferguson/select_raw_non_raw
select_raw_non_raw
2026-04-04 15:01:46 -04:00
wpferguson
97dade06cf Merge pull request #650 from wpferguson/group_persistence
official/group_persistence
2026-04-04 15:01:25 -04:00
wpferguson
30167631e7 Merge pull request #649 from wpferguson/toggle_group_view
toggle_group_view
2026-04-04 15:01:01 -04:00
wpferguson
8235de1f0a Merge pull request #648 from wpferguson/select_unaltered
select_unaltered
2026-04-04 15:00:26 -04:00
wpferguson
d67ada8300 Merge pull request #646 from wpferguson/recent_bookmarks
recent_bookmarks
2026-04-04 15:00:07 -04:00
wpferguson
c6788e4370 Merge pull request #645 from wpferguson/use_paired_jpg_as_mipmap
use_paired_jpg_as_mipmap
2026-04-04 14:59:46 -04:00
wpferguson
d238fcee61 Merge pull request #644 from wpferguson/selection2collection
selection2collection
2026-04-04 14:58:52 -04:00
wpferguson
28d71c989f Merge pull request #643 from wpferguson/extract_burst_roll_images
extract_burst_roll_images
2026-04-04 14:58:16 -04:00
wpferguson
74f020c809 Merge pull request #642 from wpferguson/auto_straighten
auto_straighten
2026-04-04 14:57:43 -04:00
wpferguson
80769a753a Merge pull request #641 from wpferguson/select_duplicates
select duplicates
2026-04-04 14:57:14 -04:00
wpferguson
427359769e Merge pull request #647 from wpferguson/scheduler
scheduler
2026-04-04 14:56:17 -04:00
Bill Ferguson
5e5b073cec official/use_paired_jpg_as_mipmap - fixed typo in namespace table 2026-03-30 21:12:51 -04:00
Bill Ferguson
c771c5fc1b official/use_paired_jpg_as_mipmap - changed isc message to a broadcast
so that any cache building script could catch it and then execute.
2026-03-30 20:51:37 -04:00
Bill Ferguson
7cdf1d4c7f official/use_paired_jpg_as_mipmap - Added more logging and changed
trigger message for cache building scripts
2026-03-30 13:19:41 -04:00
wpferguson
b6ea6211db Merge pull request #653 from deekayhd/no_exif_date_corr
fix wrong month in EXIF.MONTH.LONG and EXIF_MONTH.SHORT when no exif date available in image

Thanks
2026-03-30 13:00:31 -04:00
deekayhd
4d3924939b remove unnecessary variable datetime_taken 2026-03-29 23:45:40 +02:00
deekayhd
80ff1ef0aa add warning message 2026-03-29 23:37:22 +02:00
deekayhd
af362315e8 set all vars derived from datetime_taken to "" 2026-03-29 23:22:04 +02:00
deekayhd
28f3e9f6a2 fix wrong month in EXIF.MONTH.LONG and EXIF_MONTH.SHORT when no date available 2026-03-29 14:29:39 +02:00
Bill Ferguson
4f05672809 contrib/selected_image_visible - add script that creates a shortcut
to make the selected image visible in lighttable.
2026-03-26 23:34:50 -04:00
Bill Ferguson
8d8f295c77 official/select_raw_non_raw - updated help url 2026-03-26 23:17:25 -04:00
Bill Ferguson
a81ab437cf official/select_raw_non_raw - added script that creates two selection
buttons, one to select raw images and the other to select non raw
images
2026-03-26 22:43:14 -04:00
Bill Ferguson
a94a0c2c90 contrib/toggle_group_view - updated help link 2026-03-26 22:41:28 -04:00
Bill Ferguson
0eda1b6ac5 official/group_persistence - add script to store group information in
xmp files so that groups can be maintained across database instances
2026-03-26 22:06:33 -04:00
Bill Ferguson
58bc3a69df contrib/toggle_group_view - moved from official to contrib to handle
event conflict with official/group_persistence.  Moving to contrib forces
toggle_group_view to load before group_persistence and thus get events that
both need, first.  If the order is reversed then toggle_group_view can bog
down in a loop making darktable unresponsive.

Fixed bad call in destroy and added check for selection-changed to make sure
there are images before trying to set sensitivity.
2026-03-26 13:57:04 -04:00
Bill Ferguson
35398c55e5 official/toggle_group_view - fix no images selected so can't get a count of nil 2026-03-25 13:07:03 -04:00
Bill Ferguson
1762b372b1 official/toggle_group_view - made button sensitive to group members
selected
2026-03-25 12:33:21 -04:00
Bill Ferguson
1907f9a1f5 official/toggle_group_view - added a script to view a group as
a collection when toggled from lighttable
2026-03-24 22:57:45 -04:00
Bill Ferguson
b1dd3eca5a official/select_unaltered - add select button to select images that
haven't been altered in any way
2026-03-24 22:18:46 -04:00
Bill Ferguson
732d2788ca official/scheduler - script to provide a FIFO scheduler for cooperative
Lua multitasking.
2026-03-24 21:38:20 -04:00
Bill Ferguson
04908e3f0b official/recent_bookmarks - Update the recently-used.xbel file showing
recently used files for images edited in or exported from darktable.

Current this only works on Linux
2026-03-24 21:01:20 -04:00
Bill Ferguson
3ba3facad2 official/use_paired_jpg_as_mipmap - Canon R series cameras don't
included a full size embedded jpeg so generating mipmap cache for
anything larger than mipmap 3 requires the raw to be loaded and
processed.  This script copies the paired jpeg from the RAW+JPEG
pair to the full resolution mipmap.  This can then be used to generate
the remaining mipmap cache.  The jpeg image can be discarded after
being copied to the cache or retained based on a preference in Lua
options.
2026-03-24 20:45:44 -04:00
Bill Ferguson
cb209d1d56 official/selection2collection - create a temporary collection from
a selection.  Temporary collections are removed when darktable exits.
Temporary collections can be kept by setting a preference in the Lua options.
2026-03-24 20:29:35 -04:00
Bill Ferguson
36533b1004 official/extract_burst_roll_images - script to extract Canon burst
roll images from a burst roll image container
2026-03-24 20:07:48 -04:00
Bill Ferguson
cbbf0f413f official/auto_straighten - add script to straighten image based on
roll and pitch metadata
2026-03-24 19:56:13 -04:00
Bill Ferguson
181aea5bc2 official/select_duplicates - Added script to detect and select duplicates.
Script adds a button to the select images module.
2026-03-24 17:10:08 -04:00
wpferguson
94c6dff245 Merge pull request #636 from deekayhd/rename_images_reset
enable reset in rename_images.lua
2026-03-21 10:48:09 -04:00
Bill Ferguson
79ac6fc31e tools/script_manager - added check for darktable_gui_safe to avoid
startup race condition hang
2026-03-20 16:08:20 -04:00
deekayhd
7a61d77c27 add reset of preference 2026-03-19 11:39:36 +01:00
deekayhd
2635e261a0 changes after review 2026-03-19 11:38:45 +01:00
wpferguson
d197851478 Merge pull request #637 from wpferguson/image_time_filename_sanitize
contrib/image_time - sanitize filenames so that spaces don't cause problems
2026-03-18 10:50:28 -04:00
Bill Ferguson
6f519b3ce8 contrib/image_time - sanitize filenames so that spaces don't cause
problems
2026-03-18 10:46:12 -04:00
deekayhd
03eb79e83a enable reset in rename_images.lua 2026-03-13 21:55:14 +01:00
wpferguson
fb03b49a93 Merge pull request #633 from lefth/photils-cli-path
Fix the path to photils-cli on mac

Thanks
2026-02-15 13:17:46 -05:00
Dan Zwell
c0865c37bb Fix the path to photils-cli on mac 2026-02-15 17:15:32 +08:00
Bill Ferguson
6bbb619513 tools/script_manager - Changed to allow running scripts included
with the darktable distribution.  User installed
                       scripts will override the bundled scripts
2026-02-02 19:29:37 -05:00
wpferguson
182a2c96c3 Merge pull request #630 from wpferguson/select_non_existing_speedup
Speed up select_non_existing
2026-01-28 21:22:46 -05:00
Bill Ferguson
06522802d8 contrib/select_non_existing - changed the logic to get a list of
files in the filmroll and check the
                              "known" files against the file list.
                              This cuts the number of system calls
                              from 1 per image to 1 per film roll
                              resulting an incredible speed up on
                              windows and a good speedup on other
                              platforms.
2026-01-28 21:14:39 -05:00
wpferguson
ad263fed11 Merge pull request #626 from ipalopezhentsev/patch-1
reorder lines
2026-01-04 14:17:42 -05:00
Ilya Palopezhentsev
ced60016fb reorder lines
oops, committed line before the line it should go after
2026-01-04 20:12:35 +01:00
wpferguson
1d6d0695fe Merge pull request #625 from ipalopezhentsev/patch-2
Fix enfuseAdvanced command line building bugs
2026-01-04 13:27:22 -05:00
wpferguson
fbe504b47a Merge pull request #624 from ipalopezhentsev/patch-1
fix dtutils_system.external_command ignoring bad return code on Windows
2026-01-04 13:26:52 -05:00
Ilya Palopezhentsev
7da38b3665 Fix enfuseAdvanced command line building bugs
1) enfuse has argument --contrast-min-curvature taking string with a percent sign. When that runs on Windows, it fails due to the fact that % means env var substitution in Windows command lines. we have to escape it with double percent
2) when enfuse produces a jpg, it takes argument --compression with an integer value. But the script takes it from GUI and passes as a float which breaks enfuse. 
3) When errors like these happen, the script forgets to stop darktable job leaving dangling string in UI.
2026-01-04 12:17:35 +01:00
Ilya Palopezhentsev
08fe1ba410 fix dtutils_system.external_command ignoring bad return code on Windows
On Windows, external_command calls windows_command which prepares a bat file with the command, surrounded with code page saving/restoration. If the "real" command fails, its error return code does not get anywhere and 0 is always returned, because the last line is a chcp call which always succeeds.
This breaks error logic in scripts, e.g. enfuseAdvanced, where they think everything went fine when it didn't.
2026-01-04 10:20:03 +01:00
wpferguson
088c79e140 Merge pull request #622 from dterrahe/patch-1
Update author name in x-touch.lua metadata
2026-01-01 11:47:19 -05:00
dterrahe
f06eebe316 Update author name in x-touch.lua metadata 2025-12-26 11:29:35 +01:00
wpferguson
d6a1cec9fa Merge pull request #620 from wpferguson/revert_ext_editors_sanitize
reverted export2collection sanitize for new file name
2025-12-20 19:53:12 -05:00
Bill Ferguson
e194ad2bc7 reverted export2collection sanitizing new file name as it causes a
problem on windows.
2025-12-20 19:50:24 -05:00
wpferguson
22e3c59d69 Merge pull request #611 from scorpi11/website_gallery
Website gallery replacement
2025-12-18 12:18:44 -05:00
Tino Mettler
eb743e4b3b Make some further strings transtlatable 2025-12-18 18:11:04 +01:00
Tino Mettler
7a8f3afc40 Portability: use system specific path separator, sanitize filenames 2025-12-18 17:23:00 +01:00
Tino Mettler
2b3910c698 CSS style cleanup 2025-12-18 08:19:14 +01:00
Tino Mettler
c584ee05c9 Remove unused JavaScript code and outdated comments 2025-12-18 08:02:43 +01:00
Tino Mettler
bb3a74feac CSS cleanup, separate functional and visual CSS code
Added file style.css which is aimed to contain CSS code that affects
the look of the gallery.

This could be extended to provide different or user-modified styles
to give each gallery an individual look.
2025-12-18 07:55:53 +01:00
wpferguson
90d2764a5a Merge pull request #604 from MStraeten/patch-3
Refactor x-touch script, basic AgX support
2025-12-10 20:39:29 -05:00
Tino Mettler
b0373f7166 Use subdirectories in the generated gallery
JavaScript files are now in js/, CSS files in css/, thumbnail images in
thumbnails/.
2025-12-10 08:46:24 +01:00
Tino Mettler
c6002e7eb7 Make UI text elements translatable 2025-12-10 08:46:11 +01:00
Tino Mettler
a47de82afd Show progress during thumbnail export 2025-12-08 14:53:58 +01:00
wpferguson
7f09e55525 Merge pull request #616 from wpferguson/fix_multi_os
examples/multi_os - replaced ufraw-batch with exiftool
2025-12-07 14:18:29 -05:00
wpferguson
f83ac1366a Merge pull request #617 from wpferguson/ext_editor_sanitize
ext_editor sanitize file name
2025-12-07 14:18:11 -05:00
Bill Ferguson
6cda27172e contrib/ext_editor - sanitize filename before importing to collection 2025-12-06 19:11:12 -05:00
Bill Ferguson
df78522a79 examples/multi_os - replaced ufraw-batch with exiftool to extract
the embedded jpeg or preview image.
2025-12-06 18:40:37 -05:00
wpferguson
6e64d6db6f Merge pull request #613 from wpferguson/string_lib_fixes
String lib fixes
2025-12-04 15:13:56 -05:00
Tino Mettler
7f00cb8d6c Update TODO list for the website gallery export 2025-12-04 19:09:22 +01:00
Tino Mettler
dfeff73fcd Add callback to check if export format is supported
Currently JPG, TIFF, PNG and WebP are supported.
2025-12-04 19:06:26 +01:00
Bill Ferguson
d75f00daa0 lib/dtutils/string - fixed ROLL.NAME to support the original and
the ROLL.NAME[n] variant in the same variable
                     string
2025-12-03 20:54:47 -05:00
Bill Ferguson
3e430ee2be lib/dtutils/string - converted element from string to number for use in test 2025-12-03 18:26:13 -05:00
Bill Ferguson
3856ae3449 lib/dtutils/string - added format_exposure function to take an
exif_exposure and return it as an exposure
                     string (exif_exposure = 500, string 1/500)
2025-12-02 20:40:03 -05:00
Bill Ferguson
d54a9f8b7e lib/dtutils/string - implemented variable substitution for ROLL.NAME[n] 2025-12-02 19:54:10 -05:00
Bill Ferguson
8f6cc850fb lib/dtutils/string - Added Xmp.darktable.version and Xmp.dc.[title,
description,publisher,rights,creator]
2025-12-02 18:57:05 -05:00
Bill Ferguson
e9f18a3d36 lib/dtutils/string - add IMAGE.ID and IMAGE.ID.NEXT variables 2025-12-02 18:34:18 -05:00
wpferguson
f3a89a89bd Merge pull request #612 from wpferguson/fix_apply_camera_style
official/apply_camera_style - fixed pattern matching bugs
2025-12-02 16:30:56 -05:00
Bill Ferguson
d075fb7128 official/apply_camera_style - fixed pattern matching bug that allowed
camera styles to be applied twice

                              fixed pattern matching bug affecting
                              Canon EOS R, RP, and R5

                              Added more debugging statements to
                              pattern matching functions
2025-12-02 16:23:28 -05:00
Tino Mettler
7f4bae4234 Update TODO 2025-11-26 22:31:10 +01:00
Tino Mettler
071a3527c1 Add new website gallery module to README.md 2025-11-26 21:32:19 +01:00
Tino Mettler
22c9a7cd6e Comments, remove cruft 2025-11-26 21:32:10 +01:00
Tino Mettler
12ccefcc3b Add missing copyright/license information 2025-11-26 21:31:41 +01:00
Tino Mettler
fa6014fefa Export module for a website gallery with selected images
This export module should replace the PhotoSwipe copy in darktable.

See https://github.com/darktable-org/darktable/issues/16205 for more
details.
2025-11-26 20:46:11 +01:00
wpferguson
e831f377bf Merge pull request #610 from wpferguson/fix_selection_to_pdf_open
official/selection_to_pdf
2025-11-23 21:09:02 -05:00
Bill Ferguson
d696e6e3f2 official/selection_to_pdf - added default opens for Linux, MacOS,
and Windows
2025-11-23 21:04:54 -05:00
wpferguson
db505f1e0a Merge pull request #608 from wpferguson/fix_metadata_string_substitution
lib/dtutils/string - image metadata may return a nil so check to
2025-11-14 20:04:29 -05:00
Bill Ferguson
57d15a3fa6 lib/dtutils/string - image metadata may return a nil so check to
see if there is a value and return it otherwise
                     return an empty string
2025-11-11 21:59:07 -05:00
Martin Straeten
3c131c41d1 adaption to recent agx update
renamed action and reordered sliders
2025-11-11 22:00:25 +01:00
wpferguson
4d54b5eaba Merge pull request #606 from wpferguson/add_official_regenerate_thumbnails
official/regenerate_thumbnails - add a script to fix skull thumbnails
2025-11-09 22:56:05 -05:00
Martin Straeten
ffa125b134 further improved customizability
to add a new module, simply add an entry to MODULE_PROFILES below.
direct usage of action paths from darktable's shortcut interface.
2025-11-09 20:15:34 +01:00
Bill Ferguson
dbf0c5d853 official/regenerate_thumbnails - added check to ensure job has not
been canceled.
2025-11-09 00:13:48 -05:00
Bill Ferguson
193a615772 official/regenerate_thumbnails - add a script to fix skull thumbnails
by dropping the cache for the image
                                 and regenerating it.  Adds a button
                                 to the actions on selected images
                                 module.  Also has a shortcut to
                                 apply the script by hovering and
                                 triggering the shortcut.
2025-11-08 21:27:14 -05:00
Martin Straeten
056f616283 Refactor x-touch script, basic AgX support
Refactored x-touch script to improve structure and maintainability. Mainly by adding helper functions for handling different module types.
the added support for AgX module
(credit goes to claude.ai which helped a lot )
2025-11-06 19:19:46 +01:00
wpferguson
447980ddc4 Merge pull request #603 from wpferguson/fix_stringlib_sequence_arguments
Fix sequence string substitution variable to accept starting number > 9
2025-11-02 17:05:22 -05:00
Bill Ferguson
8af5476f8d lib/dtutils/string - fixed string substitution sequence to accept
start argument of more than 1 digit
2025-11-02 16:59:32 -05:00
wpferguson
c95547caa7 Merge pull request #584 from balping/patch-1
Add manual coordinate input option to geoToolbox
2025-08-18 17:01:22 -04:00
wpferguson
b4d3b2901f Merge pull request #585 from mreiger/master
Add a script to automatically tag manually modified images
2025-08-18 16:57:00 -04:00
wpferguson
cb6b3161d8 Merge pull request #595 from sjjh/patch-1
Update README.md: added link to script_manager docs
2025-08-18 16:55:27 -04:00
wpferguson
2d82eea237 Merge pull request #594 from deekayhd/master
rename_images: error when renaming images without exif data
2025-08-18 16:53:28 -04:00
Michael Reiger
3beee93af4 Merge branch 'darktable-org:master' into master 2025-08-01 07:36:10 +00:00
sjjh
68291414f3 Update README.md: added link to script_manager docs
just for convenience: added a link to dt documenation for script manager module in the 'enable' section
2025-07-13 16:56:22 +02:00
deekayhd
5662519314 Merge pull request #1 from deekayhd/no_exif_data
rename_images: error with images without exif data
2025-07-09 21:15:42 +02:00
deekayhd
28ab065293 Merge branch 'darktable-org:master' into no_exif_data 2025-07-09 20:52:02 +02:00
wpferguson
aed3275943 Merge pull request #593 from wpferguson/592_fix_RL_out_sharp
fix RL_out_sharp crash
2025-07-05 12:43:12 -04:00
Bill Ferguson
d7aeb43340 contrib/RL_out_sharp - changed MODULE to MODULE_NAME so that message
would print and script wouldn't crash
2025-07-05 12:39:36 -04:00
deekayhd
ea79d10b11 Update string.lua
initialize datetime_taken with proper value if no exif_datetime_taken can be found (e.g. in TIFF files)
2025-07-01 13:55:47 +02:00
Michael Reiger
e4001f984f Merge branch 'darktable-org:master' into master 2025-06-26 22:13:59 +00:00
wpferguson
453d744b69 Merge pull request #588 from wpferguson/18366_fix_apply_style_camera_decoding
fix apply_camera_style style decoding
2025-06-01 17:07:36 -04:00
Bill Ferguson
b9cc0dccce official/apply_camera_style - made spaces in camera name conditional
matches as some are squashed and others
                              (Olympus) are not.
2025-06-01 16:36:52 -04:00
Michael Reiger
84fea20ee5 fixed script desription 2025-06-01 13:28:44 +02:00
Michael Reiger
e054bfc59d Added script to automatically tag manually modified images 2025-06-01 13:12:56 +02:00
Michael Reiger
8cd10dce68 Revert "Modified apply_camera_style script so that the application of a camera style does not mark the image as changed" because that attempt did not work.
This reverts commit f8d49ccd01.
2025-06-01 12:50:59 +02:00
Michael Reiger
f8d49ccd01 Modified apply_camera_style script so that the application of a camera style does not mark the image as changed 2025-05-24 19:31:23 +02:00
Balázs Dura-Kovács
25afb41924 Create tooltips for geoToolbox 2025-05-06 19:37:09 +02:00
Balázs Dura-Kovács
3001a0dc1a Add manual coordinate input option to geoToolbox 2025-05-04 23:23:33 +02:00
wpferguson
62e932990e Merge pull request #583 from wpferguson/fix_image_time_issue_582
Fix image time exiftime generation with blank fields
2025-05-01 20:40:48 -04:00
Bill Ferguson
980fe3962c image_time - added default values of 0 for fields not supplied in
set time so that an exiftime can be created.\
2025-05-01 20:37:24 -04:00
wpferguson
0043769946 Merge pull request #579 from tabac/rename-images-sequence-tooltip
Align sequence tooltip with code.
2025-04-10 20:40:31 -04:00
Tasos Bakogiannis
0cb9a8872e Update sequence variable order. 2025-04-10 19:26:45 +02:00
Tasos Bakogiannis
5309ff1726 Revert "Align sequence tooltip with code."
This reverts commit c6eee15d75.
2025-04-10 19:25:01 +02:00
Tasos Bakogiannis
c6eee15d75 Align sequence tooltip with code. 2025-04-09 23:48:48 +02:00
wpferguson
8c2d0ddf2e Merge pull request #577 from kmilos/patch-1
Fix de_DE translation charset
2025-04-02 22:24:42 -04:00
Miloš Komarčević
87d18549a6 Fix de_DE translation charset 2025-04-01 16:11:56 +02:00
wpferguson
6fdd78114f Merge pull request #576 from fjb2020/master
Fix issue #571, use of for _,image in ipairs(sel_images) do conflicts with function _(msgid)
2025-03-19 14:52:19 -04:00
fjb2020
f20721373a Fix issue #571, use of for _,image in ipairs(sel_images) do conflicts with function _(msgid) 2025-03-19 15:57:10 +00:00
wpferguson
9c0f8d63e6 Merge pull request #575 from Yurivanderburg/fix_typo_in_hugin
Fixed typo in hugin script - should resolve issue # 570

Thanks
2025-03-18 17:24:58 -04:00
Yuri van der Burg
ea2596ae13 Fixed typo in hugin script - should resolve issue # 570 2025-03-18 20:38:21 +01:00
wpferguson
a9191cf084 Merge pull request #568 from wpferguson/fix_script_manager_startup_button_states
Fix script_manager startup power button state
2025-03-08 15:52:26 -05:00
Bill Ferguson
59b5039579 tools/script_manager - Moved start_scripts() later in the script to ensure
the UI is populated before starting the scripts.  Added
                       a check to see if the script is started and if so the
                       power button is updated to show the script is running.
2025-03-08 15:46:55 -05:00
wpferguson
e070a1462c Merge pull request #553 from MStraeten/patch-2
Update x-touch.lua switch colorequal tab
2025-03-01 20:02:54 -05:00
wpferguson
faa3302a37 Merge pull request #566 from sercxanto/fix_dbmaint_log
Fix dbmaint.lua
2025-03-01 14:03:57 -05:00
wpferguson
115dbbec32 Merge pull request #563 from sercxanto/fix_version_comparison
Fix version comparison
2025-03-01 14:00:32 -05:00
Georg Lutz
9bf7efe71f Fix dbmaint.lua
"log.log_msg" triggers a lua function error, because that function does not exist.

Additionally add add set_log_level / restore_log_level to improve compatibility with other files.
2025-03-01 08:46:00 +01:00
wpferguson
3ec9dc4963 Merge pull request #565 from wpferguson/fix_autostyle_pattern_match
fix autostyle pattern match
2025-02-28 15:14:13 -05:00
Bill Ferguson
cddb86ded0 contrib/autostyle - fixed pattern match for tag result to handle spaces in the pattern 2025-02-28 15:11:15 -05:00
Georg Lutz
2624338100 Rename compare_versions to compare_api_versions 2025-02-23 18:29:34 +01:00
Georg Lutz
067fe29f76 Fix version comparison
Currently this is a textual string comparison which will break with 10.0.0 API version.
2025-02-23 12:23:37 +01:00
wpferguson
926272b1cd Merge pull request #560 from wpferguson/script_manager_delayed_start
script_manager - delay script start until all libraries are scanned
2025-02-15 20:28:26 -05:00
Bill Ferguson
363524fbaf tools/script_manager - added a start queue for scripts that start at
start up to allow for including and using libraries
                       in other than the default path.  Scripts are now
                       started after all directories are scanned instead of
                       being started as they were found.
2025-02-15 20:19:29 -05:00
wpferguson
edeceb268e Merge pull request #558 from wpferguson/fix_acs_pattern_match
Fix apply_camera_style pattern matching
2025-02-15 13:59:49 -05:00
Bill Ferguson
252cbc96f2 apply_camera_style - made spaces conditional in the pattern match
so that pattern names with spaces will match
                     models that don't have spaces
2025-02-15 13:55:08 -05:00
Bill Ferguson
b70fdc0ecf official/apply_camera_style - Fixed decoding errors caused by #18332. 2025-02-07 14:29:45 -05:00
Martin Straeten
96457bc598 Update x-touch.lua switch colorequal tab
added recommended additional shortcuts to switch between hue, saturation and brightness tabs:
midi:D0=iop/colorequal/page;previous
midi:D#0=iop/colorequal/page;next
2025-01-18 23:44:48 +01:00
wpferguson
cd31d18075 Merge pull request #552 from wpferguson/fix_windows_command_escaping
lib/dtutils/system.lua escaping fix
2025-01-17 14:22:07 -05:00
Bill Ferguson
4e140f10d1 lib/dtutils/system - fixed Lua escaping in windows_command() 2025-01-17 14:18:19 -05:00
wpferguson
593f8049a0 Merge pull request #550 from wpferguson/fix_autogrouper_use_of_group_leader
Fix AutoGrouper group mechanics
2025-01-07 13:43:13 -05:00
wpferguson
89973b3f9a Merge pull request #549 from Naahuel/master
Add change encoding to bat file for dtutils/system.lua windows_command

Thanks.
2025-01-07 13:42:54 -05:00
wpferguson
deca366ce4 Merge pull request #502 from koto/ultrahdr
Added UltraHDR export plugin to generate UltraHDR JPEG images.

Thanks
2025-01-07 13:36:22 -05:00
Bill Ferguson
914a17fb4e contrib/AutoGrouper - create group leader for first image in table.
Add grouped images to the group leader and not
                      the previous image.
2025-01-06 22:06:35 -05:00
Nahuel José
1e33ea4192 fix: add change encoding to bat file for dtutils windows_command 2025-01-06 18:47:57 -03:00
Krzysztof Kotowicz
9f3fb16161 Addressed review comments. 2025-01-04 00:49:56 +01:00
wpferguson
3f064783b7 Merge pull request #544 from MStraeten/patch-2
Enhanced x-touch.lua for colorequalizer
2025-01-02 15:45:34 -05:00
Krzysztof Kotowicz
0ab4b0cd6d Added 50ms sleep to the colorspace change.
The old profile still seems to be used occasionally without the sleep.
2025-01-02 15:21:59 +01:00
wpferguson
e2f5c993b8 Merge pull request #547 from darktable-org/update_links
README.md - updated links to current documentation
2025-01-01 17:36:24 -05:00
Bill Ferguson
3def8b43d9 README.md - fixed git clone links 2025-01-01 17:33:20 -05:00
Bill Ferguson
7b2942ca29 README.md fixed darktable-org to darktable.org 2025-01-01 17:28:03 -05:00
Bill Ferguson
bfed38f1db README.md - updated links to current documentation 2025-01-01 17:24:30 -05:00
Krzysztof Kotowicz
6d16a8778b Removed .DS_Store 2024-12-22 08:39:38 +01:00
Krzysztof Kotowicz
3c67c0c693 Create output directory if it doesn't exist. 2024-12-21 10:20:02 +01:00
Martin Straeten
1ba488d073 Enhanced x-touch.lua for colorequalizer
just adapted the colorzone stuff for color equalizer.
requires explicitly set the focus via a further switch
2024-12-15 19:08:24 +01:00
Krzysztof Kotowicz
3bdbfd12bf Apply write_image workaround only in 9.3.0 2024-12-05 00:45:34 +01:00
Krzysztof Kotowicz
77c405882d Added logging code and a workaround for https://github.com/darktable-org/darktable/issues/17528 in 9.4.0. 2024-12-03 19:49:57 +01:00
Krzysztof Kotowicz
ecc3fb29f5 Merge branch 'darktable-org:master' into ultrahdr 2024-12-03 07:20:51 +01:00
Krzysztof Kotowicz
a09140c03c Variable substitution in output directory. Force exporting files, but left the code to reconfigure it. Added more robust way to set export profile. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
95e2e4924e Added HDR only encoding variant.
- Increase timeouts to improve stability of choosing export profiles.
- Added peak nits setting.
2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
72ce0b0f93 Small fixes 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
e4da226661 Added gainmap downsampling. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
a3404b64de Renamed gainmap to "gain map" in UI. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
13ae486ab3 Verify raw file size in pixels.
Added option to keep temporary files around for analysis.
2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
268aa7df42 Removed darktable capitalization. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
ea7ac8e474 Abort and cleanup early when any of the binaries fail. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
2a4452cd20 Workaround https://github.com/darktable-org/darktable/pull/17529 for future DT versions. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
749db97dd5 Speed up export in HDR+SDR. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
84a1c1ddfe Use single channel gainmaps in SDR+HDR. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
3791b47914 Use lowercase labels to adjust to DT style. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
f6a1d1c20b Made the UltraHDR generation more robust.
- Appropriate color profiles are used when generating UltraHDR files. DT_COLORSPACE_DISPLAY_P3 is used for SDR, and DT_COLORSPACE_PQ_P3 is used for HDR, regardless of what was selected in the export module UI.
- Added correction for odd image dimensions in SDR + HDR mode.
- Added quality setting for JPEG compression
- SDR + HDR option can source from any DT image (JPEG-XL is generated on the fly).
- generation can abort on errors and display error messages.
2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
118bbad5cb Exposed sliders for crucial settings in a metadata file, removed file picker for metadata.cfg.
Temporary metadata file will be created during UltraHDR generation, using the values from the UI.
2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
44e442e7eb Support grouping images into stacks by different criteria (or forcing the selection to be interpreted as a single UltraHDR stack). 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
08726427aa Don't re-export JPEG files if their images are not altered. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
39f63cc8c5 Fallback to reading image's width/height, as sometimes final_width/height is 0. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
99c3548950 Copy only EXIF tags to UltraHDR. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
7e8340c526 Don't overwrite existing stack slots (that way the first matching image in a stack will be used, vs the last one). 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
2e95c4837b Added option to generate metadata file.
Fixed pref load / saving code defaults.
Metadata file is required in ENCODING_VARIANT_SDR_AUTO_GAINMAP mode.
2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
fe6d262d9b Display all error messages when checking configuration.
Changed config defaults to require less user input (use_original_directory=TRUE, import_to_darktable=TRUE).
Account for nil values when checking for file existence.
2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
794b12305c Fixed tooltip language. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
1a3a7f17ff Added SDR + auto gainmap generation encoding variant. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
496f8e704d Fixed doc. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
28f87566e1 Added better messaging. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
865d29fb40 Changed the UltraHDR JPEG quality to 95. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
1986f4e148 Major overhaul of the plugin. Added SDR + HDR (JPEG-XL) support. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
b963eaa38c Added encoding variant as an explicit UI setting (though only API-4 is supported now). 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
3b520d6291 Added UltraHDR export plugin to generate UltraHDR JPEG images. 2024-12-03 07:06:26 +01:00
Krzysztof Kotowicz
87ca8bde27 Fixed $(VERSION.IF_MULTI)
Variable substitution actually errored out when duplicates were present.
2024-12-02 23:45:12 +01:00
Krzysztof Kotowicz
d27df0c0ed Fixed $ROLL.NAME substitution
Accd to 57d3ad4bd3/src/common/variables.c (L660) it should be a basename of the file path.
2024-12-02 23:45:12 +01:00
Krzysztof Kotowicz
9bceb79a15 Fixed $(FILE.NAME) variable substitution
According to comments and https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/ it should be a basename.
2024-12-02 23:45:12 +01:00
Bill Ferguson
ed9f2bb0d0 official/apply_camera_sytle - ensure style is only applied to raw
images.  Fixed comment.
2024-12-02 23:45:12 +01:00
Bill Ferguson
b5c93e55d1 lib/dtutils - added a gen_uuid() function to generate a UUID string
for use as a unique identifier.
2024-12-02 23:45:12 +01:00
Bill Ferguson
9bfda93b51 official/apply_camera_style - make the translatable strings
contrib/hif_group_leader      translation safe
contrib/jpg_group_leader
2024-12-02 23:45:12 +01:00
wpferguson
daa0877b4c Merge pull request #541 from koto/patch-1
Fixed variable substitutions
2024-12-02 11:22:02 -05:00
Krzysztof Kotowicz
da275c2a66 Fixed $(VERSION.IF_MULTI)
Variable substitution actually errored out when duplicates were present.
2024-12-02 10:11:23 +01:00
Krzysztof Kotowicz
3fa913d68a Fixed $ROLL.NAME substitution
Accd to 57d3ad4bd3/src/common/variables.c (L660) it should be a basename of the file path.
2024-12-02 09:56:45 +01:00
Krzysztof Kotowicz
e4e194b0ec Fixed $(FILE.NAME) variable substitution
According to comments and https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/ it should be a basename.
2024-12-02 09:01:48 +01:00
wpferguson
c70ebf4297 Merge pull request #540 from wpferguson/fix_17914_apply_camera_style_to_raw
Ensure apply_camera_style is only applied to raw images
2024-12-01 12:50:23 -05:00
Bill Ferguson
7e3c6471ff official/apply_camera_sytle - ensure style is only applied to raw
images.  Fixed comment.
2024-12-01 12:27:16 -05:00
wpferguson
a39f6feba7 Merge pull request #536 from wpferguson/add_gen_uuid_to_dtutils_library
Add gen_uuid() function to dtutils library
2024-11-13 23:18:11 -05:00
wpferguson
ee7e92019a Merge pull request #537 from darktable-org/fix_collection_selection_strings_for_translation
Make strings translation safe
2024-11-13 23:17:51 -05:00
Bill Ferguson
96b7ba24bc official/apply_camera_style - make the translatable strings
contrib/hif_group_leader      translation safe
contrib/jpg_group_leader
2024-11-13 23:09:14 -05:00
Bill Ferguson
8581af29d2 lib/dtutils - added a gen_uuid() function to generate a UUID string
for use as a unique identifier.
2024-11-13 19:05:16 -05:00
wpferguson
ab532a724c Merge pull request #535 from Electro707/fix_file_path_sanitazation
Sanitization for file.mkdir and file.rmdir

Thanks
2024-11-03 13:25:13 -05:00
Electro707
b1e6bb8daa Added sanitization around path for mkdir and rmdir 2024-11-02 21:29:53 -04:00
wpferguson
26e797d5e5 Merge pull request #534 from wpferguson/autostyle_17731_EXIF_msg_fix
Fix contrib/autostyle confusing error message
2024-10-29 13:16:49 -04:00
wpferguson
3941c7ab52 Merge pull request #530 from wpferguson/fix_should_be_sanitized
fix string lib _should_be_sanitized
2024-10-29 13:16:35 -04:00
wpferguson
b7c8759af3 Merge pull request #523 from koto/sanitize_filename
Properly sanitize filenames in dtutils.file file_copy and file_move.

Thanks
2024-10-29 13:16:09 -04:00
Bill Ferguson
c5711880d3 contrib/autostyle - added check to make sure autostyle has been
configured so that it doesn't print out
                    a confusing error message.

                    Fixed preference to use translated script name,
                    (script_data.metadata.name).
2024-10-29 12:23:25 -04:00
Bill Ferguson
e7dbd6e369 lib/dtutils/string - fixed constant names to correctly define the
patterns as the unsafe filename characters.
2024-10-28 20:41:02 -04:00
Bill Ferguson
1b5cb1ae6e lib/dtutils/string - corrected constrant names in assignment 2024-10-28 20:18:22 -04:00
wpferguson
2953cf7e91 Merge pull request #533 from wpferguson/fix_rename_images
fix contrib/rename_images crash
2024-10-28 15:53:21 -04:00
Bill Ferguson
5e4ba9da5e contrib/rename_images - fixed call to sting library for building
the string substitution list
2024-10-28 15:49:32 -04:00
wpferguson
3a1f6bbc7a Merge pull request #531 from wpferguson/fix_17699_lua_translation
Lua Translation remaining tasks
2024-10-27 16:17:42 -04:00
Bill Ferguson
21ec2883fe Added translation to module name 2024-10-23 11:42:41 -04:00
Bill Ferguson
a0af01884d official/enfuse - fixed name and widget values to be translatable 2024-10-23 10:57:29 -04:00
Bill Ferguson
8e129d3825 tools/script_manager - added translated folder names.
cleaned up metadata handling (trimmed whitespace and comments)
		       fixed bug with folder names partially matching other folder names
2024-10-22 16:38:03 -04:00
Bill Ferguson
d123634679 Changed script names in metadata from filenames to names by
removing underscores, spelling out abbreviations, etc.
2024-10-22 00:13:30 -04:00
Bill Ferguson
13f210bec9 official/apply_camera_style - made name tranlatable and made it into
name string instead of the filename

tools/script_manager - change get_script_metadata to retreive a
                       script metadata block and store it as a
                       metadata block making the indiviual fields
                       accessible.

                       change the script buttons to use the translated
                       name if available and just the purpose for the
                       tooltip.
2024-10-21 22:52:11 -04:00
Bill Ferguson
8e4767c42a tools/script_manager - fixed translation of page %d of %d 2024-10-21 14:32:53 -04:00
Bill Ferguson
300bb9d443 lib/dtutils/string - fixed _should_be_sanitized with new patterns
to detect non safe filename characters
2024-10-19 22:04:56 -04:00
wpferguson
4f8df88fec Merge pull request #528 from wpferguson/fix_dgettext
fix message translation calls
2024-10-17 22:21:52 -04:00
Bill Ferguson
44da339202 alias gettext to darktable.gettext.gettext then change the call to gettext
to only pass the message to be translated
2024-10-17 22:17:38 -04:00
wpferguson
0e37a287ca Merge pull request #519 from koto/fix_gui_action
Fix types in examples/gui_action script.  Thanks
2024-10-16 21:21:03 -04:00
wpferguson
2af2530e45 Merge pull request #527 from wpferguson/fix_ret_val_return
fix string library missing end
2024-10-16 21:03:56 -04:00
Bill Ferguson
99eb595f4c lib/dtutils/string added end to if to ensure that ret_val has a value 2024-10-16 21:00:27 -04:00
wpferguson
31cdd3f24a Merge pull request #526 from wpferguson/substitution_library_updates
Substitution library updates
2024-10-16 20:50:15 -04:00
wpferguson
631d89e581 Merge pull request #518 from wpferguson/contrib_p_to_z
contrib/[p-z]*.lua - cleaned up translatable strings
2024-10-16 20:50:01 -04:00
wpferguson
526e97a4e1 Merge pull request #517 from wpferguson/contrib_g_to_o
contrib/[g-o]*.lua - clean up translatable strings
2024-10-16 20:49:46 -04:00
Bill Ferguson
a835ab89bc lib/dtutils/string - added new variable. Removed invalid variables.
Fixed build_substitute list call.  Fixed invalid
                     category return.
2024-10-16 00:42:08 -04:00
Bill Ferguson
da7674ca51 lib/dtutils/string - updated tooltip to use same translatable strings
as gui/gtkentry.c for consistency and eas of
                     translation
2024-10-14 12:54:13 -04:00
Bill Ferguson
0e535b850c Incorporated TurboGit's suggestions 2024-10-12 20:41:02 -04:00
Bill Ferguson
64840089e9 clean up a few issues with parentheses and missed strings. 2024-10-12 20:28:01 -04:00
Krzysztof Kotowicz
d754e37cf4 Properly sanitize filenames in dtutils.file file_copy and file_move. 2024-09-23 10:42:31 +02:00
Krzysztof Kotowicz
f6cb8091e7 Fix types in examples/gui_action script. 2024-09-20 15:07:37 +02:00
wpferguson
e359567620 Merge pull request #503 from wpferguson/apply_camera_styles
apply camera style
2024-09-17 18:33:55 -04:00
Bill Ferguson
36a6a54364 official/apply_camera_string - clean up translatable strings 2024-09-17 18:29:05 -04:00
Bill Ferguson
ecff920537 contrib/[p-z]*.lua - cleaned up translatable strings 2024-09-17 15:29:16 -04:00
Bill Ferguson
842f2fca66 contrib/[g-o]*.lua - clean up translatable strings 2024-09-16 21:16:34 -04:00
wpferguson
ee9384b9a7 Merge pull request #516 from wpferguson/contrib_a_to_f
contrib/[a-f]*.lua - fixed translatable strings to prepare for darktable translation
2024-09-16 20:06:38 -04:00
wpferguson
272c35499a Merge pull request #515 from wpferguson/tools_messages
updated tools/*.lua messages for translation
2024-09-16 20:06:18 -04:00
Bill Ferguson
4165d46b33 tools/script_manager - fixed capitalization 2024-09-16 20:04:29 -04:00
Bill Ferguson
5bed7c914c contrib/[a-f]*.lua - fixed translatable strings to prepare for darktable
translation
2024-09-13 19:47:44 -04:00
Bill Ferguson
684178b27e tools/script_manager - cleaned up translatable strings. Changed displayed
name to scripts.
2024-09-13 14:48:30 -04:00
Bill Ferguson
a72abe0ad0 tools/executable_manager - cleaned up translatable strings. Changed
displayed name to executables.
2024-09-13 14:47:37 -04:00
Bill Ferguson
8863f6ade7 tools/get*.lua - updated translable stirngs 2024-09-13 14:46:47 -04:00
wpferguson
e0463eba83 Merge pull request #512 from wpferguson/rename_images_variable_replacement
Rename images variable replacement
2024-09-13 14:14:36 -04:00
wpferguson
3a2ff7a7b9 Merge pull request #513 from wpferguson/example_messages
examples/*.lua - cleaned up translatable messages
2024-09-13 14:13:33 -04:00
Bill Ferguson
ce7ddfa472 examples/*.lua - cleaned up translatable messages 2024-09-10 21:19:37 -04:00
Bill Ferguson
3fb00586c3 contrib/rename_images - replaced pattern substitution code with the
better implementation in the string library.
2024-09-10 16:36:54 -04:00
Bill Ferguson
167620fadf lib/dtutils/string - Added get_substitution_tooltip() function to return
the substitution pattern tooltip so it only has to be
                     translated in one place.

                     Spelled substitution properly to that calls to
                     build_substitution_list succeed.
2024-09-10 16:34:36 -04:00
Bill Ferguson
f0bdc890c1 official/apply_camera_styles - updated message to specify the camera
styles are darktable camera styles
2024-09-09 22:50:49 -04:00
wpferguson
abdfc9de0b Merge pull request #510 from wpferguson/transllist
Fix string errors preventing translation
2024-09-08 23:51:05 -04:00
Bill Ferguson
c65e070a76 contrib/geoJSON - don't translate the module name 2024-09-08 23:43:39 -04:00
Bill Ferguson
05c57c0247 contrib/geoTooxbox - fix latitude and longitude labels 2024-09-08 23:42:45 -04:00
Bill Ferguson
5be3d6986e official/selection_to_pdf - dont translate internal module name 2024-09-08 23:24:33 -04:00
Bill Ferguson
f0e3627800 contrib/exportLUT - fixed file chooser labels for translation 2024-09-08 22:54:42 -04:00
Bill Ferguson
ea08557a0e tools/script_manager - removed translation of empty string 2024-09-08 22:49:50 -04:00
wpferguson
ac43afd8c9 Merge pull request #507 from darktable-org/fix_contrib_rate_group
contrib/rate_group - fixed formatting of tooltips
2024-09-03 19:16:19 -04:00
Bill Ferguson
ee98f96a4f contrib/rate_group - fixed formatting of tooltips 2024-09-03 19:14:31 -04:00
wpferguson
a33eeddbd6 Merge pull request #504 from darktable-org/darktable_translate_official
prepare official scripts for darktable translation
2024-08-31 21:44:41 -04:00
wpferguson
746c6a07c4 Merge pull request #505 from darktable-org/script_manager_running_script
tools/script_manager - restore script_manager_running_script
2024-08-31 21:43:55 -04:00
Bill Ferguson
96fee48ef2 tools/script_manager - restore script_manager_running_script to
"script_manager" after changing it to start
                       a script
2024-08-31 21:39:50 -04:00
Bill Ferguson
6c686128d2 official/*.lua - Removed bindtextdoamin so that darktable can handle
the translation
2024-08-31 16:48:36 -04:00
Bill Ferguson
6ca8f0034e official/apply_camera_style - apply a darktable camera style to an
image based on maker and model.  Styles
                              may be applied to selections, collections,
                              and on newly imported images.
2024-08-31 15:53:38 -04:00
wpferguson
3d1f12fe44 Merge pull request #500 from darktable-org/fix_create_tmp_file_windows
lib/dtutils/file - os.tmpname now works correctly on windows, removed
2024-08-13 17:50:23 -04:00
Bill Ferguson
a079860cd8 lib/dtutils/file - os.tmpname now works correctly on windows, removed
check and fix
2024-08-13 17:46:28 -04:00
wpferguson
b07c680332 Merge pull request #499 from wpferguson/update_3rd_party
README.md - updated 3rd party scripts
2024-08-10 23:48:24 -04:00
Bill Ferguson
200fc059c0 README.md - updated 3rd party scripts 2024-08-10 23:38:06 -04:00
wpferguson
aeb1f2226c Merge pull request #498 from darktable-org/fix_translatable_strings
fixed translatable strings for inclusion into darktable
2024-08-08 19:47:26 -04:00
Bill Ferguson
46e729b40e fixed translatable strings for inclusion into darktable 2024-08-08 19:43:33 -04:00
wpferguson
66b613dc29 Merge pull request #497 from wpferguson/remove_quote_windows_command
lib/dtutils/system - remove _quote_windows_command
2024-07-21 22:47:05 -04:00
Bill Ferguson
535ba78a06 lib/dtutils/system - remove _quote_windows_command 2024-07-21 22:43:59 -04:00
Bill Ferguson
cc11c1c3b8 tools/script_manager - set log level to warn instead of debug 2024-07-18 12:42:12 -04:00
wpferguson
fba639e98a Merge pull request #496 from wpferguson/desanitize_libs
Desanitize libs
2024-07-13 14:50:47 -04:00
Bill Ferguson
3e37c06b84 lib/dtutils/string - Added check to determine if string needs to be
sanitized.  Check looks for unprintable characters
                     and spaces.
2024-07-13 14:44:01 -04:00
Bill Ferguson
1b272be5f5 various scripts - reverted the calls to io_popen and os_execute back
to the respective lua calls.
2024-07-13 12:48:37 -04:00
Bill Ferguson
9f423eeef3 lib/dtutils/file - reverted io_popen and os_execute wrappers as
lib/dtutils/system   they don't work in this situation (no control
                     over the input strings)
2024-07-13 12:46:34 -04:00
wpferguson
dc81c0b1c5 Merge pull request #494 from wpferguson/add_preserve_metadata_to_RL_out_sharp
RL_out_sharp - preserve metadata
2024-07-10 15:22:35 -04:00
Bill Ferguson
e8a99c8396 contrib/RL_out_sharp - add function to preserve the exported images
metadata in the sharpened image using exiftool
2024-07-10 15:17:35 -04:00
wpferguson
7c93868bf0 Merge pull request #491 from wpferguson/fix_quote_windows_command_call
lib/dtutils/system - fixed typo in quote_windows_command call
2024-07-08 11:46:30 -04:00
Bill Ferguson
36023d8d4f lib/dtutils/system - fixed typo in quote_windows_command call 2024-07-08 11:43:42 -04:00
wpferguson
97aa4ce363 Merge pull request #490 from wpferguson/fix_email
fix email address
2024-06-30 12:46:51 -04:00
Bill Ferguson
316a89d6a8 contrib/auto_snapshot - fixed email address
contrib/dbmain
2024-06-30 12:44:02 -04:00
wpferguson
d4a479b025 Merge pull request #486 from wpferguson/ensure_renamed_image_visible
ensure renamed image is visible
2024-06-21 10:04:06 -04:00
wpferguson
dd22c947c6 Merge pull request #487 from wpferguson/fix_fujifilm_dynamic_range_duplicates
fujifilm_dynamic_range - fix duplicate processing
2024-06-21 10:03:45 -04:00
wpferguson
09f47b7b09 Merge pull request #488 from wpferguson/auto_snapshot
auto_snapshot
2024-06-21 10:03:30 -04:00
Bill Ferguson
ba94914c0e contrib/auto_snapshot - added Lua option to create always create a snapshot
when opening an image in darkroom, even if it is
                        altered
2024-06-20 18:09:10 -04:00
Bill Ferguson
3bb4e9c59a contrib/auto_snapshot - take a snapshot automatically when opening an image in darkroom 2024-06-20 17:46:54 -04:00
Bill Ferguson
f8c076f308 contrib/fujifilm_dynamic_range - added check for duplicates after an
image is processed.  If duplicates are
                                 found the exposure bias is applied to
                                 them.
2024-06-19 20:50:54 -04:00
Bill Ferguson
94f41db6fc contrib/rename_images - make sure the renamed image, or first renamed
image of a group is visible after renaming.
2024-06-19 11:41:54 -04:00
wpferguson
5727b2d251 Merge pull request #485 from wpferguson/script_manager_api_solution
script_manager api check solution
2024-06-18 00:34:30 -04:00
Bill Ferguson
c95f8a23b2 lib/dtutils/log - fix screen print check 2024-06-18 00:30:34 -04:00
Bill Ferguson
88e4ac1a64 tools/script_manager - changed API check from hard (crash) to soft
which allows script_manager to fix tthe problem
                       by checking out the correct version of the
                       scripts for the API version.
2024-06-18 00:29:27 -04:00
wpferguson
a3fe02bf95 Merge pull request #482 from wpferguson/fix_non_windows_command_sanitize
fix non windows external command sanitize
2024-06-14 12:37:16 -04:00
Bill Ferguson
ff20098175 lib/dtuils/system - removed ds.sanitize wrapped around non windows command. 2024-06-14 12:33:35 -04:00
wpferguson
cdf4b743dd Merge pull request #479 from wpferguson/script_mangaer_4_8
New script_manger UI for darktable 4.8
2024-06-09 17:36:52 -04:00
Bill Ferguson
3efc1805ca tools/script_manager - new user interface for darktable 4.8
wrappers added for io.popen and os.execute
                       to handle windows special characters and
                       spaces
2024-06-09 17:33:06 -04:00
wpferguson
0b46cf8abf Merge pull request #477 from darktable-org/revert-474-script_manager_new_ui
Revert "script_manager new UI"
2024-06-09 16:42:17 -04:00
wpferguson
28622e620e Revert "script_manager new UI" 2024-06-09 16:42:05 -04:00
wpferguson
f21a117d29 Merge pull request #474 from wpferguson/script_manager_new_ui
script_manager new UI
2024-06-09 16:25:50 -04:00
wpferguson
35cbff03dd Merge pull request #475 from wpferguson/doc_update
Update documentation
2024-06-08 22:33:57 -04:00
Bill Ferguson
5571e2f2dc ChangeLog - Updated with the latest repository changes
README - Updated the script additions
2024-06-08 21:51:37 -04:00
wpferguson
3c77f2e863 Merge branch 'master' into script_manager_new_ui 2024-06-07 19:31:25 -04:00
Bill Ferguson
4ff1111341 tools/script_manaager added sanitized io.popen and os.execute functions
for windows character and space issues.  Cleaned
                      up translatable strings to make future translation
                      easier.
2024-06-07 15:23:06 -04:00
wpferguson
abfa8e9878 Merge pull request #473 from wpferguson/fix_472_fujifilm_ratings
Fix fujifilm ratings
2024-06-06 13:02:29 -04:00
wpferguson
bbe9109fca Merge pull request #469 from wpferguson/win_sanitize
Sanitize windows command interactions
2024-06-06 13:01:55 -04:00
wpferguson
34d5df65b2 Merge pull request #468 from wpferguson/dbmaint
database maintenance script
2024-06-06 13:01:19 -04:00
Bill Ferguson
539f25517b contrib/fujifilm_ratings - added check to ensure the image is a
fujifilm file.
2024-06-05 22:16:40 -04:00
wpferguson
5b55a3116d Merge pull request #470 from wpferguson/windows_executuable_manager_fix
executable_manager - fix position not visible on windows
2024-06-05 14:51:10 -04:00
Bill Ferguson
3e032616a0 tools/executable_manager - fix position not visible on windows 2024-06-05 14:49:06 -04:00
Bill Ferguson
9f2dbef4f0 lib/dtutils/system added local helper function quote_windows_command() to
wrap a windows command in quotes.  Added a sanitize step
                   to windows_external_command() plus quote_windows_command()
                   to deal with all the windows username possibilities
2024-06-05 12:15:01 -04:00
Bill Ferguson
2abb83b485 tools/get_lib_manpages - more code cleanup 2024-06-05 00:09:18 -04:00
Bill Ferguson
32e966f8d9 tools/git_lib_manpages code cleanup 2024-06-05 00:01:47 -04:00
Bill Ferguson
67538a4de2 lib/dtutils/system code cleanup 2024-06-05 00:01:17 -04:00
Bill Ferguson
96471ae7e7 lib/dtutils - fixed documentation error 2024-06-05 00:00:43 -04:00
Bill Ferguson
038418b89f contrib/autostyle replaced io.popen functions with wrapper
contrib/color_profile_manager    to properly quote windows commands so
contrib/fujifilm_dynamic_range   that they are sanitized for special
contrib/fujifilm_ratings         characters and spaces
contrib/geoJSON_export
contrib/geoToolbox
contrib/image_stack
contrib/image_time
contrib/kml_export
official/enfuse
tools/executable_manager
tools/get_lib_manpages
tools/get_libdoc
2024-06-04 23:43:21 -04:00
Bill Ferguson
fe48d7498c lib/dtutils/file - replaced os.execute and io.popen with wrapper functions
to sanitize windows calls
2024-06-04 23:41:07 -04:00
Bill Ferguson
2f330ea9cc lib/dtutils/system - added wrapper functions io_popen and os_execute
to wrap io.popen and os.execute system calls
                     respectively.  These wrapper functions provide
                     the necessary quoting on windows to get handle
                     strings with spaces and special characters.
2024-06-04 23:16:19 -04:00
Bill Ferguson
81b0f1fb48 contrib/dbmaint - remove database records for missing film rolls and images. 2024-06-03 18:08:30 -04:00
Bill Ferguson
9088bc9144 tools/script_manager - make the constants <const> to ensure they
stay that way
2024-06-03 14:48:44 -04:00
wpferguson
c3ce8d532d Merge pull request #467 from koto/open_in_file_explorer_osx
Support selecting multiple files in MacOS in OpenInExplorer.
2024-05-30 13:36:47 -04:00
Krzysztof Kotowicz
f1d8ad7fce Fix quoting. 2024-05-27 18:26:35 +02:00
Krzysztof Kotowicz
a3b295112f Update OpenInExplorer.lua
In OpenInExplorer, use AppleScript instead of `open -R` for OSX.

That way multiple files can be selected.
2024-05-27 13:03:00 +02:00
wpferguson
c36618f5fb Merge pull request #466 from wpferguson/more_string_doc_updates
lib/dtutils/string - more embedded doc updates
2024-05-20 23:45:07 -04:00
Bill Ferguson
fecbb39347 lib/dtutils/string - more embedded doc updates 2024-05-20 23:43:47 -04:00
wpferguson
cbe2c5329a Merge pull request #412 from wpferguson/add_substitution_to_string_lib
Add substitution to string lib
2024-05-20 22:18:01 -04:00
Bill Ferguson
dcbe575e9c lib/dtutils/string - updated embedded documentation 2024-05-20 22:13:29 -04:00
Bill Ferguson
417c165d16 lib/dtutils/string - updated documentation 2024-05-20 18:18:46 -04:00
Bill Ferguson
232baab86b lib/dtutils/string - removed debugging statements 2024-05-20 10:07:13 -04:00
Bill Ferguson
3ed4219e6c lib/dtutils/string - added [] to sanitize_lua().
added 4.8 syntax to CATEGORY

                     added 4.8 SEQUENCE changes (syntax, start, width)
2024-05-20 00:56:01 -04:00
Bill Ferguson
97e33672ea lib/dtutils/string - fixed default formatting of sequence 2024-05-19 23:04:01 -04:00
Bill Ferguson
7b2018412a lib/dtutils/string - Cleaned up code to make it more readable.
Fixed VERSION_IF_MULTI.  Added safety check
                     in category to make sure we don't try and
                     read a tag field that doesn't exist.
2024-05-18 22:01:10 -04:00
Bill Ferguson
3e2afffed8 lib/dtutils/string - Added categories to substitution functionality.
This makes it feature complete.  Some substitutions
                         aren't implemented since the current use case for
                         this is creating export directories.  The results
                         from the not implemented substitutions wouldn't
                         work well in path/file names (such as comma
                         separated lists or newlines).
2024-05-18 21:59:11 -04:00
wpferguson
1399e8a4b4 Merge pull request #465 from wpferguson/fix_script_manager
Fix script_manager crash
2024-05-16 11:47:29 -04:00
Bill Ferguson
5c3b2777f0 tools/script_manager - fixed gettext.bindtextdomain 2024-05-16 11:44:35 -04:00
Bill Ferguson
bf138abeb2 tools/script_manager - removed debugging statement 2024-05-15 21:36:15 -04:00
Bill Ferguson
284c0f9714 tools/script_manager - fixed metadata decoding to get rid of spurious
commas
2024-05-15 21:33:29 -04:00
wpferguson
e41029d3f4 Merge pull request #464 from wpferguson/implement_script_metadata
Implement script metadata
2024-05-15 21:27:54 -04:00
Bill Ferguson
55e68f8cd0 Added metadata fields to scripts that script_manager can access and
display as a tooltip.

Added script_manager integration to those scripts that didn't have it.
2024-05-15 21:23:13 -04:00
wpferguson
ac945b9843 Merge pull request #455 from wpferguson/prep_translation
Prepare translatable strings, step 1
2024-05-15 15:18:11 -04:00
Bill Ferguson
f6916c3a15 tools/script_manager - lower cased mixed case buttons and headings 2024-05-15 14:51:14 -04:00
Bill Ferguson
cd3788f012 all scripts - added bindtextdomain back in because 4.8 isn't ready to
handle the translations yet.  In 5.0 this will be removed
              and darktable will handle translating the strings.
2024-05-14 22:39:22 -04:00
wpferguson
ebd2cd7bf7 Merge pull request #462 from menschmaschine/master
Cropping guide for German passport and ID card photos.
2024-05-11 20:09:45 -04:00
Christian Sueltrop
52b4508e08 Added LUA script "passport_guide_germany.lua", which implements a cropping guide for passport photos according to the standards for German passport and ID card photos. 2024-05-06 21:53:46 +02:00
Bill Ferguson
b1b3392b92 tools/script_manager removed color interface check button. Color can
be specified in the CSS using the tags that are
                     present if desired.

                    Cleaned up formatting of configuration items.

                    Added a few debugging messages.
2024-04-09 21:41:42 -04:00
wpferguson
19fdbc14c5 Merge pull request #461 from wpferguson/reimplement_ensure_lib_in_path
reimplemented ensure_lib_in_path with a windows aware path specification
2024-04-08 23:33:12 -04:00
Bill Ferguson
63d5aad465 tools/script_manager reimplemented ensure_lib_in_path with a windows
aware path specification
2024-04-08 23:30:30 -04:00
wpferguson
9d6ff85893 Merge pull request #458 from wpferguson/revert_ensure_lib_in_path
revert ensure_lib_in_path
2024-04-06 21:51:26 -04:00
Bill Ferguson
125af63bda tools/script_manager - revert ensure_lib_in_path because of windows problems. 2024-04-06 21:48:51 -04:00
Bill Ferguson
ddf0c89402 tools/script_manager - cleaned up layout of panels and added some
spacing so what is being operated on is more
                         appearent
2024-04-06 21:28:58 -04:00
Bill Ferguson
3b0ff3475a tools/script_manager added blank lines to improve code readability 2024-04-05 21:12:45 -04:00
Bill Ferguson
401509cfe4 tools/script_manager added parser for script_data.metadata blocks in
scripts.  Parsed data block is used as the tooltip.
                      Fallback is the comment block at the top of the
                      script.
2024-04-05 20:38:10 -04:00
Bill Ferguson
9c4e906033 data/icons - added 20px icons 2024-04-05 20:37:34 -04:00
wpferguson
65c22e475a Merge pull request #457 from wpferguson/script_manager_lib_search_path
script_manager - support libraries in "local" directories
2024-04-03 13:54:46 -04:00
Bill Ferguson
a11726889d tools/script_manager - add support for "local" libraries by adding
entries in the package.path search path if
                       required
2024-04-02 19:09:31 -04:00
Bill Ferguson
e78e718769 tools/script_manager - allow for inclusion of directory/package specific
libraries by adding "local" libraries to the search
                       path to ensure they are found when needed.
2024-04-02 17:54:10 -04:00
Bill Ferguson
86ee8adbce tools/script_manager - changed category/categories to folder/folders to
accurately refrect reality and to open the possibility
                       of actually categorizing scripts.
2024-04-01 18:26:41 -04:00
wpferguson
40949b3855 Merge pull request #456 from wpferguson/fix_NaN
examples/gui_action.lua - changed nan to NaN
2024-03-29 16:05:22 -04:00
Bill Ferguson
06ab263e74 examples/gui_action.lua - changed nan to NaN 2024-03-29 16:04:07 -04:00
wpferguson
bccc86c70a Merge pull request #362 from dterrahe/lua_action
simple module to call dt_action_process
2024-03-29 16:01:37 -04:00
Bill Ferguson
231e9b352b changed translatable strings to lower case 2024-03-29 14:17:19 -04:00
Bill Ferguson
e47ab34745 changed translatable strings to lower case 2024-03-29 13:10:41 -04:00
Bill Ferguson
1fd2ca20ab changed translatable strings to lower case 2024-03-29 12:55:08 -04:00
Bill Ferguson
05a73d8810 changed translatable strings to lower case 2024-03-29 12:45:28 -04:00
wpferguson
12640c7d29 Merge pull request #454 from mime01/script_manager
tools/script_manager - fixed crash
2024-01-28 12:40:44 -05:00
Michael Meister
febc3b1659 tools/script_manager - fixed crash
The script manager crashes if a LUA script was not copied to a
subfolder but directly into the LUA directory. The reason is that
the regular expression, which among other things extracts the
category of the script from its file path, does not match and thus
the category is null. To prevent this, a null check has been added.
2024-01-27 12:27:40 +01:00
wpferguson
9fdd69556c Merge pull request #453 from wpferguson/452_fix
don't set lib visibility unless we are in lighttable view
2024-01-24 13:18:59 -05:00
Bill Ferguson
4735be470a tools/script_manager - don't set lib visibility unless we are in
lighttable view
2024-01-24 13:16:42 -05:00
Bill Ferguson
66b33d6f48 updated contrib scripts to use darktable translation facilities 2024-01-20 20:22:32 -05:00
Bill Ferguson
37dba718a2 updated official scripts to use darktable translation facilities 2024-01-20 15:25:16 -05:00
Bill Ferguson
c11845cbf9 updated example scripts to use darktable translation facilities 2024-01-19 18:57:18 -05:00
Bill Ferguson
f9fe33f930 updated script_manager and executable_manager to use darktable
translation facilities.  Changed translatable messages with
arguments to use string.format for one complete string versus snippets.

Removed gen_i18n_mo since we will be using darktable's translation
facilities and there's no need to generate lua specific mo files.
2024-01-19 15:24:56 -05:00
wpferguson
3b8e802e42 Merge pull request #444 from MStraeten/patch-1
Update x-touch.lua with support for primaries slider
2024-01-15 11:11:47 -05:00
Martin Straeten
f54d449316 increase required api version
… to match darktable 4.6.x
2024-01-15 07:01:43 +01:00
wpferguson
1b0fe4d7b9 Merge pull request #449 from wpferguson/cycle_group_leader
cycle group leader
2024-01-14 22:33:13 -05:00
Bill Ferguson
cf1dca9e1a contrib/cycle_group_leader.lus - adds shortcut to change the group
leader to the next image in a group
                                 wrapping around when the end of the
                                 group is reached.
2024-01-14 22:29:27 -05:00
wpferguson
45d54ef4e8 Merge pull request #448 from darktable-org/revert-447-change_group_leader
Revert "Change group leader"
2024-01-14 22:14:03 -05:00
wpferguson
8bfab0325b Revert "Change group leader" 2024-01-14 22:13:38 -05:00
wpferguson
4b99d00ab5 Merge pull request #447 from wpferguson/change_group_leader
Change group leader
2024-01-14 21:25:00 -05:00
Bill Ferguson
a5961eb066 contrib/change_group_leader.lua - adds a shortcut to cycle through
the images in a group making the
                                  next on the new group leader.
                                  When the end of the group is reached
                                  the first image in the group is
                                  selected.
2024-01-14 21:19:09 -05:00
wpferguson
95e19ef163 Merge pull request #446 from wpferguson/hif_group_leader
Make hif image group leader for raw + hif pairs
2024-01-14 17:31:07 -05:00
wpferguson
d4eb6577bf Merge pull request #445 from wpferguson/jpg_group_leader
Make jpg group leader for raw + jpg pairs
2024-01-14 17:30:52 -05:00
Bill Ferguson
294b693905 contrib/hif_group_leader.lua - make the hif image the group leader
for raw + hif image pairs.  Works
                               on import, selection, and collection.
2024-01-14 17:15:28 -05:00
Bill Ferguson
23835edc1f contrib/jpg_group_leader.lua - New script to make jpg images the
group leader for raw + jpg image
                               combinations.
2024-01-14 17:10:43 -05:00
Martin Straeten
5f29211f79 Update x-touch.lua with support for primaries slider
maps knobs to the primaries sliders
requires darktable 4.6.x
2024-01-11 20:42:32 +01:00
wpferguson
025f4299ea Merge pull request #435 from ddittmar/feature/select_non_existing
select non-existing images
2023-11-10 18:42:01 -05:00
ddittmar
dc50d40886 select non-existing images 2023-10-28 11:14:51 +02:00
wpferguson
867c49b1b9 Merge pull request #433 from wpferguson/script_manager_quote_fix
tools/script_manager - fixed quote
2023-10-17 16:04:42 -04:00
Bill Ferguson
13f02c95ea tools/script_manager - fixed quote with a " instead of s 2023-10-17 16:02:27 -04:00
wpferguson
d55dae73f0 Merge pull request #431 from wpferguson/script_manager_username_space_fix
Fix script_manager doesn't start on windows when username has a space
2023-10-16 22:44:10 -04:00
Bill Ferguson
34b9831472 tools/script_manager.lua - wrapped the scipt_dir in quotes to handle
the space in username problem on windows.
2023-10-16 22:41:12 -04:00
wpferguson
014a0fc1c9 Merge pull request #430 from wpferguson/explicitly_set_false
Explicitly set non running scriopts to FALSE
2023-09-20 13:54:02 -04:00
Bill Ferguson
bf17dccd69 tools/script_manager.lua - Explicitly set non running scriopts to FALSE
instead of relying on no value being intrepreted
                           as false.
2023-09-20 13:48:18 -04:00
wpferguson
890168d0d0 Merge pull request #426 from wpferguson/425_fix_executable_manager_not_appearing
Change window position of executable_manager and script_manager
2023-08-18 12:27:28 -04:00
Bill Ferguson
191aecacdf tools - Moved script_manager to bottom of lower left panel and
moved executable_manager to middle of lower left panel
        to work around windows GTK window placement error.
2023-08-18 12:23:24 -04:00
wpferguson
44ec01b136 Merge pull request #424 from wpferguson/fix_check_for_updates_comment
official/check_for_updates - updated instructions for additional modules
2023-07-17 17:41:10 -04:00
Bill Ferguson
a177da2040 official/check_for_updates = updated instructions for additional modules
required
2023-07-17 17:39:24 -04:00
wpferguson
c899040688 Merge pull request #423 from wpferguson/update_flatpak_readme
README.md - updated (fixed typos) echo commands to build luarc file
2023-07-16 13:24:56 -04:00
Bill Ferguson
e8af665b3c README.md - updated (fixed typos) echo commands to build luarc file 2023-07-16 13:22:06 -04:00
wpferguson
875734d455 Merge pull request #422 from wpferguson/fix_script_data_show
Fix script data show
2023-07-16 13:08:05 -04:00
Bill Ferguson
14d5a79db2 Added script_data.show to script_data blocks 2023-07-16 13:04:00 -04:00
Bill Ferguson
e1b2715ee9 contrib/gpx_export - added show function to script_data to fix #418 and #419. 2023-07-16 12:50:28 -04:00
wpferguson
7ecd5ca55d Merge pull request #421 from wpferguson/script_manager_updates
Script manager updates
2023-07-15 20:00:24 -04:00
wpferguson
931e7a4d37 Merge pull request #416 from wpferguson/check_max_api_version
Add check for max api version
2023-07-15 19:58:57 -04:00
Bill Ferguson
1792410efe lib/dtutils.lua - corrected test 2023-07-15 19:52:39 -04:00
wpferguson
8cbd9224f1 Merge pull request #377 from spaceChRiS/filter_manager_tooltip
Added tooltip to filter manager to better match new import scheme.
2023-07-14 17:40:10 -04:00
Bill Ferguson
24b552d619 lib/dtutils.lua - fix typo from testing 2023-07-12 13:18:01 -04:00
Bill Ferguson
68ae9326bf tools/script_manager.lua - added translation to preference setting 2023-07-12 12:37:14 -04:00
Bill Ferguson
b4626f2787 lib/dtutils.lua - changed log message to error on fail to load. Added
printing of the returned error.
2023-07-04 15:57:12 -04:00
Bill Ferguson
2fc8ed78e4 tools/script_manager.lua - added check to ensure script_data.show() runction
was passed as part of the script_data table
                           before trying to use it. Fixes #418.
2023-07-03 23:44:13 -04:00
Bill Ferguson
236b25a235 tools/script_manager.lua - clean up formatting and documentation 2023-07-03 23:42:47 -04:00
Bill Ferguson
a4f6af73ec tools/script_manager.lua - enabled log levels in callback routines 2023-06-24 19:56:51 -04:00
Bill Ferguson
fad74d6438 tools/script_manager.lua - cleaned up deactivate comments 2023-06-23 19:09:57 -04:00
Bill Ferguson
3e2d4c956d tools/script_manager.lua - Documented more fields for the sm.scripts table
Removed extra declaration of check_update preference
2023-06-22 16:04:26 -04:00
wpferguson
cd1d623390 Merge pull request #409 from dterrahe/x_touch_mimic
example slider mimic script for x-touch mini
2023-05-30 18:38:48 -04:00
Bill Ferguson
9e0374d597 lib/dtutils.lua - fix typos 2023-05-06 14:56:58 -04:00
Bill Ferguson
e47672b495 lib/dtutils.lua - added delay in check_min_api_version to sllow display of the error
message before script_manager displays an error message.
2023-05-06 14:36:22 -04:00
Bill Ferguson
1905be8577 lib/dtutils.lua - added check_max_api_version to handle the case where part of
the API is removed as in the case of the darktable.gui.libs.fixter functions.
2023-05-06 14:31:15 -04:00
dterrahe
4726821e7d Fix 8th knob 2023-05-05 23:19:00 +01:00
dterrahe
7ac37ddc1a Probe individual mask sliders 2023-05-03 23:43:01 +01:00
Bill Ferguson
ad10542b30 lib/dtutils/string - Added variable substitution functions that
are equivalent to the darktable variable
                     substitution functions.
2023-03-26 14:55:40 -04:00
Bill Ferguson
5331a38f10 lib/dtutils/string - added % and + to sanitize_lua() to prevent
pattern matching problems
2023-03-25 17:05:11 -04:00
Bill Ferguson
c9943a2065 lib/dtutils/string - Added logging capability with the ability to
set library logging level from a user script
2023-03-25 17:02:48 -04:00
dterrahe
3889742e06 add x-touch mini script documentation 2023-03-25 18:49:55 +00:00
dterrahe
6bf3706455 add x-touch color calibration customisation 2023-03-25 18:14:19 +00:00
wpferguson
612ecaff6c Merge pull request #411 from wpferguson/doc_update
Updated documentation
2023-03-25 13:25:45 -04:00
Bill Ferguson
a85364ecff Updated documentation 2023-03-25 13:24:04 -04:00
wpferguson
c0af03c503 Merge pull request #408 from wpferguson/move_filename_string_functions_to_strings_library
Move filename/path/extension string functions to string library
2023-03-25 13:22:50 -04:00
wpferguson
9b61e4793b Merge pull request #410 from darktable-org/script_manager_running_script
script_manager darktable shortcut integration
2023-03-25 13:07:00 -04:00
wpferguson
75a19e74a0 Merge branch 'master' into script_manager_running_script 2023-03-25 13:03:55 -04:00
Bill Ferguson
21d7e72b0c tools/script_manager - resolved conflicts 2023-03-25 13:01:40 -04:00
Bill Ferguson
05d555eae5 tools/script_manager - let the shortcut system know that script_manager
widgets belong to script_manager
2023-03-25 12:46:59 -04:00
dterrahe
8e412c3eb3 example slider mimic script for x-touch mini 2023-03-25 10:59:19 -04:00
Bill Ferguson
80147a9ebc lib/dtugiles.file - Moved filename/path/extension string functions to string library. Left
the existing function calls in place for backward compatibility.  They
                      now call the new functions in the string library

lib/dtutils.string - Moved the filename/path/extension string functions to the string library
                     to prevent a circular library depnedency.  The file library includes the
                     string library.  If the string library needed to manipulate filename
                     related strings then the string library would need to include the
                     file library which would lead to a stack overflow.
2023-03-24 13:40:34 -04:00
Bill Ferguson
3f6a3e25f1 added global variable script+_manager_running_script to convey the script name to the darktable shortcut system 2023-03-16 20:59:10 -04:00
wpferguson
800cbeef9d Merge pull request #407 from wpferguson/script_manager_check_for_updates_pref
script_manager preference - check for updated scripts
2023-03-06 23:08:54 -05:00
Bill Ferguson
574f6a976f script_manager.lua - added a preference allowing the user to disable
automatic checking for updated scripts.
2023-03-06 22:56:55 -05:00
supertobi
030b360e57 Merge pull request #404 from darktable-org/macOS_HOME
Mac os home
2023-02-27 09:30:44 +01:00
supertobi
b883714192 fix HOME environment variable name for macOS 2023-02-27 09:29:16 +01:00
supertobi
d556f48168 fix HOME environment variable name for macOS 2023-02-27 09:27:25 +01:00
wpferguson
cd7cac0eec Merge pull request #403 from rds13/featureFixKML
fix HOME environment variable name
2023-02-25 12:21:41 -05:00
Romuald du Song
9b31f18470 fix HOME environment variable name 2023-02-25 17:41:45 +01:00
wpferguson
42a194b3d7 Merge pull request #400 from wpferguson/update_change_group_leader
Update contrib/change_group_leader
2022-10-16 14:03:16 -04:00
Bill Ferguson
6748a7125e README.md Added change_group_leader to contrib section
change_group_leader.lua  Updated copyright information

                         Added script_manager integration

                         Added i18n
2022-10-16 13:58:19 -04:00
wpferguson
c2b3f29f3b Merge pull request #399 from dexterlb/change_group_leader
Add group leader script
2022-10-15 13:02:49 -04:00
dexterlb
572a634d90 fix title 2022-10-10 21:39:38 +03:00
dexterlb
652606ffb0 fix copyright message 2022-10-10 21:38:30 +03:00
dexterlb
803537c63e add comments to change_group_leader 2022-10-09 12:00:29 +03:00
dexterlb
4648c2d9af add change_group_leader script 2022-10-09 11:54:02 +03:00
supertobi
edceb3e7e9 Merge pull request #396 from mpaglia0/master - Added Italian translation + improved label
Added Italian translation + improved label
2022-07-15 09:52:14 +02:00
Maurizio Paglia
da6bddcd77 Clear button now can be translated 2022-07-14 18:44:42 +02:00
Maurizio Paglia
b11f43e903 Added Italian translations 2022-07-14 18:43:02 +02:00
supertobi
ef218a4d2e Merge pull request #394 from manu-p/master
OpenInExplorer fr_FR translation
2022-06-28 10:02:45 +02:00
manu-p
b061f27e7a OpenInExplorer fr_FR translation 2022-06-27 17:36:47 +02:00
wpferguson
8e64d54ab1 Merge pull request #389 from wpferguson/fix_readme_links
[README.md] fix links to documentation
2022-03-16 13:12:59 -04:00
Bill Ferguson
56b40a80ac [README.md] fix links to documentation 2022-03-16 13:10:06 -04:00
supertobi
d359d8ecb4 Merge pull request #383 from dtorop/fuji-dr-load-only-with-exiftool
fujifilm_dynamic_range: only load if exiftool present
2022-02-14 09:58:13 +01:00
Dan Torop
2bc55d00bb fujifilm_dynamic_range: only load if exiftool present
Fail to load if exiftool binary is not present. This may be better
than silently (in UI) failing to read dyanmic range data.
2022-02-13 22:14:57 -05:00
wpferguson
d3cf564b71 Merge pull request #379 from wpferguson/cleanup_changelog
fix changelog formatting
2022-01-12 14:59:04 -05:00
Bill Ferguson
4fb8b97084 fix changelog formatting 2022-01-12 14:57:57 -05:00
wpferguson
32c5cceb1d Merge pull request #378 from wpferguson/update_docs
Updated documentation
2022-01-12 14:54:30 -05:00
Bill Ferguson
f38c14d6b5 Updated documentation 2022-01-12 14:53:02 -05:00
Christian Mandel
3070ca0bc4 Added tooltip to filter manager to better match new import scheme. 2022-01-09 15:46:29 +01:00
wpferguson
0122b3f717 Merge pull request #374 from wpferguson/which_deprecation_369
Fix dtutils/file.lua deprecation warning
2021-12-28 20:50:21 -05:00
Bill Ferguson
79678fd82f [lib/dtutils/file.lua] replaced deprecated "which" call with recommended
"command -v" call to check the existence of a
                       program
2021-12-28 20:45:50 -05:00
wpferguson
034ddfbafb Merge pull request #373 from wpferguson/deprecate_rename_tags
Deprecate rename-tags.lua
2021-12-28 11:22:25 -05:00
Bill Ferguson
aebd26d03a [lib/dtutils.lua] - Added deprecated function to use when deprecating
scripts

[contrib/rename-tags.lua] - Added deprecation warning
2021-12-27 19:36:15 -05:00
wpferguson
d609823491 Merge pull request #368 from wpferguson/fix_rename_sequence_leading_zeros
Fix rename_images sequence substitution
2021-10-22 11:15:19 -04:00
Bill Ferguson
8f414f783a [Changelog] Updated with latest changes 2021-10-22 11:12:24 -04:00
Bill Ferguson
2e521553bf [rename_images] Added changed suggested by Volker Bödker to make sure
sequence was 4 digits with leading zeros.
2021-10-22 11:09:01 -04:00
wpferguson
e93f5fc812 Merge pull request #365 from MarcusGama/devel
Update pt_BR translation
2021-10-04 13:48:37 -04:00
Marcus Gama
3dbbb7dfd5 Update pt_BR translation 2021-10-02 05:52:20 -03:00
supertobi
fbb92e1023 Initial pt_BR translation - Merge pull request #364 from MarcusGama/devel
Initial pt_BR translation
2021-09-29 09:54:15 +02:00
Marcus Gama
ea4c6373f9 Initial pt_BR translation 2021-09-28 20:08:30 -03:00
supertobi
40b2569a21 Merge pull request #363 from lbschenkel - Fix URL of Finnish police document
Fix URL of Finnish police document
2021-09-14 09:49:15 +02:00
Leonardo Brondani Schenkel
ef6e4d8f0b Fix URL of Finnish police document
The old URL returns a 404, this is the current replacement.
2021-09-13 17:23:10 +02:00
Diederik ter Rahe
28ae2ea501 simple module to call dt_action_process 2021-09-12 18:16:09 +02:00
wpferguson
657407c7a0 Merge pull request #361 from chrisaga/master
Minor errors in dtutils_log.libdoc.Example : error levels are log.xxxx
2021-09-02 14:40:09 -04:00
chrisaga
b254577bc8 Merge branch 'master' of github.com:chrisaga/lua-scripts 2021-09-02 11:26:57 +02:00
chrisaga
63763bedc8 Minor errors in dtutils_log.libdoc.Example : error levels are log.xxxx 2021-09-02 11:23:36 +02:00
wpferguson
3d50d0fdb4 Merge pull request #360 from wpferguson/fix_AutoGroup_style_hide
Remove styles hiding from AutoGrouper.lua
2021-08-31 12:32:10 -04:00
Bill Ferguson
1ca6b18f5e Remove styles hiding from AutoGrouper.lua 2021-08-31 12:31:04 -04:00
wpferguson
b82ffc2af3 Merge pull request #359 from johnny-bit/dt3139_moar_guidez
add new script harmonic armature guide
2021-08-15 17:09:59 -04:00
Hubert Kowalski
d142151055 fix lines and contrib require 2021-08-15 22:43:13 +02:00
wpferguson
91b78529fe Merge pull request #341 from wpferguson/color_profile_manager
Add new script color_profile_manager
2021-08-15 16:35:19 -04:00
wpferguson
7aec6884f4 Merge pull request #351 from wpferguson/add_rename_script
[contrib/rename] Add rename_images script
2021-08-15 16:34:41 -04:00
Hubert Kowalski
7f140828bb add harmonic armature guide 2021-08-14 13:01:00 +02:00
supertobi
4f13a56814 Merge pull request #358 from chrisaga/master
Add french translation for photils.lua
2021-08-13 10:36:54 +02:00
chrisaga
27d3ea253d Add simple file header 2021-08-13 10:15:38 +02:00
chrisaga
5209b9899d Add french translation for photils 2021-08-13 10:06:58 +02:00
wpferguson
6a40c836c0 Merge pull request #355 from wpferguson/fix_geoToolbox
geoToolbox 5.4 compatibility
2021-08-05 18:55:08 -04:00
Bill Ferguson
c4df81bdef [contrib/geoToolbox] changed atan2 call to atan for lua 5.4 compatibility 2021-08-05 18:53:05 -04:00
wpferguson
e99827d237 Merge pull request #354 from wpferguson/fix_quicktag_entry
Fix quicktag tag entry
2021-08-04 11:30:44 -04:00
Bill Ferguson
a0cbe8efd5 [contrib/quicktag] changed is_password to false on entry so user can
see what they typed.
2021-08-04 11:28:12 -04:00
Bill Ferguson
b4ee97a768 got register_lib arguments in the correct order 2021-08-02 23:44:05 -04:00
Bill Ferguson
4bdaaaf1ce renamed rename to rename_images to better reflect the purpose 2021-08-02 23:26:22 -04:00
Bill Ferguson
3cf0fea092 [contrib/rename] Rename files including moving them into new or other
film rolls.
2021-07-28 01:18:51 -04:00
wpferguson
97e1451dc6 Merge pull request #348 from wpferguson/fix_script_manager_register_event
[script_manager] updated register_event syntax to API 7.0.0
2021-07-14 11:45:54 -04:00
Bill Ferguson
4cbb7724f2 [script_manager] updated register_event syntax to API 7.0.0 2021-07-14 11:44:27 -04:00
wpferguson
8f6c2c82d3 Merge pull request #345 from wpferguson/sm_git_branch_pattern_matching
Solution to pattern matching current git directory
2021-07-06 00:49:04 -04:00
Bill Ferguson
a6d5001e84 [script_manager] set logging to error 2021-07-06 00:46:21 -04:00
Bill Ferguson
6af180a313 [script_manager] after numerous attempts to solve this with just pattern
matching I realized the lack of conditionals made it
                 impossible to solve in that manner, so I resorted to
                 splitting the output into lines and testing each line.
2021-07-06 00:42:55 -04:00
wpferguson
86cc22d943 Merge pull request #344 from wpferguson/sm_fix_current_branch_not_detected
[script_manager] fix current branch not detected
2021-07-05 23:25:00 -04:00
Bill Ferguson
c1fbfd80d4 [script_manager] fix current branch not detected 2021-07-05 23:24:19 -04:00
wpferguson
040a83fb17 Merge pull request #343 from wpferguson/sm_i18n_git_fix
script_manager: fix i18n git integration
2021-07-05 23:05:46 -04:00
Bill Ferguson
2bc23a1446 [script_manager] fixed log message and reset log level for production 2021-07-05 23:01:30 -04:00
Bill Ferguson
97f4c911ed [script_manager] modified dirty repo test 2021-07-05 22:55:30 -04:00
Bill Ferguson
53d9a42a1a [script_manager] modified git interface to not rely on locale 2021-07-05 22:49:39 -04:00
wpferguson
c96c5234c4 Merge pull request #342 from wpferguson/sm_fix_no_current_branch
Script Manager crash on no current branch
2021-07-05 15:15:25 -04:00
Bill Ferguson
9a3eb7bc3e [script_manager] added check for nil current branch and dumped repo_data 2021-07-05 15:13:56 -04:00
Bill Ferguson
53f15ba8b8 [color_profile_manager] created color_profile_manager to manage the
darktable input and output color profiles
2021-07-02 21:51:20 -04:00
wpferguson
ecdc74b5a6 Merge pull request #340 from wpferguson/script_manager_final_bug_fix
Script manager final bug fix
2021-07-02 21:35:16 -04:00
Bill Ferguson
422fdbe2cf [script_manager] make sure we have branches before comparing 2021-07-02 21:33:50 -04:00
Bill Ferguson
887bc03f32 bug 2021-07-02 21:29:36 -04:00
Bill Ferguson
e32c0c70a8 bug 2021-07-02 21:28:26 -04:00
Bill Ferguson
440dfcadfd bug 2021-07-02 21:26:45 -04:00
Bill Ferguson
93cc0adb39 bug 2021-07-02 21:25:36 -04:00
Bill Ferguson
f8ec85ddae bug 2021-07-02 21:22:43 -04:00
wpferguson
688333f756 Merge pull request #339 from wpferguson/script_manager_no_branch_bugfix
[script_manager] handle the case where no branches are detected
2021-07-02 21:14:50 -04:00
Bill Ferguson
8f314059e4 [script_manager] handle the case where no branches are detected 2021-07-02 21:13:33 -04:00
wpferguson
9386a08af0 Merge pull request #338 from wpferguson/script_manager_noise
Stop script_manager "noise"
2021-07-02 20:30:32 -04:00
Bill Ferguson
19cd118f8f [script_manager] converted dt.print_log statements to log.msg with the appropriate level 2021-07-02 20:28:48 -04:00
Bill Ferguson
c196f98bc6 [ChangeLog.md] fixed formatting 2021-07-01 21:51:26 -04:00
Bill Ferguson
56ec258cc7 [script_manager] Added API awareness so that the correct version of
scripts that match the API version of darktable are
                 used.

[Changelog.md]   Updated the change log with the latest changes.
2021-07-01 21:45:38 -04:00
Bill Ferguson
5da64a7da0 [API 7.0.0 compatibility] Made changes to take advantage of the latest API changes
including the ability to start and stop most scripts.
2021-06-19 23:34:14 -04:00
wpferguson
f272aeb9fe Merge pull request #335 from wpferguson/revert-dt35-api-changes
Revert API 6.1+ compatibility changes.  Partially fixes #334
2021-06-19 16:38:36 -04:00
wpferguson
cf26b82d87 Merge pull request #336 from wpferguson/312_image_path_in_ui
Fix 312 - change case of image_path_in_ui module label
2021-06-19 14:01:46 -04:00
Bill Ferguson
2041fb34c9 [image_path_in_ui] changed case of module title to respect darktable
UI.
2021-06-19 13:57:39 -04:00
Bill Ferguson
4b63240ab9 Reverted register_event calls back to API 6.1.0 2021-06-11 21:44:38 -04:00
Bill Ferguson
9d59479049 Revert changes to accomadate API changes greater than 6.1 2021-06-11 19:47:43 -04:00
wpferguson
350cdfa436 Merge pull request #333 from wpferguson/fix_quicktag
Fix contrib/quicktag
2021-06-02 23:44:26 -04:00
Bill Ferguson
15de64dcf5 [contrib/quicktag] changed new entry field is_password setting to false
so that the user can see what they are typing.
2021-06-02 23:41:02 -04:00
wpferguson
d9c2519a83 Merge pull request #331 from wpferguson/fix_HDRmerge
Fix HDRmerge
2021-03-21 23:34:14 -04:00
wpferguson
9885e34f82 [Changelog] Added latest changes 2021-03-20 00:01:44 -04:00
wpferguson
ed0adfbae1 [Changelog] Added latest changes 2021-03-20 00:01:25 -04:00
wpferguson
beafb6c21b [HDRmerge] fixed crash resulting users renaming images from
camera default.

           Minor code cleanup
2021-03-19 23:52:28 -04:00
supertobi
7810791991 Merge pull request #330 from scheckmedia/master
photils plugin
2021-03-15 13:36:04 +01:00
Tobias Scheck
1d10d5234f refactore print method 2021-03-15 11:41:52 +01:00
Tobias Scheck
9900f46be4 add option to apply selected tags from a single image to multiple images (close #3); add setting parameter to enable/disable the export of an image before tag suggestion (close #5) 2021-03-15 11:36:37 +01:00
wpferguson
0d2c4dfb3a Merge pull request #329 from wpferguson/gimp_detached
GIMP run detached
2021-02-25 21:07:05 -05:00
wpferguson
752e27d97f Added detached mode to Edit with GIMP so that GIMP can keep running and accepting new images 2021-02-25 21:04:59 -05:00
wpferguson
33523fbe36 Merge pull request #328 from wpferguson/doc_updates
Update ChangeLog
2021-02-24 20:18:56 -05:00
wpferguson
db47dd5110 Update for Mark64/ext_editor_darkroom 2021-02-24 20:11:30 -05:00
wpferguson
a71097a8c9 Merge pull request #327 from Mark-64/ext_editor_darkroom
External editors: make gui visible in darkroom
2021-02-24 20:02:30 -05:00
U-DESKTOP-HQME86J\marco
931e448ebd more compact code 2021-02-24 19:21:32 +01:00
U-DESKTOP-HQME86J\marco
abd8bf4a60 option to display module in darkroom 2021-02-24 14:14:38 +01:00
U-DESKTOP-HQME86J\marco
aee1f82d04 restore current module registration 2021-02-23 07:40:30 +01:00
U-DESKTOP-HQME86J\marco
8112472df5 restore initialization line deleted by mistake 2021-02-22 08:17:46 +01:00
U-DESKTOP-HQME86J\marco
c9fa13b97b revert MAX_EDITORS to 9 2021-02-21 20:59:30 +01:00
U-DESKTOP-HQME86J\marco
73cd301ff4 ext_editor visible in darkroom 2021-02-21 20:55:23 +01:00
wpferguson
aa33ae0706 Merge pull request #325 from wpferguson/doc_updates
Update documentation
2021-02-19 17:30:13 -05:00
wpferguson
4e9c45c162 Updated README.md with links to the lua documentation. Added fujifilm_dynamic_range script to contrib. Added ChangeLog.md to keep track of recent changes. 2021-02-19 17:17:53 -05:00
wpferguson
f35cea75d3 Merge pull request #324 from wpferguson/API_6_2_3_register_action_changes
API 6.2.3 compatibility
2021-02-17 22:30:35 -05:00
wpferguson
4053ceb9e5 [API 6.2.3] Added changes for API 6.2.3 compatibility 2021-02-17 22:24:02 -05:00
wpferguson
448da35327 Merge pull request #322 from wpferguson/bugfix_select_untagged
fix select_untagged
2021-02-10 14:53:43 -05:00
wpferguson
6d9c4c90ba Used image table returned to callback, and returned selection table to caller so that it could do the selection. 2021-02-10 14:52:03 -05:00
wpferguson
5b8282e933 Merge pull request #321 from wpferguson/fix_6_2_1
Fix API 6.2.1 transition
2021-02-10 00:21:20 -05:00
wpferguson
b1498aa664 inline if didn't work. Added intermediate register_event function in transition library 2021-02-10 00:18:48 -05:00
wpferguson
2b28f021ee Merge pull request #320 from wpferguson/fix_6_2_2
fix API 6.2.2 compatibility
2021-02-09 23:38:56 -05:00
wpferguson
dc4938c377 inline if didn't work. Replaced with full if/else 2021-02-09 23:36:54 -05:00
wpferguson
69b846a856 Merge pull request #319 from wpferguson/api_6_2_2_register_selection
API 6.2.2 compatibility
2021-02-04 23:07:56 -05:00
wpferguson
1460396d4f [contrib/select_untagged] Added check for API 6.2.2 and added name
field to register_selection if it is
2021-02-04 23:04:21 -05:00
wpferguson
b9804d9d48 Merge pull request #318 from wpferguson/api_6_2_1_lua_event_changes
Added compatibility for lua events API change 6.2.1.
2021-02-01 18:31:19 -05:00
wpferguson
ade7008127 Added compatibility for lua events API change 6.2.1. 2021-02-01 18:27:20 -05:00
wpferguson
da0235ab27 Merge pull request #317 from wpferguson/correct_api_version
[dtutils] fixed api version check
2021-01-21 21:36:36 -05:00
wpferguson
c17e832572 [dtutils] fixed api version check 2021-01-21 21:34:43 -05:00
wpferguson
c12d6f01e7 Merge pull request #316 from wpferguson/add_database_get_image_to_find_by_image_id
add database.get_image() to dutils.find_image_by_id()
2021-01-21 21:26:08 -05:00
wpferguson
778ad2ce20 [dtutils] added database.get_image() to find_image_by_id() for users with lua API 6.2.0 or greater 2021-01-21 21:20:17 -05:00
wpferguson
33ef58ac97 Merge pull request #315 from schwerdf/master
New utility function for random image access by ID.
2021-01-19 17:37:23 -05:00
wpferguson
8091ba9398 Merge pull request #313 from chrisaga/master
copy_attach_detachtags.lua localization proposal
2021-01-19 14:20:09 -05:00
August Schwerdfeger
8e0bf9e991 Corrected names in documentation of 'find_image_by_id'. 2021-01-19 01:34:36 -06:00
August Schwerdfeger
daee82aa6a Added a new utility function to look up an image in the database by ID. 2021-01-19 01:05:50 -06:00
chrisaga
359a1fa3a9 As discussed, localize module name in GUI 2021-01-10 19:54:10 +01:00
wpferguson
021b32c1d4 Merge pull request #314 from dtorop/fujifilm-dynamic-range
Fujifilm dynamic range
2021-01-07 23:38:11 -05:00
Dan Torop
1d1eb6a768 fujifilm-dynamic-range: remove translations, use full exiftool path
Suggestions as per @wpferguson:

"We came to a decision a while ago to not translate debug
messages(i.e. print_log() and print_error()) because the author might
not be able to read the output if it was a language they weren't
familiar with. So the only thing we translate are labels, tooltips,
and dt.print() messages."

"df.check_if_bin_exists() returns the full path to the
executable. Using the returned value would let the script run on
windows and macos too."
2021-01-07 22:55:55 -05:00
Dan Torop
a9fd0718b0 fujifilm-dynamic-range: skip non-Fuji non-raws and clean-ups
Don't bother to pull EXIF data unless the imported image is a Fujifilm
raw.

Cleanup conditionals for error handling by immediately returning on
failure, avoiding deep nested conditionals.

Cleanup log/error output with clearer messages.
2021-01-06 11:21:05 -05:00
Dan Torop
6341a0a459 fujifilm-dynamic-range: better logging and error handling
Use dt.print_log() for log messages. Print error output if no results
or results not parseable from exiftool.

Also update the date, it's 2021.
2021-01-06 10:28:52 -05:00
Dan Torop
952b8b2eb2 fujifilm-dynamic-range: better exiftool parsing & comments
Get tab-delimited output from exiftool without rounding.  Add comments
to explain the choice of tags being read and tool to read them.
2021-01-06 10:11:59 -05:00
Dan Torop
b08d7f27d2 fujifilm_dynamic_range : first draft
Read RawExposureBias and adjust "EXIF Exposure Bias" based upon this.
2021-01-05 22:38:27 -05:00
chrisaga
a71889021f add copy_attach_detachtags.po header 2021-01-03 21:02:25 +01:00
chrisaga
367631af99 copy_attach_detachtags.lua localization 2021-01-03 20:55:45 +01:00
wpferguson
62753d1a50 Merge pull request #309 from chrisaga/master
script_manager.lua function clear_button reset tooltip
2020-12-29 11:12:26 -05:00
chrisaga
dfe589e843 script_manager.lua function clear_button must reset tooltip too 2020-12-28 19:18:35 +01:00
wpferguson
d09574bb17 Merge pull request #308 from chrisaga/master
script_manager.lua localization proposal (including minor changes in …
2020-12-28 12:50:14 -05:00
chrisaga
2be12e5b37 script_manager.lua localization proposal (including minor changes in the script) 2020-12-28 10:42:15 +01:00
wpferguson
d2086b6480 Merge pull request #307 from chrisaga/master
More clear_GPS.lua localization
2020-12-27 20:40:09 -05:00
chrisaga
132668d04c More clear_GPS.lua localization 2020-12-27 18:22:19 +01:00
wpferguson
0a4aa95965 Merge pull request #306 from chrisaga/localize-copy_paste_metadata.lua
Localize copy paste metadata.lua
2020-12-26 17:18:21 -05:00
wpferguson
af1e774d1f Merge pull request #305 from chrisaga/patch-1
Update clear_GPS.lua
2020-12-26 17:16:45 -05:00
chrisaga
e5271d79a0 Localize copy_paste_metadata.lua 2020-12-26 13:27:53 +01:00
chrisaga
dfd714540e fr po-file for copy_paste_metadata 2020-12-26 13:21:00 +01:00
chrisaga
a0fd22be19 Update clear_GPS.lua
Changed first line of header which was referring to another plugin (gimp.lua)
2020-12-26 12:08:38 +01:00
wpferguson
c0dc55b18e Merge pull request #303 from wpferguson/script_manager_3.4
Merry Christmas aka new script manager
2020-12-24 09:56:22 -05:00
wpferguson
258def1cf8 Merge pull request #304 from wpferguson/logging_cleanup
Reduce the amount of trace information in log messages.
2020-12-24 09:55:24 -05:00
wpferguson
b2095fd947 Merge pull request #299 from wpferguson/fix_check_if_bin_exists_and_executable_paths
Fix check if bin exists and executable paths
2020-12-24 09:54:52 -05:00
wpferguson
e51278f76e Merge branch 'master' into fix_check_if_bin_exists_and_executable_paths 2020-12-24 09:54:29 -05:00
wpferguson
9af60063d8 [dtutils.log] changed the amount of trace information based on log level.
script, caller, line number, msg were printed for all log
              levels, but changed to return less info except in log.debug
              log.error, and log.critical messages.  All messages print
              the script name except for log.screen.
2020-12-23 22:52:46 -05:00
wpferguson
37b523f989 [script_manager] rewrite of script_manager for a better fit with
darktable 3.4 and the new scripts_installer. Changed
                 interface to paged instead of scrolled so that less
                 screen space is required.  Added capability to
                 install/update external repositories and seamlessly
                 integrate with script_manager.
2020-12-23 19:54:16 -05:00
wpferguson
96ba075e86 Removed debugging statement 2020-12-22 22:00:41 -05:00
Bill Ferguson
e86a3a3236 [file.lua] removed debugging statement 2020-12-18 11:08:12 -05:00
wpferguson
622a9ebab4 Merge pull request #301 from pitbuster/master
Sanitize filename in fujifilm_ratings.lua to handle spaces.
2020-12-14 00:07:28 -05:00
Felipe Contreras
b26ec58f9f Sanitize filename in fujifilm_ratings.lua to handle spaces. 2020-12-13 22:00:39 -03:00
Bill Ferguson
b41b2a1566 [file.lua] Added create_tmp_file() back in. 2020-11-30 21:51:55 -05:00
Bill Ferguson
b893383353 [file.lua] Added comments for local functions. Added more API
documentation for check_if_bin_exists.  Added save
           executable path preference to _search_for_nix_bin()
           so that we can save a little time on subsequent runs
           once an executable is found.
2020-11-30 21:06:57 -05:00
Bill Ferguson
ddcf3ed3f4 [file.lua] fixed line trimming in macos search for bin 2020-11-29 18:41:32 -05:00
Bill Ferguson
da05748393 [file.lua] combined the is_ functions into file_test() which allows
testing to see if a path exists, is a dir, is a file, or
	   is executable.  Added case insensitive search to the
           windows search for bin.
2020-11-29 18:00:47 -05:00
wpferguson
976ad4ab17 Merge pull request #297 from wpferguson/remove_yield
Remove official/yield.lua
2020-11-26 19:08:57 -05:00
wpferguson
8dcf19834c Merge pull request #298 from wpferguson/add_sanitize_decimals_to_enfuseAdvanced
Add sanitize_decimals() to enfuseAdvanced
2020-11-25 19:42:25 -05:00
Bill Ferguson
43315bb9cc [enfuseAdvanced] added a sanitize_decimals function to sanitize the command lines. 2020-11-25 19:39:49 -05:00
Bill Ferguson
4ab20d8088 Removed official/yield.lua since it was only required for lua API 3.x and less (darktable 2.0 and before). Removed require from scripts that had included it. 2020-11-21 18:33:26 -05:00
wpferguson
485de32bb4 Merge pull request #294 from wpferguson/fix_enfuseAdvanced_decimal_separator
fix enfuseAdvanced crash on comma decimal separator
2020-11-19 00:38:06 -05:00
Bill Ferguson
536b9726b2 [enfuseAdvanced] Added check for comma decimal separator. Replace with period if found. 2020-11-19 00:35:17 -05:00
wpferguson
41ae43e741 Merge pull request #293 from wpferguson/fix_multi_os_crash
Fix multi_os crash
2020-11-17 19:05:23 -05:00
Bill Ferguson
b5b49245fa Needlessly creating the executable path preference for the ufraw-batch executable on linux caused the value to be saved as the startup directory, which in turn caused a crash when trying to extract the embedded jpg. 2020-11-17 18:53:54 -05:00
wpferguson
d172ac7049 Merge pull request #290 from wpferguson/get_enfusedAdvanced_running
Fix translation in enfuseAdvanced
2020-09-16 00:10:23 -04:00
Bill Ferguson
8808306a5f [contrib/enfusedAdvanced.lua] _ was declared globally in a subroutine, thus negating translation once the subroutine was called. 2020-09-16 00:05:53 -04:00
wpferguson
00dac577f6 Merge pull request #289 from wpferguson/fix_enfuseAdvanced_print_error
fix print errors in enfuseAdvanced - this takes care of the current last comment in #186
2020-09-14 20:56:10 -04:00
Bill Ferguson
85c218b350 [contrib/enfuseAdvanced] fixed translations in for loops by changing the unused variable _ to x so that the translation function _() doesn't get replaced with the value of _. 2020-09-14 20:47:29 -04:00
supertobi
6f71ef4d6b Merge pull request #288 from homer3018/patch-1
Typo
2020-09-11 16:04:19 +02:00
homer3018
394cbdb454 Typo 2020-09-11 00:05:22 +02:00
supertobi
62e2a7969d Merge pull request #287 from darktable-org/supertobi-patch-1
README - suggested update #286 by heviiguy
2020-09-06 20:13:28 +02:00
supertobi
d9c284b54d Update README.md
README - suggested update #286
2020-09-06 20:01:34 +02:00
wpferguson
bd2bc3659c Merge pull request #284 from wpferguson/update_install_instructions
Update README.md
2020-08-23 00:40:49 -04:00
Bill Ferguson
f76fa01240 Updated install instructions adding flatpak information and updating preferred way to enable and disable scripts. 2020-08-18 12:08:23 -04:00
wpferguson
45d62f418c Merge pull request #283 from wpferguson/script_manager_collapsible
script_manager resizes smaller
2020-08-16 15:59:06 -04:00
Bill Ferguson
650325257b made button a local variable 2020-08-16 15:57:41 -04:00
Bill Ferguson
a0a8a61d56 Enabled latest dev changes in lua API so that script_manager responds better to panel resizing 2020-08-16 15:52:57 -04:00
wpferguson
998732287b Merge pull request #282 from wpferguson/change_name_to_id
Fix script_manager not starting (and any other register_lib module)
2020-08-16 13:21:48 -04:00
Bill Ferguson
509c7da384 changed darktable.current_view().name to darktable.current_view().id for view check because name is translated and id is not. 2020-08-16 13:18:27 -04:00
wpferguson
771e03ce76 Merge pull request #279 from wpferguson/278_fix_register_lib_hang
278 fix register lib hang
2020-08-14 20:23:54 -04:00
supertobi
7d9b8dbdda Update video_ffmpeg.lua
added more framerate options
2020-08-10 15:21:25 +02:00
Bill Ferguson
912ae93d10 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:56:48 -04:00
Bill Ferguson
b613cc266a Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:56:38 -04:00
Bill Ferguson
dfb8d4e7c6 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:48:36 -04:00
Bill Ferguson
b8e3ec4302 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:43:31 -04:00
Bill Ferguson
d4e21b7877 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:43:20 -04:00
Bill Ferguson
d10cf968f1 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:33:09 -04:00
Bill Ferguson
880ec7fc58 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:26:45 -04:00
Bill Ferguson
432b6b5dd7 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:21:17 -04:00
Bill Ferguson
5f5ad426bf Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 01:01:33 -04:00
Bill Ferguson
52acdb9036 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 00:45:27 -04:00
Bill Ferguson
138b23674a Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 00:36:43 -04:00
Bill Ferguson
8a710798f7 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-09 00:17:34 -04:00
Bill Ferguson
be5557080b Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-08 23:38:48 -04:00
Bill Ferguson
994b9c6472 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-08 23:28:50 -04:00
Bill Ferguson
d05536bed4 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-08 23:21:54 -04:00
Bill Ferguson
cc4f1d7251 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-08 23:11:33 -04:00
Bill Ferguson
2ddd0965cd Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-08 22:40:54 -04:00
Bill Ferguson
4e68e420cc removed debugging print statemant 2020-08-08 22:40:40 -04:00
Bill Ferguson
50be8a9240 Added check to ensure we are in lighttable view prior to callling darktable.register_lib. If darktable was invoked in single image (darkroom) mode, then an event handler is registered to detect the change from darkroom mode to lighttable mode so that we can then call darktable.register_lib. 2020-08-08 22:11:29 -04:00
Bill Ferguson
d1e330eee0 Added check to ensure we are in lighttable view prior to callling
darktable.register_lib.  If darktable was invoked in single
image (darkroom) mode, then an event handler is registered to
detect the change from darkroom mode to lighttable mode so that
we can then call darktable.register_lib.
2020-08-08 22:01:58 -04:00
supertobi
6aa5893b5f Merge pull request #277 from wpferguson/update_readme
Update README.md
2020-08-04 21:32:43 +02:00
Bill Ferguson
797d668ebb Fixed typo 2020-08-04 11:11:41 -04:00
Bill Ferguson
b23595ff54 Added latest scripts 2020-08-03 18:52:28 -04:00
wpferguson
6903f8385b Merge pull request #267 from wpferguson/image_time
Image time
2020-08-02 17:36:24 -04:00
wpferguson
e8205c6435 Merge pull request #276 from wpferguson/executable_manager_feedback
executable_manager provide feedback
2020-08-02 17:35:03 -04:00
wpferguson
c114856699 Merge pull request #275 from camjc/patch-1
Typo in README
2020-07-19 15:39:58 -04:00
Bill Ferguson
57a9a8495c made newly added strings translatable 2020-07-19 15:38:08 -04:00
Bill Ferguson
53bb95e6fd Added entry widget to display the current setting, or a placeholder specifying what would or should happen depending on operating system to provide visual feedback. 2020-07-19 13:13:24 -04:00
Cameron Chamberlain
a40fe3099d Typo in README 2020-07-19 10:54:48 +10:00
wpferguson
8d38ea0cf2 Merge pull request #270 from Mark-64/RL_output_sharpen_doc
add doc comments to RL_output_sharpen
2020-07-05 20:51:14 -04:00
wpferguson
ac81c95f7e Merge pull request #271 from Mark-64/ext_editor_add_doc
add documentation comments to ext_editor
2020-07-05 20:50:14 -04:00
wpferguson
48b915b0b2 Merge pull request #247 from noahwc/exportlut
Add an exporthaldclut lighttable module
2020-07-05 20:48:22 -04:00
Noah
593917db9a Fix gettext implementation 2020-07-04 23:20:30 -07:00
U-DESKTOP-HQME86J\marco
2d49b49bec add doc comments to ext_editor 2020-07-04 10:45:59 +02:00
U-DESKTOP-HQME86J\marco
323c8b6845 add doc comments 2020-07-04 10:27:06 +02:00
Bill Ferguson
e5ab30006b Added reset function. Changed namespace to img_time. 2020-06-24 21:51:40 -04:00
Bill Ferguson
14b97cdbd9 Created image_time.lua to adjust non-destructively adjust image time. 2020-06-21 17:14:36 -04:00
wpferguson
8f0f0a5298 Merge pull request #265 from wpferguson/correct_OpenInExplorer_shortcut
Fix OpenInExplorer shortcut function call
2020-06-21 16:37:27 -04:00
Bill Ferguson
497dbc8eaa Fixed function call arguments in shortcut. 2020-06-21 16:32:14 -04:00
wpferguson
11baf14280 Merge pull request #263 from wpferguson/preklov_OpenInExplorer_use_links
OpenInExplorer links option - This is @preklov's PR and he deserves the credit for the changes.  I just wasn't git ninja enough to figure out how to fix the problems, so I pulled it into a local branch and fixed it.
2020-06-20 15:02:42 -04:00
Bill Ferguson
948e8690dd Pulled preklov's branch, resolved conflicts, then merged into new branch with only 1 commit, essentially squashing the delete and replace file commits. 2020-06-20 14:49:31 -04:00
Noah
802c17d590 Added translated text 2020-06-16 01:59:43 -07:00
Noah
71e72b688f Exported luts now have folder hierarchy 2020-06-16 01:50:59 -07:00
supertobi
877b56ad67 Merge pull request #261 from scheckmedia/master
move function to create temp files to dtutils/file.lua
2020-06-15 14:08:03 +02:00
Tobias Scheck
87502fb17f move function to create temp files to dtutils/file.lua 2020-06-15 11:16:14 +02:00
supertobi
ed3e141601 Merge pull request #259 from GLLM/patch-1
Add the possibility to define a shortcut for OpenInExplorer LUA script
2020-06-15 10:33:30 +02:00
supertobi
f66d3ed3aa Merge pull request #236 from Mark-64/ext_editor_v4
Ext editor v4
2020-06-15 10:32:13 +02:00
supertobi
5488e45c78 Merge pull request #256 from scheckmedia/master
add photils plugin
2020-06-15 10:27:38 +02:00
Tobias Scheck
72bd66d3f5 add option to display confidence value, add notice that windows version is not available yet 2020-06-14 08:57:17 +02:00
Tobias Scheck
1378441dfa change labels to lower case to fit darktable style 2020-06-14 07:47:28 +02:00
GLLM
26d97b8cdf Update OpenInExplorer.lua
Add the possibility to define a shortcut for this LUA script.
2020-06-11 22:06:00 +02:00
Tobias Scheck
2e27b812d6 fix spelling 2020-06-10 14:48:27 +02:00
Tobias Scheck
fd97ac18da add comments for clarification; formatting 2020-06-10 14:48:18 +02:00
wpferguson
b9d14a2711 Merge pull request #257 from wpferguson/script_manager_sanitize
Bug Fix: script_manager can't process paths with lua special characters
2020-06-09 20:30:04 -04:00
Bill Ferguson
1c9042c496 Added a sanitize_lua function to the string library for sanitizing
strings of lua "magic" characters before using the string in a string
function that involves matching.

script_manager needs the path to the lua directory sanitized of lua
"magic" characters to prevent failure to extract the category and script
names.
2020-06-09 20:22:02 -04:00
Tobias Scheck
60fa4b9ee0 update description 2020-06-09 08:50:26 +02:00
Tobias Scheck
33f91af1af add photils plugin 2020-06-06 12:39:26 +02:00
wpferguson
05d86e89da Merge pull request #255 from wpferguson/update_README.md
Update README.md
2020-05-30 20:18:21 -04:00
Bill Ferguson
a02176fc63 Added instructions for using script_manager.lua as the luarc file. 2020-05-30 20:12:39 -04:00
Noah Clarke
23131a301b Fix include path 2020-05-07 12:15:31 -07:00
Noah
aef7363bee make 8bit not 16bit luts (extra depth uneeded) 2020-05-06 13:19:12 -07:00
Noah
4455347930 Exporting luts now creates a job 2020-05-06 01:14:34 -07:00
Noah
5709cfebd4 Remove unneeded API check 2020-05-05 00:02:47 -07:00
Noah
01184bee72 Updated API version check and forced PNG to 16 bits 2020-05-04 23:58:52 -07:00
Noah
286ef77663 Some error checking and warnings 2020-04-19 00:36:47 -07:00
Noah
28b9073010 Code and UI cleanup 2020-04-16 23:42:51 -07:00
Noah
fe97e29654 Working? version 2020-04-16 22:59:33 -07:00
Noah
792e782e8c basic frontend 2020-04-16 22:27:57 -07:00
Noah
33b99a97e4 initial module minus style picker 2020-04-16 17:51:09 -07:00
U-DESKTOP-HQME86J\marco
a8e5a9b453 default no. of editors 2020-03-22 17:55:15 +01:00
U-DESKTOP-HQME86J\marco
2d69536489 fixes and code cleaning 2020-03-22 17:50:51 +01:00
157 changed files with 24312 additions and 3095 deletions

184
ChangeLog.md Normal file
View File

@@ -0,0 +1,184 @@
## Changes from most recent to oldest
**06 Jun 2024 - wpferguson**
* fix fujifilm_ratings running on all images
* added string library functions to sanitize windows io.popen and os.execute functions
* added database maintenance script
**05 Jun 2024 - wpferguson**
* added fix for executable_manager not being visible on windows
**30 May 2024 - kkotowicz**
* open in explorer now uses applescript on macos to open multiple files
**20 May 2024 - wpferguson**
* added string variable substitution to the string library
**16 May 2024 - wpferguson**
* fix crash in script_manager
**15 May 2024 - wpferguson**
* added metadata to scripts (name, author, purpose, help url)
**06 May 2024 - christian.sueltrop**
* added passport_guide_germany script
**08 Apr 2024 - wpferguson**
* made script_manager aware of library modules in other directories besides lib
**29 Mar 2024 - wpferguson**
* updated examples/gui_action to use NaN instead of nan
**29 Mar 2024 - dterrahe**
* add lua action script example
**28 Jan 2024 - wpferguson**
* fix script_manager crash when script existed in top level lua directory
**24 Jan 2024 - wpferguson**
* don't set lib visibility unless we are in lighttable mode
**15 Jan 2024 - MStraeten**
* update x-touch.lua with support for primaries slider
**14 Jan 2024 - wpferguson**
* added cycle_group_leader script
* added hif_group_leader script
* added jpg_group_leader script
**28 Oct 2023 - ddittmar**
* Added select non existing image script
**17 Oct 2023 - wpferguson**
* script_manager wrap username in quotes to handle spaces in username
**20 Sep 2023 - wpferguson**
* script_manager explicitly set stopped scripts to false
**18 Aug 2023 - wpferguson**
* swap the position of executable_manager and script_manager due to windows gtk bug
**17 Jul 2023 - wpferguson**
* update check for update instructions
* update flatpak readme
**16 Jul 2023 - wpferguson**
* added script_data.show function to contrib/gpx_export
**15 Jul 2023 - wpferguson**
* script_manager updates
* added check_max_api_version for the case where the API no longer supports the required
functions
**14 Jul 2023 - spaceChRis**
* add tooltip to filter manager
**25 Mar 2023 - wpferguson**
* Added script_manager darktable shortcut integration
* Moved filename/path/extension string functions to string library
**06 Mar 2023 - wpferguson - Added option to disable script_manager update check**
**12 Jan 2022 - wpferguson - Documented contrib/rename_images.lua**
**28 Dec 2021 - wpferguson - Replaced deprecated _which_ command with _command -v_ in lib/dtutils/file.lua**
**12 Dec 2021 - wpferguson**
* Added deprecated function to library
* Added deprecation warning to contrib/rename-tags.lua
**22 Oct 2021 - wpferguson for Volker Bödker - make sure sequence is 4 digits in rename_images.lua**
**31 Aug 2021 - wpferguson - remove styles hiding from AutoGrouper.lua**
**02 Jul 2021 - wpferguson - merged API-7.0.0-dev branch to master**
* API-7.0.0 is darktable 3.6
* breaking changes
* register_event argments changed
* register_action arguments changed
* register_selection arguments changed
* register_event arguments changed
* scripts updated to API-7.0.0 compatibility
* script_manger updated to be API aware and check out the proper branch
based on darktable API version
**01 Jul 2021 - wpferguson - created branch for API-6.1.0 - darktable 3.4**
**20 Jun 2021 - wpferguson - created branches for older API versions**
* API-6.0.0 - darktable 3.2.1
* API-5.0.2 - darktable 3.0
* API-5.0.1 - darktable 2.6.1
* API-5.0.0 - darktable 2.4
* API-4.0.0 - darktable 2.2
* API-3.0.0 - darktable 2.0
**19 Jun 2021 - wpferguson - fix issue 312, image_path_in_ui**
**02 Jun 2021 - wpferguson - fix contrib/quicktag**
* set new entry field is_password to false so entry
is visible to user while typing.
**19 Mar 2021 - wpferguson - fixed crash in contrib/HDRmerge.lua**
* Made generated filename routine gracefully handle names that
are not in the expected format.
**15 Mar 2021 - scheckmedia - updated contrib/photils.lua**
* refactor print method
* add option to apply selected tags from a single image to multiple images
* add setting parameter to enable/disable the export of an image before tag suggestion
**25 Feb 2021 - wpferguson - added detached mode to contrib/gimp.lua**
* Added run_detached checkbox to the exporter GUI. Selecting run_detached
let's GIMP keep running and accepting additional images. It does not return
the edited images to darktable.
**24 Feb 2021 - Mark64 - make ext_editor lib visible in darkroom view**
**17 Feb 2021 - wpferguson - API 6.2.3 register_action changes**
* Added check for API version and supplied a name argument if the
API version was greater than or equal to 6.2.3
**10 Feb 2021 - wpferguson - bugfix select_untagged**
* Fixed callback to return a list of images as expected instead of
doing the selection in the callback
**10 Feb 2021 - wpferguson - bugfix API 6.2.1 compatibility**
* The inline check for API version didn't handle argument return
correctly so added a transition library with a register_event function
override to check the API version and process the arguments correctly.
**9 Feb 2021 - wpferguson - bugfix API 6.2.2 compatibility**
* The inline check for API version didn't handle argument return
correctly so changed it to a full if/else block
**4 Reb 2021 - wpferguson - API 6.2.2 compatibililty**
* Added check for API version and supplied a name argument to register_selection
if the API version was greater than or eqal to 6.2.2
**1 Feb 2021 - wpferguson - API 6.2.1 compatibility**
* Added check for API version and supplied a name argument to register_event
if the API version was greater than or eqal to 6.2.1
**21 Jan 2021 - wpferguson - Modified dtutils function find_image_by_id**
* For users with API 6.2.0 or greater - Enabled use of new API function
darktable.database_get_image() in find_image_by_id().
**19 Jan 2021 - schwerdf - Added dtutils library function find_image_by_id()**
* Added new library function to retrieve an image from the library based on it's ID instead
of it's row number in the database
**10 Jan 2021 - chrisaga - copy_attach_detach_tags localization**
**7 Jan 2021 - dtorop - add contrib/fujifilm_dynamic_range**
* add a new contrib script, fujifilm_dynamic_range to adjust exposure
based on the exposure bias camera setting

199
README.md
View File

@@ -7,23 +7,25 @@ efforts of the darktable developers, maintainers, contributors and community. Th
contained in the repository, whether they can be run by themselves (Standalone - Yes) or depend on other
scripts (Standalone - No), what operating systems they are known to work on (L - Linux, M - MacOS, W - Windows), and their purpose.
For the latest changes, see the [ChangeLog](ChangeLog.md)
### Official Scripts
These scripts are written primarily by the darktable developers and maintained by the authors and/or repository maintainers. They are located in the official/ directory.
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
check_for_updates|Yes|LMW|Check for updates to darktable
copy_paste_metadata|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images
delete_long_tags|Yes|LMW|Delete all tags longer than a specified length
delete_unused_tags|Yes|LMW|Delete tags that have no associated images
enfuse|No|L|Exposure blend several images (HDR)
generate_image_txt|No|L|Generate txt sidecar files to be overlaid on zoomed images
image_path_in_ui|Yes|LMW|Plugin to display selected image path
import_filter_manager|Yes|LMW|Manager for import filters
import_filters|No|LMW|Two import filters for use with import_filter_manager
save_selection|Yes|LMW|Provide save and restore from multiple selection buffers
selection_to_pdf|No|L|Generate a PDF file from the selected images
[check_for_updates](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates)|Yes|LMW|Check for updates to darktable
[copy_paste_metadata](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata)|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images
[delete_long_tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length
[delete_unused_tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images
[enfuse](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR)
[generate_image_txt](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt)|No|L|Generate txt sidecar files to be overlaid on zoomed images
[image_path_in_ui](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path
[import_filter_manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters
[import_filters](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager
[save_selection](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers
[selection_to_pdf](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf)|No|L|Generate a PDF file from the selected images
### Contributed Scripts
@@ -32,36 +34,52 @@ These scripts are contributed by users. They are meant to have an "owner", i.e.
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
AutoGrouper|Yes|LMW|Group images together by time
autostyle|Yes|LMW|Automatically apply styles on import
clear_GPS|Yes|LMW|Reset GPS information for selected images
CollectHelper|Yes|LMW|Add buttons to selected images module to manipulate the collection
copy_attach_detach_tags|Yes|LMW|Copy and paste tags from/to images
cr2hdr|Yes|L|Process image created with Magic Lantern Dual ISO
enfuseAdvanced|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images
ext_editor|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors
face_recognition|No|LM|Identify and tag images using facial recognition
fujifilm_ratings|No|LM|Support importing Fujifilm ratings
geoJSON_export|No|L|Create a geo JSON script with thumbnails for use in ...
geoToolbox|No|LMW|A toolbox of geo functions
gimp|No|LMW|Open an image in GIMP for editing and return the result
gpx_export|No|LMW|Export a GPX track file from selected images GPS data
HDRMerge|No|LMW|Combind the selected images into an HDR DNG and return the result
hugin|No|LMW|Combine selected images into a panorama and return the result
image_stack|No|LMW|Combine a stack of images to remove noise or transient objects
kml_export|No|L|Export photos with a KML file for usage in Google Earth
LabelsToTags|Yes|LMW|Apply tags based on color labels and ratings
OpenInExplorer|No|LMW|Open the selected images in the system file manager
passport_guide|Yes|LMW|Add passport cropping guide to darkroom crop tool
pdf_slideshow|No|LM|Export images to a PDF slideshow
quicktag|Yes|LMW|Create shortcuts for quickly applying tags
rate_group|Yes|LMW|Apply or remove a star rating from grouped images
rename-tags|Yes|LMW|Change a tag name
RL_out_sharp|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
select_untagged|Yes|LMW|Enable selection of untagged images
slideshowMusic|No|L|Play music during a slideshow
transfer_hierarchy|Yes|LMW|Image move/copy preserving directory hierarchy
video_ffmpeg|No|LMW|Export video from darktable
[AutoGrouper](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time
[autostyle](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import
[auto_tag_modified](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_tag_modified)|Yes|LMW|Automatically tag images which have been manually modified in the darkroom
change_group_leader|Yes|LMW|Change which image leads group
[clear_GPS](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images
[CollectHelper](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection
color_profile_manager|Yes|LMW|Manage darktable input and output color profiles
[copy_attach_detach_tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images
[cr2hdr](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO
cycle_group_leader|Yes|LMW|cycle through images of a group making each the group leader in turn
dbmaint|Yes|LMW|find and remove database entries for missing film rolls and images
[enfuseAdvanced](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images
[exportLUT](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it
[ext_editor](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors
[face_recognition](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition
[fujifilm_dynamic_range](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting
[fujifilm_ratings](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
[geoJSON_export](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ...
[geoToolbox](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions
[gimp](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result
[gpx_export](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data
harmonic_armature|Yes|LMW|provide a harmonic armature guide
hif_group_leader|Yes|LMW|change the group leader in a raw+heif image pair to the heif image
[HDRMerge](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result
[hugin](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result
[image_stack](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects
[image_time](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time
jpg_group_leader|Yes|LMW|change the group leader for a raw+jpg pair to the jpg image
[kml_export](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth
[LabelsToTags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings
[OpenInExplorer](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager
[passport_guide](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool
passport_guide_germany|Yes|LMW|Add passport cropping guide for German passports to darkroom crop tool
[pdf_slideshow](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow
[photils](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images
[quicktag](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags
[rate_group](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images
[rename_images](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images
[rename-tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name
[RL_out_sharp](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
[select_non_existing](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images
[select_untagged](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images
[slideshowMusic](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow
[transfer_hierarchy](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy
[video_ffmpeg](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable
website_gallery_export|No|LMW|Export a website gallery for selected images
### Example Scripts
@@ -69,17 +87,19 @@ These scripts provide examples of how to use specific portions of the API. They
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
api_version|Yes|LMW|Print the current API version
darkroom_demo|Yes|LMW|Demonstrate changing images in darkoom
gettextExample|Yes|LM|How to use translation
hello_world|Yes|LMW|Prints hello world when darktable starts
lighttable_demo|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering
moduleExample|Yes|LMW|How to create a lighttable module
multi_os|No|LMW|How to create a cross platform script that calls an external executable
panels_demo|Yes|LMW|Demonstrate hiding and showing darktable panels
preferenceExamples|Yes|LMW|How to use preferences in a script
printExamples|Yes|LMW|How to use various print functions from a script
running_os|Yes|LMW|Print out the running operating system
[api_version](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version
[darkroom_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom
[gettextExample](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation
gui_actions|Yes|LMW|demonstrate controlling the GUI using darktable.gui.action calls
[hello_world](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts
[lighttable_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering
[moduleExample](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module
[multi_os](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/multi_os)|No|LMW|How to create a cross platform script that calls an external executable
[panels_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels
[preferenceExamples](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script
[printExamples](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script
[running_os](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system
x-touch|Yes|LMW|demonstrate how to use an x-touch mini MIDI controller to control the darktable GUI
### Tools
@@ -87,11 +107,11 @@ Tool scripts perform functions relating to the repository, such as generating do
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
executable_manager|Yes|LMW|Manage the external executables used by the lua scripts
gen_i18n_mo|No|LMW|Generate compiled translation files (.mo) from source files (.po)
get_lib_manpages|No|LM|Retrieve the library documentation and output it in man page and PDF format
get_libdoc|No|LMW|Retrieve the library documentation and output it as text
script_manager|No|LMW|Manage (install, update, enable, disable) the lua scripts
[executable_manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/executable_manager)|Yes|LMW|Manage the external executables used by the lua scripts
[gen_i18n_mo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po)
[get_lib_manpages](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/get_lib_manpages)|No|LM|Retrieve the library documentation and output it in man page and PDF format
[get_libdoc](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text
[script_manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/script_manager)|No|LMW|Manage (install, update, enable, disable) the lua scripts
### Related third-party projects
@@ -108,6 +128,9 @@ The following third-party projects are listed for information only. Think of thi
* [arru/darktable-scripts](https://github.com/arru/darktable-scripts)
* [nbremond77/darktable](https://github.com/nbremond77/darktable/tree/master/scripts)
* [s5k6/dtscripts](https://github.com/s5k6/dtscripts)
* [ChristianBirzer/darktable_extra_scripts](https://github.com/ChristianBirzer/darktable_extra_scripts)
* [fjb2020/darktable-scripts](https://github.com//fjb2020/darktable-scripts)
* [bastibe/Darktable-Film-Simulation-Panel](https://github.com/bastibe/Darktable-Film-Simulation-Panel)
## Download and Install
@@ -116,14 +139,24 @@ are met as well as providing an easy update path. Single scripts listed as stand
### snap packages
The snap version of darktable comes with lua included starting with version 2.4.3snap2. It is currently in the edge channel, but should reach the stable channel soon.
The snap version of darktable comes with lua included starting with version 2.4.3snap2.
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
cd ~/snap/darktable/current
git clone https://github.com/darktable-org/lua-scripts.git lua
### flatpak and appimage packages
### flatpak packages
Flatpak packages now use the internal lua interpreter.
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
cd ~/.var/app/org.darktable.Darktable/config/darktable
git clone https://github.com/darktable-org/lua-scripts.git lua
### appimage packages
These packages run in their own environment and don't have access to a lua interpreter, therefore the scripts can't run. The packagers could enable the internal interpreter, or allow the package to link the interpreter from the operating system, or bundle a copy of lua with the package. If you use one of these packages and wish to use the lua scripts, please contact the package maintainer and suggest the above fixes.
@@ -138,8 +171,6 @@ Ensure git is installed on your system. If it isn't, use the package manager to
Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run:
Open a command prompt.
cd %LOCALAPPDATA%\darktable
git clone https://github.com/darktable-org/lua-scripts.git lua
@@ -149,6 +180,24 @@ If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least o
When darktable starts it looks for a file name `~/.config/darktable/luarc` (`%LOCALAPPDATA%\darktable\luarc` for windows) and reads it to see which scripts to include. The file is a plain text file with entries of the form `require "<directory>/<name>"` where directory is the directory containing the scripts, from the above list, and name is the name from the above list. To include GIMP the line would be `require "contrib/gimp"`.
The recommended way to enable and disable specific scripts is using the [script manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/script_manager/) module. To use script manager do the following:
### Linux or MacOS
echo 'require "tools/script_manager"' > ~/.config/darktable/luarc
### Windows
echo require "tools/script_manager" > %LOCALAPPDATA%\darktable\luarc
### Snap
echo 'require "tools/script_manager"' > ~/snap/darktable/current/luarc
### Flatpak
echo 'require "tools/script_manager"' > ~/.var/app/org.darktable.Darktable/config/darktable/luarc
You can also create or add lines to the luarc file from the command line:
`echo 'require "contrib/gimp"' > ~/.config/darktable/luarc` to create the file with a gimp entry\
@@ -172,6 +221,12 @@ To update the script repository, open a terminal or command prompt and do the fo
cd ~/snap/darktable/current/lua
git pull
### Flatpak
cd ~/.var/app/org.darktable.Darktable/config/darktable/lua
git pull
### Linux and MacOS
cd ~/.config/darktable/lua/
@@ -184,15 +239,19 @@ To update the script repository, open a terminal or command prompt and do the fo
## Documentation
Each script includes its own documentation and usage in its header, please refer to them.
The [Lua Scripts Manual](https://docs.darktable.org/lua/stable/lua.scripts.manual/) provides documentation
for the scripts transcribed from the header comments. Each script also contains comments and usage instructions
in the header comments.
Lua-script libraries documentation may be generated using the tools in the tools/ directory.
The [Lua Scripts Library API Manual](https://docs.darktable.org/lua/stable/lua.scripts.api.manual/) provides
documentation of the libraries and functions. Lua-script libraries documentation may also be generated using
the tools in the tools/ directory.
More information about the scripting with Lua can be found in the darktable user manual:
https://www.darktable.org/usermanual/en/lua_chapter.html
[Scripting with Lua](https://darktable.org.github.io/dtdocs/lua/)
The darktable Lua API documentation is here:
https://www.darktable.org/lua-api/
The [Lua API Manual](https://docs.darktable.org/lua/stable/lua.api.manual/) provides docuemntation of the
darktable Lua API.
## Troubleshooting
@@ -202,13 +261,17 @@ Running darktable with Lua debugging enabled provides more information about wha
Open a terminal and start darktable with the command `snap run darktable -d lua`. This provides debugging information to give you insight into what is happening.
### Linux and MacOS
### Linux
Open a terminal and start darktable with the command `darktable -d lua`. This provides debugging information to give you insight into what is happening.
### MacOS
Open a terminal and start darktable with the command `/Applications/darktable.app/Contents/MacOS/darktable -d lua`. This provides debugging information to give you insight into what is happening.
### Windows
Open the Git Bash Shell. Start darktable with the command `/c/Program\ Files/darktable/bin/darktable -d lua`. This provides debugging information to give you insight into what is happening.
Open a command prompt. Start darktable with the command "C:\Program Files\darktable\bin\darktable" -d lua > log.txt. This provides debugging information to give you insight into what is happening.
## Contributing

View File

@@ -37,122 +37,189 @@ selected images, the other button performs grouping on the entire active collect
]]
local dt = require "darktable"
local du = require "lib/dtutils"
du.check_min_api_version("7.0.0", "AutoGrouper")
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/")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext("AutoGrouper", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("auto group"),
purpose = _("automatically group images by time interval"),
author = "Kevin Ertel",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/AutoGrouper/"
}
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, otherwise leave as nil
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
local Ag = {}
Ag.module_installed = false
Ag.event_registered = false
local GUI = {
gap = {},
selected = {},
collection = {}
}
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()
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
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
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
for i, image in ipairs(images) do
if i == 1 then
prev_image = image
image:make_group_leader()
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].group_leader)
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, 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 = 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
}
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"){
local function install_module()
if not Ag.module_installed then
dt.print_log("installing module")
dt.register_lib(
MOD, -- 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
}
)
}
)
Ag.module_installed = true
dt.print_log("module installed")
dt.print_log("styles module visibility is " .. tostring(dt.gui.libs["styles"].visible))
end
end
local function destroy()
dt.gui.libs[MOD].visible = false
end
local function restart()
dt.gui.libs[MOD].visible = true
end
-- GUI --
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 = 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
}
GUI.collection = dt.new_widget("button"){
label = _('auto group: collection'),
tooltip =_('auto group the entire collection'),
clicked_callback = function() main(true) end
}
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not Ag.event_registered then
dt.register_event(
"AutoGrouper", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
Ag.event_registered = true
end
end
script_data.destroy = destroy
script_data.destroy_method = "hide"
script_data.restart = restart
script_data.show = restart
return script_data

View File

@@ -46,25 +46,42 @@ In the "Selected Images" module click on "Collect on this Image"
]]
local dt = require "darktable"
local gettext = dt.gettext
local du = require "lib/dtutils"
local gettext = dt.gettext.gettext
local previous = nil
local all_active = false
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("CollectHelper",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "CollectHelper")
local function _(msgid)
return gettext.dgettext("CollectHelper", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("collection helper"),
purpose = _("add collection helper buttons"),
author = "Kevin Ertel",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/CollectHelper/"
}
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, otherwise leave as nil
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
-- FUNCTION --
local function CheckSingleImage(selection)
if #selection ~= 1 then
dt.print(_("Please select a single image"))
dt.print(_("please select a single image"))
return true
end
return false
end
local function CheckHasColorLabel(selection)
local ret = false
for _,image in pairs(selection) do
@@ -81,6 +98,7 @@ local function PreviousCollection()
previous = dt.gui.libs.collect.filter(previous)
end
end
local function CollectOnFolder(all_rules, all_active)
local images = dt.gui.selection()
if CheckSingleImage(images) then
@@ -101,6 +119,7 @@ local function CollectOnFolder(all_rules, all_active)
previous = dt.gui.libs.collect.filter(rules)
end
end
local function CollectOnColors(all_rules, all_active)
local images = dt.gui.selection()
if CheckSingleImage(images) then
@@ -157,6 +176,7 @@ local function CollectOnColors(all_rules, all_active)
end
end
end
local function CollectOnAll_AND()
local images = dt.gui.selection()
if CheckSingleImage(images) then
@@ -172,50 +192,67 @@ local function CollectOnAll_AND()
previous = dt.gui.libs.collect.filter(rules)
end
local function destroy()
dt.gui.libs.image.destroy_action("CollectHelper_prev")
if dt.preferences.read('module_CollectHelper','folder','bool') then
dt.gui.libs.image.destroy_action("CollectHelper_folder")
end
if dt.preferences.read('module_CollectHelper','colors','bool') then
dt.gui.libs.image.destroy_action("CollectHelper_labels")
end
if dt.preferences.read('module_CollectHelper','all_and','bool') then
dt.gui.libs.image.destroy_action("CollectHelper_and")
end
end
-- GUI --
dt.gui.libs.image.register_action(
_("collect: previous"),
"CollectHelper_prev", _("collect: previous"),
function() PreviousCollection() end,
_("Sets the Collect parameters to be the previously active parameters")
_("sets the collect parameters to be the previously active parameters")
)
if dt.preferences.read('module_CollectHelper','folder','bool') then
dt.gui.libs.image.register_action(
_("collect: folder"),
dt.gui.libs.image.register_action(
"CollectHelper_folder", _("collect: folder"),
function() CollectOnFolder(_ , false) end,
_("Sets the Collect parameters to be the selected images's folder")
_("sets the collect parameters to be the selected images's folder")
)
end
if dt.preferences.read('module_CollectHelper','colors','bool') then
dt.gui.libs.image.register_action(
_("collect: color label(s)"),
dt.gui.libs.image.register_action(
"CollectHelper_labels", _("collect: color label(s)"),
function() CollectOnColors(_ , false) end,
_("Sets the Collect parameters to be the selected images's color label(s)")
_("sets the collect parameters to be the selected images's color label(s)")
)
end
if dt.preferences.read('module_CollectHelper','all_and','bool') then
dt.gui.libs.image.register_action(
_("collect: all (AND)"),
dt.gui.libs.image.register_action(
"CollectHelper_and", _("collect: all (AND)"),
function() CollectOnAll_AND() end,
_("Sets the Collect parameters based on all activated CollectHelper options")
_("sets the collect parameters based on all activated CollectHelper options")
)
end
-- PREFERENCES --
dt.preferences.register("module_CollectHelper", "all_and", -- name
"bool", -- type
_('CollectHelper: All'), -- label
_('Will create a collect parameter set that utelizes all enabled CollectHelper types (AND)'), -- tooltip
_('CollectHelper: all'), -- label
_('creates a collect parameter set that utilizes all enabled CollectHelper types (and)'), -- tooltip
true -- default
)
dt.preferences.register("module_CollectHelper", "colors", -- name
"bool", -- type
_('CollectHelper: Color Label(s)'), -- label
_('Enable the button that allows you to swap to a collection based on selected image\'s COLOR LABEL(S)'), -- tooltip
_('CollectHelper: color label(s)'), -- label
_('enable the button that allows you to swap to a collection based on selected image\'s color label(s)'), -- tooltip
true -- default
)
dt.preferences.register("module_CollectHelper", "folder", -- name
"bool", -- type
_('CollectHelper: Folder'), -- label
_('Enable the button that allows you to swap to a collection based on selected image\'s FOLDER location'), -- tooltip
_('CollectHelper: folder'), -- label
_('enable the button that allows you to swap to a collection based on selected image\'s folder location'), -- tooltip
true -- default
)
script_data.destroy = destroy
return script_data

View File

@@ -45,380 +45,442 @@ Select a style, whether you want tags to be copied from the original, and any ad
]]
local dt = require 'darktable'
local du = require "lib/dtutils"
local df = require 'lib/dtutils.file'
local dsys = require 'lib/dtutils.system'
du.check_min_api_version("7.0.0", "HDRmerge")
-- Tell gettext where to find the .mo file translating messages for a particular domain
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("HDR merge"),
purpose = _("merge bracketed images into an HDR DNG image"),
author = "Kevin Ertel",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/HDRmerge"
}
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, otherwise leave as nil
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
local mod = 'module_HDRMerge'
local os_path_seperator = '/'
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
-- Tell gettext where to find the .mo file translating messages for a particular domain
local gettext = dt.gettext
gettext.bindtextdomain('HDRMerge',dt.configuration.config_dir..'/lua/locale/')
local function _(msgid)
return gettext.dgettext('HDRMerge', msgid)
end
local CURR_API_STRING = dt.configuration.api_version_string
local temp
local HDRM = { --HDRMerge Program Table
name = 'HDRMerge',
bin = '',
first_run = true,
install_error = false,
arg_string = '',
images_string = '',
args = {
bps ={text = '-b ', style = 'integer'},
size ={text = '-p ', style = 'string'},
batch ={text = '-B ', style = 'bool'},
gap ={text = '-g ', style = 'integer'}
}
name = 'HDRMerge',
bin = '',
first_run = true,
install_error = false,
arg_string = '',
images_string = '',
args = {
bps = {text = '-b ', style = 'integer'},
size = {text = '-p ', style = 'string'},
batch = {text = '-B ', style = 'bool'},
gap = {text = '-g ', style = 'integer'}
}
}
local GUI = { --GUI Elements Table
HDR = {
bps ={},
size ={},
batch ={},
gap ={}
},
Target = {
style ={},
copy_tags ={},
add_tags ={}
},
run = {},
stack = {},
options = {},
exes = {
HDRMerge = {},
update = {},
}
HDR = {
bps ={},
size ={},
batch ={},
gap ={}
},
Target = {
style ={},
copy_tags ={},
add_tags ={}
},
run = {},
stack = {},
options = {},
exes = {
HDRMerge = {},
update = {},
}
}
HDRM.module_installed = false
HDRM.event_registered = false
--Detect User Styles--
local styles = dt.styles
local styles_count = 1 -- 'none' = 1
for _,i in pairs(dt.styles) do
if type(i) == 'userdata' then styles_count = styles_count + 1 end
if type(i) == 'userdata' then styles_count = styles_count + 1 end
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 GetFileName(full_path) --Parses a full path (path/filename_identifier.extension) into individual parts
--[[Input: Folder1/Folder2/Folder3/Img_0001.CR2
Returns:
path: Folder1/Folder2/Folder3/
filename: Img_0001
identifier: 0001
extension: .CR2
Returns:
path: Folder1/Folder2/Folder3/
filename: Img_0001
identifier: 0001
extension: .CR2
EX:
path_1, file_1, id_1, ext_1 = GetFileName(full_path_1)
]]
local path = string.match(full_path, '.*[\\/]')
local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' )
local identifier = string.match(filename, '%d*$')
local extension = string.match(full_path, '%.%w*')
return path, filename, identifier, extension
EX:
path_1, file_1, id_1, ext_1 = GetFileName(full_path_1)
]]
local path = string.match(full_path, '.*[\\/]')
local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' )
local identifier = string.match(filename, '%d*$')
local extension = string.match(full_path, '%.%w*')
return path, filename, identifier, extension
end
local function CleanSpaces(text) --removes spaces from the front and back of passed in text
text = string.gsub(text,'^%s*','')
text = string.gsub(text,'%s*$','')
return text
text = string.gsub(text,'^%s*','')
text = string.gsub(text,'%s*$','')
return text
end
local function BuildExecuteCmd(prog_table) --creates a program command using elements of the passed in program table
local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string)
return result
local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string)
return result
end
local function PreCall(prog_tbl) --looks to see if this is the first call, if so checks to see if program is installed properly
for _,prog in pairs(prog_tbl) do
if prog.first_run then
prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then
prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false)
else
prog.bin = CleanSpaces(prog.bin)
end
prog.first_run = false
end
end
if not dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 2
dt.print(_('please update you binary locatoin'))
for _,prog in pairs(prog_tbl) do
if prog.first_run then
prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then
prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false)
else
prog.bin = CleanSpaces(prog.bin)
end
prog.first_run = false
end
end
if not dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 2
dt.print(_('please update you binary location'))
end
end
local function ExeUpdate(prog_tbl)
dt.preferences.write(mod, 'bin_exists', 'bool', true)
for _,prog in pairs(prog_tbl) do
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then
prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false)
else
prog.bin = CleanSpaces(prog.bin)
end
prog.first_run = false
end
if dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 1
dt.print(_('update successful'))
dt.preferences.write(mod, 'bin_exists', 'bool', true)
for _,prog in pairs(prog_tbl) do
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then
prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false)
else
dt.print(_('update unsuccessful, please try again'))
prog.bin = CleanSpaces(prog.bin)
end
prog.first_run = false
end
if dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 1
dt.print(_('update successful'))
else
dt.print(_('update unsuccessful, please try again'))
end
end
local function UpdateActivePreference() --sliders & entry boxes do not have a click/changed callback, so their values must be saved to the active preference
temp = GUI.HDR.gap.value
dt.preferences.write(mod, 'active_gap', 'integer', temp)
temp = GUI.Target.add_tags.text
dt.preferences.write(mod, 'active_add_tags', 'string', temp)
temp = GUI.HDR.gap.value
dt.preferences.write(mod, 'active_gap', 'integer', temp)
temp = GUI.Target.add_tags.text
dt.preferences.write(mod, 'active_add_tags', 'string', temp)
end
local function main()
PreCall({HDRM}) --check if furst run then check if install OK
if HDRM.install_error then
dt.print_error(_('HDRMerge install issue'))
dt.print(_('HDRMerge install issue, please ensure the binary path is proper'))
return
PreCall({HDRM}) --check if furst run then check if install OK
if HDRM.install_error then
dt.print_error('HDRMerge install issue')
dt.print(_('HDRMerge install issue, please ensure the binary path is correct'))
return
end
images = dt.gui.selection() --get selected images
if #images < 2 then --ensure enough images selected
dt.print(_('not enough images selected, select at least 2 images to merge'))
return
end
UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup
--create image string and output path
HDRM.images_string = ''
local out_path = ''
local smallest_id = math.huge
local smallest_name = ''
local largest_id = 0
local source_raw = {}
for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng)
local curr_image = image.path..os_path_seperator..image.filename
HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' '
out_path = image.path
_unused, source_name, source_id = GetFileName(image.filename)
source_id = tonumber(source_id) or 0
if source_id < smallest_id then
smallest_id = source_id
smallest_name = source_name
source_raw = image
end
images = dt.gui.selection() --get selected images
if #images < 2 then --ensure enough images selected
dt.print(_('not enough images selected, select at least 2 images to merge'))
return
if source_id > largest_id then largest_id = source_id end
end
out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng'
out_path = df.create_unique_filename(out_path)
--create argument string
HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' '
if GUI.HDR.batch.value then
HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a'
else
HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path)
end
-- create run command and execute
local run_cmd = BuildExecuteCmd(HDRM)
resp = dsys.external_command(run_cmd)
if resp == 0 and not GUI.HDR.batch.value then
local imported = dt.database.import(out_path) -- import the new file
if GUI.Target.style.selected > 1 then -- apply selected style
local set_style = styles[GUI.Target.style.selected - 1]
dt.styles.apply(set_style , imported)
end
UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup
--create image string and output path
HDRM.images_string = ''
local out_path = ''
local smallest_id = math.huge
local smallest_name = ''
local largest_id = 0
local source_raw = {}
for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng)
local curr_image = image.path..os_path_seperator..image.filename
HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' '
out_path = image.path
_, source_name, source_id = GetFileName(image.filename)
source_id = tonumber(source_id)
if source_id < smallest_id then
smallest_id = source_id
smallest_name = source_name
source_raw = image
end
if source_id > largest_id then largest_id = source_id end
if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags)
local all_tags = dt.tags.get_tags(source_raw)
for _,tag in pairs(all_tags) do
if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end
end
end
out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng'
out_path = df.create_unique_filename(out_path)
--create argument string
HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' '
if GUI.HDR.batch.value then
HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a'
else
HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path)
local set_tag = GUI.Target.add_tags.text
if set_tag ~= nil then -- add additional user-specified tags
for tag in string.gmatch(set_tag, '[^,]+') do
tag = CleanSpaces(tag)
tag = dt.tags.create(tag)
dt.tags.attach(tag, imported)
end
end
dt.print(_('HDRMerge completed successfully'))
else
dt.print_error('HDRMerge failed')
dt.print(_('HDRMerge failed'))
end
-- create run command and execute
local run_cmd = BuildExecuteCmd(HDRM)
resp = dsys.external_command(run_cmd)
end
if resp == 0 and not GUI.HDR.batch.value then
local imported = dt.database.import(out_path) -- import the new file
if GUI.Target.style.selected > 1 then -- apply selected style
local set_style = styles[GUI.Target.style.selected - 1]
dt.styles.apply(set_style , imported)
end
if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags)
local all_tags = dt.tags.get_tags(source_raw)
for _,tag in pairs(all_tags) do
if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end
end
end
local set_tag = GUI.Target.add_tags.text
if set_tag ~= nil then -- add additional user-specified tags
for tag in string.gmatch(set_tag, '[^,]+') do
tag = CleanSpaces(tag)
tag = dt.tags.create(tag)
dt.tags.attach(tag, imported)
end
end
dt.print(_('HDRMerge completed successfully'))
else
dt.print_error(_('HDRMerge failed'))
dt.print(_('HDRMerge failed'))
end
local function install_module()
if not HDRM.module_installed then
dt.register_lib( -- register HDRMerge module
'HDRMerge_Lib', -- Module name
_('HDRMerge'), -- name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers
dt.new_widget('box'){
orientation = 'vertical',
GUI.stack
}
)
HDRM.module_installed = true
end
end
local function destroy()
dt.gui.libs["HDRMerge_Lib"].visible = false
end
local function restart()
dt.gui.libs["HDRMerge_Lib"].visible = true
end
-- GUI Elements --
local lbl_hdr = dt.new_widget('section_label'){
label = _('HDRMerge options')
label = _('HDRMerge options')
}
temp = dt.preferences.read(mod, 'active_bps_ind', 'integer')
if not InRange(temp, 1, 3) then temp = 3 end
GUI.HDR.bps = dt.new_widget('combobox'){
label = _('bits per sample'),
tooltip =_('number of bits per sample in the output image'),
selected = temp,
'16','24','32',
changed_callback = function(self)
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
end,
reset_callback = function(self)
self.selected = 3
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
end
label = _('bits per sample'),
tooltip =_('number of bits per sample in the output image'),
selected = temp,
'16','24','32',
changed_callback = function(self)
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
end,
reset_callback = function(self)
self.selected = 3
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
end
}
temp = dt.preferences.read(mod, 'active_size_ind', 'integer')
if not InRange(temp, 1, 3) then temp = 2 end
GUI.HDR.size = dt.new_widget('combobox'){
label = _('embedded preview size'),
tooltip =_('size of the embedded preview in output image'),
selected = temp,
_('none'),_('half'),_('full'),
changed_callback = function(self)
dt.preferences.write(mod, 'active_size', 'string', self.value)
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
end,
reset_callback = function(self)
self.selected = 2
dt.preferences.write(mod, 'active_size', 'string', self.value)
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
end
label = _('embedded preview size'),
tooltip =_('size of the embedded preview in output image'),
selected = temp,
_('none'),_('half'),_('full'),
changed_callback = function(self)
dt.preferences.write(mod, 'active_size', 'string', self.value)
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
end,
reset_callback = function(self)
self.selected = 2
dt.preferences.write(mod, 'active_size', 'string', self.value)
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
end
}
GUI.HDR.batch = dt.new_widget('check_button'){
label = _('batch mode'),
value = dt.preferences.read(mod, 'active_batch', 'bool'),
tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'),
clicked_callback = function(self)
dt.preferences.write(mod, 'active_batch', 'bool', self.value)
GUI.HDR.gap.sensitive = self.value
end,
reset_callback = function(self) self.value = false end
label = _('batch mode'),
value = dt.preferences.read(mod, 'active_batch', 'bool'),
tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'),
clicked_callback = function(self)
dt.preferences.write(mod, 'active_batch', 'bool', self.value)
GUI.HDR.gap.sensitive = self.value
end,
reset_callback = function(self) self.value = false end
}
temp = dt.preferences.read(mod, 'active_gap', 'integer')
if not InRange(temp, 1, 3600) then temp = 3 end
GUI.HDR.gap = dt.new_widget('slider'){
label = _('batch gap [sec.]'),
tooltip = _('gap, in seconds, between batch mode groups'),
soft_min = 1,
soft_max = 30,
hard_min = 1,
hard_max = 3600,
step = 1,
digits = 0,
value = temp,
sensitive = GUI.HDR.batch.value,
reset_callback = function(self)
self.value = 3
end
label = _('batch gap [sec.]'),
tooltip = _('gap, in seconds, between batch mode groups'),
soft_min = 1,
soft_max = 30,
hard_min = 1,
hard_max = 3600,
step = 1,
digits = 0,
value = temp,
sensitive = GUI.HDR.batch.value,
reset_callback = function(self)
self.value = 3
end
}
local lbl_import = dt.new_widget('section_label'){
label = _('import options')
label = _('import options')
}
GUI.Target.style = dt.new_widget('combobox'){
label = _('apply style on import'),
tooltip = _('Apply selected style on auto-import to newly created image'),
selected = 1,
_('none'),
changed_callback = function(self)
dt.preferences.write(mod, 'active_style', 'string', self.value)
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
end,
reset_callback = function(self)
self.selected = 1
dt.preferences.write(mod, 'active_style', 'string', self.value)
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
end
label = _('apply style on import'),
tooltip = _('apply selected style on auto-import to newly created image'),
selected = 1,
_('none'),
changed_callback = function(self)
dt.preferences.write(mod, 'active_style', 'string', self.value)
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
end,
reset_callback = function(self)
self.selected = 1
dt.preferences.write(mod, 'active_style', 'string', self.value)
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
end
}
for k=1, (styles_count-1) do
GUI.Target.style[k+1] = styles[k].name
GUI.Target.style[k+1] = styles[k].name
end
temp = dt.preferences.read(mod, 'active_style_ind', 'integer')
if not InRange(temp, 1, styles_count) then temp = 1 end
GUI.Target.style.selected = temp
GUI.Target.copy_tags = dt.new_widget('check_button'){
label = _('copy tags'),
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
tooltip = _('copy tags from first source image'),
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
reset_callback = function(self) self.value = true end
label = _('copy tags'),
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
tooltip = _('copy tags from first source image'),
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
reset_callback = function(self) self.value = true end
}
temp = dt.preferences.read(mod, 'active_add_tags', 'string')
if temp == '' then temp = nil end
GUI.Target.add_tags = dt.new_widget('entry'){
tooltip = _('Additional tags to be added on import. Seperate with commas, all spaces will be removed'),
text = temp,
placeholder = _('Enter tags, seperated by commas'),
editable = true
tooltip = _('additional tags to be added on import, separate with commas, all spaces will be removed'),
text = temp,
placeholder = _('enter tags, separated by commas'),
editable = true
}
GUI.run = dt.new_widget('button'){
label = _('merge'),
tooltip =_('run HDRMerge with the above specified settings'),
clicked_callback = function() main() end
label = _('merge'),
tooltip =_('run HDRMerge with the above settings'),
clicked_callback = function() main() end
}
GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){
title = _('Select HDRmerge executable'),
value = df.get_executable_path_preference(HDRM.name),
is_directory = false
title = _('select HDRmerge executable'),
value = df.get_executable_path_preference(HDRM.name),
is_directory = false
}
GUI.exes.update = dt.new_widget('button'){
label = _('update'),
tooltip =_('update the binary path with current value'),
clicked_callback = function() ExeUpdate({HDRM}) end
label = _('update'),
tooltip =_('update the binary path with current value'),
clicked_callback = function() ExeUpdate({HDRM}) end
}
GUI.options = dt.new_widget('box'){
orientation = 'vertical',
lbl_hdr,
GUI.HDR.bps,
GUI.HDR.size,
GUI.HDR.batch,
GUI.HDR.gap,
lbl_import,
GUI.Target.style,
GUI.Target.copy_tags,
GUI.Target.add_tags,
GUI.run
orientation = 'vertical',
lbl_hdr,
GUI.HDR.bps,
GUI.HDR.size,
GUI.HDR.batch,
GUI.HDR.gap,
lbl_import,
GUI.Target.style,
GUI.Target.copy_tags,
GUI.Target.add_tags,
GUI.run
}
local exes_box = dt.new_widget('box'){
orientation = 'vertical',
GUI.exes.HDRMerge,
GUI.exes.update
orientation = 'vertical',
GUI.exes.HDRMerge,
GUI.exes.update
}
GUI.stack = dt.new_widget('stack'){
GUI.options,
exes_box
GUI.options,
exes_box
}
if dt.preferences.read(mod, 'bin_exists', 'bool') then
GUI.stack.active = 1
GUI.stack.active = 1
else
GUI.stack.active = 2
GUI.stack.active = 2
end
dt.register_lib( -- register HDRMerge module
'HDRMerge_Lib', -- Module name
_('HDRMerge'), -- name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers
dt.new_widget('box'){
orientation = 'vertical',
GUI.stack
}
)
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not HDRM.event_registered then
dt.register_event(
"HDRmerge", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
HDRM.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -50,12 +50,38 @@
local darktable = require("darktable")
local du = require "lib/dtutils"
du.check_min_api_version("3.0.0", "LabelsToTags")
du.check_min_api_version("7.0.0", "LabelsToTags")
local gettext = darktable.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("labels to tags"),
purpose = _("allows the mass-application of tags using color labels and ratings as a guide"),
author = "August Schwerdfeger (august@schwerdfeger.name)",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/LabelsToTags"
}
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, otherwise leave as nil
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
-- Lua 5.3 no longer has "unpack" but "table.unpack"
unpack = unpack or table.unpack
local LIB_ID = "LabelsToTags"
local ltt = {}
ltt.module_installed = false
ltt.event_registered = false
local LIB_ID = _("LabelsToTags")
-- Helper functions: BEGIN
@@ -96,23 +122,23 @@ end
local initialAvailableMappings = {
["Colors"] = { ["+*****"] = { "Red" },
["*+****"] = { "Yellow" },
["**+***"] = { "Green" },
["***+**"] = { "Blue" },
["****+*"] = { "Purple" } },
["Single colors"] = { ["+----*"] = { "Red", "Only red" },
["-+---*"] = { "Yellow", "Only yellow" },
["--+--*"] = { "Green", "Only green" },
["---+-*"] = { "Blue", "Only blue" },
["----+*"] = { "Purple", "Only purple" } },
["Ratings"] = { ["*****0"] = { "No stars", "Not rejected" },
["*****1"] = { "One star", "Not rejected" },
["*****2"] = { "Two stars", "Not rejected" },
["*****3"] = { "Three stars", "Not rejected" },
["*****4"] = { "Four stars", "Not rejected" },
["*****5"] = { "Five stars", "Not rejected" },
["*****R"] = { "Rejected" } }
[_("colors")] = { ["+*****"] = { _("red") },
["*+****"] = { _("yellow") },
["**+***"] = { _("green") },
["***+**"] = { _("blue") },
["****+*"] = { _("purple") } },
[_("single colors")] = { ["+----*"] = { _("red"), _("only red") },
["-+---*"] = { _("yellow"), _("only yellow") },
["--+--*"] = { _("green"), _("only green") },
["---+-*"] = { _("blue"), _("only blue") },
["----+*"] = { _("purple"), _("only purple") } },
[_("ratings")] = { ["*****0"] = { _("no stars"), _("not rejected") },
["*****1"] = { _("one star"), _("not rejected") },
["*****2"] = { _("two stars"), _("not rejected") },
["*****3"] = { _("three stars"), _("not rejected") },
["*****4"] = { _("four stars"), _("not rejected") },
["*****5"] = { _("five stars"), _("not rejected") },
["*****R"] = { _("rejected") } }
}
local availableMappings = {}
@@ -127,28 +153,28 @@ end
local function getComboboxTooltip()
if availableMappings == nil or next(availableMappings) == nil then
return("No registered mappings -- using defaults")
return(_("no registered mappings -- using defaults"))
else
return("Select a label-to-tag mapping")
return(_("select a label-to-tag mapping"))
end
end
local mappingComboBox = darktable.new_widget("combobox"){
label = "mapping",
label = _("mapping"),
value = 1,
tooltip = getComboboxTooltip(),
reset_callback = function(selfC)
if selfC == nil then
return
return
end
i = 1
for _,m in pairs(keySet(getAvailableMappings())) do
selfC[i] = m
i = i+1
selfC[i] = m
i = i+1
end
n = #selfC
for j = i,n do
selfC[i] = nil
selfC[i] = nil
end
selfC.value = 1
selfC.tooltip = getComboboxTooltip()
@@ -157,7 +183,7 @@ local mappingComboBox = darktable.new_widget("combobox"){
}
local function doTagging(selfC)
local job = darktable.gui.create_job(string.format("labels to tags (%d image" .. (#(darktable.gui.action_images) == 1 and "" or "s") .. ")",#(darktable.gui.action_images)),true)
local job = darktable.gui.create_job(string.format(_("labels to tags (%d image%s)"), #(darktable.gui.action_images), (#(darktable.gui.action_images) == 1 and "" or "s")), true)
job.percent = 0.0
local pctIncrement = 1.0 / #(darktable.gui.action_images)
@@ -167,29 +193,29 @@ local function doTagging(selfC)
local tagsToApply = {}
local hash = generateLabelHash(img)
for k,v in pairs(availableMappings[mappingComboBox.value]) do
if hashMatch(hash,k) then
for _,tag in ipairs(v) do
tagsToApply[tag] = true
end
end
if hashMatch(hash,k) then
for _,tag in ipairs(v) do
tagsToApply[tag] = true
end
end
end
for k,_ in pairs(tagsToApply) do
if memoizedTags[k] == nil then
memoizedTags[k] = darktable.tags.create(k)
end
darktable.tags.attach(memoizedTags[k],img)
if memoizedTags[k] == nil then
memoizedTags[k] = darktable.tags.create(k)
end
darktable.tags.attach(memoizedTags[k],img)
end
job.percent = job.percent + pctIncrement
end
job.valid = false
end
local my_widget = darktable.new_widget("box") {
ltt.my_widget = darktable.new_widget("box") {
orientation = "vertical",
mappingComboBox,
darktable.new_widget("button") {
label = "start",
tooltip = "Tag all selected images",
label = _("start"),
tooltip = _("tag all selected images"),
clicked_callback = doTagging
}
}
@@ -203,20 +229,37 @@ darktable.register_tag_mapping = function(name, mapping)
end
for pattern,tags in pairs(mapping) do
if string.match(pattern,PATTERN_PATTERN) == nil then
darktable.print_error("In tag mapping '" .. name .. "': Pattern '" .. pattern .. "' invalid")
return
darktable.print_error("In tag mapping '" .. name .. "': Pattern '" .. pattern .. "' invalid")
return
end
for _,tag in ipairs(tags) do
if type(tag) ~= "string" then
darktable.print_error("In tag mapping '" .. name .. "': All tag mappings must be lists of strings")
return
end
if type(tag) ~= "string" then
darktable.print_error("In tag mapping '" .. name .. "': All tag mappings must be lists of strings")
return
end
end
end
availableMappings[name] = mapping
mappingComboBox.reset_callback(mappingComboBox)
end
local function install_module()
if not ltt.module_installed then
darktable.register_lib(LIB_ID,_("labels to tags"),true,true,{
[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20},
},ltt.my_widget,nil,nil)
ltt.module_installed = true
end
end
local function destroy()
darktable.gui.libs[LIB_ID].visible = false
end
local function restart()
darktable.gui.libs[LIB_ID].visible = true
end
--[[
darktable.register_tag_mapping("Example",
{ ["+----*"] = { "Red", "Only red" },
@@ -229,6 +272,25 @@ darktable.register_tag_mapping("Example",
["*****R"] = { "Rejected" } })
]]
darktable.register_lib(LIB_ID,"labels to tags",true,true,{
[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20},
},my_widget,nil,nil)
if darktable.gui.current_view().id == "lighttable" then
install_module()
else
if not ltt.event_registered then
darktable.register_event(
LIB_ID, "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
ltt.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -32,12 +32,18 @@ Install: (see here for more detail: https://github.com/darktable-org/lua-scripts
Select the photo(s) you wish to find in your operating system's file manager and press "show in file explorer" in the "selected images" section.
- Nautilus (Linux), Explorer (Windows), and Finder (macOS before Catalina) will open one window for each selected image at the file's location. The file name will be highlighted.
- Nautilus (Linux), Explorer (Windows), and Finder (macOS prior to Mojave) will open one window for each selected image at the file's location. The file name will be highlighted.
- On macOS Catalina the Finder will open one window for each different directory. In these windows only the last one of the corresponding files will be highlighted (bug or feature?).
- On macOS Mojave and Catalina the Finder will open one window for each different directory. In these windows only the last one of the corresponding files will be highlighted (bug or feature?).
- Dolphin (Linux) will open one window with tabs for the different directories. All the selected images' file names are highlighted in their respective directories.
As an alternative option you can choose to show the image file names as symbolic links in an arbitrary directory. Go to preferences|Lua options. This option is not available for Windows users as on Windows solely admins are allowed to create links.
- Pros: You do not clutter up your display with multiple windows. So there is no need to limit the number of selections.
- Cons: If you want to work with the files you are one step behind the original data.
----KNOWN ISSUES----
]]
@@ -45,67 +51,211 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
--Check API version
du.check_min_api_version("5.0.0", "OpenInExplorer")
gettext.bindtextdomain("OpenInExplorer",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "OpenInExplorer")
local function _(msgid)
return gettext.dgettext("OpenInExplorer", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("open in explorer"),
purpose = _("open a selected file in the system file manager"),
author = "Kevin Ertel",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/OpenInExplorer"
}
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, otherwise leave as nil
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
local act_os = dt.configuration.running_os
local PS = act_os == "windows" and "\\" or "/"
--Detect OS and quit if it is not supported--
local proper_install = true
--Detect OS and quit if it is not supported.
if act_os ~= "macos" and act_os ~= "windows" and act_os ~= "linux" then
proper_install = false
dt.print_error(_('OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time'))
dt.print(_("OpenInExplorer plug-in only supports linux, macos, and windows at this time"))
dt.print_error("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time")
return
end
--Format strings for the commands to open the corresponding OS' file manager
local fmng_cmd = {}
fmng_cmd.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s""]]
fmng_cmd.macos = 'open -Rn %s'
fmng_cmd.windows = 'explorer.exe /select, %s'
local use_links, links_dir = false, ""
if act_os ~= "windows" then
use_links = dt.preferences.read("OpenInExplorer", "use_links", "bool")
links_dir = dt.preferences.read("OpenInExplorer", "linked_image_files_dir", "string")
end
--The working function that opens the file manager windows with the selected image file names highlighted.
local function open_in_fmanager(op_sys, fmcmd)
local images = dt.gui.selection()
local curr_image, run_cmd, file_uris = '', '', ''
if #images == 0 then
dt.print(_('Please select an image'))
elseif #images <= 15 then
for _,image in pairs(images) do
curr_image = image.path..PS..image.filename
if op_sys == 'linux' then
file_uris = file_uris .. df.sanitize_filename("file://" .. curr_image) .. " "
dt.print_log("file_uris is " .. file_uris)
else
run_cmd = string.format(fmcmd, df.sanitize_filename(curr_image))
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
dsys.external_command(run_cmd)
end
end
if op_sys == 'linux' then
run_cmd = string.format(fmcmd, #images, file_uris)
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
dsys.external_command(run_cmd)
end
else
dt.print(_('Please select fewer images (max 15)'))
--Check if the directory exists that was chosen for the file links. Return boolean.
local function check_if_links_dir_exists()
local dir_exists = true
if not links_dir then
--Just for paranoic reasons. I tried, but I couldn't devise a setting for a nil value.
dt.print(_("no links directory selected\nplease check the dt preferences (lua options)"))
dt.print_error("OpenInExplorer: No links directory selected")
dir_exists = false
elseif not df.check_if_file_exists(links_dir) then
dt.print(string.format(_("links directory '%s' not found\nplease check the dt preferences (lua options)"), links_dir))
dt.print_error(string.format("OpenInExplorer: Links directory '%s' not found", links_dir))
dir_exists = false
end
return dir_exists
end
--Format strings for the commands to open the corresponding OS's file manager.
local open_dir = {}
open_dir.windows = "explorer.exe /n, %s"
open_dir.macos = "open %s"
open_dir.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowFolders ass 1 %s ""]]
local open_files = {}
open_files.windows = "explorer.exe /select, %s"
open_files.macos = "osascript -e 'tell application \"Finder\" to (reveal {%s}) activate'"
open_files.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s ""]]
reveal_file_osx_cmd = "\"%s\" as POSIX file"
--Call the osx Finder with each selected image selected.
--For images in multiple folders, Finder will open a separate window for each
local function call_list_of_files_osx(selected_images)
local cmds = {}
for _, image in pairs(selected_images) do
current_image = image.path..PS..image.filename
-- AppleScript needs double quoted strings, and the whole command is wrapped in single quotes.
table.insert(cmds, string.format(reveal_file_osx_cmd, string.gsub(string.gsub(current_image, "\"", "\\\""), "'", "'\"'\"'")))
end
reveal_cmd = table.concat(cmds, ",")
run_cmd = string.format(open_files.macos, reveal_cmd)
dt.print_log("OSX run_cmd = "..run_cmd)
dsys.external_command(run_cmd)
end
--Call the file mangager for each selected image on Linux.
--There is one call to busctl containing a list of all the image file names.
local function call_list_of_files(selected_images)
local current_image, file_uris, run_cmd = "", "", ""
for _, image in pairs(selected_images) do
current_image = image.path..PS..image.filename
file_uris = file_uris .. df.sanitize_filename("file://" .. current_image) .. " "
dt.print_log("file_uris is " .. file_uris)
end
run_cmd = string.format(open_files.linux, #selected_images, file_uris)
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
dsys.external_command(run_cmd)
end
--Call the file manager for each selected image on Windows and macOS.
local function call_file_by_file(selected_images)
local current_image, run_cmd = "", ""
for _, image in pairs(selected_images) do
current_image = image.path..PS..image.filename
run_cmd = string.format(open_files[act_os], df.sanitize_filename(current_image))
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
dsys.external_command(run_cmd)
end
end
--Create a link for each selected image, and finally call the file manager.
local function set_links(selected_images)
local current_image, link_target, run_cmd, k = "", "", "", nil
for k, image in pairs(selected_images) do
current_image = image.path..PS..image.filename
link_target = df.create_unique_filename(links_dir .. PS .. image.filename)
run_cmd = string.format("ln -s %s %s", df.sanitize_filename(current_image), df.sanitize_filename(link_target))
--[[
In case Windows will allow normal users to create soft links:
if act_os == "windows" then
run_cmd = string.format("mklink %s %s", df.sanitize_filename(link_target), df.sanitize_filename(current_image))
end
]]
if dsys.external_command(run_cmd) ~= 0 then
dt.print(_("failed to create links, missing rights?"))
dt.print_error("OpenInExplorer: Failed to create links")
return
end
end
--The URI format is necessary only for the Linux busctl command.
--But it is accepted by the Windows Explorer and macOS's Finder all the same.
run_cmd = string.format(open_dir[act_os], df.sanitize_filename("file://"..links_dir))
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
dsys.external_command(run_cmd)
end
--The working function that starts the particular task.
local function open_in_fmanager()
local images = dt.gui.selection()
if #images == 0 then
dt.print(_("please select an image"))
else
if use_links and not check_if_links_dir_exists() then
return
end
if #images > 15 and not use_links then
dt.print(_("please select fewer images (max. 15)"))
elseif use_links then
set_links(images)
else
if act_os == "linux" then
call_list_of_files(images)
elseif act_os == "macos" then
call_list_of_files_osx(images)
else
call_file_by_file(images)
end
end
end
end
local function destroy()
dt.gui.libs.image.destroy_action("OpenInExplorer")
dt.destroy_event("OpenInExplorer", "shortcut")
if act_os ~= "windows" then
dt.preferences.destroy("OpenInExplorer", "linked_image_files_dir")
end
dt.preferences.destroy("OpenInExplorer", "use_links")
end
-- GUI --
if proper_install then
dt.gui.libs.image.register_action(
_("show in file explorer"),
function() open_in_fmanager(act_os, fmng_cmd[act_os]) end,
_("Opens the file manager at the selected image's location")
dt.gui.libs.image.register_action(
"OpenInExplorer", _("show in file explorer"),
function() open_in_fmanager() end,
_("open the file manager at the selected image's location")
)
if act_os ~= "windows" then
dt.preferences.register("OpenInExplorer", "linked_image_files_dir", -- name
"directory", -- type
_("OpenInExplorer: linked files directory"), -- label
_("directory to store the links to the file names, requires restart to take effect"), -- tooltip
"Links to image files", -- default
dt.new_widget("file_chooser_button"){
title = _("select directory"),
is_directory = true,
}
)
dt.preferences.register("OpenInExplorer", "use_links", -- name
"bool", -- type
_("OpenInExplorer: use links"), -- label
_("use links instead of multiple windows, requires restart to take effect"), -- tooltip
false, -- default
""
)
end
dt.register_event(
"OpenInExplorer", "shortcut",
function(event, shortcut) open_in_fmanager() end,
"OpenInExplorer"
)
script_data.destroy = destroy
return script_data

View File

@@ -1,11 +1,30 @@
--[[
Richardson-Lucy output sharpening for darktable using GMic
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 <http://www.gnu.org/licenses/>.
]]
--[[
DESCRIPTION
RL_out_sharp.lua - Richardson-Lucy output sharpening using GMic
This script provides a new target storage "RL output sharpen".
Images exported will be sharpened using GMic (RL deblur algorithm)
REQUIRED SOFTWARE
GMic command line interface (CLI) https://gmic.eu/download.shtml
USAGE
* require this script from main lua file
* in lua preferences, select the GMic cli executable
@@ -33,7 +52,6 @@
CHANGES
* 20200308 - initial version
]]
local dt = require "darktable"
@@ -45,25 +63,52 @@ local dtsys = require "lib/dtutils.system"
local MODULE_NAME = "RL_out_sharp"
-- check API version
du.check_min_api_version("5.0.0", MODULE_NAME)
du.check_min_api_version("7.0.0", MODULE_NAME)
-- translation
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("RL output sharpening"),
purpose = _("Richardson-Lucy output sharpening using GMic"),
author = "Marco Carrarini <marco.carrarini@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/RL_out_sharp"
}
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, otherwise leave as nil
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
-- OS compatibility
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- translation
local gettext = dt.gettext
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
end
-- initialize module preferences
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
dt.preferences.write(MODULE_NAME, "sigma", "string", "0.7")
dt.preferences.write(MODULE_NAME, "iterations", "string", "10")
dt.preferences.write(MODULE_NAME, "jpg_quality", "string", "95")
dt.preferences.write(MODULE_NAME, "initialized", "bool", true)
end
-- preserve original image metadata in the output image -----------------------
local function preserve_metadata(original, sharpened)
local exiftool = df.check_if_bin_exists("exiftool")
if exiftool then
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
else
dt.print_log(MODULE_NAME .. " exiftool not found, metadata not preserved")
end
end
-- setup export ---------------------------------------------------------------
@@ -110,7 +155,7 @@ local function export2RL(storage, image_table, extra_data)
for image, temp_name in pairs(image_table) do
i = i + 1
dt.print(_("sharpening image ")..i.." ...")
dt.print(string.format(_("sharpening image %d ..."), i))
-- create unique filename
new_name = output_folder..PS..df.get_basename(temp_name)..".jpg"
new_name = df.create_unique_filename(new_name)
@@ -127,7 +172,10 @@ local function export2RL(storage, image_table, extra_data)
if result ~= 0 then
dt.print(_("sharpening error"))
return
end
end
-- copy metadata from input_file to output_file
preserve_metadata(input_file, output_file)
-- delete temp image
os.remove(temp_name)
@@ -137,6 +185,12 @@ local function export2RL(storage, image_table, extra_data)
dt.print(_("finished exporting"))
end
-- script_manager integration
local function destroy()
dt.destroy_storage("exp2RL")
end
-- new widgets ----------------------------------------------------------------
output_folder_selector = dt.new_widget("file_chooser_button"){
@@ -156,7 +210,7 @@ sigma_slider = dt.new_widget("slider"){
soft_max = 2.0,
hard_min = 0.0,
hard_max = 3.0,
step = 0.1,
step = 0.05,
digits = 2,
value = 1.0
}
@@ -206,6 +260,12 @@ sigma_slider.value = dt.preferences.read(MODULE_NAME, "sigma", "float")
iterations_slider.value = dt.preferences.read(MODULE_NAME, "iterations", "float")
jpg_quality_slider.value = dt.preferences.read(MODULE_NAME, "jpg_quality", "float")
-- script_manager integration
script_data.destroy = destroy
return script_data
-- end of script --------------------------------------------------------------
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

132
contrib/auto_snapshot.lua Normal file
View File

@@ -0,0 +1,132 @@
--[[
auto_snapshot.lua - automatically take a snapshot when an image is loaded in darkroom
Copyright (C) 2024 Bill Ferguson <wpferguson@gamil.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
auto_snapshot -
automatically take a snapshot when an image is loaded in darkroom
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* start the script from script_manager
* open an image in darkroom
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson@gamil.com>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local log = require "lib/dtutils.log"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE <const> = "auto_snapshot"
local DEFAULT_LOG_LEVEL <const> = log.error
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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 = _("auto snapshot"), -- name of script
purpose = _("automatically take a snapshot when an image is loaded in darkroom"), -- purpose of script
author = "Bill Ferguson <wpferguson@gamil.com>", -- your name and optionally e-mail address
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_snapshot/" -- URL to help/documentation
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O G L E V E L
-- - - - - - - - - - - - - - - - - - - - - - - -
log.log_level(DEFAULT_LOG_LEVEL)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local auto_snapshot = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.preferences.register(MODULE, "always_create_snapshot", "bool", "auto_snapshot - " .. _("always automatically create_snapshot"),
_("create a snapshot even if the image is altered"), false)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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, "darkroom-image-loaded")
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_event(MODULE, "darkroom-image-loaded",
function(event, clean, image)
local always = dt.preferences.read(MODULE, "always_create_snapshot", "bool")
if clean and always then
dt.gui.libs.snapshots.take_snapshot()
elseif clean and not image.is_altered then
dt.gui.libs.snapshots.take_snapshot()
end
end
)
return script_data

View File

@@ -0,0 +1,129 @@
--[[
auto_tag_modified.lua - automatically tag an image when it has been manually modified in darkroom
Copyright (C) 2025 Michael Reiger <michael@rauschpfeife.net>, modeled after auto_snapshot.lua by Bill Ferguson <wpferguson@gamil.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
auto_tag_modified -
automatically tag an image when it has been manually modified in the darkroom, that is if its history has changed at the time it is closed from the darkroom.
(This will not catch applying styles from the light table though.)
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* start the script from script_manager
* open an image in darkroom, and close it after making an operation that changes the history
BUGS, COMMENTS, SUGGESTIONS
Michael Reiger <michael@rauschpfeife.net>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local log = require "lib/dtutils.log"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE <const> = "auto_tag_modified"
local DEFAULT_TAG_NAME <const> = "darktable manually modified"
local DEFAULT_LOG_LEVEL <const> = log.error
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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 = _("auto tag modified"), -- name of script
purpose = _("automatically tag an image when it has been manually modified in darkroom"), -- purpose of script
author = "Michael Reiger <michael@rauschpfeife.net>", -- your name and optionally e-mail address
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_tag_modified/" -- URL to help/documentation
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O G L E V E L
-- - - - - - - - - - - - - - - - - - - - - - - -
log.log_level(DEFAULT_LOG_LEVEL)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local auto_tag_modified = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.preferences.register(MODULE, "auto_modified_tag_name", "string", "auto_tag_modified - " .. _("tag name to use for auto tagging modified images"),
_("tag name to use for auto tagging modified images"), DEFAULT_TAG_NAME)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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, "darkroom-image-history-changed")
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_event(MODULE, "darkroom-image-history-changed",
function(event, image)
local tag_name = dt.preferences.read(MODULE, "auto_modified_tag_name", "string")
local tag = dt.tags.create(tag_name)
dt.tags.attach(tag, image)
end
)
return script_data

View File

@@ -39,101 +39,36 @@ GPLv2
local darktable = require "darktable"
local du = require "lib/dtutils"
local filelib = require "lib/dtutils.file"
local syslib = require "lib/dtutils.system"
-- Forward declare the functions
local autostyle_apply_one_image,autostyle_apply_one_image_event,autostyle_apply,exiftool_attribute,capture
du.check_min_api_version("7.0.0", "autostyle")
-- Tested it with darktable 1.6.1 and darktable git from 2014-01-25
du.check_min_api_version("2.0.2", "autostyle")
local gettext = darktable.gettext.gettext
-- Receive the event triggered
function autostyle_apply_one_image_event(event,image)
autostyle_apply_one_image(image)
local function _(msgid)
return gettext(msgid)
end
-- Apply the style to an image, if it matches the tag condition
function autostyle_apply_one_image (image)
-- We need the tag, the value and the style_name provided from the configuration string
local tag,value,style_name=string.match(darktable.preferences.read("autostyle","exif_tag","string"),"(%g+)%s*=%s*(%g+)%s*=>%s*(%g+)")
-- return data structure for script_manager
-- check they all exist (correct syntax)
if (not tag) then
darktable.print("EXIF TAG not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
return 0
end
if (not value) then
darktable.print("value to match not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
return 0
end
if (not style_name) then
darktable.print("style name not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
return 0
end
if not filelib.check_if_bin_exists("exiftool") then
darktable.print("Can't find exiftool")
return 0
end
local script_data = {}
local have_not_printed_config_message = true
-- First find the style (we have its name)
local styles= darktable.styles
local style
for _,s in ipairs(styles) do
if s.name == style_name then
style=s
end
end
if (not style) then
darktable.print("style not found for autostyle: " .. style_name)
return 0
end
script_data.metadata = {
name = _("auto style"),
purpose = _("automatically apply a style based on image EXIF tag"),
author = "Marc Cousin <cousinmarc@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle/"
}
-- Apply the style to image, if it is tagged
local ok,auto_dr_attr= pcall(exiftool_attribute,image.path .. '/' .. image.filename,tag)
--darktable.print_error("dr_attr:" .. auto_dr_attr)
-- If the lookup fails, stop here
if (not ok) then
darktable.print("Couldn't get attribute " .. auto_dr_attr .. " from exiftool's output")
return 0
end
if auto_dr_attr==value then
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. darktable.preferences.read("autostyle","exif_tag","string") )
darktable.styles.apply(style,image)
return 1
else
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. darktable.preferences.read("autostyle","exif_tag","string") .. " not matched: " .. auto_dr_attr)
return 0
end
end
function autostyle_apply( shortcut)
local images = darktable.gui.action_images
local images_processed =0
local images_submitted =0
for _,image in pairs(images) do
images_submitted = images_submitted +1
images_processed = images_processed + autostyle_apply_one_image(image)
end
darktable.print("Applied auto style to " .. images_processed .. " out of " .. images_submitted .. " image(s)")
end
-- Retrieve the attribute through exiftool
function exiftool_attribute(path,attr)
local cmd="exiftool -" .. attr .. " '" .. path .. "'";
local exifresult=get_stdout(cmd)
local attribute=string.match(exifresult,": (.*)")
if (attribute == nil) then
darktable.print_error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">")
-- Raise an error to the caller
error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">");
end
-- darktable.print_error("Returning attribute: " .. attribute)
return attribute
end
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, otherwise leave as nil
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
-- run command and retrieve stdout
function get_stdout(cmd)
local function get_stdout(cmd)
-- Open the command, for reading
local fd = assert(io.popen(cmd, 'r'))
darktable.control.read(fd)
@@ -150,12 +85,116 @@ function get_stdout(cmd)
return data
end
-- Retrieve the attribute through exiftool
local function exiftool_attribute(path, attr)
local cmd = "exiftool -" .. attr .. " '" .. path .. "'";
local exifresult = get_stdout(cmd)
local attribute = string.match(exifresult, ": (.*)")
if (attribute == nil) then
darktable.print_error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">")
-- Raise an error to the caller
error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">");
end
-- darktable.print_error("Returning attribute: " .. attribute)
return attribute
end
-- Apply the style to an image, if it matches the tag condition
local function autostyle_apply_one_image (image)
local pref = darktable.preferences.read("autostyle", "exif_tag", "string")
if pref and string.len(pref) >= 6 then
-- We need the tag, the value and the style_name provided from the configuration string
local tag, value, style_name = string.match(pref, "(%g+)%s*=%s*([%g ]-)%s*=>%s*(%g+)")
-- check they all exist (correct syntax)
if (not tag) then
darktable.print(string.format(_("EXIF tag not found in %s"), pref))
return 0
end
if (not value) then
darktable.print(string.format(_("value to match not found in %s"), pref))
return 0
end
if (not style_name) then
darktable.print(string.format(_("style name not found in %s"), pref))
return 0
end
if not filelib.check_if_bin_exists("exiftool") then
darktable.print(_("can't find exiftool"))
return 0
end
-- First find the style (we have its name)
local styles = darktable.styles
local style
for _, s in ipairs(styles) do
if s.name == style_name then
style = s
end
end
if (not style) then
darktable.print(string.format(_("style not found for autostyle: %s"), style_name))
return 0
end
-- Apply the style to image, if it is tagged
local ok, auto_dr_attr = pcall(exiftool_attribute, image.path .. '/' .. image.filename,tag)
--darktable.print_error("dr_attr:" .. auto_dr_attr)
-- If the lookup fails, stop here
if (not ok) then
darktable.print(string.format(_("couldn't get attribute %s from exiftool's output"), auto_dr_attr))
return 0
end
if auto_dr_attr == value then
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. pref)
darktable.styles.apply(style,image)
return 1
else
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. pref .. " not matched: " .. auto_dr_attr)
return 0
end
elseif have_not_printed_config_message then
have_not_printed_config_message = false
darktable.print(string.format(_("%s is not configured, please configure the preference in Lua options"), script_data.metadata.name))
end
end
-- Receive the event triggered
local function autostyle_apply_one_image_event(event,image)
autostyle_apply_one_image(image)
end
local function autostyle_apply(shortcut)
local images = darktable.gui.action_images
local images_processed = 0
local images_submitted = 0
for _,image in pairs(images) do
images_submitted = images_submitted + 1
images_processed = images_processed + autostyle_apply_one_image(image)
end
darktable.print(string.format(_("applied auto style to %d out of %d image(s)"), images_processed, images_submitted))
end
local function destroy()
darktable.destroy_event("autostyle", "shortcut")
darktable.destroy_event("autostyle", "post-import-image")
end
-- Registering events
darktable.register_event("shortcut",autostyle_apply,
"Apply your chosen style from exiftool tags")
darktable.register_event("autostyle", "shortcut", autostyle_apply,
_("apply your chosen style from exiftool tags"))
darktable.preferences.register("autostyle","exif_tag","string","Autostyle: EXIF_tag=value=>style","apply a style automatically if an EXIF_tag matches value. Find the tag with exiftool","")
darktable.preferences.register("autostyle", "exif_tag", "string",
string.format("%s: EXIF_tag=value=>style", script_data.metadata.name),
_("apply a style automatically if an EXIF tag matches value, find the tag with exiftool"), "")
darktable.register_event("post-import-image",autostyle_apply_one_image_event)
darktable.register_event("autostyle", "post-import-image",
autostyle_apply_one_image_event)
script_data.destroy = destroy
return script_data

View File

@@ -0,0 +1,205 @@
--[[
change group leader
copyright (c) 2020, 2022 Bill Ferguson <wpferguson@gmail.com>
copyright (c) 2021 Angel Angelov
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 <http://www.gnu.org/licenses/>.
]]
--[[
CHANGE GROUP LEADER
automatically change the leader of raw+jpg paired image groups
INSTALLATION
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
* add the following line in the file $CONFIGDIR/luarc
require "change_group_leader"
USAGE
* in lighttable mode, select the image groups you wish to process,
select whether you want to set the leader to "jpg" or "raw",
and click "Execute"
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext.gettext
local MODULE = "change_group_leader"
du.check_min_api_version("3.0.0", MODULE)
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("change group leader"),
purpose = _("automatically change the leader of raw+jpg paired image groups"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/change_group_leader"
}
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, otherwise leave as nil
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
-- create a namespace to contain persistent data and widgets
local chg_grp_ldr = {}
local cgl = chg_grp_ldr
cgl.widgets = {}
cgl.event_registered = false
cgl.module_installed = false
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function install_module()
if not cgl.module_installed then
dt.register_lib(
MODULE, -- Module name
_("change group leader"), -- Visible name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700}}, -- containers
cgl.widgets.box,
nil,-- view_enter
nil -- view_leave
)
cgl.module_installed = true
end
end
local function find_group_leader(images, mode)
for _, img in ipairs(images) do
dt.print_log("checking image " .. img.id .. " named " .. img.filename)
local found = false
if mode == "jpg" then
if string.match(string.lower(img.filename), "jpg$") then
dt.print_log("jpg matched image " .. img.filename)
found = true
end
elseif mode == "raw" then
if img.is_raw and img.duplicate_index == 0 then
dt.print_log("found raw " .. img.filename)
found = true
end
elseif mode == "non-raw" then
if img.is_ldr then
dt.print_log("found ldr " .. img.filename)
found = true
end
else
dt.print_error(MODULE .. ": unrecognized mode " .. mode)
return
end
if found then
dt.print_log("making " .. img.filename .. " group leader")
img:make_group_leader()
return
end
end
end
local function process_image_groups(images)
if #images < 1 then
dt.print(_("no images selected"))
dt.print_log(MODULE .. "no images seletected, returning...")
else
local mode = cgl.widgets.mode.value
for _,img in ipairs(images) do
dt.print_log("checking image " .. img.id)
local group_images = img:get_group_members()
if group_images == 1 then
dt.print_log("only one image in group for image " .. image.id)
else
find_group_leader(group_images, mode)
end
end
end
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 function destroy()
dt.gui.libs[MODULE].visible = false
end
local function restart()
dt.gui.libs[MODULE].visible = true
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- W I D G E T S
-- - - - - - - - - - - - - - - - - - - - - - - -
cgl.widgets.mode = dt.new_widget("combobox"){
label = _("select new group leader"),
tooltip = _("select type of image to be group leader"),
selected = 1,
"jpg", "raw", "non-raw",
}
cgl.widgets.execute = dt.new_widget("button"){
label = _("execute"),
clicked_callback = function()
process_image_groups(dt.gui.action_images)
end
}
cgl.widgets.box = dt.new_widget("box"){
orientation = "vertical",
cgl.widgets.mode,
cgl.widgets.execute,
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not cgl.event_registered then
dt.register_event(
"view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
cgl.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -1,6 +1,6 @@
--[[
clear_GPS.lua - export and edit with GIMP
clear_GPS.lua - plugin for Darktable
Copyright (C) 2016 Bill Ferguson <wpferguson@gmail.com>.
@@ -38,20 +38,33 @@
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("clear GPS info"),
purpose = _("remove GPS data from selected image(s)"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps/"
}
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
-- not a number
local NaN = 0/0
du.check_min_api_version("3.0.0", "clear_GPS")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("clear_GPS",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("clear_GPS", msgid)
end
du.check_min_api_version("7.0.0", "clear_GPS")
local function clear_GPS(images)
for _, image in ipairs(images) do
@@ -62,15 +75,23 @@ local function clear_GPS(images)
end
end
local function destroy()
dt.destroy_event("clear_GPS", "shortcut")
dt.gui.libs.image.destroy_action("clear_GPS")
end
script_data.destroy = destroy
dt.gui.libs.image.register_action(
_("clear GPS data"),
"clear_GPS", _("clear GPS data"),
function(event, images) clear_GPS(images) end,
"clear GPS data"
_("clear GPS data from selected images")
)
dt.register_event(
"shortcut",
"clear_GPS", "shortcut",
function(event, shortcut) clear_GPS(dt.gui.action_images) end,
"clear GPS data"
_("clear GPS data from selected images")
)
return script_data

View File

@@ -0,0 +1,377 @@
--[[
color_profile_manager.lua - manage external darktable color profiles
Copyright (C) 2021 Bill Ferguson <wpferguson@gmail.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
color_profile_manager - manage external darktable color profiles
This script provides a tool to manage input and output external color
profiles used by darktable. Color profiles can be added or removed
to/from the correct directories so that darktable can find and use
them.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* None
USAGE
* require this script from your main lua file
* select the input or output set of color profiles
* a list of profiles is displayed. Click the check box beside the
profile name to select it for removal. Click the "remove profile"
button to remove the profile.
* use the file selector to select a color profile to add to the currently
selected set (input or output)
* click the "add profile" button to add the profile to the selected set
BUGS, COMMENTS, SUGGESTIONS
* Send to Bill Ferguson, wpferguson@gmail.com or raise an issue on
https://github.com/dakrtable-org/lua-scripts
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
du.check_min_api_version("7.0.0", "color_profile_manager")
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O C A L I Z A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE_NAME = "color_profile_manager"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
local CS = dt.configuration.running_os == "windows" and "&" or ";"
local DIR_CMD = dt.configuration.running_os == "windows" and "dir /b" or "ls"
local COLOR_IN_DIR = dt.configuration.config_dir .. PS .. "color" .. PS .. "in"
local COLOR_OUT_DIR = dt.configuration.config_dir .. PS .. "color" .. PS .. "out"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local cpm = {}
cpm.widgets = {}
cpm.widgets.profile_box = dt.new_widget("box"){
orientation = "vertical",
}
cpm.module_installed = false
cpm.event_registered = false
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function check_for_directories()
if not df.test_file(COLOR_IN_DIR, "d") then
dt.print_log("didn't find " .. COLOR_IN_DIR)
return false
elseif not df.test_file(COLOR_OUT_DIR, "d") then
dt.print_log("didn't find " .. COLOR_OUT_DIR)
return false
else
dt.print_log("both directories exist")
return true
end
end
local function add_directories()
df.mkdir(COLOR_IN_DIR)
df.mkdir(COLOR_OUT_DIR)
end
local function list_profiles(dir)
local files = {}
local p = io.popen(DIR_CMD .. " " .. dir)
if p then
for line in p:lines() do
table.insert(files, line)
end
end
p:close()
return files
end
local function add_profile(file, dir)
df.file_copy(file, dir)
dt.print(string.format(_("added color profile %s to %s"), file, dir))
dt.print_log("color profile " .. file .. " added to " .. dir)
end
local function remove_profile(file, dir)
os.remove(dir .. PS .. file)
dt.print(string.format(_("removed color profile %s from %s"), file, dir))
dt.print_log("color profile " .. file .. " removed from " .. dir)
end
local function clear_profile_box()
for i = #cpm.widgets.profile_box, 1, -1 do
cpm.widgets.profile_box[i] = nil
end
end
local function add_profile_callback()
local profile_dir = COLOR_IN_DIR
if cpm.widgets.profile_set.selected == 2 then
profile_dir = COLOR_OUT_DIR
end
local new_profile = cpm.widgets.profile_selector.value
if string.lower(df.get_filetype(new_profile)) ~= "icm" and string.lower(df.get_filetype(new_profile)) ~= "icc" then
dt.print(_("ERROR: color profile must be an icc or icm file"))
dt.print_error(new_profile .. " selected and isn't an icc or icm file")
return
end
-- set selector value to directory that new profile came from
-- in case there are more
cpm.widgets.profile_selector.value = df.get_path(cpm.widgets.profile_selector.value)
add_profile(new_profile, profile_dir)
local files = list_profiles(profile_dir)
local widgets = {}
local profile_ptr = 1
for i, file in ipairs(files) do
if #cpm.widgets.profile_box == 0 or cpm.widgets.profile_box[profile_ptr].label ~= file then
table.insert(widgets, dt.new_widget("check_button"){value = false, label = file})
else
table.insert(widgets, cpm.widgets.profile_box[profile_ptr])
profile_ptr = profile_ptr + 1
if profile_ptr > #cpm.widgets.profile_box then
profile_ptr = #cpm.widgets.profile_box
end
end
end
clear_profile_box()
for _, widget in ipairs(widgets) do
table.insert(cpm.widgets.profile_box, widget)
end
if not cpm.widgets.remove_box.visible then
cpm.widgets.remove_box.visible = true
end
end
local function remove_profile_callback()
local widgets_to_keep = {}
local profile_dir = COLOR_IN_DIR
if cpm.widgets.profile_set.selected == 2 then
profile_dir = COLOR_OUT_DIR
end
for _, widget in ipairs(cpm.widgets.profile_box) do
if widget.value == true then
remove_profile(widget.label, profile_dir)
else
table.insert(widgets_to_keep, widget)
end
end
clear_profile_box()
for _, widget in ipairs(widgets_to_keep) do
table.insert(cpm.widgets.profile_box, widget)
end
if #cpm.widgets.profile_box == 0 then
cpm.widgets.remove_box.visible = false
else
cpm.widgets.remove_box.visible = true
end
end
local function list_profile_callback(choice)
local list_dir = COLOR_IN_DIR
if choice == 2 then
list_dir = COLOR_OUT_DIR
end
local files = list_profiles(list_dir)
if #files == 0 then
cpm.widgets.remove_box.visible = false
else
cpm.widgets.remove_box.visible = true
end
clear_profile_box()
for i, file in ipairs(files) do
cpm.widgets.profile_box[i] = dt.new_widget("check_button"){value = false, label = file}
end
end
local function install_module()
if not cpm.module_installed then
dt.register_lib(
MODULE_NAME, -- Module name
_("color profile manager"), -- Visible name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers
cpm.widgets.main_widget,
nil,-- view_enter
nil -- view_leave
)
cpm.module_installed = true
dt.control.sleep(500)
if not cpm.initialized then
cpm.widgets.profile_set.visible = false
cpm.widgets.add_box.visible = false
cpm.widgets.remove_box.visible = false
else
cpm.widgets.profile_set.selected = 1
end
end
end
local function destroy()
dt.gui.libs[MODULE_NAME].visible = false
return
end
local function restart()
dt.gui.libs[MODULE_NAME].visible = true
return
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- W I D G E T S
-- - - - - - - - - - - - - - - - - - - - - - - -
cpm.initialized = check_for_directories()
dt.print_log("cpm.initialized is " .. tostring(cpm.initialized))
if not cpm.initialized then
dt.print_log("creating init_box")
cpm.widgets.init_box = dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("button"){
label = _("initialize color profiles"),
tooltip = _("create the directory structure to contain the color profiles"),
clicked_callback = function(this)
add_directories()
cpm.initialized = true
cpm.widgets.profile_set.visible = true
cpm.widgets.add_box.visible = true
cpm.widgets.remove_box.visible = false
cpm.widgets.init_box.visible = false
cpm.widgets.profile_set.selected = 1
end
}
}
end
cpm.widgets.profile_set = dt.new_widget("combobox"){
label = _("select profile set"),
tooltip = _("select input or output profiles"),
visible = cpm.initialized and false or true,
changed_callback = function(this)
if cpm.initialized then
list_profile_callback(this.selected)
end
end,
_("input"), _("output"),
}
cpm.widgets.profile_selector = dt.new_widget("file_chooser_button"){
title = _("select color profile to add"),
tooltip = _("select the .icc or .icm file to add"),
is_directory = false
}
cpm.widgets.add_box = dt.new_widget("box"){
orientation = "vertical",
visible = cpm.initialized and true or false,
dt.new_widget("section_label"){label = _("add profile")},
cpm.widgets.profile_selector,
dt.new_widget("button"){
label = _("add selected color profile"),
tooltip = _("add selected file to profiles"),
clicked_callback = function(this)
add_profile_callback()
end
}
}
cpm.widgets.remove_box = dt.new_widget("box"){
orientation = "vertical",
visible = cpm.initialized and true or false,
dt.new_widget("section_label"){label = _("remove profile")},
cpm.widgets.profile_box,
dt.new_widget("button"){
label = _("remove selected profile(s)"),
tooltip = _("remove the checked profile(s)"),
clicked_callback = function(this)
remove_profile_callback()
end
}
}
local main_widgets = {}
if not cpm.initialized then
table.insert(main_widgets, cpm.widgets.init_box)
end
table.insert(main_widgets, cpm.widgets.profile_set)
table.insert(main_widgets, cpm.widgets.remove_box)
table.insert(main_widgets, cpm.widgets.add_box)
cpm.widgets.main_widget = dt.new_widget("box"){
orientation = "vertical",
table.unpack(main_widgets)
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N
-- - - - - - - - - - - - - - - - - - - - - - - -
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not cpm.event_registered then
dt.register_event(
MODULE_NAME, "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
cpm.event_registered = true
end
end
local script_data = {}
script_data.metadata = {
name = _("color profile manager"),
purpose = _("manage external darktable color profiles"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/color_profile_manager"
}
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -40,17 +40,35 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local debug = require "darktable.debug"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
du.check_min_api_version("3.0.0", "copy_attach_detach_tags")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "copy_attach_detach_tags")
local function _(msgid)
return gettext.dgettext("copy_attach_detach_tags", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("copy attach detach tags"),
purpose = _("shortcuts to copy, paste, replace, or remove tags from images"),
author = "Christian Kanzian",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags"
}
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, otherwise leave as nil
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
local cadt = {}
cadt.module_installed = false
cadt.event_registered = false
cadt.widget_table = {}
local image_tags = {}
@@ -85,7 +103,7 @@ local function mcopy_tags()
end
end
dt.print(_('Image tags copied ...'))
dt.print(_('image tags copied ...'))
--create UI tag list
local taglist = ""
@@ -107,7 +125,7 @@ local function mcopy_tags()
local function attach_tags()
if next(image_tags) == nil then
dt.print(_('No tags to attached, please copy tags first.'))
dt.print(_('no tag to attach, please copy tags first.'))
return true
end
@@ -131,7 +149,7 @@ local function attach_tags()
end
end
end
dt.print(_('Tags attached ...'))
dt.print(_('tags attached ...'))
end
local function detach_tags()
@@ -147,13 +165,68 @@ local function detach_tags()
end
end
end
dt.print(_('Tags removed from image(s).'))
dt.print(_('tags removed from image(s).'))
end
local function replace_tags()
detach_tags()
attach_tags()
dt.print(_('Tags replaced'))
dt.print(_('tags replaced'))
end
local function install_module()
if not cadt.module_installed then
dt.register_lib("tagging_addon",_('tagging addon'),true,true,{
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",500}
},
dt.new_widget("box") {
-- orientation = "vertical",
reset_callback = function()
taglist_label.label = ""
image_tags = {}
end,
table.unpack(cadt.widget_table),
},
nil,
nil
)
cadt.module_installed = true
end
end
local function destroy()
dt.destroy_event("cadt_ct", "shortcut")
dt.destroy_event("cadt_at", "shortcut")
dt.destroy_event("cadt_dt", "shortcut")
dt.destroy_event("cadt_rt", "shortcut")
dt.gui.libs["tagging_addon"].visible = false
end
local function restart()
-- shortcut for copy
dt.register_event("cadt_ct", "shortcut",
mcopy_tags,
_('copy tags from selected image(s)'))
-- shortcut for attach
dt.register_event("cadt_at", "shortcut",
attach_tags,
_('paste tags to selected image(s)'))
-- shortcut for detaching tags
dt.register_event("cadt_dt", "shortcut",
detach_tags,
_('remove tags from selected image(s)'))
-- shortcut for replace tags
dt.register_event("cadt_rt", "shortcut",
replace_tags,
_('replace tags from selected image(s)'))
dt.gui.libs["tagging_addon"].visible = true
end
local function show()
dt.gui.libs["tagging_addon"].visible = true
end
-- create modul Tagging addons
@@ -169,73 +242,82 @@ local taglabel = dt.new_widget("label") {
local box1 = dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("button") {
label = _('multi copy tags'),
clicked_callback = mcopy_tags},
label = _('multi copy tags'),
tooltip = _('copy tags from selected image(s)'),
clicked_callback = mcopy_tags},
dt.new_widget("button") {
label = _('paste tags'),
clicked_callback = attach_tags}
tooltip = _('paste tags to selected image(s)'),
label = _('paste tags'),
clicked_callback = attach_tags}
}
local box2 = dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("button") {
label = _('replace tags'),
clicked_callback = replace_tags},
label = _('replace tags'),
tooltip = _('replace tags from selected image(s)'),
clicked_callback = replace_tags},
dt.new_widget("button") {
label = _('remove all tags'),
clicked_callback = detach_tags}
label = _('remove all tags'),
tooltip = _('remove tags from selected image(s)'),
clicked_callback = detach_tags}
}
local sep = dt.new_widget("separator"){}
-- pack elements into widget table for a nicer layout
local widget_table = {}
widget_table[1] = box1
widget_table[#widget_table+1] = box2
cadt.widget_table[1] = box1
cadt.widget_table[#cadt.widget_table+1] = box2
widget_table[#widget_table+1] = sep
widget_table[#widget_table+1] = taglabel
widget_table[#widget_table+1] = taglist_label
cadt.widget_table[#cadt.widget_table+1] = sep
cadt.widget_table[#cadt.widget_table+1] = taglabel
cadt.widget_table[#cadt.widget_table+1] = taglist_label
-- create modul
dt.register_lib("tagging_addon","Tagging addon",true,true,{
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",500}
},
dt.new_widget("box") {
-- orientation = "vertical",
reset_callback = function()
taglist_label.label = ""
image_tags = {}
end,
table.unpack(widget_table),
},
nil,
nil
)
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not cadt.event_registered then
dt.register_event(
"cadt", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
cadt.event_registered = true
end
end
-- shortcut for copy
dt.register_event("shortcut",
dt.register_event("cadt_ct", "shortcut",
mcopy_tags,
_('copy tags from selected image(s)'))
-- shortcut for attach
dt.register_event("shortcut",
dt.register_event("cadt_at", "shortcut",
attach_tags,
_('paste tags to selected image(s)'))
-- shortcut for detaching tags
dt.register_event("shortcut",
dt.register_event("cadt_dt", "shortcut",
detach_tags,
_('remove tags from selected image(s)'))
-- shortcut for replace tags
dt.register_event("shortcut",
dt.register_event("cadt_rt", "shortcut",
replace_tags,
_('replace tags from selected image(s)'))
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = show
return script_data
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;

View File

@@ -35,8 +35,29 @@ USAGE
local darktable = require "darktable"
local du = require "lib/dtutils"
-- Tested with darktable 2.0.1
du.check_min_api_version("2.0.0", "cr2hdr")
du.check_min_api_version("7.0.0", "cr2hdr")
local gettext = darktable.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("cr2hdr"),
purpose = _("process Magic Lantern dual ISO images"),
author = "Till Theato <theato@ttill.de>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr"
}
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, otherwise leave as nil
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
local queue = {}
local processed_files = {}
@@ -79,7 +100,7 @@ end
local function convert_images()
if next(queue) == nil then return end
job = darktable.gui.create_job("Dual ISO conversion", true, stop_conversion)
job = darktable.gui.create_job(_("dual ISO conversion"), true, stop_conversion)
for key,image in pairs(queue) do
if job.valid then
job.percent = (key-1)/#queue
@@ -90,7 +111,7 @@ local function convert_images()
end
local success_count = 0
for _ in pairs(processed_files) do success_count = success_count + 1 end
darktable.print("Dual ISO conversion successful on " .. success_count .. "/" .. #queue .. " images.")
darktable.print(string.format(_("dual ISO conversion successful on %d/%d images."), success_count, #queue))
job.valid = false
processed_files = {}
queue = {}
@@ -107,8 +128,21 @@ local function convert_action_images(shortcut)
convert_images()
end
darktable.register_event("shortcut", convert_action_images, "Run cr2hdr (Magic Lantern DualISO converter) on selected images")
darktable.register_event("post-import-image", file_imported)
darktable.register_event("post-import-film", film_imported)
local function destroy()
darktable.destroy_event("cr2hdr", "shortcut")
darktable.destroy_event("cr2hdr", "post-import-image")
darktable.destroy_event("cr2hdr", "post-import-film")
end
darktable.preferences.register("cr2hdr", "onimport", "bool", "Invoke on import", "If true then cr2hdr will try to proccess every file during importing. Warning: cr2hdr is quite slow even in figuring out on whether the file is Dual ISO or not.", false)
darktable.register_event("cr2hdr", "shortcut",
convert_action_images, _("run cr2hdr (Magic Lantern DualISO converter) on selected images"))
darktable.register_event("cr2hdr", "post-import-image",
file_imported)
darktable.register_event("cr2hdr", "post-import-film",
film_imported)
darktable.preferences.register("cr2hdr", "onimport", "bool", _("invoke on import"), _("if true then cr2hdr will try to proccess every file during importing\nwarning: cr2hdr is quite slow even in figuring out on whether the file is dual ISO or not."), false)
script_data.destroy = destroy
return script_data

View File

@@ -0,0 +1,162 @@
--[[
cycle_group_leader.lua - change image group leader
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>
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 <http://www.gnu.org/licenses/>.
]]
--[[
cycle_group_leader - change image grouip leader
cycle_group_leader changes the group leader to the next
image in the group. If the end of the group is reached
then the next image is wrapped around to the first image.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* enable with script_manager
* assign a key to the shortcut
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson@gmail.com>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE = "cycle_group_leader"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- A P I C H E C K
-- - - - - - - - - - - - - - - - - - - - - - - -
du.check_min_api_version("7.0.0", MODULE)
-- - - - - - - - - - - - - - - - - - - - - - - - - -
-- 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.metadata = {
name = _("cycle group leader"),
purpose = _("change image group leader"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cycle_group_leader"
}
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
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function toggle_global_toolbox_grouping()
dt.gui.libs.global_toolbox.grouping = false
dt.gui.libs.global_toolbox.grouping = true
end
local function hinter_msg(msg)
dt.print_hinter(msg)
dt.control.sleep(1500)
dt.print_hinter(" ")
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N P R O G R A M
-- - - - - - - - - - - - - - - - - - - - - - - -
local function cycle_group_leader(image)
local group_images = image:get_group_members()
if #group_images < 2 then
hinter_msg(_("no images to cycle through in group"))
return
else
local position = nil
for i, img in ipairs(group_images) do
if image == img then
position = i
end
end
if position == #group_images then
position = 1
else
position = position + 1
end
new_leader = group_images[position]
new_leader:make_group_leader()
dt.gui.selection({new_leader})
if dt.gui.libs.global_toolbox.grouping then
-- toggle the grouping to make the new leader show
toggle_global_toolbox_grouping()
end
end
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function destroy()
-- put things to destroy (events, storages, etc) here
dt.destroy_event(MODULE, "shortcut")
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_event(MODULE, "shortcut",
function(event, shortcut)
-- ignore the film roll, it contains all the images, not just the imported
local images = dt.gui.selection()
if #images < 1 then
dt.print(_("no image selected, please select an image and try again"))
else
cycle_group_leader(images[1])
end
end,
_("cycle group leader")
)
return script_data

334
contrib/dbmaint.lua Normal file
View File

@@ -0,0 +1,334 @@
--[[
dbmaint.lua - perform database maintenance
Copyright (C) 2024 Bill Ferguson <wpferguson@gamil.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
dbmaint - perform database maintenance
Perform database maintenance to clean up missing images and filmstrips.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* start dbmaint from script_manager
* scan for missing film rolls or missing images
* look at the results and choose to delete or not
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson@gamil.com>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local log = require "lib/dtutils.log"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE <const> = "dbmaint"
local DEFAULT_LOG_LEVEL <const> = log.error
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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 = _("db maintenance"),
purpose = _("perform database maintenance"),
author = "Bill Ferguson <wpferguson@gamil.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/"
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O G L E V E L
-- - - - - - - - - - - - - - - - - - - - - - - -
log.log_level(DEFAULT_LOG_LEVEL)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local dbmaint = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- G L O B A L V A R I A B L E S
-- - - - - - - - - - - - - - - - - - - - - - - -
dbmaint.main_widget = nil
-- - - - - - - - - - - - - - - - - - - - - - - -
-- A L I A S E S
-- - - - - - - - - - - - - - - - - - - - - - - -
local namespace = dbmaint
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
-------------------
-- helper functions
-------------------
local function set_log_level(level)
local old_log_level = log.log_level()
log.log_level(level)
return old_log_level
end
local function restore_log_level(level)
log.log_level(level)
end
local function scan_film_rolls()
local missing_films = {}
for _, filmroll in ipairs(dt.films) do
if not df.check_if_file_exists(filmroll.path) then
table.insert(missing_films, filmroll)
end
end
return missing_films
end
local function scan_images(film)
local old_log_level = set_log_level(DEFAULT_LOG_LEVEL)
local missing_images = {}
if film then
for i = 1, #film do
local image = film[i]
log.msg(log.debug, "checking " .. image.filename)
if not df.check_if_file_exists(image.path .. PS .. image.filename) then
log.msg(log.info, image.filename .. " not found")
table.insert(missing_images, image)
end
end
end
restore_log_level(old_log_level)
return missing_images
end
local function remove_missing_film_rolls(list)
for _, filmroll in ipairs(list) do
filmroll:delete(true)
end
end
-- force the lighttable to reload
local function refresh_lighttable(film)
local rules = dt.gui.libs.collect.filter()
dt.gui.libs.collect.filter(rules)
end
local function remove_missing_images(list)
local film = list[1].film
for _, image in ipairs(list) do
image:delete()
end
refresh_lighttable(film)
end
local function install_module()
if not namespace.module_installed then
dt.register_lib(
MODULE, -- Module name
_("DB maintenance"), -- Visible name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers
namespace.main_widget,
nil,-- view_enter
nil -- view_leave
)
namespace.module_installed = true
end
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- U S E R I N T E R F A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
dbmaint.list_widget = dt.new_widget("text_view"){
editable = false,
reset_callback = function(this)
this.text = ""
end
}
dbmaint.chooser = dt.new_widget("combobox"){
label = _("scan for"),
selected = 1,
_("film rolls"), _("images"),
reset_callback = function(this)
this.selected = 1
end
}
dbmaint.scan_button = dt.new_widget("button"){
label = _("scan"),
tooltip = _("click to scan for missing film rolls/files"),
clicked_callback = function(this)
local found = nil
local found_text = ""
local old_log_level = set_log_level(DEFAULT_LOG_LEVEL)
log.msg(log.debug, "Button clicked")
if dbmaint.chooser.selected == 1 then -- film rolls
found = scan_film_rolls()
if #found > 0 then
for _, film in ipairs(found) do
local dir_name = du.split(film.path, PS)
found_text = found_text .. dir_name[#dir_name] .. "\n"
end
end
else
log.msg(log.debug, "checking path " .. dt.collection[1].path .. " for missing files")
found = scan_images(dt.collection[1].film)
if #found > 0 then
for _, image in ipairs(found) do
found_text = found_text .. image.filename .. "\n"
end
end
end
if #found > 0 then
log.msg(log.debug, "found " .. #found .. " missing items")
dbmaint.list_widget.text = found_text
dbmaint.found = found
dbmaint.remove_button.sensitive = true
else
log.msg(log.debug, "no missing items found")
end
restore_log_level(old_log_level)
end,
reset_callback = function(this)
dbmaint.found = nil
end
}
dbmaint.remove_button = dt.new_widget("button"){
label = _("remove"),
tooltip = _("remove missing film rolls/images"),
sensitive = false,
clicked_callback = function(this)
if dbmaint.chooser.selected == 1 then -- film rolls
remove_missing_film_rolls(dbmaint.found)
else
remove_missing_images(dbmaint.found)
end
dbmaint.found = nil
dbmaint.list_widget.text = ""
this.sensitive = false
end,
reset_callback = function(this)
this.sensitive = false
end
}
dbmaint.main_widget = dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("section_label"){label = _("missing items")},
dbmaint.list_widget,
dt.new_widget("label"){label = ""},
dbmaint.chooser,
dt.new_widget("label"){label = ""},
dbmaint.scan_button,
dbmaint.remove_button
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function destroy()
dt.gui.libs[MODULE].visible = false
if namespace.event_registered then
dt.destroy_event(MODULE, "view-changed")
end
return
end
local function restart()
dt.gui.libs[MODULE].visible = true
return
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not namespace.event_registered then
dt.register_event(MODULE, "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
namespace.event_registered = true
end
end
return script_data

View File

@@ -65,15 +65,31 @@ local mod = 'module_enfuseAdvanced'
local os_path_seperator = '/'
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
du.check_min_api_version('5.0.0', 'enfuseAdvanced')
du.check_min_api_version("7.0.0", "enfuseAdvanced")
-- Tell gettext where to find the .mo file translating messages for a particular domain
local gettext = dt.gettext
gettext.bindtextdomain('enfuseAdvanced',dt.configuration.config_dir..'/lua/locale/')
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext('enfuseAdvanced', msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("enfuse advanced"),
purpose = _("focus stack or exposure blend images"),
author = "Kevin Ertel",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseAdvanced"
}
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, otherwise leave as nil
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
-- INITS --
local AIS = {
name = 'align_image_stack',
@@ -180,6 +196,11 @@ for _,i in pairs(dt.styles) do
end
-- FUNCTION --
local function sanitize_decimals(cmd) -- make sure decimal separator is a '.'
return string.gsub(cmd, '(%d),(%d)', "%1.%2")
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
@@ -240,13 +261,13 @@ end
local function ExeUpdate(prog_tbl) --updates executable paths and verifies them
dt.preferences.write(mod, 'bin_exists', 'bool', true)
for _,prog in pairs(prog_tbl) do
for x,prog in pairs(prog_tbl) do
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
prog.bin = df.check_if_bin_exists(prog.name)
if not prog.bin then
prog.install_error = true
dt.preferences.write(mod, 'bin_exists', 'bool', false)
dt.print(_('issue with ')..prog.name.._(' executable'))
dt.print(string.format(_("issue with %s executable"), prog.name))
else
prog.bin = CleanSpaces(prog.bin)
end
@@ -267,7 +288,13 @@ local function GetArgsFromPreference(prog_table, prefix) --for each arg in a pro
local temp = dt.preferences.read(mod, prefix..argument, arg_data.style)
if arg_data.style == 'bool' and temp then
prog_table.arg_string = prog_table.arg_string..arg_data.text..' '
elseif arg_data.style == 'integer' or arg_data.style == 'string' then
elseif arg_data.style == 'integer' then
prog_table.arg_string = prog_table.arg_string..arg_data.text..temp..' '
elseif arg_data.style == 'string' then
if dt.configuration.running_os == "windows" then
--escape % with %% as % means env var on windows and call will fail otherwise
temp = string.gsub(temp, "%%", "%%%%")
end
prog_table.arg_string = prog_table.arg_string..arg_data.text..temp..' '
elseif arg_data.style == 'float' then
temp = string.sub(tostring(temp),1,3)
@@ -311,7 +338,7 @@ local function UpdateENFargs(image_table, prefix) --updates the Enfuse arguments
local largest_id = 0
local first_raw = {}
for raw, temp_image in pairs(image_table) do
_, source_name, source_id = GetFileName(raw.filename)
local _, source_name, source_id = GetFileName(raw.filename)
source_id = tonumber(source_id)
if source_id < smallest_id then
smallest_id = source_id
@@ -324,7 +351,7 @@ local function UpdateENFargs(image_table, prefix) --updates the Enfuse arguments
end
ENF.arg_string = ENF.arg_string..' --depth='..GUI.Target.depth.value..' '
if GUI.Target.format.value == 'tif' then ENF.arg_string = ENF.arg_string..'--compression='..GUI.Target.compression_level_tif.value..' '
elseif GUI.Target.format.value == 'jpg' then ENF.arg_string = ENF.arg_string..'--compression='..GUI.Target.compression_level_jpg.value..' '
elseif GUI.Target.format.value == 'jpg' then ENF.arg_string = ENF.arg_string..'--compression='..tostring(math.floor(GUI.Target.compression_level_jpg.value))..' '
end
if not GUI.Target.source_location.value then out_path = GUI.Target.output_directory.value end
out_name = smallest_name..'-'..largest_id
@@ -363,7 +390,7 @@ local function SaveToPreference(preset) --save the present values of enfuse GUI
dt.preferences.write(mod, preset..argument, arg_data.style, temp)
end
end
dt.print(_('saved to ')..preset)
dt.print(string.format(_("saved to %s"), preset))
end
local function LoadFromPreference(preset) --load values from the specified 'preset' into the GUI elements
@@ -379,7 +406,7 @@ local function LoadFromPreference(preset) --load values from the specified 'pres
dt.preferences.write(mod, 'active_'..argument, arg_data.style, temp)
end
end
dt.print(_('loaded from ')..preset)
dt.print(string.format(_("loaded from %s"), preset))
end
local function remove_temp_files(images_to_remove) --deletes all files specified by the input string
@@ -414,7 +441,7 @@ local function support_format(storage, format) --tells dt we only support TIFF e
end
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) --outputs message to user showing script export status
dt.print(_('export for image fusion ')..tostring(math.floor(number))..' / '..tostring(math.floor(total)))
dt.print(string.format(_("export for image fusion %d / %d"), math.floor(number), math.floor(total)))
end
local function main(storage, image_table, extra_data)
@@ -422,7 +449,7 @@ local function main(storage, image_table, extra_data)
dt.print(_('too few images selected, please select at least 2 images'))
return
elseif extra_data[1] == 2 then
dt.print(_('installation error, please verify binary paths are proper'))
dt.print(_('installation error, please verify binary paths are correct'))
return
end
local images_to_remove = ''
@@ -435,12 +462,15 @@ local function main(storage, image_table, extra_data)
local job = dt.gui.create_job('aligning images')
image_table, images_to_remove, AIS.images_string = UpdateAISargs(image_table, images_to_remove)
local run_cmd = BuildExecuteCmd(AIS)
dt.print_log("AIS run command is " .. run_cmd)
run_cmd = sanitize_decimals(run_cmd)
dt.print_log("AIS decimaal sanitized command is " .. run_cmd)
local resp = dsys.external_command(run_cmd)
job.valid = false
if resp ~= 0 then
remove_temp_files(images_to_remove)
dt.print_error(AIS.name.._(' failed'))
dt.print(AIS.name.._(' failed'))
dt.print(string.format(_("%s failed"), AIS.name))
dt.print_error(AIS.name .. ' failed')
return
end
end
@@ -455,26 +485,35 @@ local function main(storage, image_table, extra_data)
local image_num = 0
local job = dt.gui.create_job('blending '..#variants..' image(s)', true) --create a GUI job bar to display enfuse progress
UpdateActivePreference() --load current GUI values into active preference (only applies to elements without a clicked/changed callback)
for _,prefix in pairs(variants) do --for each image to be created load in the preference values, build arguments string, output image, and run command then execute.
for x,prefix in pairs(variants) do --for each image to be created load in the preference values, build arguments string, output image, and run command then execute.
job.percent = image_num /(#variants)
image_num = image_num+1
ENF.images_string, final_image, source_raw = UpdateENFargs(image_table, prefix)
local run_cmd = BuildExecuteCmd(ENF)
dt.print_log("ENF run command is " .. run_cmd)
run_cmd = sanitize_decimals(run_cmd)
dt.print_log("ENF decimaal sanitized command is " .. run_cmd)
local resp = dsys.external_command(run_cmd)
if resp ~= 0 then
remove_temp_files(images_to_remove)
dt.print_error(ENF.name.._(' failed'))
dt.print(ENF.name.._(' failed'))
dt.print_error(ENF.name..' failed')
dt.print(string.format(_("%s failed"), ENF.name))
job.valid = false
return
end
--copy exif data from original file
run_cmd = EXF.bin..' -TagsFromFile '..df.sanitize_filename(source_raw.path..os_path_seperator..source_raw.filename)..' -exif:all --subifd:all -overwrite_original '..df.sanitize_filename(final_image)
-- replace comma decimal separator with period
dt.print_log("EXF run command is " .. run_cmd)
run_cmd = sanitize_decimals(run_cmd)
dt.print_log("EXF decimaal sanitized command is " .. run_cmd)
resp = dsys.external_command(run_cmd)
if GUI.Target.auto_import.value then --import image into dt if specified
local imported = dt.database.import(final_image)
dt.print_log("image imported")
if GUI.Target.apply_style.selected > 1 then --apply specified style to imported image
local set_style = styles[GUI.Target.apply_style.selected - 1]
dt.styles.apply(set_style , imported)
@@ -500,6 +539,10 @@ local function main(storage, image_table, extra_data)
dt.print('image fusion process complete')
end
local function destroy()
dt.destroy_storage('module_enfuseAdvanced')
end
--GUI--
stack_compression = dt.new_widget('stack'){}
local label_AIS_options= dt.new_widget('section_label'){
@@ -700,7 +743,7 @@ GUI.ENF.hard_masks = dt.new_widget('check_button'){
GUI.ENF.save_masks = dt.new_widget('check_button'){
label = _('save masks'),
value = dt.preferences.read(mod, 'active_save_masks', 'bool'),
tooltip = _('Save the generated weight masks to your home directory,\nenblend saves masks as 8 bit grayscale, \ni.e. single channel images. \nfor accuracy we recommend to choose a lossless format.'),
tooltip = _('save the generated weight masks to your home directory,\nenblend saves masks as 8 bit grayscale, \ni.e. single channel images. \nfor accuracy we recommend to choose a lossless format.'),
clicked_callback = function(self) dt.preferences.write(mod, 'active_save_masks', 'bool', self.value) end,
reset_callback = function(self) self.value = false end
}
@@ -853,7 +896,7 @@ GUI.Target.output_directory = dt.new_widget('file_chooser_button'){
GUI.Target.source_location = dt.new_widget('check_button'){
label = _('save to source image location'),
value = dt.preferences.read(mod, 'active_source_location', 'bool'),
tooltip = _('If checked ignores the location above and saves output image(s) to the same location as the source images.'),
tooltip = _('if checked ignores the location above and saves output image(s) to the same location as the source images.'),
clicked_callback = function(self) dt.preferences.write(mod, 'active_source_location', 'bool', self.value) end,
reset_callback = function(self) self.value = true end
}
@@ -887,8 +930,8 @@ GUI.Target.auto_import = dt.new_widget('check_button'){
}
temp = dt.preferences.read(mod, 'active_apply_style_ind', 'integer')
GUI.Target.apply_style = dt.new_widget('combobox'){
label = _('Apply Style on Import'),
tooltip = _('Apply selected style on auto-import to newly created blended image'),
label = _('apply style on Import'),
tooltip = _('apply selected style on auto-import to newly created blended image'),
selected = 1,
'none',
changed_callback = function(self)
@@ -909,16 +952,16 @@ GUI.Target.apply_style.selected = temp
GUI.Target.copy_tags = dt.new_widget('check_button'){
label = _('copy tags'),
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
tooltip = _('Copy tags from first image.'),
tooltip = _('copy tags from first image.'),
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
reset_callback = function(self) self.value = true end
}
temp = dt.preferences.read(mod, 'active_add_tags', 'string')
if temp == '' then temp = nil end
GUI.Target.add_tags = dt.new_widget('entry'){
tooltip = _('Additional tags to be added on import. Seperate with commas, all spaces will be removed'),
tooltip = _('additional tags to be added on import, seperate with commas, all spaces will be removed'),
text = temp,
placeholder = 'Enter tags, seperated by commas',
placeholder = _('enter tags, separated by commas'),
editable = true
}
temp = dt.preferences.read(mod, 'active_current_preset_ind', 'integer')
@@ -939,7 +982,7 @@ GUI.Presets.current_preset = dt.new_widget('combobox'){
end
}
GUI.Presets.load = dt.new_widget('button'){
label = _('laod fusion preset'),
label = _('load fusion preset'),
tooltip = _('load current fusion parameters from selected preset'),
clicked_callback = function() LoadFromPreference(GUI.Presets.current_preset.value) end
}
@@ -985,7 +1028,7 @@ GUI.Presets.variants_type = dt.new_widget('combobox'){
GUI.Presets.variants_type.sensitive = GUI.Presets.variants.value
temp = df.get_executable_path_preference(AIS.name)
GUI.exes.align_image_stack = dt.new_widget('file_chooser_button'){
title = 'AIS binary path',
title = 'align_image_stack ' .. _('binary path'),
value = temp,
tooltip = temp,
is_directory = false,
@@ -993,7 +1036,7 @@ GUI.exes.align_image_stack = dt.new_widget('file_chooser_button'){
}
temp = df.get_executable_path_preference(ENF.name)
GUI.exes.enfuse = dt.new_widget('file_chooser_button'){
title = 'enfuse binary path',
title = 'enfuse ' .. _('binary path'),
value = temp,
tooltip = temp,
is_directory = false,
@@ -1001,7 +1044,7 @@ GUI.exes.enfuse = dt.new_widget('file_chooser_button'){
}
temp = df.get_executable_path_preference(EXF.name)
GUI.exes.exiftool = dt.new_widget('file_chooser_button'){
title = 'Exiftool binary path',
title = 'exiftool ' .. _('binary path'),
value = temp,
tooltip = temp,
is_directory = false,
@@ -1126,3 +1169,7 @@ else
GUI.options_contain.active = 4
GUI.show_options.sensitive = false
end
script_data.destroy = destroy
return script_data

204
contrib/exportLUT.lua Normal file
View File

@@ -0,0 +1,204 @@
--[[
This file is part of darktable,
copyright (c) 2020 Noah Clarke
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 <http://www.gnu.org/licenses/>.
]]
--[[
Add the following line to .config/darktable/luarc to enable this lightable module:
require "contrib/exportLut"
Given a haldCLUT identity file this script generates haldCLUTS from all the user's
styles and exports them to a location of their choosing.
Warning: during export if a naming collision occurs the older file is automatically
overwritten silently.
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require("lib/dtutils.file")
local ds = require("lib/dtutils.system")
du.check_min_api_version("7.0.0", "exportLUT")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("export LUT"),
purpose = _("export a style as a LUT"),
author = "Noah Clarke",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportLUT"
}
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, otherwise leave as nil
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
du.check_min_api_version("5.0.0", "exportLUT")
local eL = {}
eL.module_installed = false
eL.event_registered = false
eL.widgets = {}
-- Thanks Kevin Ertel for this bit
local os_path_seperator = '/'
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
local mkdir_command = 'mkdir -p '
if dt.configuration.running_os == 'windows' then mkdir_command = 'mkdir ' end
local file_chooser_button = dt.new_widget("file_chooser_button"){
title = _("choose the identity file"),
value = "",
is_directory = false
}
local export_chooser_button = dt.new_widget("file_chooser_button"){
title = _("choose the export location"),
value = "",
is_directory = true
}
local identity_label = dt.new_widget("label"){
label = _("choose the identity haldclut file")
}
local output_label = dt.new_widget("label"){
label = _("choose the output location")
}
local warning_label = dt.new_widget("label"){
label = _("WARNING: files may be silently overwritten")
}
local function end_job(job)
job.valid = false
end
local function output_path(style_name, job)
local output_location = export_chooser_button.value .. os_path_seperator .. style_name .. ".png"
output_location = string.gsub(output_location, "|", os_path_seperator)
local output_dir = string.reverse(output_location)
output_dir = string.gsub(output_dir, ".-" .. os_path_seperator, os_path_seperator, 1)
output_dir = string.reverse(output_dir)
if(output_dir ~= "") then
df.mkdir(df.sanitize_filename(output_dir))
end
return output_location
end
local function export_luts()
local identity = dt.database.import(file_chooser_button.value)
if(type(identity) ~= "userdata") then
dt.print(_("invalid identity lut file"))
else
local job = dt.gui.create_job(_('exporting styles as haldCLUTs'), true, end_job)
local size = 1
for style_num, style in ipairs(dt.styles) do
size = size + 1
end
local count = 0
for style_num, style in ipairs(dt.styles) do
identity:reset()
dt.styles.apply(style, identity)
local io_lut = dt.new_format("png")
io_lut.bpp = 8
io_lut:write_image(identity, output_path(style.name, job))
count = count + 1
job.percent = count / size
dt.print(string.format(_("exported: %s"), output_path(style.name, job)))
end
dt.print(_("done exporting haldCLUTs"))
job.valid = false
identity:reset()
end
end
local function install_module()
if not eL.module_installed then
dt.register_lib(
"export haldclut",
_("export haldclut"),
true,
false,
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
dt.new_widget("box")
{
orientation = "vertical",
table.unpack(eL.widgets),
},
nil,
nil
)
eL.module_installed = true
end
end
local function destroy()
dt.gui.libs["export haldclut"].visible = false
end
local function restart()
dt.gui.libs["export haldclut"].visible = true
end
local export_button = dt.new_widget("button"){
label = _("export"),
clicked_callback = export_luts
}
table.insert(eL.widgets, identity_label)
table.insert(eL.widgets, file_chooser_button)
table.insert(eL.widgets, output_label)
table.insert(eL.widgets, export_chooser_button)
table.insert(eL.widgets, warning_label)
table.insert(eL.widgets, export_button)
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not eL.event_registered then
dt.register_event(
"exportLUT", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
eL.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -1,64 +1,67 @@
--[[
ext_editor.lua - edit images with external editors
DESCRIPTION
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 <http://www.gnu.org/licenses/>.
]]
--[[
ext_editor.lua - edit images with external editors
This script provides helpers to edit image files with programs external to darktable.
It adds:
- a new target storage "collection". Image exported will be reimported to collection for
further edit with external programs
- a new lighttable module "external editors", to select a program from a list of up to
- 9 external editors and run it on a selected image (adjust this limit by changing MAX_EDITORS)
- a set of lua preferences in order to configure name and path of up to 9 external editors
- a set of lua shortcuts in order to quick launch the external editors
This script provides helpers to edit image files with programs external to darktable. It adds:
- a new target storage "collection". Image exported will be reimported to collection for further edit with external programs
- a new module "external editors", visible in lightable and darkroom, to select a program from a list
- of up to 9 external editors and run it on a selected image (adjust this limit by changing MAX_EDITORS)
- a set of lua preferences in order to configure name and path of up to 9 external editors
- a set of lua shortcuts in order to quick launch the external editors
USAGE
* require this script from main lua file
-- setup --
* in "preferences/lua options" configure name and path/command of external programs
* note that if a program name is left empty, that and all following entries will be ignored
* in "preferences/shortcuts/lua" configure shortcuts for external programs (optional)
* whenever programs preferences are changed, in lighttable/external editors, press "update list"
-- setup --
* in "preferences/lua options" configure name and path/command of external programs
* note that if a program name is left empty, that and all following entries will be ignored
* in "preferences/shortcuts/lua" configure shortcuts for external programs (optional)
* whenever programs preferences are changed, in external editors GUI, press "update list"
-- use --
* in the export dialog choose "collection" and select the format and bit depth for the
exported image
* press "export"
* the exported image will be imported into collection and grouped with the original image
-- use --
* in the export dialog choose "collection" and select the format and bit depth for the
exported image
* press "export"
* the exported image will be imported into collection and grouped with the original image
* select an image for editing with en external program, and:
* in lighttable/external editors, select program and press "edit"
* edit the image with the external editor, overwite the file, quit the external program
* the selected image will be updated
or
* in lighttable/external editors, select program and press "edit a copy"
* edit the image with the external editor, overwite the file, quit the external program
* a copy of the selected image will be created and updated
or
* in lighttable select target storage "collection"
* enter in darkroom
* to create an export or a copy press CRTL+E
* use the shortcut to edit the current image with the corresponding external editor
* overwite the file, quit the external program
* the darkroom view will be updated
* in lighttable, select an image for editing with en external program
* (or in darkroom for the image being edited):
* in external editors GUI, select program and press "edit"
* edit the image with the external editor, overwite the file, quit the external program
* the selected image will be updated
or
* in external editors GUI, select program and press "edit a copy"
* edit the image with the external editor, overwite the file, quit the external program
* a copy of the selected image will be created and updated
or
* use the shortcut to edit the current image with the corresponding external editor
* overwite the file, quit the external program
* the image will be updated
* warning: mouseover on lighttable/filmstrip will prevail on current image
* this is the default DT behavior, not a bug of this script
* warning: mouseover on lighttable/filmstrip will prevail on current image
* this is the default DT behavior, not a bug of this script
CAVEATS
* MAC compatibility not tested
CAVEATS
* MAC compatibility not tested
TODO
* send multiple images to the same program, maybe
BUGS, COMMENTS, SUGGESTIONS
BUGS, COMMENTS, SUGGESTIONS
* send to Marco Carrarini, marco.carrarini@gmail.com
CHANGES
* 20191224 - initial version
* 20191227 - added button "update list", better error handling, fixed bug with groups/tags in "edit"
]]
@@ -70,22 +73,40 @@ local dtsys = require "lib/dtutils.system"
-- module name
local MODULE_NAME = "ext_editor"
du.check_min_api_version("7.0.0", MODULE_NAME)
-- translation
local gettext = dt.gettext.gettext
-- check API version
du.check_min_api_version("5.0.2", MODULE_NAME) -- darktable 3.0
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("external editors"),
purpose = _("edit images with external editors"),
author = "Marco Carrarini, marco.carrarini@gmail.com",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor"
}
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, otherwise leave as nil
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
-- OS compatibility
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- namespace
local ee = {}
ee.module_installed = false
ee.event_registered = false
ee.widgets = {}
-- translation
local gettext = dt.gettext
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
end
-- maximum number of external programs, can be increased to necessity
local MAX_EDITORS = 9
@@ -98,10 +119,11 @@ local n_entries
local allowed_file_types = {"JPG", "jpg", "JPEG", "jpeg", "TIF", "tif", "TIFF", "tiff", "EXR", "exr", "PNG", "png"}
-- last used editor initialization
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", 0)
dt.preferences.write(MODULE_NAME, "initialized", "bool", true)
end
end
local lastchoice = 0
@@ -126,24 +148,24 @@ local function UpdateProgramList(combobox, button_edit, button_edit_copy, update
program_names[i] = name
program_paths[i] = df.sanitize_filename(dt.preferences.read(MODULE_NAME, "program_path_"..i, "string"))
n_entries = i
end
end
lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer")
if lastchoice == 0 and n_entries > 0 then lastchoice = 1 end
if lastchoice > n_entries then lastchoice = n_entries end
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice)
-- widgets enabled if there is at least one program configured
combobox.selected = lastchoice
local active = n_entries > 0
combobox.sensitive = active
button_edit.sensitive = active
button_edit_copy.sensitive = active
if update_button_pressed then dt.print(n_entries.._(" editors configured")) end
end
lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer")
if lastchoice == 0 and n_entries > 0 then lastchoice = 1 end
if lastchoice > n_entries then lastchoice = n_entries end
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice)
-- widgets enabled if there is at least one program configured
combobox.selected = lastchoice
local active = n_entries > 0
combobox.sensitive = active
button_edit.sensitive = active
button_edit_copy.sensitive = active
if update_button_pressed then dt.print(string.format(_("%d editors configured"), n_entries)) end
end
-- callback for buttons "edit" and "edit a copy" ------------------------------
local function OpenWith(images, choice, copy)
@@ -152,22 +174,18 @@ local function OpenWith(images, choice, copy)
if choice > n_entries then
dt.print(_("not a valid choice"))
return
end
end
-- check if one image is selected, return if not
if #images ~= 1 then
dt.print(_("please select one image"))
return
end
end
local bin = program_paths[choice]
local friendly_name = program_names[choice]
-- check if external program executable exists, return if not
if not df.check_if_bin_exists(bin) then
dt.print(friendly_name.._(" not found"))
return
end
if dt.configuration.running_os == "macos" then bin = "open -W -a "..bin end
-- image to be edited
local image
@@ -175,24 +193,24 @@ local function OpenWith(images, choice, copy)
local name = image.path..PS..image.filename
-- check if image format is allowed
local file_ext = df.get_filetype (image.filename)
local file_ext = df.get_filetype(image.filename)
local allowed = false
for i,v in pairs(allowed_file_types) do
if v == file_ext then
allowed = true
break
end
end
end
if not allowed then
dt.print(_("file type not allowed"))
return
end
end
-- save image tags, rating and color
local tags = {}
for i, tag in ipairs(dt.tags.get_tags(image)) do
for i, tag in ipairs(dt.tags.get_tags(image)) do
if not (string.sub(tag.name, 1, 9) == "darktable") then table.insert(tags, tag) end
end
end
local rating = image.rating
local red = image.red
local blue = image.blue
@@ -207,28 +225,24 @@ local function OpenWith(images, choice, copy)
if copy then
-- create unique filename
while df.check_if_file_exists(df.sanitize_filename(new_name)) do
new_name = df.filename_increment(new_name)
-- limit to 50 more exports of the original export
if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end
end
new_name = df.create_unique_filename(new_name)
-- physical copy, check result, return if error
local copy_success = df.file_copy(name, new_name)
if not copy_success then
dt.print(_("error copying file ")..name)
dt.print(string.format(_("error copying file %s"), name))
return
end
end
end
-- launch the external editor, check result, return if error
local run_cmd = bin.." "..df.sanitize_filename(new_name)
dt.print(_("launching ")..friendly_name.."...")
dt.print(string.format(_("launching %s..."), friendly_name))
local result = dtsys.external_command(run_cmd)
if result ~= 0 then
dt.print(_("error launching ")..friendly_name)
dt.print(string.format(_("error launching %s"), friendly_name))
return
end
end
if copy then
-- import in database and group
@@ -253,7 +267,7 @@ local function OpenWith(images, choice, copy)
while not found do
index, new_leader = next(group_members, index)
if new_leader ~= image_leader then found = true end
end
end
new_leader:make_group_leader()
image:delete()
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
@@ -266,21 +280,17 @@ local function OpenWith(images, choice, copy)
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
new_image = dt.database.import(name)
new_image:group_with()
end
end
else
-- case 3: image is in a group but is not leader
image:delete()
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
new_image = dt.database.import(name)
new_image:group_with(image_leader)
end
-- refresh darkroom view
if dt.gui.current_view() == dt.gui.views.darkroom then
dt.gui.views.darkroom.display_image(new_image)
end
end
end
-- restore image tags, rating and color, must be put after refresh darkroom view
-- restore image tags, rating and color
for i, tag in ipairs(tags) do dt.tags.attach(tag, new_image) end
new_image.rating = rating
new_image.red = red
@@ -292,21 +302,25 @@ local function OpenWith(images, choice, copy)
-- select the new image
local selection = {}
table.insert(selection, new_image)
dt.gui.selection (selection)
dt.gui.selection(selection)
-- refresh darkroom view
if dt.gui.current_view().id == "darkroom" then
dt.gui.views.darkroom.display_image(new_image)
end
end
-- callback function for shortcuts --------------------------------------------
local function program_shortcut(event, shortcut)
OpenWith(dt.gui.action_images, tonumber(string.sub(shortcut, -2)), false)
end
end
-- export images and reimport in collection -----------------------------------
local function export2collection(storage, image_table, extra_data)
local new_name, new_image, result
local temp_name, new_name, new_image, move_success
for image, temp_name in pairs(image_table) do
@@ -314,32 +328,72 @@ local function export2collection(storage, image_table, extra_data)
-- create unique filename
new_name = image.path..PS..df.get_filename(temp_name)
while df.check_if_file_exists(df.sanitize_filename(new_name)) do
new_name = df.filename_increment(new_name)
-- limit to 50 more exports of the original export
if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end
end
new_name = df.create_unique_filename(new_name)
-- move image to collection folder, check result, return if error
move_success = df.file_move(temp_name, new_name)
if not move_success then
dt.print(_("error moving file ")..temp_name)
dt.print(string.format(_("error moving file %s"), temp_name))
return
end
end
-- import in database and group
new_image = dt.database.import(new_name)
new_image:group_with(image.group_leader)
end
dt.print (_("finished exporting"))
end
dt.print(_("finished exporting"))
end
-- register new storage -------------------------------------------------------
-- note that placing this declaration later makes the export selected module
-- not to remember the choice "collection" when restarting DT, don't know why
dt.register_storage("exp2coll", _("collection"), null, export2collection)
-- install the module in the UI -----------------------------------------------
local function install_module(dr)
local views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 90}}
if dr then
views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 90},
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}}
end
if not ee.module_installed then
-- register new module "external editors" in lighttable and darkroom ----
dt.register_lib(
MODULE_NAME,
_("external editors"),
true, -- expandable
false, -- resetable
views,
dt.new_widget("box") {
orientation = "vertical",
table.unpack(ee.widgets),
},
nil, -- view_enter
nil -- view_leave
)
ee.module_installed = true
end
end
local function destroy()
for i = 1, MAX_EDITORS do
dt.destroy_event(MODULE_NAME .. i, "shortcut")
end
dt.destroy_storage("exp2coll")
dt.gui.libs[MODULE_NAME].visible = false
end
local function restart()
for i = 1, MAX_EDITORS do
dt.register_event(MODULE_NAME .. i, "shortcut",
program_shortcut, string.format(_("edit with program %02d"), i))
end
dt.register_storage("exp2coll", _("collection"), nil, export2collection)
dt.gui.libs[MODULE_NAME].visible = true
end
local function show()
dt.gui.libs[MODULE_NAME].visible = true
end
-- combobox, with variable number of entries ----------------------------------
@@ -350,7 +404,7 @@ local combobox = dt.new_widget("combobox") {
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", self.selected)
end,
""
}
}
-- button edit ----------------------------------------------------------------
@@ -360,8 +414,8 @@ local button_edit = dt.new_widget("button") {
--sensitive = false,
clicked_callback = function()
OpenWith(dt.gui.action_images, combobox.selected, false)
end
}
end
}
-- button edit a copy ---------------------------------------------------------
@@ -370,8 +424,8 @@ local button_edit_copy = dt.new_widget("button") {
tooltip = _("create a copy of the selected image and open it in external editor"),
clicked_callback = function()
OpenWith(dt.gui.action_images, combobox.selected, true)
end
}
end
}
-- button update list ---------------------------------------------------------
@@ -380,58 +434,81 @@ local button_update_list = dt.new_widget("button") {
tooltip = _("update list of programs if lua preferences are changed"),
clicked_callback = function()
UpdateProgramList(combobox, button_edit, button_edit_copy, true)
end
}
end
}
-- box for the buttons --------------------------------------------------------
-- it doesn't seem there is a way to make the buttons equal in size
local box1 = dt.new_widget("box") {
orientation = "horizontal",
orientation = "horizontal",
button_edit,
button_edit_copy,
button_update_list
}
}
-- register new module "external editors" in lighttable ------------------------
dt.register_lib(
MODULE_NAME,
_("external editors"),
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
dt.new_widget("box") {
orientation = "vertical",
combobox,
box1
},
nil, -- view_enter
nil -- view_leave
)
-- table with all the widgets --------------------------------------------------
table.insert(ee.widgets, combobox)
table.insert(ee.widgets, box1)
-- register new module, but only when in lighttable ----------------------------
local show_dr = dt.preferences.read(MODULE_NAME, "show_in_darkrooom", "bool")
if dt.gui.current_view().id == "lighttable" then
install_module(show_dr)
else
if not ee.event_registered then
dt.register_event(
MODULE_NAME, "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module(show_dr)
end
end
)
ee.event_registered = true
end
end
-- initialize list of programs and widgets ------------------------------------
UpdateProgramList(combobox, button_edit, button_edit_copy, false)
-- register new storage -------------------------------------------------------
dt.register_storage("exp2coll", _("collection"), nil, export2collection)
-- register the new preferences -----------------------------------------------
for i = MAX_EDITORS, 1, -1 do
dt.preferences.register(MODULE_NAME, "program_path_"..i, "file",
_("executable for external editor ")..i,
_("select executable for external editor") , _("(None)"))
string.format(_("executable for external editor %d"), i),
_("select executable for external editor") , _("(none)"))
dt.preferences.register(MODULE_NAME, "program_name_"..i, "string",
_("name of external editor ")..i,
string.format(_("name of external editor %d"), i),
_("friendly name of external editor"), "")
end
end
dt.preferences.register(MODULE_NAME, "show_in_darkrooom", "bool",
_("show external editors in darkroom"),
_("check to show external editors module also in darkroom (requires restart)"), false)
-- register the new shortcuts -------------------------------------------------
for i = 1, MAX_EDITORS do
dt.register_event("shortcut", program_shortcut, _("edit with program ")..string.format("%02d", i))
end
dt.register_event(MODULE_NAME .. i, "shortcut",
program_shortcut, string.format(_("edit with program %02d"), i))
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = show
return script_data
-- end of script --------------------------------------------------------------
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -44,7 +44,7 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
-- constants
@@ -52,19 +52,33 @@ local MODULE = "face_recognition"
local PS = dt.configuration.running_os == "windows" and '\\' or '/'
local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt"
du.check_min_api_version("7.0.0", MODULE)
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("face recognition"),
purpose = _("use facial recognition to tag images"),
author = "Sebastian Witt",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition"
}
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, otherwise leave as nil
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
-- namespace
local fc = {}
-- ensure we meet the minimum api
du.check_min_api_version("5.0.0", "face_recognition")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("face_recognition", dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("face_recognition", msgid)
end
fc.module_installed = false
fc.event_registered = false
local function build_image_table(images)
local image_table = {}
@@ -125,7 +139,7 @@ local function do_export(img_tbl, images)
job.percent = 0.0
for export,img in pairs(img_tbl) do
exp_cnt = exp_cnt + 1
dt.print(string.format(_("Exporting image %i of %i images"), exp_cnt, images))
dt.print(string.format(_("exporting image %i of %i images"), exp_cnt, images))
exporter:write_image(img, export, upsize)
job.percent = job.percent + percent_step
end
@@ -195,7 +209,7 @@ local function face_recognition ()
local bin_path = df.check_if_bin_exists("face_recognition")
if not bin_path then
dt.print(_("Face recognition not found"))
dt.print(_("face recognition not found"))
return
end
@@ -247,7 +261,7 @@ local function face_recognition ()
local command = bin_path .. " --cpus " .. nrCores .. " --tolerance " .. tolerance .. " " .. knownPath .. " " .. path .. " > " .. OUTPUT
os.setlocale()
dt.print_log("Face recognition: Running command: " .. command)
dt.print(_("Starting face recognition..."))
dt.print(_("starting face recognition..."))
dtsys.external_command(command)
@@ -255,9 +269,9 @@ local function face_recognition ()
local f = io.open(OUTPUT, "rb")
if not f then
dt.print(_("Face recognition failed"))
dt.print(_("face recognition failed"))
else
dt.print(_("Face recognition finished"))
dt.print(_("face recognition finished"))
f:close ()
end
@@ -337,6 +351,30 @@ local function face_recognition ()
end
end
local function install_module()
if not fc.module_installed then
dt.register_lib(
MODULE, -- Module name
_("face recognition"), -- Visible name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers
fc.widget,
nil,-- view_enter
nil -- view_leave
)
fc.module_installed = true
end
end
local function destroy()
dt.gui.libs[MODULE].visible = false
end
local function restart()
dt.gui.libs[MODULE].visible = true
end
-- build the interface
fc.unknown_tag = dt.new_widget("entry"){
@@ -365,7 +403,7 @@ fc.category_tags = dt.new_widget("entry"){
fc.tolerance = dt.new_widget("slider"){
label = _("tolerance"),
tooltip = ("detection tolerance - 0.6 default - lower if too many faces detected"),
tooltip = _("detection tolerance - 0.6 default - lower if too many faces detected"),
soft_min = 0.0,
hard_min = 0.0,
soft_max = 1.0,
@@ -448,12 +486,12 @@ table.insert(widgets, fc.num_cores)
table.insert(widgets, fc.export_format)
table.insert(widgets, dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("label"){ label = _("width ")},
dt.new_widget("label"){ label = _("width")},
fc.width,
})
table.insert(widgets, dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("label"){ label = _("height ")},
dt.new_widget("label"){ label = _("height")},
fc.height,
})
table.insert(widgets, fc.execute)
@@ -466,16 +504,21 @@ fc.widget = dt.new_widget("box"){
table.unpack(widgets),
}
dt.register_lib(
"face_recognition", -- Module name
_("face recognition"), -- Visible name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers
fc.widget,
nil,-- view_enter
nil -- view_leave
)
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not fc.event_registered then
dt.register_event(
MODULE, "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
fc.event_registered = true
end
end
fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float")
@@ -487,5 +530,11 @@ if not dt.preferences.read(MODULE, "initialized", "bool") then
dt.preferences.write(MODULE, "initialized", "bool", true)
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -0,0 +1,156 @@
--[[ fujifilm_dynamic_range-0.1
Compensate for Fujifilm raw files made using "dynamic range".
Copyright (C) 2021, 2022 Dan Torop <dant@pnym.net>
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 2 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
]]
--[[About this Plugin
Support for adjusting darktable exposure by Fujifilm raw exposure
bias. This corrects for a DR100/DR200/DR400 "dynamic range" setting.
Dependencies:
- exiftool (https://www.sno.phy.queensu.ca/~phil/exiftool/)
Based upon fujifilm_ratings by Ben Mendis
The relevant tag is RawExposureBias (0x9650). This appears to
represent the shift in EV for the chosen DR setting (whether manual or
automatic). Note that even at 100DR ("standard") there is an EV shift:
100 DR -> -0.72 EV
200 DR -> -1.72 EV
400 DR -> -2.72 EV
The ideal would be to use exiv2 to read this tag, as this is the same
code which darktable import uses. Unfortunately, exiv2 as of v0.27.3
can't read this tag. As it is encoded as a 4-byte ratio of two signed
shorts -- a novel data type -- it will require some attention to fix
this.
There is an exiv2-readable DevelopmentDynamicRange tag which maps to
RawExposureBias as above. DevelopmentDynamicRange is only present
when tag DynamicRangeSetting (0x1402) is Manual/Raw (0x0001). When it
is Auto (0x0000), the equivalent data is tag AutoDynamicRange
(0x140b). But exiv2 currently can't read that tag either.
Hence for now this code uses exiftool to read RawExposureBias, as a
more general solution. As exiftool is approx. 10x slower than exiv2
(Perl vs. C++), this may slow large imports.
These tags have been checked on a Fujifilm X100S and X100V. Other
cameras may behave in other ways.
--]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext.gettext
du.check_min_api_version("7.0.0", "fujifilm_dynamic_range")
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("fujifilm dynamic range"),
purpose = _("compensate for Fujifilm raw files made using \"dynamic range\""),
author = "Dan Torop <dant@pnym.net>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range"
}
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, otherwise leave as nil
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
local function detect_dynamic_range(event, image)
if image.exif_maker ~= "FUJIFILM" then
dt.print_log("[fujifilm_dynamic_range] ignoring non-Fujifilm image")
return
end
-- it would be nice to check image.is_raw but this appears to not yet be set
if not string.match(image.filename, "%.RAF$") then
dt.print_log("[fujifilm_dynamic_range] ignoring non-raw image")
return
end
local command = df.check_if_bin_exists("exiftool")
if not command then
dt.print_error("[fujifilm_dynamic_range] exiftool not found")
return
end
local RAF_filename = df.sanitize_filename(tostring(image))
-- without -n flag, exiftool will round to the nearest tenth
command = command .. " -RawExposureBias -n -t " .. RAF_filename
dt.print_log(command)
output = io.popen(command)
local raf_result = output:read("*all")
output:close()
if #raf_result == 0 then
dt.print_error("[fujifilm_dynamic_range] no output returned by exiftool")
return
end
raf_result = string.match(raf_result, "\t(.*)")
if not raf_result then
dt.print_error("[fujifilm_dynamic_range] could not parse exiftool output")
return
end
if image.exif_exposure_bias ~= image.exif_exposure_bias then
-- is NAN (this is unlikely as RAFs should have ExposureBiasValue set)
image.exif_exposure_bias = 0
end
-- this should be auto-applied if plugins/darkroom/workflow is scene-referred
-- note that scene-referred workflow exposure preset also pushes exposure up by 0.5 EV
image.exif_exposure_bias = image.exif_exposure_bias + tonumber(raf_result)
dt.print_log("[fujifilm_dynamic_range] raw exposure bias " .. tostring(raf_result))
-- handle any duplicates
if #image:get_group_members() > 1 then
local basename = df.get_basename(image.filename)
local grouped_images = image:get_group_members()
for _, img in ipairs(grouped_images) do
if string.match(img.filename, basename) and img.duplicate_index > 0 then
-- its a duplicate
img.exif_exposure_bias = img.exif_exposure_bias + tonumber(raf_result)
end
end
end
end
local function destroy()
dt.destroy_event("fujifilm_dr", "post-import-image")
end
if not df.check_if_bin_exists("exiftool") then
dt.print_log("Please install exiftool to use fujifilm_dynamic_range")
error "[fujifilm_dynamic_range] exiftool not found"
end
dt.register_event("fujifilm_dr", "post-import-image",
detect_dynamic_range)
dt.print_log("[fujifilm_dynamic_range] loaded")
script_data.destroy = destroy
return script_data

View File

@@ -26,46 +26,70 @@ Dependencies:
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local gettext = dt.gettext
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext.gettext
du.check_min_api_version("4.0.0", "fujifilm_ratings")
gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "fujifilm_ratings")
local function _(msgid)
return gettext.dgettext("fujifilm_ratings", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("fujifilm ratings"),
purpose = _("import Fujifilm in-camera ratings"),
author = "Ben Mendis <ben.mendis@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings"
}
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, otherwise leave as nil
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
local function detect_rating(event, image)
if not df.check_if_bin_exists("exiftool") then
dt.print_error(_("exiftool not found"))
if not string.match(image.filename, "%.RAF$") and not string.match(image.filename, "%.raf$") then
return
end
local RAF_filename = tostring(image)
if not df.check_if_bin_exists("exiftool") then
dt.print_error("exiftool not found")
return
end
local RAF_filename = df.sanitize_filename(tostring(image))
local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG")
local command = "exiftool -Rating " .. JPEG_filename
dt.print_error(command)
dt.print_log(command)
local output = io.popen(command)
local jpeg_result = output:read("*all")
output:close()
if string.len(jpeg_result) > 0 then
jpeg_result = string.gsub(jpeg_result, "^Rating.*(%d)", "%1")
image.rating = tonumber(jpeg_result)
dt.print_error(_("Using JPEG Rating: ") .. tostring(jpeg_result))
dt.print_log("using JPEG rating: " .. jpeg_result)
return
end
command = "exiftool -Rating " .. RAF_filename
dt.print_error(command)
dt.print_log(command)
output = io.popen(command)
local raf_result = output:read("*all")
output:close()
if string.len(raf_result) > 0 then
raf_result = string.gsub(raf_result, "^Rating.*(%d)", "%1")
image.rating = tonumber(raf_result)
dt.print_error(_("Using RAF Rating: ") .. tostring(raf_result))
dt.print_log("using RAF rating: " .. raf_result)
end
end
dt.register_event("post-import-image", detect_rating)
local function destroy()
dt.destroy_event("fujifilm_rat", "post-import-image")
end
print(_("fujifilm_ratings loaded."))
dt.register_event("fujifilm_rat", "post-import-image",
detect_rating)
script_data.destroy = destroy
return script_data

View File

@@ -35,18 +35,31 @@ USAGE
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext.gettext
du.check_min_api_version("3.0.0", "geoJSON_export")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "geoJSON_export")
local function _(msgid)
return gettext.dgettext("geoJSON_export", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("geoJSON export"),
purpose = _("export a geoJSON file from geo data"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoJSON_export"
}
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, otherwise leave as nil
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
-- Sort a table
local function spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
-- collect the keys
@@ -72,24 +85,24 @@ local function spairs(_table, order) -- Code copied from http://stackoverflow.co
end
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
dt.print(string.format(_("export image %i/%i"), number, total))
end
local function create_geoJSON_file(storage, image_table, extra_data)
if not df.check_if_bin_exists("mkdir") then
dt.print_error(_("mkdir not found"))
dt.print_error("mkdir not found")
return
end
if not df.check_if_bin_exists("convert") then
dt.print_error(_("convert not found"))
dt.print_error("convert not found")
return
end
if not df.check_if_bin_exists("xdg-open") then
dt.print_error(_("xdg-open not found"))
dt.print_error("xdg-open not found")
return
end
if not df.check_if_bin_exists("xdg-user-dir") then
dt.print_error(_("xdg-user-dir not found"))
dt.print_error("xdg-user-dir not found")
return
end
@@ -280,7 +293,7 @@ local function create_geoJSON_file(storage, image_table, extra_data)
file:write(geoJSON_file)
file:close()
dt.print("geoJSON file created in "..exportDirectory)
dt.print(string.format(_("%s file created in %s"), "geoJSON", exportDirectory))
-- Open the file with the standard programm
if ( dt.preferences.read("geoJSON_export","OpengeoJSONFile","bool") == true ) then
@@ -292,24 +305,28 @@ local function create_geoJSON_file(storage, image_table, extra_data)
end
local function destroy()
dt.destroy_storage("geoJSON_export")
end
-- Preferences
dt.preferences.register("geoJSON_export",
"CreateMapBoxHTMLFile",
"bool",
_("geoJSON export: Create an additional HTML file"),
_("Creates a HTML file, that loads the geoJASON file. (Needs a MapBox key"),
"geoJSON export: ".._("Create an additional HTML file"),
_("creates an HTML file that loads the geoJSON file. (needs a MapBox key"),
false )
dt.preferences.register("geoJSON_export",
"mapBoxKey",
"string",
_("geoJSON export: MapBox Key"),
_("https://www.mapbox.com/studio/account/tokens"),
"geoJSON export: MapBox " .. _("key"),
"https://www.mapbox.com/studio/account/tokens",
'' )
dt.preferences.register("geoJSON_export",
"OpengeoJSONFile",
"bool",
_("geoJSON export: Open geoJSON file after export"),
_("Opens the geoJSON file after the export with the standard program for geoJSON files"),
"geoJSON export: " .. _("open geoJSON file after export"),
_("opens the geoJSON file after the export with the standard program for geoJSON files"),
false )
local handle = io.popen("xdg-user-dir DESKTOP")
@@ -321,9 +338,13 @@ end
dt.preferences.register("geoJSON_export",
"ExportDirectory",
"directory",
_("geoJSON export: Export directory"),
_("A directory that will be used to export the geoJSON files"),
_("geoJSON export: export directory"),
_("a directory that will be used to export the geoJSON files"),
result )
-- Register
dt.register_storage("geoJSON_export", "geoJSON Export", nil, create_geoJSON_file)
script_data.destroy = destroy
return script_data

View File

@@ -1,6 +1,7 @@
--[[
This file is part of darktable,
copyright (c) 2016 Tobias Jakobs
copyright (c) 2025 Balázs Dura-Kovács
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -28,37 +29,68 @@ require "geoToolbox"
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext.gettext
du.check_min_api_version("3.0.0", "geoToolbox")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "geoToolbox")
local function _(msgid)
return gettext.dgettext("geoToolbox", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("geo toolbox"),
purpose = _("geodata tools"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoToolbox"
}
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, otherwise leave as nil
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
local gT = {}
gT.module_installed = false
gT.event_registered = false
-- <GUI>
local labelDistance = dt.new_widget("label")
labelDistance.label = _("Distance:")
labelDistance.label = _("distance:")
local label_copy_gps_lat = dt.new_widget("check_button")
local checkbox_copy_gps_lat = dt.new_widget("check_button")
{
label = _("latitude:"),
value = true
}
local label_copy_gps_lon = dt.new_widget("check_button")
local entry_gps_lat = dt.new_widget("entry")
{
tooltip = _("latitude (editable)"),
placeholder = _("latitude")
}
local checkbox_copy_gps_lon = dt.new_widget("check_button")
{
label = _("longitude:"),
value = true
}
local label_copy_gps_ele = dt.new_widget("check_button")
local entry_gps_lon = dt.new_widget("entry")
{
tooltip = _("longitude (editable)"),
placeholder = _("longitude")
}
local checkbox_copy_gps_ele = dt.new_widget("check_button")
{
label = _("elevation:"),
value = true
}
local entry_gps_ele = dt.new_widget("entry")
{
tooltip = _("elevation (editable)"),
placeholder = _("elevation")
}
-- </GUI>
local function select_with_gps()
@@ -148,7 +180,7 @@ local function get_first_coordinate()
first_elevation = ''
first_image_date = 0
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if not image then
first_have_data = false
else
@@ -187,7 +219,7 @@ local function get_second_coordinate()
second_elevation = ''
second_image_date = 0
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if not image then
second_have_data = false
else
@@ -223,7 +255,7 @@ local calc_in_between_slider = dt.new_widget("slider")
--ToDo: this needs more love
local function calc_in_between()
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if image then
image_date = make_time_stamp(image.exif_datetime_taken)
if (first_have_data and second_have_data) then
@@ -270,25 +302,25 @@ local function copy_gps()
copy_gps_longitude = ''
copy_gps_elevation = ''
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if not image then
copy_gps_have_data = false
else
copy_gps_have_data = true
if (image.latitude and label_copy_gps_lat.value) then
if (image.latitude and checkbox_copy_gps_lat.value) then
copy_gps_latitude = image.latitude
end
if (image.longitude and label_copy_gps_lon.value) then
if (image.longitude and checkbox_copy_gps_lon.value) then
copy_gps_longitude = image.longitude
end
if (image.elevation and label_copy_gps_ele.value) then
if (image.elevation and checkbox_copy_gps_ele.value) then
copy_gps_elevation = image.elevation
end
end
label_copy_gps_lat.label = _("latitude: ") .. copy_gps_latitude
label_copy_gps_lon.label = _("longitude: ") ..copy_gps_longitude
label_copy_gps_ele.label = _("elevation: ") .. copy_gps_elevation
entry_gps_lat.text = copy_gps_latitude
entry_gps_lon.text = copy_gps_longitude
entry_gps_ele.text = copy_gps_elevation
return
end
@@ -297,15 +329,15 @@ end
local function paste_gps(image)
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
if (label_copy_gps_lat.value) then
image.latitude = copy_gps_latitude
for jj,image in ipairs(sel_images) do
if (checkbox_copy_gps_lat.value) then
image.latitude = entry_gps_lat.text
end
if (label_copy_gps_lon.value) then
image.longitude = copy_gps_longitude
if (checkbox_copy_gps_lon.value) then
image.longitude = entry_gps_lon.text
end
if (label_copy_gps_ele.value) then
image.elevation = copy_gps_elevation
if (checkbox_copy_gps_ele.value) then
image.elevation = entry_gps_ele.text
end
end
end
@@ -324,7 +356,7 @@ local function open_location_in_gnome_maps()
local i = 0;
-- Use the first image with geo information
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
@@ -351,12 +383,12 @@ end
local function reverse_geocode()
if not df.check_if_bin_exists("curl") then
dt.print_error(_("curl not found"))
dt.print_error("curl not found")
return
end
if not df.check_if_bin_exists("jq") then
dt.print_error(_("jq not found"))
dt.print_error("jq not found")
return
end
@@ -367,7 +399,7 @@ local function reverse_geocode()
local i = 0;
-- Use the first image with geo information
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
@@ -417,7 +449,7 @@ local function get_distance(lat1, lon1, ele1, lat2, lon2, ele2)
math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) *
math.sin(dLon/2) * math.sin(dLon/2)
;
local angle = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
local angle = 2 * math.atan(math.sqrt(a), math.sqrt(1-a));
local distance = earthRadius * angle; -- Distance in km
-- Add the elevation to the calculation
@@ -442,7 +474,7 @@ local function calc_distance()
local sel_images = dt.gui.selection()
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
@@ -474,12 +506,12 @@ local function calc_distance()
if (distance < 1) then
distance = distance * 1000
distanceUnit = _("m")
distanceUnit = "m"
else
distanceUnit = _("km")
distanceUnit = "km"
end
return string.format(_("Distance: %.2f %s"), distance, distanceUnit)
return string.format(_("distance: %.2f %s"), distance, distanceUnit)
end
local function print_calc_distance()
@@ -502,12 +534,12 @@ local altitude_filename = dt.new_widget("entry")
text = "altitude.csv",
placeholder = "altitude.csv",
editable = true,
tooltip = _("Name of the exported file"),
tooltip = _("name of the exported file"),
reset_callback = function(self) self.text = "text" end
}
local function altitude_profile()
dt.print(_("Start export"))
dt.print(_("start export"))
local sel_images = dt.gui.action_images
local lat1 = 0;
@@ -526,7 +558,7 @@ local function altitude_profile()
local elevationAdd = 0;
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
for jj,image in ipairs(sel_images) do
if ((not isnan(image.longitude) and not isnan(image.latitude) and not isnan(image.elevation) and image.elevation) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
@@ -568,10 +600,53 @@ local function altitude_profile()
file = io.open(exportDirectory.."/"..exportFilename, "w")
file:write(csv_file)
file:close()
dt.print(_("File created in ")..exportDirectory)
dt.print(string.format(_("file created in %s"), exportDirectory))
end
local function install_module()
if not gT.module_installed then
dt.register_lib(
"geoToolbox", -- Module name
_("geo toolbox"), -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
gT.widget,
nil,-- view_enter
nil -- view_leave
)
gT.module_installed = true
end
end
local function destroy()
dt.gui.libs["geoToolbox"].visible = false
dt.destroy_event("geoToolbox_cd", "shortcut")
dt.destroy_event("geoToolbox", "mouse-over-image-changed")
dt.destroy_event("geoToolbox_wg", "shortcut")
dt.destroy_event("geoToolbox_ng", "shortcut")
end
local function restart()
dt.register_event("geoToolbox_cd", "shortcut",
print_calc_distance, _("calculate the distance from latitude and longitude in km"))
dt.register_event("geoToolbox", "mouse-over-image-changed",
toolbox_calc_distance)
dt.register_event("geoToolbox_wg", "shortcut",
select_with_gps, _("select all images with GPS information"))
dt.register_event("geoToolbox_ng", "shortcut",
select_without_gps, _("select all images without GPS information"))
dt.gui.libs["geoToolbox"].visible = true
end
local function show()
dt.gui.libs["geoToolbox"].visible = true
end
local separator = dt.new_widget("separator"){}
local separator2 = dt.new_widget("separator"){}
@@ -579,41 +654,47 @@ local separator3 = dt.new_widget("separator"){}
local separator4 = dt.new_widget("separator"){}
local separator5 = dt.new_widget("separator"){}
dt.register_lib(
"geoToolbox", -- Module name
"geo toolbox", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
dt.new_widget("box")
gT.widget = dt.new_widget("box")
{
orientation = "vertical",
dt.new_widget("button")
{
label = _("select geo images"),
tooltip = _("Select all images with GPS information"),
tooltip = _("select all images with GPS information"),
clicked_callback = select_with_gps
},
dt.new_widget("button")
{
label = _("select non-geo images"),
tooltip = _("Select all images without GPS information"),
tooltip = _("select all images without GPS information"),
clicked_callback = select_without_gps
},
separator,--------------------------------------------------------
dt.new_widget("button")
{
label = _("copy GPS data"),
tooltip = _("Copy GPS data"),
tooltip = _("copy GPS data"),
clicked_callback = copy_gps
},
label_copy_gps_lat,
label_copy_gps_lon,
label_copy_gps_ele,
dt.new_widget("box"){
orientation = "horizontal",
checkbox_copy_gps_lat,
entry_gps_lat
},
dt.new_widget("box"){
orientation = "horizontal",
checkbox_copy_gps_lon,
entry_gps_lon
},
dt.new_widget("box"){
orientation = "horizontal",
checkbox_copy_gps_ele,
entry_gps_ele
},
dt.new_widget("button")
{
label = _("paste GPS data"),
tooltip = _("Paste GPS data"),
label = _("apply GPS data to image"),
tooltip = _("apply GPS data to image"),
clicked_callback = paste_gps
},
separator2,--------------------------------------------------------
@@ -643,14 +724,14 @@ dt.register_lib(
dt.new_widget("button")
{
label = _("open in Gnome Maps"),
tooltip = _("Open location in Gnome Maps"),
tooltip = _("open location in Gnome Maps"),
clicked_callback = open_location_in_gnome_maps
},
separator4,--------------------------------------------------------
dt.new_widget("button")
{
label = _("reverse geocode"),
tooltip = _("This just shows the name of the location, but doesn't add it as tag"),
tooltip = _("this just shows the name of the location, but doesn't add it as tag"),
clicked_callback = reverse_geocode
},
separator5,--------------------------------------------------------
@@ -660,29 +741,53 @@ dt.register_lib(
dt.new_widget("button")
{
label = _("export altitude CSV file"),
tooltip = _("Create an altitude profile using the GPS data in the metadata"),
tooltip = _("create an altitude profile using the GPS data in the metadata"),
clicked_callback = altitude_profile
},
labelDistance
},
nil,-- view_enter
nil -- view_leave
)
}
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not gT.event_registered then
dt.register_event(
"geoToolbox", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
gT.event_registered = true
end
end
-- Preferences
dt.preferences.register("geoToolbox",
"mapBoxKey",
"string",
_("geoToolbox export: MapBox Key"),
_("https://www.mapbox.com/studio/account/tokens"),
"https://www.mapbox.com/studio/account/tokens",
'' )
-- Register
dt.register_event("shortcut", print_calc_distance, _("Calculate the distance from latitude and longitude in km"))
dt.register_event("mouse-over-image-changed", toolbox_calc_distance)
dt.register_event("geoToolbox_cd", "shortcut",
print_calc_distance, _("calculate the distance from latitude and longitude in km"))
dt.register_event("geoToolbox", "mouse-over-image-changed",
toolbox_calc_distance)
dt.register_event("shortcut", select_with_gps, _("Select all images with GPS information"))
dt.register_event("shortcut", select_without_gps, _("Select all images without GPS information"))
dt.register_event("geoToolbox_wg", "shortcut",
select_with_gps, _("select all images with GPS information"))
dt.register_event("geoToolbox_ng", "shortcut",
select_without_gps, _("select all images without GPS information"))
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = show
return script_data
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: hl Lua;

View File

@@ -41,7 +41,9 @@
* require this script from your main lua file
* select an image or images for editing with GIMP
* in the export dialog select "Edit with GIMP" and select the format and bit depth for the
exported image
exported image. Check the "run_detached" button to run GIMP in detached mode. Images
will not be returned to darktable in this mode, but additional images can be sent to
GIMP without stopping it.
* Press "export"
* Edit the image with GIMP then save the changes with File->Overwrite....
* Exit GIMP
@@ -67,18 +69,31 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
local gimp_widget = nil
du.check_min_api_version("5.0.0", "gimp")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "gimp")
local function _(msgid)
return gettext.dgettext("gimp", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("edit with GIMP"),
purpose = _("export and edit with GIMP"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gimp"
}
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, otherwise leave as nil
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
local function group_if_not_member(img, new_img)
local image_table = img:get_group_members()
local is_member = false
@@ -98,20 +113,26 @@ end
local function show_status(storage, image, format, filename,
number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
dt.print(string.format(_("export image %i/%i"), number, total))
end
local function gimp_edit(storage, image_table, extra_data) --finalize
local run_detached = dt.preferences.read("gimp", "run_detached", "bool")
local gimp_executable = df.check_if_bin_exists("gimp")
if not gimp_executable then
dt.print_error(_("GIMP not found"))
dt.print_error("GIMP not found")
return
end
if dt.configuration.running_os == "macos" then
gimp_executable = "open -W -a " .. gimp_executable
if run_detached then
gimp_executable = "open -a " .. gimp_executable
else
gimp_executable = "open -W -a " .. gimp_executable
end
end
-- list of exported images
@@ -125,60 +146,80 @@ local function gimp_edit(storage, image_table, extra_data) --finalize
img_list = img_list ..exp_img.. " "
end
dt.print(_("Launching GIMP..."))
dt.print(_("launching GIMP..."))
local gimpStartCommand
gimpStartCommand = gimp_executable .. " " .. img_list
if run_detached then
if dt.configuration.running_os == "windows" then
gimpStartCommand = "start /b \"\" " .. gimpStartCommand
else
gimpStartCommand = gimpStartCommand .. " &"
end
end
dt.print_log(gimpStartCommand)
dtsys.external_command(gimpStartCommand)
-- for each of the image, exported image pairs
-- move the exported image into the directory with the original
-- then import the image into the database which will group it with the original
-- and then copy over any tags other than darktable tags
if not run_detached then
for image,exported_image in pairs(image_table) do
-- for each of the image, exported image pairs
-- move the exported image into the directory with the original
-- then import the image into the database which will group it with the original
-- and then copy over any tags other than darktable tags
local myimage_name = image.path .. "/" .. df.get_filename(exported_image)
for image,exported_image in pairs(image_table) do
while df.check_if_file_exists(myimage_name) do
myimage_name = df.filename_increment(myimage_name)
-- limit to 99 more exports of the original export
if string.match(df.get_basename(myimage_name), "_(d-)$") == "99" then
break
local myimage_name = image.path .. "/" .. df.get_filename(exported_image)
while df.check_if_file_exists(myimage_name) do
myimage_name = df.filename_increment(myimage_name)
-- limit to 99 more exports of the original export
if string.match(df.get_basename(myimage_name), "_(d-)$") == "99" then
break
end
end
end
dt.print_log("moving " .. exported_image .. " to " .. myimage_name)
local result = df.file_move(exported_image, myimage_name)
dt.print_log("moving " .. exported_image .. " to " .. myimage_name)
local result = df.file_move(exported_image, myimage_name)
if result then
dt.print_log("importing file")
local myimage = dt.database.import(myimage_name)
if result then
dt.print_log("importing file")
local myimage = dt.database.import(myimage_name)
group_if_not_member(image, myimage)
group_if_not_member(image, myimage)
for _,tag in pairs(dt.tags.get_tags(image)) do
if not (string.sub(tag.name,1,9) == "darktable") then
dt.print_log("attaching tag")
dt.tags.attach(tag,myimage)
for _,tag in pairs(dt.tags.get_tags(image)) do
if not (string.sub(tag.name,1,9) == "darktable") then
dt.print_log("attaching tag")
dt.tags.attach(tag,myimage)
end
end
end
end
end
end
local function destroy()
dt.destroy_storage("module_gimp")
end
-- Register
local executables = {"gimp"}
gimp_widget = dt.new_widget("check_button"){
label = _("run detached"),
tooltip = _("don't import resulting image back into darktable"),
value = dt.preferences.read("gimp", "run_detached", "bool"),
clicked_callback = function(this)
dt.preferences.write("gimp", "run_detached", "bool", this.value)
end
}
if dt.configuration.running_os ~= "linux" then
gimp_widget = df.executable_path_widget(executables)
end
dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
dt.register_storage("module_gimp", _("edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
--
script_data.destroy = destroy
return script_data

View File

@@ -25,17 +25,34 @@ For each source folder, a separate <trk> is generated in the gpx file.
local dt = require "darktable"
local df = require "lib/dtutils.file"
local dl = require "lib/dtutils"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
dl.check_min_api_version("3.0.0", "gpx-export")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gpx_export",dt.configuration.config_dir.."/lua/locale/")
dl.check_min_api_version("7.0.0", "gpx_export")
local function _(msgid)
return gettext.dgettext("gpx_export", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("gpx export"),
purpose = _("export gpx information to a file"),
author = "Jannis_V",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export"
}
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, otherwise leave as nil
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
local gpx = {}
gpx.module_installed = false
gpx.event_registered = false
local path_entry = dt.new_widget("entry")
{
text = dt.preferences.read("gpx_exporter", "gpxExportPath", "string"),
@@ -119,39 +136,78 @@ local function create_gpx_file()
local file = io.open(path, "w")
if (file == nil) then
dt.print(_("invalid path: ")..path)
dt.print(string.format(_("invalid path: %s"), path))
else
file:write(gpx_file)
file:close()
dt.print(_("gpx file created: ")..path)
dt.print(string.format(_("gpx file created: "), path))
end
end
dt.register_lib(
"gpx_exporter",
"gpx export",
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
local function install_module()
if not gpx.module_installed then
dt.register_lib(
"gpx_exporter",
_("gpx export"),
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
gpx.widget,
nil,-- view_enter
nil -- view_leave
)
gpx.module_installed = true
end
end
local function destroy()
dt.gui.libs["gpx_exporter"].visible = false
end
local function restart()
dt.gui.libs["gpx_exporter"].visible = true
end
gpx.widget = dt.new_widget("box")
{
orientation = "vertical",
dt.new_widget("button")
{
label = _("export"),
tooltip = _("export gpx file"),
clicked_callback = create_gpx_file
},
dt.new_widget("box")
{
orientation = "vertical",
dt.new_widget("button")
orientation = "horizontal",
dt.new_widget("label")
{
label = _("export"),
tooltip = _("export gpx file"),
clicked_callback = create_gpx_file
},
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label")
{
label = _("file:"),
},
path_entry
label = _("file:"),
},
path_entry
},
nil,-- view_enter
nil -- view_leave
)
}
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not gpx.event_registered then
dt.register_event(
"gpx_export", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
gpx.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -0,0 +1,97 @@
--[[
harmonic artmature guide for darktable
copyright (c) 2021 Hubert Kowalski
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 <http://www.gnu.org/licenses/>.
]]
--[[
HARMONIC ARMATURE GUIDE
Harmonic Armature (also known as 14 line armature)
INSTALLATION
* copy this file in $CONFIGDIR/lua/contrib where CONFIGDIR is your darktable configuration directory
* add the following line in the file $CONFIGDIR/luarc
require "contrib/harmonic_armature_guide"
USAGE
* when using guides, select "harmonic armature" as guide
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext.gettext
du.check_min_api_version("2.0.0", "harmonic_armature_guide")
local function _(msgid)
return gettext(msgid)
end
local script_data = {}
script_data.metadata = {
name = _("harmonic armature guide"),
purpose = _("harmonic artmature guide"),
author = "Hubert Kowalski",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/harmonic_armature_guide"
}
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, otherwise leave as nil
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
dt.guides.register_guide("harmonic armature",
-- draw
function(cairo, x, y, width, height, zoom_scale)
cairo:save()
cairo:translate(x, y)
cairo:scale(width, height)
cairo:move_to(0,0)
cairo:line_to(1, 0.5)
cairo:line_to(0.5, 1)
cairo:line_to(0,0)
cairo:line_to(1, 1)
cairo:line_to(0.5, 0)
cairo:line_to(0, 0.5)
cairo:line_to(1, 1)
cairo:move_to(1, 0)
cairo:line_to(0, 0.5)
cairo:line_to(0.5, 1)
cairo:line_to(1, 0)
cairo:line_to(0, 1)
cairo:line_to(0.5, 0)
cairo:line_to(1, 0.5)
cairo:line_to(0, 1)
cairo:restore()
end,
-- gui
function()
return dt.new_widget("label"){label = _("harmonic armature"), halign = "start"}
end
)
local function destroy()
-- nothing to destroy
end
script_data.destroy = destroy
return script_data

View File

@@ -0,0 +1,200 @@
--[[
hif_group_leader.lua - Make hif image group leader
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
hif_group_leader - Make hif image group leader
After a film roll is imported, check for RAW-JPG image groups
and make the JPG image the group leader. This is on by default
but can be disabled in preferences.
Shortcuts are included to filter existing collections or
selections of images and make the hif the group leader.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
Start script from script_manager
Assign keys to the shortcuts
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson@gmail.com>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE = "hif_group_leader"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- A P I C H E C K
-- - - - - - - - - - - - - - - - - - - - - - - -
du.check_min_api_version("7.0.0", MODULE)
-- - - - - - - - - - - - - - - - - - - - - - - - - -
-- 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.metadata = {
name = _("HIF group leader"),
purpose = _("make hif image group leader"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hif_group_leader"
}
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
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.preferences.register(MODULE, "on_import", "bool", _("make hif group leader on import"), _("automatically make the hif file the group leader when raw + hif are imported"), true)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local jgloi = {}
jgloi.images = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function toggle_global_toolbox_grouping()
dt.gui.libs.global_toolbox.grouping = false
dt.gui.libs.global_toolbox.grouping = true
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N P R O G R A M
-- - - - - - - - - - - - - - - - - - - - - - - -
local function make_hif_group_leader(images)
-- if the image is part of a group, make it the leader
for _, image in ipairs(images) do
if #image:get_group_members() > 1 then
image:make_group_leader()
end
end
if dt.gui.libs.global_toolbox.grouping then
-- toggle the grouping to make the new leader show
toggle_global_toolbox_grouping()
end
end
local function make_existing_hif_group_leader(images)
for _, image in ipairs(images) do
if string.lower(df.get_filetype(image.filename)) == "hif" then
if #image:get_group_members() > 1 then
image:make_group_leader()
end
end
end
if dt.gui.libs.global_toolbox.grouping then
-- toggle the grouping to make the new leader show
toggle_global_toolbox_grouping()
end
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function destroy()
if dt.preferences.read(MODULE, "on_import", "bool") then
dt.destroy_event(MODULE, "post-import-film")
dt.destroy_event(MODULE, "post-import-image")
end
dt.destroy_event(MODULE .. "_collect", "shortcut")
dt.destroy_event(MODULE .. "_select", "shortcut")
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
if dt.preferences.read(MODULE, "on_import", "bool") then
dt.register_event(MODULE, "post-import-film",
function(event, film_roll)
-- ignore the film roll, it contains all the images, not just the imported
local images = jgloi.images
if #images > 0 then
jgloi.images = {}
make_hif_group_leader(images)
end
end
)
dt.register_event(MODULE, "post-import-image",
function(event, image)
if string.lower(df.get_filetype(image.filename)) == "hif" then
table.insert(jgloi.images, image)
end
end
)
end
dt.register_event(MODULE .. "_collect", "shortcut",
function(event, shortcut)
-- ignore the film roll, it contains all the images, not just the imported
local images = dt.collection
make_existing_hif_group_leader(images)
end,
_("make hif group leader for collection")
)
dt.register_event(MODULE .. "_select", "shortcut",
function(event, shortcut)
local images = dt.gui.selection()
make_existing_hif_group_leader(images)
end,
_("make hif group leader for selection")
)
return script_data

View File

@@ -40,8 +40,7 @@ local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local log = require "lib/dtutils.log"
local dtsys = require "lib/dtutils.system"
require "official/yield"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
local namespace = 'module_hugin'
local user_pref_str = 'prefer_gui'
@@ -55,15 +54,28 @@ local executable_table = {"hugin", "hugin_executor", "pto_gen"}
local PQ = dt.configuration.running_os == "windows" and '"' or "'"
-- works with darktable API version from 5.0.0 on
du.check_min_api_version("5.0.0", "hugin")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "hugin")
local function _(msgid)
return gettext.dgettext("hugin", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("hugin"),
purpose = _("stitch images into a panorama"),
author = "Wolfgang Goetz",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin"
}
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, otherwise leave as nil
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
local function user_preference_changed(widget)
user_prefer_gui = widget.value
dt.preferences.write(namespace, user_pref_str, "bool", user_prefer_gui)
@@ -71,7 +83,7 @@ end
local function show_status(storage, image, format, filename,
number, total, high_quality, extra_data)
dt.print("exporting to hugin: "..tostring(number).."/"..tostring(total))
dt.print(string.format(_("exporting to hugin: %d / %d"), number, total))
end
local function create_panorama(storage, image_table, extra_data) --finalize
@@ -132,7 +144,7 @@ local function create_panorama(storage, image_table, extra_data) --finalize
end
if first_file == nil then
dt.print("no file selected")
dt.print(_("no file selected"))
return
end
@@ -193,7 +205,7 @@ local function create_panorama(storage, image_table, extra_data) --finalize
if df.check_if_file_exists(src_path) then
log.msg(log.debug, "found ", src_path, " importing to ", dst_path)
df.file_move(src_path, dst_path)
dt.print(_("importing file "..dst_path))
dt.print(string.format(_("importing file %s"), dst_path))
dt.database.import(dst_path)
end
end
@@ -206,6 +218,10 @@ local function create_panorama(storage, image_table, extra_data) --finalize
end
end
local function destroy()
dt.destroy_storage(namespace)
end
-- Register
if dt.configuration.running_os ~= "linux" then
exec_widget = df.executable_path_widget(executable_table)
@@ -215,7 +231,7 @@ hugin_widget = dt.new_widget("box") {
orientation = "vertical",
dt.new_widget("check_button")
{
label = _(" launch hugin gui"),
label = _("launch hugin gui"),
value = user_prefer_gui,
tooltip = _('launch hugin in gui mode'),
clicked_callback = user_preference_changed
@@ -225,6 +241,8 @@ hugin_widget = dt.new_widget("box") {
dt.register_storage(namespace, _("hugin panorama"), show_status, create_panorama, nil, nil, hugin_widget)
script_data.destroy = destroy
return script_data
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -64,22 +64,35 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
local job = nil
-- path separator constant
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- works with LUA API version 5.0.0
du.check_min_api_version("5.0.0", "image_stack")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("image_stack",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "image_stack")
local function _(msgid)
return gettext.dgettext("image_stack", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("image stack"),
purpose = _("process a stack of images"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack"
}
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, otherwise leave as nil
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
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- GUI definitions
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -126,19 +139,19 @@ local chkbtn_will_align = dt.new_widget("check_button"){
local chkbtn_radial_distortion = dt.new_widget("check_button"){
label = _('optimize radial distortion for all images'),
value = dt.preferences.read("align_image_stack", "def_radial_distortion", "bool"),
tooltip = _('optimize radial distortion for all images, \nexcept for first'),
tooltip = _('optimize radial distortion for all images, \nexcept the first'),
}
local chkbtn_optimize_field = dt.new_widget("check_button"){
label = _('optimize field of view for all images'),
value = dt.preferences.read("align_image_stack", "def_optimize_field", "bool"),
tooltip =_('optimize field of view for all images, except for first. \nUseful for aligning focus stacks (DFF) with slightly \ndifferent magnification.'),
tooltip =_('optimize field of view for all images, except the first. \nUseful for aligning focus stacks (DFF) with slightly \ndifferent magnification.'),
}
local chkbtn_optimize_image_center = dt.new_widget("check_button"){
label = _('optimize image center shift for all images'),
value = dt.preferences.read("align_image_stack", "def_optimize_image_center", "bool"),
tooltip =_('optimize image center shift for all images, \nexcept for first.'),
tooltip =_('optimize image center shift for all images, \nexcept the first.'),
}
local chkbtn_auto_crop = dt.new_widget("check_button"){
@@ -250,7 +263,7 @@ end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
dt.print(string.format(_("export image %i/%i"), number, total))
end
-- read the gui and populate the align_image_stack arguments
@@ -443,7 +456,7 @@ local function copy_image_attributes(from, to, ...)
to.rights = from.rights
to.description = from.description
else
dt.print_error(_("Unrecognized option to copy_image_attributes: " .. arg))
dt.print_error("Unrecognized option to copy_image_attributes: " .. arg)
end
end
end
@@ -452,6 +465,10 @@ local function stop_job()
job.valid = false
end
local function destroy()
dt.destroy_storage("module_image_stack")
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- main program
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -483,11 +500,11 @@ local function image_stack(storage, image_table, extra_data)
if image_count < 2 then
dt.print(_("ERROR: at least 2 images required for image stacking, exiting..."))
dt.print_error(_(image_count .. " image(s) selected, at least 2 required"))
dt.print_error(image_count .. " image(s) selected, at least 2 required")
return
end
job = dt.gui.create_job("image stack", true, stop_job)
job = dt.gui.create_job(_("image stack"), true, stop_job)
job.percent = job.percent + percent_step
-- align images if requested
@@ -507,13 +524,13 @@ local function image_stack(storage, image_table, extra_data)
job.percent = job.percent + percent_step
else
dt.print(_("ERROR: image alignment failed"))
dt.print_error(_("image alignment failed"))
dt.print_error("image alignment failed")
cleanup(img_list)
return
end
else
dt.print(_("ERROR: align_image_stack not found"))
dt.print_error(_("align_image_stack not found"))
dt.print_error("align_image_stack not found")
cleanup(img_list)
return
end
@@ -527,7 +544,7 @@ local function image_stack(storage, image_table, extra_data)
local ignore_tif_tags = " -quiet -define tiff:ignore-tags=40965,42032,42033,42034,42036,18246,18249,36867,34864,34866 "
if convert_executable then
local convert_command = convert_executable .. ignore_tif_tags .. convert_arguments
dt.print_log(_("convert command is " .. convert_command))
dt.print_log("convert command is " .. convert_command)
dt.print(_("processing image stack"))
local result = dtsys.external_command(convert_command)
if result == 0 then
@@ -541,7 +558,7 @@ local function image_stack(storage, image_table, extra_data)
local import_filename = df.create_unique_filename(film_roll_path .. PS .. df.get_filename(output_filename))
df.file_move(output_filename, import_filename)
imported_image = dt.database.import(import_filename)
local created_tag = dt.tags.create(_("Created with|image_stack"))
local created_tag = dt.tags.create(_("created with|image_stack"))
dt.tags.attach(created_tag, imported_image)
-- all the images are the same except for time, so just copy the attributes
-- from the first
@@ -555,7 +572,7 @@ local function image_stack(storage, image_table, extra_data)
if tag_source then
dt.print(_("tagging source images"))
local source_tag = dt.tags.create(_("Source file|" .. imported_image.filename))
local source_tag = dt.tags.create(_("source file|" .. imported_image.filename))
for img, _ in pairs(image_table) do
dt.tags.attach(source_tag, img)
end
@@ -567,7 +584,7 @@ local function image_stack(storage, image_table, extra_data)
end
else
dt.print(_("ERROR: convert executable not found"))
dt.print_error(_("convert executable not found"))
dt.print_error("convert executable not found")
cleanup(img_list)
end
job.valid = false
@@ -579,9 +596,12 @@ end
dt.preferences.register("align_image_stack", "align_use_gpu", -- name
"bool", -- type
_('align image stack: use GPU for remaping'), -- label
_('align image stack: use GPU for remapping'), -- label
_('set the GPU remapping for image align'), -- tooltip
false)
dt.register_storage("module_image_stack", _("image stack"), show_status, image_stack, nil, nil, image_stack_widget)
script_data.destroy = destroy
return script_data

602
contrib/image_time.lua Normal file
View File

@@ -0,0 +1,602 @@
--[[
image_time.lua - synchronize image time for images shot with different cameras
Copyright (C) 2019, 2020 Bill Ferguson <wpferguson@gmail.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
image_time - non-destructively modify the image time
DESCRIPTION
image_time non destructively adjusts image times by modifying the
database image exif_datetime_taken field. There are 4 modes: adjust time,
set time, synchronize time, and reset time.
ADJUST TIME
adjust time mode lets you chose an offset in terms of years, months,
days, hours, minutes, and seconds. The adjustment can be added or
subtracted.
WARNING: When adding and subtracting months the result will usually
be what is expected unless the time being adjusted is at the end of
the month. This is because a month is a variable amount of time that
can be 28, 29, 30 or 31 days depending on the month. Example: It's
March 31st and I subtract a month which not sets the time to February
31st. When that gets set to a valid time, then the date changes to
March 3rd.
SET TIME
set time mode allows you to pick a date and time and set the image
time accordingly. Fields may be left out. This is useful when
importing scanned images that don't have an embedded date.
SYNCHRONIZE TIME
I recently purchased a 7DmkII to replace my aging 7D. My 7D was still
serviceable, so I bought a remote control and figured I'd try shooting
events from 2 different perspectives. I didn't think to synchonize the
time between the 2 cameras, so when I loaded the images and sorted by
time it was a disaster. I hacked a script together with hard coded values
to adjust the exif_datetime_taken value in the database for the 7D images
so that everything sorted properly. I've tried shooting with 2 cameras
several times since that first attempt. I've gotten better at getting the
camera times close, but still haven't managed to get them to sync. So I
decided to think the problem through and write a proper script to take
care of the problem.
RESET TIME
Select the images and click reset.
USAGE
ADJUST TIME
Change the year, month, day, hour, minute, second dropdowns to the amount
of change desired. Select add or subtract. Select the images. Click
adjust.
SET TIME
Set the time fields to the desired time. Select the images to change. Click
set.
SYNCHRONIZE TIME
Select 2 images, one from each camera, of the same moment in time. Click
the Calculate button to calculate the time difference. The difference is
displayed in the difference entry. You can manually adjust it by changing
the value if necessary.
Select the images that need their time adjusted. Determine which way to adjust
adjust the time (add or subtract) and select the appropriate choice.
If the image times get messed up and you just want to start over, select reset time
from the mode and reset the image times.
RESET TIME
Select the images and click reset.
ADDITIONAL SOFTWARE REQUIRED
* exiv2
BUGS, COMMENTS, SUGGESTIONS
* Send to Bill Ferguson, wpferguson@gmail.com
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 gettext = dt.gettext.gettext
local img_time = {}
img_time.module_installed = false
img_time.event_registered = false
du.check_min_api_version("7.0.0", "image_time")
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("image time"),
purpose = _("synchronize image time for images shot with different cameras or adjust or set image time"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time"
}
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, otherwise leave as nil
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
local PS <const> = dt.configuration.runnin_os == "windows" and "\\" or "/"
local ERROR <const> = -1
local HAS_INTERNAL_METADATA = dt.metadata.exists("Exif.Image.DataTime")
-- function to convert from exif time to system time
local function exiftime2systime(exiftime)
local yr,mo,dy,h,m,s = string.match(exiftime, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d+)")
return(os.time{year=yr, month=mo, day=dy, hour=h, min=m, sec=s})
end
-- function to convert from systime to exif time
local function systime2exiftime(systime)
local t = os.date("*t", systime)
return(string.format("%4d:%02d:%02d %02d:%02d:%02d", t.year, t.month, t.day, t.hour, t.min, t.sec))
end
local function vars2exiftime(year, month, day, hour, min, sec)
local y = tonumber(year) and string.format("%4d", year) or "0000"
local mo = tonumber(month) and string.format("%02d", month) or "00"
local d = tonumber(day) and string.format("%02d", day) or "00"
local h = tonumber(hour) and string.format("%02d", hour) or "00"
local m = tonumber(min) and string.format("%02d", min) or "00"
local s = tonumber(sec) and string.format("%02d", sec) or "00"
return(y .. ":" .. mo .. ":" .. d .. " " .. h .. ":" .. m .. ":" .. s)
end
local function exiftime2vars(exiftime)
return string.match(exiftime, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)")
end
local function calc_time_difference(image1, image2)
return math.abs(exiftime2systime(image1.exif_datetime_taken) - exiftime2systime(image2.exif_datetime_taken))
end
local function adjust_image_time(image, difference)
image.exif_datetime_taken = systime2exiftime(exiftime2systime(image.exif_datetime_taken) + difference)
return
end
local function calculate_difference(images)
if #images == 2 then
img_time.diff_entry.text = calc_time_difference(images[1], images[2])
img_time.btn.sensitive = true
else
dt.print(_("ERROR: 2 images must be selected"))
end
end
local function synchronize_times(images, difference)
for _, image in ipairs(images) do
adjust_image_time(image, difference)
end
end
local function synchronize_time(images)
local sign = 1
if img_time.sdir.value == _("subtract") then
sign = -1
end
synchronize_times(images, tonumber(img_time.diff_entry.text) * sign)
end
local function add_time(images)
synchronize_times(images, tonumber(img_time.diff_entry.text))
end
local function year_month2months(year, month)
year_months = tonumber(year) and year * 12 or 0
local months = tonumber(month) and tonumber(month) or 0
return year_months + months
end
local function months2year_month(months)
local year = math.floor(months / 12)
local month = months - (year * 12)
return year, month
end
local function get_image_taken_time(image)
-- get original image time
local datetime = nil
local exiv2 = df.check_if_bin_exists("exiv2")
if exiv2 then
p = io.popen(exiv2 .. " -K Exif.Image.DateTime " .. df.sanitize_filename(image.path .. PS .. image.filename))
if p then
for line in p:lines() do
if string.match(line, "Exif.Image.DateTime") then
datetime = string.match(line, "(%d-:%d-:%d- %d-:%d-:%d+)")
end
end
p:close()
end
else
dt.print(_("unable to detect exiv2"))
datetime = ERROR
end
return datetime
end
local function _get_windows_image_file_creation_time(image)
local datetime = nil
local datestring = nil
if HAS_INTERNAL_METADATA then
datestring = image.DateString
else
local p = io.popen("dir " .. image.path .. PS .. image.filename)
if p then
for line in p:lines() do
if string.match(line, ds.sanitize_lua(image.filename)) then
datestring = line
end
end
p:close()
else
dt.print(string.format(_("unable to get information for %s"), image.filename))
datetime = ERROR
end
end
if datestring then
local mo, day, yr, hr, min, apm = string.match(datestring, "(%d+)/(%d+)/(%d+) (%d-):(%d+) (%S+)")
if apm == "PM" then
hr = hr + 12
end
datetime = vars2exiftime(yr, mo, day, hr, min, 0)
end
return datetime
end
local function _get_nix_image_file_creation_time(image)
local datetime = nil
local p = io.popen("ls -lL --time-style=full-iso " .. df.sanitize_filename(image.path .. PS .. image.filename))
if p then
for line in p:lines() do
if string.match(line, ds.sanitize_lua(image.filename)) then
datetime = vars2exiftime(string.match(line, "(%d+)%-(%d-)%-(%d-) (%d-):(%d-):(%d+)."))
end
end
p:close()
else
dt.print(string.format(_("unable to get information for %s"), image.filename))
datetime = ERROR
end
return datetime
end
local function get_image_file_creation_time(image)
-- no exif time in the image file so get the creation time
local datetime = nil
if dt.configuration.running_os == "windows" then
datetime = _get_windows_image_file_creation_time(image)
else
datetime = _get_nix_image_file_creation_time(image)
end
return datetime
end
local function get_original_image_time(image)
local image_time = image.exif_datetime_taken
local reset_time = nil
reset_time = get_image_taken_time(image)
if reset_time then
if reset_time == ERROR then
return image_time
else
return reset_time
end
else
reset_time = get_image_file_creation_time(image)
if reset_time then
if reset_time == ERROR then
return image_time
else
return reset_time
end
end
end
end
local function reset_time(images)
if #images > 0 then
for _, image in ipairs(images) do
image.exif_datetime_taken = get_original_image_time(image)
end
else
dt.print_error("reset time: no images selected")
dt.print(_("please select the images that need their time reset"))
end
end
local function adjust_time(images)
local SEC_MIN = 60
local SEC_HR = SEC_MIN * 60
local SEC_DY = SEC_HR * 24
local offset = nil
local sign = 1
if #images < 1 then
dt.print(_("please select some images and try again"))
return
end
if img_time.adir.value == _("subtract") then
sign = -1
end
for _, image in ipairs(images) do
local y, mo, d, h, m, s = exiftime2vars(image.exif_datetime_taken)
local image_months = year_month2months(y, mo)
local months_diff = year_month2months(img_time.ayr.value, img_time.amo.value)
y, mo = months2year_month(image_months + (months_diff * sign))
local exif_new = vars2exiftime(y, mo, d, h, m, s)
offset = img_time.ady.value * SEC_DY
offset = offset + img_time.ahr.value * SEC_HR
offset = offset + img_time.amn.value * SEC_MIN
offset = offset + img_time.asc.value
offset = offset * sign
image.exif_datetime_taken = systime2exiftime(exiftime2systime(exif_new) + offset)
end
end
local function set_time(images)
if #images < 1 then
dt.print(_("please select some images and try again"))
return
end
local y = img_time.syr.value
local mo = img_time.smo.value
local d = img_time.sdy.value
local h = img_time.shr.value
local m = img_time.smn.value
local s = img_time.ssc.value
for _, image in ipairs(images) do
image.exif_datetime_taken = vars2exiftime(y, mo, d, h, m, s)
end
end
local function seq(first, last)
local result = {}
local num = first
while num <= last do
table.insert(result, num)
num = num + 1
end
return table.unpack(result)
end
local function reset_widgets()
dt.print_log("took the reset function")
img_time.ayr.selected = 1
img_time.amo.selected = 1
img_time.ady.selected = 1
img_time.ahr.selected = 1
img_time.ayr.selected = 1
img_time.amn.selected = 1
img_time.asc.selected = 1
img_time.adir.selected = 1
img_time.syr.selected = #img_time.syr
img_time.smo.selected = 1
img_time.sdy.selected = 1
img_time.shr.selected = 1
img_time.smn.selected = 1
img_time.ssc.selected = 1
img_time.adir.selected = 1
end
local function install_module()
if not img_time.module_installed then
dt.register_lib(
"image_time", -- Module name
_("image time"), -- Visible name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
img_time.widget,
nil,-- view_enter
nil -- view_leave
)
img_time.module_installed = true
end
end
local function destroy()
dt.gui.libs["image_time"].visible = false
end
local function restart()
dt.gui.libs["image_time"].visible = true
end
-- widgets
img_time.widgets = {
-- name, type, tooltip, placeholder,
{"ayr", "combobox", _("years"), _("years to adjust by, 0 - ?"), {seq(0,20)}, 1},
{"amo", "combobox", _("months"), _("months to adjust by, 0-12"), {seq(0,12)}, 1},
{"ady", "combobox", _("days"), _("days to adjust by, 0-31"), {seq(0,31)}, 1},
{"ahr", "combobox", _("hours"), _("hours to adjust by, 0-23"), {seq(0,23)}, 1},
{"amn", "combobox", _("minutes"), _("minutes to adjust by, 0-59"), {seq(0,59)}, 1},
{"asc", "combobox", _("seconds"), _("seconds to adjust by, 0-59"), {seq(0,59)}, 1},
{"adir", "combobox", _("add/subtract"), _("add or subtract time"), {_("add"), _("subtract")}, 1},
{"syr", "combobox", _("year"), _("year to set, 1900 - now"), {" ", seq(1900,os.date("*t", os.time()).year)}, 1},
{"smo", "combobox", _("month"), _("month to set, 1-12"), {" ", seq(1,12)}, 1},
{"sdy", "combobox", _("day"), _("day to set, 1-31"), {" ", seq(1,31)}, 1},
{"shr", "combobox", _("hour"), _("hour to set, 0-23"), {" ", seq(0,23)}, 1},
{"smn", "combobox", _("minute"), _("minutes to set, 0-59"), {" ", seq(0, 59)}, 1},
{"ssc", "combobox", _("seconds"), _("seconds to set, 0-59"), {" ", seq(0,59)}, 1},
{"sdir", "combobox", _("add/subtract"), _("add or subtract time"), {_("add"), _("subtract")}, 1},
}
for _, widget in ipairs(img_time.widgets) do
img_time[widget[1]] = dt.new_widget(widget[2]){
label = widget[3],
tooltip = widget[4],
selected = widget[6],
table.unpack(widget[5])
}
end
img_time.syr.selected = #img_time.syr
img_time.diff_entry = dt.new_widget("entry"){
tooltip = _("time difference between images in seconds"),
placeholder = _("select 2 images and use the calculate button"),
text = "",
}
img_time.calc_btn = dt.new_widget("button"){
label = _("calculate"),
tooltip = _("calculate time difference between 2 images"),
clicked_callback = function()
calculate_difference(dt.gui.action_images)
end
}
img_time.btn = dt.new_widget("button"){
label = _("synchronize image times"),
tooltip = _("apply the time difference from selected images"),
sensitive = false,
clicked_callback = function()
synchronize_time(dt.gui.action_images)
end
}
img_time.stack = dt.new_widget("stack"){
dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label"){label = _("adjust time")},
dt.new_widget("section_label"){label = _("days, months, years")},
img_time.ady,
img_time.amo,
img_time.ayr,
dt.new_widget("section_label"){label = _("hours, minutes, seconds")},
img_time.ahr,
img_time.amn,
img_time.asc,
dt.new_widget("section_label"){label = _("adjustment direction")},
img_time.adir,
dt.new_widget("button"){
label = _("adjust"),
clicked_callback = function()
adjust_time(dt.gui.action_images)
end
}
},
dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label"){label = _("set time")},
dt.new_widget("section_label"){label = _("date:")},
img_time.sdy,
img_time.smo,
img_time.syr,
dt.new_widget("section_label"){label = _("time:")},
img_time.shr,
img_time.smn,
img_time.ssc,
dt.new_widget("button"){
label = _("set"),
clicked_callback = function()
set_time(dt.gui.action_images)
end
}
},
dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label"){label = _("synchronize image time")},
dt.new_widget("section_label"){label = _("calculate difference between images")},
img_time.diff_entry,
img_time.calc_btn,
dt.new_widget("section_label"){label = _("apply difference")},
img_time.sdir,
img_time.btn,
},
dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label"){label = _("reset to original time")},
dt.new_widget("separator"){},
dt.new_widget("button"){
label = _("reset"),
clicked_callback = function()
reset_time(dt.gui.action_images)
end
}
},
}
img_time.mode = dt.new_widget("combobox"){
label = _("mode"),
tooltip = _("select mode"),
selected = 1,
changed_callback = function(this)
img_time.stack.active = this.selected
end,
_("adjust time"),
_("set time"),
_("synchronize time"),
_("reset time")
}
img_time.widget = dt.new_widget("box"){
orientation = "vertical",
reset_callback = function(this)
reset_widgets()
end,
img_time.mode,
img_time.stack,
}
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not img_time.event_registered then
dt.register_event(
"image_time", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
img_time.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -0,0 +1,200 @@
--[[
jpg_group_leader.lua - Make jpg image group leader
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
jpg_group_leader - Make jpg image group leader
After a film roll is imported, check for RAW-JPG image groups
and make the JPG image the group leader. This is on by default
but can be disabled in preferences.
Shortcuts are included to filter existing collections or
selections of images and make the jpg the group leader.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
Start script from script_manager
Assign keys to the shortcuts
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson@gmail.com>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE = "jpg_group_leader"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- A P I C H E C K
-- - - - - - - - - - - - - - - - - - - - - - - -
du.check_min_api_version("7.0.0", MODULE)
-- - - - - - - - - - - - - - - - - - - - - - - - - -
-- 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.metadata = {
name = _("JPG group leader"),
purpose = _("make jpg image group leader"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/jpg_group_leader"
}
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
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.preferences.register(MODULE, "on_import", "bool", _("make jpg group leader on import"), _("automatically make the jpg file the group leader when raw + jpg are imported"), true)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local jgloi = {}
jgloi.images = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function toggle_global_toolbox_grouping()
dt.gui.libs.global_toolbox.grouping = false
dt.gui.libs.global_toolbox.grouping = true
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N P R O G R A M
-- - - - - - - - - - - - - - - - - - - - - - - -
local function make_jpg_group_leader(images)
-- if the image is part of a group, make it the leader
for _, image in ipairs(images) do
if #image:get_group_members() > 1 then
image:make_group_leader()
end
end
if dt.gui.libs.global_toolbox.grouping then
-- toggle the grouping to make the new leader show
toggle_global_toolbox_grouping()
end
end
local function make_existing_jpg_group_leader(images)
for _, image in ipairs(images) do
if string.lower(df.get_filetype(image.filename)) == "jpg" then
if #image:get_group_members() > 1 then
image:make_group_leader()
end
end
end
if dt.gui.libs.global_toolbox.grouping then
-- toggle the grouping to make the new leader show
toggle_global_toolbox_grouping()
end
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function destroy()
if dt.preferences.read(MODULE, "on_import", "bool") then
dt.destroy_event(MODULE, "post-import-film")
dt.destroy_event(MODULE, "post-import-image")
end
dt.destroy_event(MODULE .. "_collect", "shortcut")
dt.destroy_event(MODULE .. "_select", "shortcut")
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
if dt.preferences.read(MODULE, "on_import", "bool") then
dt.register_event(MODULE, "post-import-film",
function(event, film_roll)
-- ignore the film roll, it contains all the images, not just the imported
local images = jgloi.images
if #images > 0 then
jgloi.images = {}
make_jpg_group_leader(images)
end
end
)
dt.register_event(MODULE, "post-import-image",
function(event, image)
if string.lower(df.get_filetype(image.filename)) == "jpg" then
table.insert(jgloi.images, image)
end
end
)
end
dt.register_event(MODULE .. "_collect", "shortcut",
function(event, shortcut)
-- ignore the film roll, it contains all the images, not just the imported
local images = dt.collection
make_existing_jpg_group_leader(images)
end,
_("make jpg group leader for collection")
)
dt.register_event(MODULE .. "_select", "shortcut",
function(event, shortcut)
local images = dt.gui.selection()
make_existing_jpg_group_leader(images)
end,
_("make jpg group leader for selection")
)
return script_data

View File

@@ -39,21 +39,34 @@ local df = require "lib/dtutils.file"
local ds = require "lib/dtutils.string"
local dsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
du.check_min_api_version("5.0.0", kml_export)
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("kml_export",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "kml_export")
local function _(msgid)
return gettext.dgettext("kml_export", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("kml export"),
purpose = _("export KML/KMZ data to a file"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export"
}
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, otherwise leave as nil
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
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
dt.print(string.format(_("export image %i/%i"), number, total))
end
-- Add duplicate index to filename
@@ -80,12 +93,12 @@ local function create_kml_file(storage, image_table, extra_data)
end
if not df.check_if_bin_exists(magickPath) then
dt.print_error(_("magick not found"))
dt.print_error("magick not found")
return
end
if dt.configuration.running_os == "linux" then
if not df.check_if_bin_exists("xdg-user-dir") then
dt.print_error(_("xdg-user-dir not found"))
dt.print_error("xdg-user-dir not found")
return
end
end
@@ -95,7 +108,7 @@ local function create_kml_file(storage, image_table, extra_data)
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true
and dt.configuration.running_os == "linux") then
if not df.check_if_bin_exists("zip") then
dt.print_error(_("zip not found"))
dt.print_error("zip not found")
return
end
exportDirectory = dt.configuration.tmp_dir
@@ -301,20 +314,24 @@ local function create_kml_file(storage, image_table, extra_data)
end
local function destroy()
dt.destroy_storage("kml_export")
end
-- Preferences
if dt.configuration.running_os == "windows" then
dt.preferences.register("kml_export",
"OpenKmlFile",
"bool",
_("KML export: Open KML file after export"),
_("Opens the KML file after the export with the standard program for KML files"),
_("opens the KML file after the export with the standard program for KML files"),
false )
else
dt.preferences.register("kml_export",
"OpenKmlFile",
"bool",
_("KML export: Open KML/KMZ file after export"),
_("Opens the KML file after the export with the standard program for KML files"),
_("KML export: open KML/KMZ file after export"),
_("opens the KML file after the export with the standard program for KML files"),
false )
end
@@ -322,7 +339,7 @@ local defaultDir = ''
if dt.configuration.running_os == "windows" then
defaultDir = os.getenv("USERPROFILE")
elseif dt.configuration.running_os == "macos" then
defaultDir = os.getenv("home")
defaultDir = os.getenv("HOME")
else
local handle = io.popen("xdg-user-dir DESKTOP")
defaultDir = handle:read()
@@ -333,23 +350,23 @@ end
dt.preferences.register("kml_export",
"ExportDirectory",
"directory",
_("KML export: Export directory"),
_("A directory that will be used to export the KML/KMZ files"),
_("KML export: export directory"),
_("a directory that will be used to export the KML/KMZ files"),
defaultDir )
if dt.configuration.running_os ~= "linux" then
dt.preferences.register("kml_export",
"magickPath", -- name
"file", -- type
_("KML export: ImageMagick binary Location"), -- label
_("Install location of magick[.exe]. Requires restart to take effect."), -- tooltip
_("KML export: ImageMagick binary location"), -- label
_("install location of magick[.exe], requires restart to take effect"), -- tooltip
"magick") -- default
end
dt.preferences.register("kml_export",
"CreatePath",
"bool",
_("KML export: Connect images with path"),
_("KML export: connect images with path"),
_("connect all images with a path"),
false )
@@ -357,17 +374,21 @@ if dt.configuration.running_os == "linux" then
dt.preferences.register("kml_export",
"CreateKMZ",
"bool",
_("KML export: Create KMZ file"),
_("Compress all imeges to one KMZ file"),
_("KML export: create KMZ file"),
_("compress all imeges to one KMZ file"),
true )
end
-- Register
if dt.configuration.running_os == "windows" then
dt.register_storage("kml_export", _("KML Export"), nil, create_kml_file)
dt.register_storage("kml_export", _("KML export"), nil, create_kml_file)
else
dt.register_storage("kml_export", _("KML/KMZ Export"), nil, create_kml_file)
dt.register_storage("kml_export", _("KML/KMZ export"), nil, create_kml_file)
end
script_data.destroy = destroy
return script_data
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: hl Lua;

View File

@@ -20,7 +20,7 @@
--[[
PASSPORT CROPPING GUIDE
guides for cropping passport photos based on documents from the Finnish police
(https://www.poliisi.fi/instancedata/prime_product_julkaisu/intermin/embeds/poliisiwwwstructure/38462_Passikuvaohje_EN.pdf) describing passport photo dimensions of 47x36 mm and 500x653 px for digital biometric data stored in passports. They use ISO 19794-5 standard based on ICAO 9303 regulations which should also be compliant for all of Europe.
(https://poliisi.fi/documents/25235045/31329600/Passport-photograph-instructions-by-the-police-2020-EN-fixed.pdf/1eec2f4c-aed7-68e0-c112-0a8f25e0328d/Passport-photograph-instructions-by-the-police-2020-EN-fixed.pdf) describing passport photo dimensions of 47x36 mm and 500x653 px for digital biometric data stored in passports. They use ISO 19794-5 standard based on ICAO 9303 regulations which should also be compliant for all of Europe.
INSTALLATION
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
@@ -37,17 +37,28 @@ USAGE
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
du.check_min_api_version("2.0.0", "passport_guide")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("passport_guide",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("passport_guide", msgid)
return gettext(msgid)
end
local script_data = {}
script_data.metadata = {
name = _("passport guide"),
purpose = _("guides for cropping passport photos"),
author = "Kåre Hampf",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide"
}
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, otherwise leave as nil
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
dt.guides.register_guide("passport",
-- draw
function(cairo, x, y, width, height, zoom_scale)
@@ -91,5 +102,13 @@ function()
end
)
local function destroy()
-- nothing to destroy
end
script_data.destroy = destroy
return script_data
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on; hl Lua;
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -0,0 +1,125 @@
--[[
German passport photo cropping guide for darktable.
Derived from the passport cropping guide by Kåre Hampf.
copyright (c) 2017 Kåre Hampf
copyright (c) 2024 Christian Sültrop
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 <http://www.gnu.org/licenses/>.
]]
--[[
PASSPORT CROPPING GUIDE
Guides for cropping passport and ID card ("Personalausweis") photos based on the "Passbild-Schablone"
from the German Federal Ministry of the Interior and Community.
(https://www.bmi.bund.de/SharedDocs/downloads/DE/veroeffentlichungen/themen/moderne-verwaltung/ausweise/passbild-schablone-erwachsene.pdf?__blob=publicationFile&v=3)
INSTALLATION
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
* add the following line in the file $CONFIGDIR/luarc
require "passport_guide_germany"
* (optional) add the line:
"plugins/darkroom/clipping/extra_aspect_ratios/passport 35x45mm=45:35"
to $CONFIGDIR/darktablerc
USAGE
* when using the cropping tool, select "Passport Photo Germany" as guide and if you added the line in yout rc
select "passport 35x45mm" as aspect
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext.gettext
du.check_min_api_version("2.0.0", "passport_guide_germany")
-- Tell gettext where to find the .mo file translating messages for a particular domain
local function _(msgid)
return gettext(msgid)
end
local script_data = {}
script_data.metadata = {
name = _("passport guide Germany"),
purpose = _("guides for cropping passport and ID card (\"Personalausweis\") photos based on the \"Passbild-Schablone\" from the German Federal Ministry of the Interior and Community"),
author = "menschmachine",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide_germany"
}
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, otherwise leave as nil
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
dt.guides.register_guide("Passport Photo Germany",
-- draw
function(cairo, x, y, width, height, zoom_scale)
local _width, _height
-- get the max 35x45 rectangle
local aspect_ratio = 45 / 35
if width * aspect_ratio > height then
_width = height / aspect_ratio
_height = height
else
_width = width
_height = width * aspect_ratio
end
cairo:save()
cairo:translate(x + (width - _width) / 2, y + (height - _height) / 2)
cairo:scale(_width / 35, _height / 45)
-- the outer rectangle
cairo:rectangle( 0, 0, 35, 45)
-- Nose position: The nose tip must be between these lines
cairo:draw_line(15.5, 45, 15.5, 13)
cairo:draw_line(35-15.5, 45, 35-15.5, 13)
-- Face height
-- optimum face height: The upper end of the head should be between these lines
cairo:draw_line(0, 4, 35, 4)
cairo:draw_line(0, 8, 35, 8)
-- tolerated face height: The upper end of the head must not be below this line
cairo:draw_line(6, 13, 30, 13)
-- Eye area: The eyes must be between these lines
cairo:draw_line(0, 13, 35, 13)
cairo:draw_line(0, 23, 35, 23)
-- Cheek line: The cheek must lie on this line
cairo:draw_line(9, 45-5, 27, 45-5)
cairo:restore()
end,
-- gui
function()
return dt.new_widget("label"){label = _("Passport Photo Germany"), halign = "start"}
end
)
local function destroy()
-- noting to destroy
end
script_data.destroy = destroy
return script_data
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on; hl Lua;
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -41,23 +41,35 @@ format (all fields can be the empty string):
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("pdf_slideshow",dt.configuration.config_dir.."/lua/locale/")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext("pdf_slideshow", msgid)
return gettext(msgid)
end
if not df.check_if_bin_exists("pdflatex") then
dt.print_error(_("pdflatex not found"))
dt.print_error("pdflatex not found")
return
end
du.check_min_api_version("4.0.0", "pdf_slideshow")
du.check_min_api_version("7.0.0", "pdf_slideshow")
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("PDF slideshow"),
purpose = _("generates a PDF slideshow (via Latex) containing all selected images one per slide"),
author = "Pascal Obry",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow"
}
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, otherwise leave as nil
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
dt.preferences.register
("pdf_slideshow","open with","string",
@@ -165,6 +177,10 @@ local function slide(latexfile,i,image,file)
my_write(latexfile,"\\end{figure}\n")
end
local function destroy()
dt.destroy_storage("pdf_slideshow")
end
dt.register_storage("pdf_slideshow",_("pdf slideshow"),
nil,
function(storage,image_table)
@@ -219,7 +235,7 @@ dt.register_storage("pdf_slideshow",_("pdf slideshow"),
local result = dt.control.execute(command)
if result ~= 0 then
dt.print(_("problem running pdflatex")) -- this one is probably usefull to the user
error(_("problem running ")..command)
error("problem running "..command)
end
-- open the PDF
@@ -229,7 +245,7 @@ dt.register_storage("pdf_slideshow",_("pdf slideshow"),
local result = dt.control.execute(command)
if result ~= 0 then
dt.print(_("problem running pdf viewer")) -- this one is probably usefull to the user
error(_("problem running ")..command)
error("problem running "..command)
end
-- finally do some clean-up
@@ -244,5 +260,8 @@ dt.register_storage("pdf_slideshow",_("pdf slideshow"),
nil,
widget)
script_data.destroy = destroy
return script_data
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

507
contrib/photils.lua Normal file
View File

@@ -0,0 +1,507 @@
--[[ photils Auto Tagging plugin
copyright (c) 2020 Tobias Scheck
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 <http://www.gnu.org/licenses/>.
--]]
--[[
A darktable plugin that tries to predict keywords based on the selected image.
This plugin uses photils-cli to handle this task. Photils-cli is an application
that passes the image through a neural network, classifies it, and extracts the
suggested tags. Everything happens offline without the need that your data are
sent over the internet.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* photils-cli - https://github.com/scheckmedia/photils-cli at the moment only
available for Linux and MacOS
USAGE
* require this script from your main lua file
To do this add this line to the file .config/darktable/luarc:
require "contrib/photils"
* Select an image
* Press "get tags"
* Select the tags you want from a list of suggestions
* Press "Attach .. Tags" to add the selected tags to your image
--]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local MODULE_NAME = "photils"
du.check_min_api_version("7.0.0", MODULE_NAME)
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("photils"),
purpose = _("suggest tags based on image classification"),
author = "Tobias Scheck",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils"
}
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, otherwise leave as nil
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
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
local exporter = dt.new_format("jpeg")
exporter.quality = 80
exporter.max_height = 224
exporter.max_width = 224
-- helper functions
local function num_keys(tbl)
local num = 0
for _ in pairs(tbl) do num = num + 1 end
return num
end
local function has_key(tbl, value)
for k, _ in pairs(tbl) do
if k == value then
return true
end
end
return false
end
local photils_installed = df.check_if_bin_exists("photils-cli")
--[[
local state object
maybe per_page is a preference variable but I think 10
is a good value for the ui
]]
local PHOTILS = {
tags = {},
confidences = {},
page = 1,
per_page = 10,
selected_tags = {},
in_pagination = false,
tagged_image = "",
module_installed = false,
event_registered = false,
plugin_display_views = {
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100},
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}
},
}
local GUI = {
container = dt.new_widget("box") {
orientation = "vertical",
sensitive = true,
dt.new_widget("button") {
label = _("get tags"),
sensitive = photils_installed,
clicked_callback = function() PHOTILS.on_tags_clicked() end
},
reset_callback = function() PHOTILS.on_reset(true) end
},
stack = dt.new_widget("stack"),
prev_button = dt.new_widget("button") {
label = "<",
sensitive = false,
clicked_callback = function()
PHOTILS.page = PHOTILS.page - 1
PHOTILS.paginate()
end
},
next_button = dt.new_widget("button") {
label = ">",
sensitive = false,
clicked_callback = function()
PHOTILS.page = PHOTILS.page + 1
PHOTILS.paginate()
end
},
tag_box = dt.new_widget("box") {orientation = "vertical"},
tag_view = dt.new_widget("box") {orientation = "vertical"},
page_label = dt.new_widget("label") {label = ""},
error_view = dt.new_widget("box") {orientation = "vertical"},
warning_label = dt.new_widget("label") {
label = ""
},
restart_required_label = dt.new_widget("label") {
label = _("requires a restart to be applied")
},
attach_button = dt.new_widget("button") {
label = "",
sensitive = false,
clicked_callback = function(self) PHOTILS.attach_tags() end
},
confidence_slider = dt.new_widget("slider") {
step = 1,
digits = 0,
value = 90,
hard_max = 100,
hard_min = 0,
soft_max = 100,
soft_min = 0,
label = _("min confidence value")
},
warning = dt.new_widget("label")
}
function PHOTILS.image_changed()
local current_image = tostring(dt.gui.selection()[1])
if current_image ~= PHOTILS.tagged_image then
if PHOTILS.tagged_image ~= "" then
PHOTILS.tagged_image_has_changed()
end
PHOTILS.tagged_image = tostring(current_image)
end
end
function PHOTILS.tagged_image_has_changed()
GUI.warning.label = _("the suggested tags were not generated\n for the currently selected image!")
end
function PHOTILS.paginate()
PHOTILS.in_pagination = true
local num_pages = math.ceil(#PHOTILS.tags / PHOTILS.per_page)
GUI.page_label.label = string.format(_(" page %s of %s "), PHOTILS.page,
num_pages)
if PHOTILS.page <= 1 then
PHOTILS.page = 1
GUI.prev_button.sensitive = false
else
GUI.prev_button.sensitive = true
end
if PHOTILS.page > num_pages - 1 then
PHOTILS.page = num_pages
GUI.next_button.sensitive = false
else
GUI.next_button.sensitive = true
end
--[[
calculates the start positon in the tag array based on the current page
and takes N tags from that array to show these in darktable
e.g. page 1 goes from 1 to 10, page 2 from 11 to 20 a.s.o.
the paginaton approach is related to a problem with the dynamic addition
of mutliple widgets https://github.com/darktable-org/darktable/issues/4934#event-3318100463
]]--
local offset = ((PHOTILS.page - 1) * PHOTILS.per_page) + 1
local tag_index = 1
for i = offset, offset + PHOTILS.per_page - 1, 1 do
local tag = PHOTILS.tags[i]
local conf = PHOTILS.confidences[i]
GUI.tag_box[tag_index].value = has_key(PHOTILS.selected_tags, tag)
if tag then
if dt.preferences.read(MODULE_NAME, "show_confidence", "bool") then
tag = tag .. string.format(" (%.3f)", conf)
end
GUI.tag_box[tag_index].label = tag
GUI.tag_box[tag_index].sensitive = true
else
GUI.tag_box[tag_index].label = ""
GUI.tag_box[tag_index].sensitive = false
end
tag_index = tag_index + 1
end
PHOTILS.in_pagination = false
end
function PHOTILS.attach_tags()
local num_selected = #dt.gui.selection()
local job = dt.gui.create_job(_("apply tag to image"), true)
for i = 1, num_selected, 1 do
local image = dt.gui.selection()[i]
for tag, _ in pairs(PHOTILS.selected_tags) do
local dt_tag = dt.tags.create(tag)
dt.tags.attach(dt_tag, image)
end
job.percent = i / num_selected
end
dt.print(_("tags successfully attached to image"))
job.valid = false
end
function PHOTILS.get_tags(image, with_export)
local tmp_file = df.create_tmp_file()
local in_arg = df.sanitize_filename(tostring(image))
local out_arg = df.sanitize_filename(tmp_file)
local executable = photils_installed
if with_export then
dt.print_log("use export to for prediction")
local export_file = df.create_tmp_file()
exporter:write_image(image, export_file)
in_arg = df.sanitize_filename(tostring(export_file))
end
local command = executable .. " -c " .. " -i " .. in_arg .. " -o " .. out_arg
local ret = dtsys.external_command(command)
if ret > 0 then
dt.print_error(string.format("command %s returned error code %d", command, ret))
os.remove(tmp_file)
-- try to export the image and run tagging
if not with_export then
return PHOTILS.get_tags(image, true)
end
return false
end
for i = #PHOTILS.tags, 1, -1 do
PHOTILS.tags[i] = nil
PHOTILS.confidences[i] = nil
end
for tag in io.lines(tmp_file) do
local splitted = du.split(tag, ":")
if 100 * tonumber(splitted[2]) >= GUI.confidence_slider.value then
PHOTILS.tags[#PHOTILS.tags + 1] = splitted[1]
PHOTILS.confidences[#PHOTILS.confidences+1] = splitted[2]
end
end
dt.print(string.format(_("%s found %d tags for your image"), MODULE_NAME,
#PHOTILS.tags))
os.remove(tmp_file)
return true
end
function PHOTILS.on_tags_clicked()
PHOTILS.page = 1
GUI.warning.label = ""
PHOTILS.on_reset(false)
local images = dt.gui.selection()
if #images == 0 then
dt.print(_("no image selected."))
dt.control.sleep(2000)
else
if #images > 1 then
dt.print(_("this plugin can only handle a single image."))
dt.gui.selection({images[1]})
dt.control.sleep(2000)
end
with_export = dt.preferences.read(MODULE_NAME, "export_image_before_for_tags", "bool")
if not PHOTILS.get_tags(images[1], with_export) then
local msg = string.format(_("%s failed, see terminal output for details"), MODULE_NAME)
GUI.warning_label.label = msg
GUI.stack.active = GUI.error_view
dt.print(msg)
return
end
if #PHOTILS.tags == 0 then
local msg = string.format(_("no tags were found"), MODULE_NAME)
GUI.warning_label.label = msg
GUI.stack.active = GUI.error_view
return
end
GUI.stack.active = GUI.tag_view
PHOTILS.paginate()
end
end
function PHOTILS.tag_selected(tag_button)
if PHOTILS.in_pagination then return end
if tag_button.value then
local tag = tag_button.label
if dt.preferences.read(MODULE_NAME, "show_confidence", "bool") then
local idx = string.find(tag, "%(") - 2
tag = string.sub(tag, 0, idx)
end
PHOTILS.selected_tags[tag] = tag
else
PHOTILS.selected_tags[tag_button.label] = nil
end
local num_selected = num_keys(PHOTILS.selected_tags)
if num_selected == 0 then
GUI.attach_button.label = ""
GUI.attach_button.sensitive = false
else
GUI.attach_button.label = string.format(_("attach %d tags"),
num_selected)
GUI.attach_button.sensitive = true
end
end
function PHOTILS.on_reset(with_view)
if with_view then GUI.stack.active = 1 end
for k, _ in pairs(PHOTILS.selected_tags) do
PHOTILS.selected_tags[k] = nil
end
for _, v in ipairs(GUI.tag_box) do
v.value = false
end
GUI.attach_button.label = ""
GUI.attach_button.sensitive = false
end
local function install_module()
if not PHOTILS.module_installed then
dt.register_lib(MODULE_NAME,
_("photils auto-tagger"),
true,
true,
PHOTILS.plugin_display_views,
GUI.container,
nil,
nil
)
PHOTILS.module_installed = true
end
end
local function destroy()
dt.gui.libs[MODULE_NAME].visible = false
dt.destroy_event("photils", "mouse-over-image-changed")
end
local function restart()
dt.gui.libs[MODULE_NAME].visible = true
dt.register_event("photils", "mouse-over-image-changed",
PHOTILS.image_changed)
end
local function show()
dt.gui.libs[MODULE_NAME].visible = true
end
-- add a fix number of buttons
for _ = 1, PHOTILS.per_page, 1 do
local btn_tag = dt.new_widget("check_button") {
label = "",
sensitive = false,
clicked_callback = PHOTILS.tag_selected
}
table.insert(GUI.tag_box, btn_tag)
end
if not photils_installed then
GUI.warning_label.label = _("photils-cli not found")
dt.print_log("photils-cli not found")
else
GUI.warning_label.label = _("select an image, click \"get tags\" and get \nsuggestions for tags.")
end
GUI.pagination = dt.new_widget("box") {
orientation = "horizontal",
GUI.prev_button,
GUI.page_label,
GUI.next_button
}
table.insert(GUI.error_view, GUI.warning_label)
if not photils_installed then
table.insert(GUI.error_view, df.executable_path_widget({"photils-cli"}))
table.insert(GUI.error_view, GUI.restart_required_label)
end
table.insert(GUI.stack, GUI.error_view)
table.insert(GUI.stack, GUI.tag_view)
table.insert(GUI.tag_view, GUI.pagination)
table.insert(GUI.tag_view, GUI.tag_box)
table.insert(GUI.tag_view, GUI.attach_button)
table.insert(GUI.tag_view, GUI.warning)
table.insert(GUI.container, GUI.confidence_slider)
table.insert(GUI.container, GUI.stack)
GUI.stack.active = 1
-- uses photils: prefix because script settings are all together and not seperated by script
dt.preferences.register(MODULE_NAME,
"show_confidence",
"bool",
_("photils: show confidence value"),
_("if enabled, the confidence value for each tag is displayed"),
true)
dt.preferences.register(MODULE_NAME,
"export_image_before_for_tags",
"bool",
_("photils: use exported image for tag request"),
_("if enabled, the image passed to photils for tag suggestion is based on the exported, already edited image. " ..
"otherwise, the embedded thumbnail of the RAW file will be used for tag suggestion." ..
"the embedded thumbnail could speedup the tag suggestion but can fail if the RAW file is not supported."),
true)
dt.register_event("photils", "mouse-over-image-changed",
PHOTILS.image_changed)
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not PHOTILS.event_registered then
dt.register_event(
"photils", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
PHOTILS.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = show
return script_data

View File

@@ -47,18 +47,34 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local debug = require "darktable.debug"
du.check_min_api_version("7.0.0", "quicktag")
local gettext = dt.gettext
du.check_min_api_version("3.0.0", "quicktag")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext("quicktag", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("quick tag"),
purpose = _("use buttons to quickly apply tags assigned to them"),
author = "Christian Kanzian",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag"
}
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, otherwise leave as nil
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
local qt = {}
qt.module_installed = false
qt.event_registered = false
qt.widget_table = {}
-- maximum length of button labels
dt.preferences.register("quickTag",
"labellength",
@@ -156,7 +172,7 @@ abbrevate_tags(quicktag_table)
local button = {}
-- function to create buttons with tags as labels
for j=1,qnr do
for j = 1, qnr do
button[#button+1] = dt.new_widget("button") {
label = j..": "..quicktag_table[j],
clicked_callback = function() tagattach(tostring(quicktag_table[j]),j) end}
@@ -177,12 +193,56 @@ local function update_quicktag_list()
end
end
local function install_module()
if not qt.module_installed then
dt.register_lib(
"quicktag", -- Module name
_("quick tag"), -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
dt.new_widget("box"){
orientation = "vertical",
table.unpack(qt.widget_table),
},
nil,-- view_enter
nil -- view_leave
)
qt.module_installed = true
end
end
local function destroy()
dt.gui.libs["quicktag"].visible = false
for i=1,qnr do
dt.destroy_event("quicktag " .. tostring(i), "shortcut")
end
end
local function restart()
dt.gui.libs["quicktag"].visible = true
for i = 1 ,qnr do
dt.register_event("quicktag", "shortcut",
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
string.format(_("quicktag %i"),i))
end
end
local function show()
dt.gui.libs["quicktag"].visible = true
end
update_quicktag_list()
local new_quicktag = dt.new_widget("entry"){
text = "",
placeholder = _("new tag"),
is_password = true,
is_password = false,
editable = true,
tooltip = _("enter your tag here")
}
@@ -213,40 +273,46 @@ local new_qt_widget = dt.new_widget ("box") {
-- back UI elements in a table
-- thanks to wpferguson for the hint
local widget_table = {}
for i=1,qnr do
widget_table[#widget_table + 1] = button[i]
qt.widget_table[#qt.widget_table + 1] = button[i]
end
widget_table[#widget_table + 1] = dt.new_widget("separator"){}
widget_table[#widget_table + 1] = old_quicktag
widget_table[#widget_table + 1] = new_qt_widget
qt.widget_table[#qt.widget_table + 1] = dt.new_widget("separator"){}
qt.widget_table[#qt.widget_table + 1] = old_quicktag
qt.widget_table[#qt.widget_table + 1] = new_qt_widget
--create module
dt.register_lib(
"quicktag", -- Module name
"quicktag", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
dt.new_widget("box"){
orientation = "vertical",
table.unpack(widget_table),
},
nil,-- view_enter
nil -- view_leave
)
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not qt.event_registered then
dt.register_event(
"quicktag", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
qt.event_registered = true
end
end
-- create shortcuts
for i=1,qnr do
dt.register_event("shortcut",
dt.register_event("quicktag " .. tostring(i), "shortcut",
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
string.format(_("quicktag %i"),i))
string.format(_("quick tag %i"),i))
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = show
return script_data
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;

View File

@@ -42,7 +42,29 @@ local dt = require "darktable"
local du = require "lib/dtutils"
-- added version check
du.check_min_api_version("3.0.0", "rate_group")
du.check_min_api_version("7.0.0", "rate_group")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("rate group"),
purpose = _("rate all images in a group"),
author = "Dom H (dom@hxy.io)",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group"
}
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, otherwise leave as nil
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
local function apply_rating(rating)
local images = dt.gui.action_images
@@ -53,36 +75,58 @@ local function apply_rating(rating)
end
end
if rating < 0 then
dt.print("rejecting group(s)")
dt.print(_("rejecting group(s)"))
else
dt.print("applying rating " ..rating.. " to group(s)")
dt.print(string.format(_("applying rating %d to group(s)"), rating))
end
end
dt.register_event("shortcut",function(event, shortcut)
local function destroy()
dt.destroy_event("rg_reject", "shortcut")
dt.destroy_event("rg0", "shortcut")
dt.destroy_event("rg1", "shortcut")
dt.destroy_event("rg2", "shortcut")
dt.destroy_event("rg3", "shortcut")
dt.destroy_event("rg4", "shortcut")
dt.destroy_event("rg5", "shortcut")
end
dt.register_event("rg_reject", "shortcut",
function(event, shortcut)
apply_rating(-1)
end, "Reject group")
end, _("reject group"))
dt.register_event("shortcut",function(event, shortcut)
dt.register_event("rg0", "shortcut",
function(event, shortcut)
apply_rating(0)
end, "Rate group 0")
end, string.format(_("rate group %d"), 0)
)
dt.register_event("shortcut",function(event, shortcut)
dt.register_event("rg1", "shortcut",
function(event, shortcut)
apply_rating(1)
end, "Rate group 1")
end, string.format(_("rate group %d"), 1))
dt.register_event("shortcut",function(event, shortcut)
dt.register_event("rg2", "shortcut",
function(event, shortcut)
apply_rating(2)
end, "Rate group 2")
end, string.format(_("rate group %d"), 2))
dt.register_event("shortcut",function(event, shortcut)
dt.register_event("rg3", "shortcut",
function(event, shortcut)
apply_rating(3)
end, "Rate group 3")
end, string.format(_("rate group %d"), 3))
dt.register_event("shortcut",function(event, shortcut)
dt.register_event("rg4", "shortcut",
function(event, shortcut)
apply_rating(4)
end, "Rate group 4")
end, string.format(_("rate group %d"), 4))
dt.register_event("shortcut",function(event, shortcut)
dt.register_event("rg5", "shortcut",
function(event, shortcut)
apply_rating(5)
end, "Rate group 5")
end, string.format(_("rate group %d"), 5))
script_data.destroy = destroy
return script_data

View File

@@ -33,11 +33,38 @@ local du = require "lib/dtutils"
local debug = require "darktable.debug"
-- check API version
du.check_min_api_version("3.0.0", "rename-tags")
du.check_min_api_version("7.0.0", "rename-tags")
du.deprecated("contrib/rename-tags.lua","darktable release 4.0")
local gettext = darktable.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("rename tags"),
purpose = _("rename an existing tag"),
author = "Sebastian Witt (se.witt@gmx.net)",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags"
}
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, otherwise leave as nil
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
local rt = {}
rt.module_installed = false
rt.event_registered = false
-- GUI entries
local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" }
local new_tag = darktable.new_widget("entry") { tooltip = "Enter new tag" }
local old_tag = darktable.new_widget("entry") { tooltip = _("enter old tag") }
local new_tag = darktable.new_widget("entry") { tooltip = _("enter new tag") }
local function rename_reset()
old_tag.text = ''
@@ -48,11 +75,11 @@ end
local function rename_tags()
-- If entries are empty, return
if old_tag.text == '' then
darktable.print ("Old tag can't be empty")
darktable.print (_("old tag can't be empty"))
return
end
if new_tag.text == '' then
darktable.print ("New tag can't be empty")
darktable.print (_("new tag can't be empty"))
return
end
@@ -62,12 +89,12 @@ local function rename_tags()
local ot = darktable.tags.find (old_tag.text)
if not ot then
darktable.print ("Old tag does not exist")
darktable.print (_("old tag does not exist"))
return
end
-- Show job
local job = darktable.gui.create_job ("Renaming tag", true)
local job = darktable.gui.create_job (_("renaming tag"), true)
old_tag.editable = false
new_tag.editable = false
@@ -90,7 +117,7 @@ local function rename_tags()
darktable.tags.delete (ot)
job.valid = false
darktable.print ("Renamed tags for " .. count .. " images")
darktable.print (string.format(_("renamed tags for %d images"), count))
old_tag.editable = true
new_tag.editable = true
@@ -99,27 +126,63 @@ local function rename_tags()
rename_reset()
end
local function install_module()
if not rt.module_installed then
darktable.register_lib ("rename_tags", _("rename tag"), true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rt.rename_widget, nil, nil)
rt.module_installed = true
end
end
local function destroy()
darktable.gui.libs["rename_tags"].visible = false
end
local function restart()
darktable.gui.libs["rename_tags"].visible = true
end
-- GUI
local old_widget = darktable.new_widget ("box") {
orientation = "horizontal",
darktable.new_widget("label") { label = "Old tag" },
darktable.new_widget("label") { label = _("old tag") },
old_tag
}
local new_widget = darktable.new_widget ("box") {
orientation = "horizontal",
darktable.new_widget("label") { label = "New tag" },
darktable.new_widget("label") { label = _("new tag") },
new_tag
}
local rename_widget = darktable.new_widget ("box") {
rt.rename_widget = darktable.new_widget ("box") {
orientation = "vertical",
reset_callback = rename_reset,
old_widget,
new_widget,
darktable.new_widget("button") { label = "Go", clicked_callback = rename_tags }
darktable.new_widget("button") { label = _("go"), clicked_callback = rename_tags }
}
if darktable.gui.current_view().id == "lighttable" then
install_module()
else
if not rt.event_registered then
darktable.register_event(
"rename_tags", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
rt.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data
darktable.register_lib ("rename_tags", "rename tag", true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rename_widget, nil, nil)

236
contrib/rename_images.lua Normal file
View File

@@ -0,0 +1,236 @@
--[[
rename.lua - rename image file(s)
Copyright (C) 2020, 2021 Bill Ferguson <wpferguson@gmail.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
rename - rename an image file or files
This shortcut resets the GPS information to that contained within
the image file. If no GPS info is in the image file, the GPS data
is cleared.
USAGE
* require this script from your luarc file or start it from script_manager
* select an image or images
* enter a renaming pattern
* click the button to rename the files
BUGS, COMMENTS, SUGGESTIONS
* Send to Bill Ferguson, wpferguson@gmail.com
CHANGES
TODO
* Add pattern builder
* Add new name preview
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local ds = require "lib/dtutils.string"
du.check_min_api_version("7.0.0", "rename_images")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- namespace variable
local rename = {
presets = {},
widgets = {},
}
rename.module_installed = false
rename.event_registered = false
-- script_manager integration
local script_data = {}
script_data.metadata = {
name = _("rename images"),
purpose = _("rename an image file or files"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images"
}
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, otherwise leave as nil
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
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE_NAME = "rename_images"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
local USER = os.getenv("USERNAME")
local HOME = os.getenv(dt.configuration.running_os == "windows" and "HOMEPATH" or "HOME")
local PICTURES = HOME .. PS .. dt.configuration.running_os == "windows" and _("My Pictures") or _("Pictures")
local DESKTOP = HOME .. PS .. "Desktop"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function stop_job(job)
job.valid = false
end
local function install_module()
if not rename.module_installed then
dt.register_lib(
MODULE_NAME,
_("rename images"),
true,
true,
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",700}},
dt.new_widget("box"){
orientation = "vertical",
rename.widgets.pattern,
rename.widgets.button,
},
nil,
nil
)
rename.module_installed = true
end
end
local function destroy()
dt.gui.libs[MODULE_NAME].visible = false
end
local function restart()
dt.gui.libs[MODULE_NAME].visible = true
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function do_rename(images)
if #images > 0 then
local first_image = images[1]
local pattern = rename.widgets.pattern.text
dt.preferences.write(MODULE_NAME, "pattern", "string", pattern)
dt.print_log("pattern is " .. pattern)
if string.len(pattern) > 0 then
local datetime = os.date("*t")
local job = dt.gui.create_job(_("renaming images"), true, stop_job)
for i, image in ipairs(images) do
if job.valid then
job.percent = i / #images
ds.build_substitute_list(image, i, pattern, USER, PICTURES, HOME, DESKTOP)
local new_name = ds.substitute_list(pattern)
if new_name == -1 then
dt.print(_("unable to do variable substitution, exiting..."))
stop_job(job)
return
end
ds.clear_substitute_list()
local args = {}
local path = string.sub(df.get_path(new_name), 1, -2)
if string.len(path) == 0 then
path = image.path
end
local filename = df.get_filename(new_name)
local filmname = image.path
if path ~= image.path then
if not df.check_if_file_exists(df.sanitize_filename(path)) then
df.mkdir(df.sanitize_filename(path))
end
filmname = path
end
args[1] = dt.films.new(filmname)
args[2] = image
if filename ~= image.filename then
args[3] = filename
end
dt.database.move_image(table.unpack(args))
end
end
stop_job(job)
local collect_rules = dt.gui.libs.collect.filter()
dt.gui.libs.collect.filter(collect_rules)
dt.gui.views.lighttable.set_image_visible(first_image)
dt.print(string.format(_("renamed %d images"), #images))
else -- pattern length
dt.print_error("no pattern supplied, returning...")
dt.print(_("please enter the new name or pattern"))
end
else -- image count
dt.print_error("no images selected, returning...")
dt.print(_("please select some images and try again"))
end
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- W I D G E T S
-- - - - - - - - - - - - - - - - - - - - - - - -
rename.widgets.pattern = dt.new_widget("entry"){
tooltip = ds.get_substitution_tooltip(),
placeholder = _("enter pattern") .. "$(FILE_FOLDER)/$(FILE_NAME)",
text = "",
reset_callback = function(self)
self.text = ""
dt.preferences.write(MODULE_NAME, "pattern", "string", self.text)
end
}
local pattern_pref = dt.preferences.read(MODULE_NAME, "pattern", "string")
if pattern_pref then
rename.widgets.pattern.text = pattern_pref
end
rename.widgets.button = dt.new_widget("button"){
label = _("rename"),
clicked_callback = function(this)
do_rename(dt.gui.action_images)
end
}
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not rename.event_registered then
dt.register_event(
"rename_images", "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
rename.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
return script_data

View File

@@ -0,0 +1,124 @@
--[[
This file is part of darktable,
copyright (c) 2023 Dirk Dittmar
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 <http://www.gnu.org/licenses/>.
]]
--[[
Enable selection of non-existing images in the the currently worked on images, e.g. the ones selected by the collection module.
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local ds = require "lib/dtutils.string"
local log = require "lib/dtutils.log"
-- module name
local MODULE <const> = "select_non_existing"
du.check_min_api_version("9.1.0", MODULE)
-- figure out the path separator
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
local DEFAULT_LOG_LEVEL <const> = log.info
log.log_level(DEFAULT_LOG_LEVEL)
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
local function stop_job(job)
job.valid = false
end
local function get_file_list(dir)
log.log_level(DEFAULT_LOG_LEVEL)
local file_list = {}
local cmd = nil
log.msg(log.debug, "called get_file_list with " .. dir)
if dt.configuration.running_os == "windows" then
cmd = "forfiles /P " .. ds.sanitize(dir) .. " /M * /C \"cmd /c echo @file\""
else
cmd = "cd " .. ds.sanitize(dir) ..";ls -1"
end
log.msg(log.info, "cmd is " .. cmd)
local p = io.popen(cmd)
if p then
for line in p:lines() do
if line:len() > 4 then
if not string.match(line, "xmp$") and not string.match(line, "XMP$") then
line = line:gsub("\"", "")
file_list[line] = true
end
end
end
p:close()
end
return file_list
end
local function select_nonexisting_images(event, images)
log.log_level(DEFAULT_LOG_LEVEL)
local selection = {}
local known_files = {}
local job = dt.gui.create_job(_("select non existing images"), true, stop_job)
for key,image in ipairs(images) do
if(job.valid) then
if not known_files[image.path] then
known_files[image.path] = get_file_list(image.path)
end
if key % 10 == 0 then
job.percent = key/#images
end
local file_exists = known_files[image.path][image.filename]
log.msg(log.debug, image.path .. PS .. image.filename .." exists? => "..tostring(file_exists))
if (not file_exists) then
table.insert(selection, image)
end
else
break
end
end
stop_job(job)
return selection
end
local function destroy()
dt.gui.libs.select.destroy_selection(MODULE)
end
dt.gui.libs.select.register_selection(
MODULE,
_("select non existing"),
select_nonexisting_images,
_("select all non-existing images in the current images"))
local script_data = {}
script_data.metadata = {
name = _("select non existing"),
purpose = _("enable selection of non-existing images"),
author = "Dirk Dittmar",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing"
}
script_data.destroy = destroy
return script_data

View File

@@ -20,38 +20,51 @@ Enable selection of untagged images (darktable|* tags are ignored)
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
du.check_min_api_version("3.0.0", "select_untagged")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("select_untagged",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "select_untagged")
local function _(msgid)
return gettext.dgettext("select_untagged", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("select untagged"),
purpose = _("enable selection of untagged images"),
author = "Jannis_V",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged"
}
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, otherwise leave as nil
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
local function stop_job(job)
job.valid = false
end
local function select_untagged_images()
local function select_untagged_images(event, images)
job = dt.gui.create_job(_("select untagged images"), true, stop_job)
local selection = {}
for key,image in ipairs(dt.collection) do
for key,image in ipairs(images) do
if(job.valid) then
job.percent = (key-1)/#dt.collection
job.percent = (key - 1)/#images
local tags = dt.tags.get_tags(image)
local hasTags = false
for _,tag in ipairs(tags) do
if not string.match(tag.name,"darktable|") then
if not string.match(tag.name, "darktable|") then
hasTags = true
end
end
if hasTags == false then
table.insert(selection,image)
table.insert(selection, image)
end
else
break
@@ -59,7 +72,19 @@ local function select_untagged_images()
end
job.valid = false
dt.gui.selection(selection)
-- return table of images to set the selection to
return selection
end
dt.gui.libs.select.register_selection(_("select untagged"),select_untagged_images,_("select all images containing no tags or only tags added by darktable"))
local function destroy()
dt.gui.libs.select.destroy_selection("select_untagged")
end
dt.gui.libs.select.register_selection(
"select_untagged", _("select untagged"),
select_untagged_images,
_("select all images containing no tags or only tags added by darktable"))
script_data.destroy = destroy
return script_data

View File

@@ -0,0 +1,199 @@
--[[
selected_image_visible.lua - make the selected image visible in lighttable
Copyright (C) 2026 Bill Ferguson <wpferguson@gmail.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
selected_image_visible - make the selected image visible in lighttable
selected_image_visible adds a bshortcut to make the currently
selected image or the first image of a selection visible in lighttable
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* enable the script in script_manager
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson@gmail.com>
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("9.6.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 = _("selected image visible"), -- visible name of script
purpose = _("make the selected image visible in lighttable"), -- purpose of script
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/selected_image_visible/" -- URL to help/documentation
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE <const> = "selected_image_visible"
local DEFAULT_LOG_LEVEL <const> = log.info
local TMP_DIR <const> = dt.configuration.tmp_dir
-- path separator
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
-- command separator
local CS <const> = dt.configuration.running_os == "windows" and "&" or ";"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O G L E V E L
-- - - - - - - - - - - - - - - - - - - - - - - -
log.log_level(DEFAULT_LOG_LEVEL)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local selected_image_visible = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- G L O B A L V A R I A B L E S
-- - - - - - - - - - - - - - - - - - - - - - - -
selected_image_visible.log_level = DEFAULT_LOG_LEVEL
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - -
-- A L I A S E S
-- - - - - - - - - - - - - - - - - - - - - - - -
local namespace = selected_image_visible
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function set_image_visible()
if dt.gui.current_view().name == "lighttable" and #dt.gui.action_images > 0 then
dt.gui.views.lighttable.set_image_visible(dt.gui.action_images[1])
end
end
local function set_button_sensitive()
if dt.gui.current_view().name == "lighttable"
and #dt.gui.selection() > 0
and not dt.gui.views.lighttable.is_image_visible(dt.gui.selection()[1]) then
dt.gui.libs.image.set_sensitive(MODULE, true)
else
dt.gui.libs.image.set_sensitive(MODULE, false)
end
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N P R O G R A M
-- - - - - - - - - - - - - - - - - - - - - - - -
-- - - - - - - - - - - - - - - - - - - - - - - -
-- U S E R I N T E R F A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.gui.libs.image.register_action(
MODULE, _("selected image visible"),
function(event, images) set_image_visible() end,
_("make selected image visible in lighttable")
)
set_button_sensitive()
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function destroy()
-- put things to destroy (events, storages, etc) here
dt.destroy_event(MODULE, "selection-changed")
dt.destroy_event(MODULE, "mouse-over-image-changed")
dt.gui.libs.image.destroy_action(MODULE)
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
if not dt.query_event(MODULE, "shortcut") then
dt.register_event(MODULE, "shortcut",
function(event, shortcut)
set_image_visible()
end, _("make selected image visible in lighttable")
)
end
dt.register_event(MODULE, "selection-changed",
function(event)
set_button_sensitive()
end
)
dt.register_event(MODULE, "mouse-over-image-changed",
function(event, image)
set_button_sensitive()
end
)
return script_data

View File

@@ -27,18 +27,30 @@ USAGE
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
du.check_min_api_version("2.0.2", "slideshowMusic")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/locale/")
du.check_min_api_version("7.0.0", "slideshowMusic")
local function _(msgid)
return gettext.dgettext("slideshowMusic", msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("slideshow music"),
purpose = _("play music during a slideshow"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic"
}
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, otherwise leave as nil
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
local function playSlideshowMusic(_, old_view, new_view)
local filename, playMusic
@@ -46,7 +58,7 @@ local function playSlideshowMusic(_, old_view, new_view)
playMusic = dt.preferences.read("slideshowMusic","PlaySlideshowMusic","bool")
if not df.check_if_bin_exists("rhythmbox-client") then
dt.print_error(_("rhythmbox-client not found"))
dt.print_error("rhythmbox-client not found")
return
end
@@ -62,19 +74,30 @@ local function playSlideshowMusic(_, old_view, new_view)
if (old_view and old_view.id == "slideshow") then
stopCommand = "rhythmbox-client --pause"
--dt.print_error(stopCommand)
dt.control.execute( stopCommand)
dt.control.execute(stopCommand)
end
end
end
end
function destroy()
dt.destroy_event("slideshow_music", "view-changed")
dt.preferences.destroy("slideshowMusic", "SlideshowMusic")
dt.preferences.destroy("slideshowMusic", "PlaySlideshowMusic")
end
-- Preferences
dt.preferences.register("slideshowMusic", "SlideshowMusic", "file", _("Slideshow background music file"), "", "")
dt.preferences.register("slideshowMusic", "SlideshowMusic", "file", _("slideshow background music file"), "", "")
dt.preferences.register("slideshowMusic",
"PlaySlideshowMusic",
"bool",
_("Play slideshow background music"),
_("Plays music with rhythmbox if a slideshow starts"),
_("play slideshow background music"),
_("plays music with rhythmbox if a slideshow starts"),
true)
-- Register
dt.register_event("view-changed",playSlideshowMusic)
dt.register_event("slideshow_music", "view-changed",
playSlideshowMusic)
script_data.destroy = destroy
return script_data

View File

@@ -0,0 +1,313 @@
--[[
toggle_group_view.lua - toggle only group visible in lighttable
Copyright (C) 2024 Bill Ferguson <wpferguson.com>.
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 <http://www.gnu.org/licenses/>.
]]
--[[
toggle_group_view - toggle only group visible in lighttable
toggle_group_view provides a way to "separate" a group from
the lighttable display and limit the display to just the group.
Pressing the shortcut again returns to the full lighttable view.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* place the script in your lua scripts folder
* start the script with script manager
* assign a key sequence to the shortcut
BUGS, COMMENTS, SUGGESTIONS
Bill Ferguson <wpferguson.com>
CHANGES
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local log = require "lib/dtutils.log"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE <const> = "toggle_group_view"
local DEFAULT_LOG_LEVEL <const> = log.error
local TMP_DIR <const> = dt.configuration.tmp_dir
-- path separator
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
-- command separator
local CS <const> = dt.configuration.running_os == "windows" and "&" or ";"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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
dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/")
local function _(msgid)
return gettext(MODULE, 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 = "toggle_group_view", -- name of script
purpose = _("toggle only group visible in lighttable"), -- purpose of script
author = "Bill Ferguson <wpferguson.com>", -- your name and optionally e-mail address
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/toggle_group_view/" -- URL to help/documentation
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O G L E V E L
-- - - - - - - - - - - - - - - - - - - - - - - -
log.log_level(DEFAULT_LOG_LEVEL)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local toggle_group_view = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- G L O B A L V A R I A B L E S
-- - - - - - - - - - - - - - - - - - - - - - - -
local group_active = false
local filmroll = ""
local collection_rules = {}
local hover_active = true
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function is_group_member(image)
if image then
if #image:get_group_members() > 1 then
return true
else
return false
end
end
end
local function is_group_displayed()
local rules = dt.gui.libs.collect.filter()
if rules[1].item == "DT_COLLECTION_PROP_GROUP_ID" then
local rule = dt.gui.libs.collect.new_rule()
rule.mode = "DT_LIB_COLLECT_MODE_AND"
rule.data = dt.collection[1].path
rule.item = "DT_COLLECTION_PROP_FILMROLL"
table.insert(collection_rules, rule)
group_active = true
end
end
local function set_button_sensitive(image)
local selected_images = dt.gui.selection()
local sensitive
if group_active then
dt.gui.libs.image.set_sensitive(MODULE, true)
return
end
if #selected_images > 0 then
if is_group_member(selected_images[1]) then
sensitive = true
end
else
if image then
if is_group_member(image) then
sensitive = true
else
sensitive = false
end
else
sensitive = false
end
end
dt.gui.libs.image.set_sensitive(MODULE, sensitive)
end
local function add_collection_rule(rule_string)
local rule = dt.gui.libs.collect.new_rule()
rule.mode = "DT_LIB_COLLECT_MODE_AND"
rule.data = rule_string
rule.item = "DT_COLLECTION_PROP_FILENAME"
table.insert(collection_rules, rule)
end
local function show_group(image)
local collection_rule_string = ""
local first = true
collection_rules = {}
local group_id = image.group_leader
local rule = dt.gui.libs.collect.new_rule()
rule.mode = "DT_LIB_COLLECT_MODE_AND"
rule.data = tostring(group_id.id)
rule.item = "DT_COLLECTION_PROP_GROUP_ID"
table.insert(collection_rules, rule)
-- dt.gui.libs.collect.filter returns the previous rule set when you install another
collection_rules = dt.gui.libs.collect.filter(collection_rules)
end
local function reset(image)
dt.gui.libs.collect.filter(collection_rules)
if image then
dt.gui.views.lighttable.set_image_visible(image)
else
log.msg(log.screen, _("no image supplied"))
end
set_button_sensitive(image)
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N
-- - - - - - - - - - - - - - - - - - - - - - - -
-- check if we are starting with a group already displayed and
-- configure appropriately
if not dt.preferences.read("darktable", "plugins/lighttable/act_on", "bool") then
hover_active = false
end
is_group_displayed()
-- - - - - - - - - - - - - - - - - - - - - - - -
-- U S E R I N T E R F A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.gui.libs.image.register_action(
MODULE, "toggle group view",
function(event, images)
if group_active then
local image
if dt.gui.action_images[1] then
image = dt.gui.action_images[1]
reset(dt.gui.action_images[1])
else
image = dt.collection[1]
reset(dt.collection[1])
end
group_active = false
set_button_sensitive(image)
else
show_group(dt.gui.action_images[1])
group_active = true
end
end,
_("toggle group view for selected image")
)
set_button_sensitive(nil)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
local function destroy()
if hover_active then
dt.destroy_event(MODULE, "mouse-over-image-changed")
end
dt.destroy_event(MODULE, "selection_changed")
dt.gui.libs.image.destroy_action(MODULE, "toggle group view")
-- leave the shortcut so it doesn't need to be reassigned
-- the next time this module starts
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_event(
MODULE, "shortcut",
function(event, shortcut)
if #dt.gui.action_images > 0 then
if group_active then
reset(dt.gui.action_images[1])
group_active = false
else
show_group(dt.gui.action_images[1])
group_active = true
end
end
end,
_("toggle group view for selected image")
)
---[[
if hover_active then
dt.register_event(MODULE, "mouse-over-image-changed",
function(event, image)
set_button_sensitive(image)
end
)
end
--]]
dt.register_event(MODULE, "selection-changed",
function(event)
set_button_sensitive(dt.gui.action_images[1])
end
)
return script_data

View File

@@ -76,9 +76,30 @@ local darktable = require("darktable")
local dtutils = require("lib/dtutils")
local dtutils_file = require("lib/dtutils.file")
local dtutils_system = require("lib/dtutils.system")
local gettext = darktable.gettext.gettext
local LIB_ID = "transfer_hierarchy"
dtutils.check_min_api_version("5.0.0", LIB_ID)
dtutils.check_min_api_version("7.0.0", LIB_ID)
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("transfer hierarchy"),
purpose = _("allows the moving or copying of images from one directory tree to another, while preserving the existing hierarchy"),
author = "August Schwerdfeger (august@schwerdfeger.name)",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy"
}
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, otherwise leave as nil
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
local MKDIR_COMMAND = darktable.configuration.running_os == "windows" and "mkdir " or "mkdir -p "
local PATH_SEPARATOR = darktable.configuration.running_os == "windows" and "\\\\" or "/"
@@ -87,18 +108,16 @@ local PATH_SEGMENT_REGEX = "(" .. PATH_SEPARATOR .. "?)([^" .. PATH_SEPARATOR ..
unpack = unpack or table.unpack
gmatch = string.gfind or string.gmatch
darktable.gettext.bindtextdomain(LIB_ID, darktable.configuration.config_dir .. PATH_SEPARATOR .. "lua" .. PATH_SEPARATOR .. "locale" .. PATH_SEPARATOR)
local function _(msgid)
return darktable.gettext.dgettext(LIB_ID, msgid)
end
-- Header material: END
-- Helper functions: BEGIN
local th = {}
th.module_installed = false
th.event_registered = false
local function pathExists(path)
local success, err, errno = os.rename(path, path)
if not success then
@@ -122,13 +141,23 @@ local function createDirectory(path)
end
end
local function install_module()
if not th.module_installed then
darktable.register_lib(LIB_ID,
_("transfer hierarchy"), true, true, {
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
}, th.transfer_widget, nil, nil)
th.module_installed = true
end
end
-- Helper functions: END
-- Widgets and business logic: BEGIN
local sourceTextBox = darktable.new_widget("entry") {
tooltip = _("Lowest directory containing all selected images"),
tooltip = _("lowest directory containing all selected images"),
editable = false
}
sourceTextBox.reset_callback = function() sourceTextBox.text = "" end
@@ -241,21 +270,21 @@ local function doTransfer(transferFunc)
local sourceBase = sourceTextBox.text
local destBase = destinationTextBox.text
local destFilms = {}
for film, _ in pairs(films) do
for film, i in pairs(films) do
films[film] = destBase .. string.sub(film.path, #sourceBase+1)
if not pathExists(films[film]) then
if createDirectory(films[film]) == nil then
darktable.print(_("transfer hierarchy: ERROR: could not create directory: " .. films[film]))
darktable.print(string.format(_("transfer hierarchy: ERROR: could not create directory: %s"), films[film]))
return
end
end
if not pathIsDirectory(films[film]) then
darktable.print(_("transfer hierarchy: ERROR: not a directory: " .. films[film]))
darktable.print(string.format(_("transfer hierarchy: ERROR: not a directory: %s"), films[film]))
return
end
destFilms[film] = darktable.films.new(films[film])
if destFilms[film] == nil then
darktable.print(_("transfer hierarchy: ERROR: could not create film: " .. film.path))
darktable.print(string.format(_("transfer hierarchy: ERROR: could not create film: %s"), film.path))
end
end
@@ -264,7 +293,7 @@ local function doTransfer(transferFunc)
srcFilms[img] = img.film
end
local job = darktable.gui.create_job(string.format(_("transfer hierarchy") .. " (%d image" .. (#(darktable.gui.action_images) == 1 and "" or "s") .. ")", #(darktable.gui.action_images)), true, stopTransfer)
local job = darktable.gui.create_job(string.format(_("transfer hierarchy (%d image%s)"), #(darktable.gui.action_images), (#(darktable.gui.action_images) == 1 and "" or "s")), true, stopTransfer)
job.percent = 0.0
local pctIncrement = 1.0 / #(darktable.gui.action_images)
for _,img in ipairs(darktable.gui.action_images) do
@@ -287,12 +316,19 @@ local function doCopy()
doTransfer(darktable.database.copy_image)
end
local function destroy()
darktable.gui.libs[LIB_ID].visible = false
end
local function restart()
darktable.gui.libs[LIB_ID].visible = true
end
local transfer_widget = darktable.new_widget("box") {
th.transfer_widget = darktable.new_widget("box") {
orientation = "vertical",
darktable.new_widget("button") {
label = _("calculate"),
@@ -315,7 +351,7 @@ local transfer_widget = darktable.new_widget("box") {
},
darktable.new_widget("button") {
label = _("copy"),
tooltip = _("Copy all selected images"),
tooltip = _("copy all selected images"),
clicked_callback = doCopy
}
}
@@ -347,12 +383,26 @@ darktable.preferences.register(
-- Preferences: END
if darktable.gui.current_view().id == "lighttable" then
install_module()
else
if not th.event_registered then
darktable.register_event(
LIB_ID, "view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
th.event_registered = true
end
end
script_data.destroy = destroy
script_data.restart = restart
script_data.destroy_method = "hide"
script_data.show = restart
darktable.register_lib(LIB_ID,
"transfer hierarchy", true, true, {
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
}, transfer_widget, nil, nil)
return script_data

1066
contrib/ultrahdr.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -37,19 +37,33 @@ local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gettext = dt.gettext.gettext
du.check_min_api_version("5.0.0")
du.check_min_api_version("7.0.0", "video_ffmpeg")
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("video ffmpeg"),
purpose = _("timelapse video plugin based on ffmpeg"),
author = "Dominik Markiewicz",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contib/video_ffmpeg"
}
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, otherwise leave as nil
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
local MODULE_NAME = "video_ffmpeg"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
end
---- DECLARATIONS
@@ -96,7 +110,7 @@ local resolutions = {
}
}
local framerates = {"15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60"}
local framerates = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60", "120", "240", "300"}
local formats = {
["AVI"] = {
@@ -232,8 +246,8 @@ local function string_pref_write(name, widget_attribute)
end
local framerates_selector = dt.new_widget("combobox"){
label = _("framerate"),
tooltip = _("select framerate of output video"),
label = _("frame rate"),
tooltip = _("select frame rate of output video"),
value = combobox_pref_read("framerate", framerates),
changed_callback = combobox_pref_write("framerate"),
table.unpack(framerates)
@@ -277,7 +291,7 @@ local defaultVideoDir = ''
if dt.configuration.running_os == "windows" then
defaultVideoDir = os.getenv("USERPROFILE")..PS .."videos"
elseif dt.configuration.running_os == "macos" then
defaultVideoDir = os.getenv("home")..PS.."Videos"
defaultVideoDir = os.getenv("HOME")..PS.."Videos"
else
local handle = io.popen("xdg-user-dir VIDEOS")
defaultVideoDir = handle:read()
@@ -285,7 +299,7 @@ else
end
local output_directory_chooser = dt.new_widget("file_chooser_button"){
title = _("Select export path"),
title = _("select export path"),
is_directory = true,
tooltip =_("select the target directory for the timelapse. \nthe filename is created automatically."),
value = string_pref_read("export_path", defaultVideoDir),
@@ -366,7 +380,7 @@ local module_widget = dt.new_widget("box") {
---- EXPORT & REGISTRATION
local function show_status(enf_storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(_("export ")..tostring(number).." / "..tostring(total))
dt.print(string.format(_("export %d / %d", number), total))
end
local function init_export(storage, img_format, images, high_quality, extra_data)
@@ -402,7 +416,7 @@ local function export(extra_data)
local ffmpeg_path = df.check_if_bin_exists("ffmpeg")
if not ffmpeg_path then
dt.print_error("ffmpeg not found")
dt.print("ERROR - ffmpeg not found")
dt.print(_("ERROR - ffmpeg not found"))
return
end
local dir = extra_data["tmp_dir"]
@@ -434,7 +448,7 @@ local function finalize_export(storage, images_table, extra_data)
dt.print_error(filename, file.filename)
df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"])
end
dt.print("Start video building...")
dt.print(_("start video building..."))
local result, path = export(extra_data)
if result ~= 0 then
dt.print(_("ERROR: cannot build image, see console for more info"))
@@ -448,9 +462,15 @@ local function finalize_export(storage, images_table, extra_data)
df.rmdir(df.sanitize_filename(tmp_dir))
end
-- script_manager integration
local function destroy()
dt.destroy_storage("module_video_ffmpeg")
end
dt.register_storage(
"module_video_ffmpeg",
_(MODULE_NAME),
_("video ffmpeg"),
show_status,
finalize_export,
nil,
@@ -458,3 +478,8 @@ dt.register_storage(
module_widget
)
-- script_manager integration
script_data.destroy = destroy
return script_data

View File

@@ -0,0 +1,294 @@
--[[Export module to create a web gallery from selected images
copyright (c) 2025 Tino Mettler
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 this software. If not, see <http://www.gnu.org/licenses/>.
]]
--[[
TODO:
- Lua: remove images dir if already existent
- Lua: translations
]]
local dt = require "darktable"
local df = require "lib/dtutils.file"
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
local temp = dt.preferences.read('web_gallery', 'title', 'string')
if temp == nil then temp = 'Darktable gallery' end
local function _(msgid)
return dt.gettext.gettext(msgid)
end
local title_widget = dt.new_widget("entry")
{
text = temp
}
local temp = dt.preferences.read('web_gallery', 'destination_dir', 'string')
if temp == nil then temp = '' end
local dest_dir_widget = dt.new_widget("file_chooser_button")
{
title = _("select output folder"),
tooltip = _("select output folder"),
value = temp,
is_directory = true,
changed_callback = function(this) dt.preferences.write('web_gallery', 'destination_dir', 'string', this.value) end
}
local gallery_widget = dt.new_widget("box")
{
orientation=vertical,
dt.new_widget("label"){label = _("gallery title")},
title_widget,
dt.new_widget("label"){label = _("destination directory")},
dest_dir_widget
}
local function get_file_name(file)
return file:match("[^" .. PS .. "]*.$")
end
function escape_js_string(str)
local replacements = {
['\\'] = '\\\\',
['"'] = '\\"',
["'"] = "\\'",
['\n'] = '\\n',
['\r'] = '\\r',
['\t'] = '\\t',
['\b'] = '\\b',
['\f'] = '\\f',
['\v'] = '\\v'
}
return (str:gsub('[\\\"\n\r\t\b\f\v\']', replacements))
end
local function export_thumbnail(image, filename)
exporter = dt.new_format("jpeg")
exporter.quality = 90
exporter.max_height = 512
exporter.max_width = 512
exporter:write_image(image, filename, true)
end
local function write_image(image, dest_dir, filename)
df.file_move(filename, dest_dir.. PS .. "images" .. PS .. get_file_name(filename))
export_thumbnail(image, dest_dir .. PS .. "thumbnails" .. PS .. "thumb_" .. get_file_name(filename))
end
function exiftool_get_image_dimensions(filename)
local handle = io.popen("exiftool " .. filename)
local result = handle:read("*a")
handle:close()
for line in result:gmatch("[^\r\n]+") do
local w = line:match("^Image Width%s*:%s*(%d+)")
if w then
width = tonumber(w)
end
local h = line:match("^Image Height%s*:%s*(%d+)")
if h then
height = tonumber(h)
end
end
if width and height then
return width, height
else
return nil, nil
end
end
local function stop_job(job)
job.valid = false
end
local function fill_gallery_table(images_ordered, images_table, title, dest_dir, sizes, exiftool)
local gallery_data = { name = escape_js_string(title) }
local images = {}
local index = 1
local job = dt.gui.create_job(_("exporting thumbnail images"), true, stop_job)
for i, image in pairs(images_ordered) do
local filename = df.sanitize_filename(images_table[image])
dt.print(_("export thumbnail image ") .. index .. "/" .. #images_ordered)
write_image(image, dest_dir, filename)
if exiftool then
width, height = exiftool_get_image_dimensions(df.sanitize_filename(dest_dir .. PS .. "images" .. PS .. get_file_name(filename)))
else
width = sizes[index].width
height = sizes[index].height
end
local entry = { filename = "images" .. PS .. get_file_name(filename),
width = width, height = height }
images[index] = entry
job.percent = index / #images_ordered
index = index + 1
end
stop_job(job)
gallery_data.images = images
return gallery_data
end
local function generate_javascript_gallery_object(gallery)
local js = 'const gallery_data = {\n'
js = js .. ' name: "' .. gallery.name .. '",\n'
js = js .. ' images: [\n'
for i, img in ipairs(gallery.images) do
js = js .. string.format(' { filename: "%s",\n height: %d,\n width: %d }', img.filename, img.height, img.width)
if i < #gallery.images then
js = js .. ',\n'
else
js = js .. '\n'
end
end
js = js .. ' ]\n};\n'
return(js)
end
local function write_javascript_file(gallery_table, dest_dir)
dt.print(_("write JavaScript file"))
javascript_object = generate_javascript_gallery_object(gallery_table)
local fileOut, errr = io.open(dest_dir .. PS .. "js" .. PS .. "images.js", 'w+')
if fileOut then
fileOut:write(javascript_object)
else
log.msg(log.error, errr)
end
fileOut:close()
end
local function copy_static_files(dest_dir)
gfsrc = dt.configuration.config_dir .. PS .. "lua" .. PS .. "data" .. PS .. "website_gallery"
local gfiles = {
"index.html",
"css" .. PS .. "gallery.css",
"css" .. PS .. "modal.css",
"css" .. PS .. "style.css",
"js" .. PS .. "gallery.js",
"js" .. PS .. "modal.js",
"js" .. PS .. "fullscreen.js"
}
dt.print(_("copy static gallery files"))
for _, file in ipairs(gfiles) do
df.file_copy(gfsrc .. PS .. file, dest_dir .. PS .. file)
end
end
local function build_gallery(storage, images_table, extra_data)
local dest_dir = dest_dir_widget.value
df.mkdir(df.sanitize_filename(dest_dir))
df.mkdir(df.sanitize_filename(dest_dir .. PS .. "images"))
df.mkdir(df.sanitize_filename(dest_dir .. PS .. "thumbnails"))
df.mkdir(df.sanitize_filename(dest_dir .. PS .. "css"))
df.mkdir(df.sanitize_filename(dest_dir .. PS .. "js"))
local images_ordered = extra_data["images"] -- process images in the correct order
local sizes = extra_data["sizes"]
local title = _("Darktable export")
if title_widget.text ~= "" then
title = title_widget.text
end
local exiftool = df.check_if_bin_exists("exiftool");
gallerydata = fill_gallery_table(images_ordered, images_table, title, dest_dir, sizes, exiftool)
write_javascript_file(gallerydata, dest_dir)
copy_static_files(dest_dir)
end
local script_data = {}
script_data.metadata = {
name = _("website gallery (new)"),
purpose = _("create a web gallery from exported images"),
author = "Tino Mettler <tino+darktable@tikei.de>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/TODO"
}
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
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
local function destroy()
dt.preferences.write('web_gallery', 'title', 'string', title_widget.text)
dt.destroy_storage("module_webgallery")
end
script_data.destroy = destroy
local function show_status(storage, image, format, filename,
number, total, high_quality, extra_data)
dt.print(string.format(_("export image ").."%i/%i", number, total))
aspect = image.aspect_ratio
-- calculate the size of the exported image and store it in extra_data
-- to make it available in the finalize function
if image.final_height == 0 then
if aspect < 1 then
dimensions = { width = image.height, height = image.width }
else
dimensions = { width = image.width, height = image.height }
end
else
dimensions = { width = image.final_width, height = image.final_height }
end
if format.max_height > 0 and dimensions.height > format.max_height then
scale = format.max_height / dimensions.height
dimensions.height = math.floor(dimensions.height * scale + 0.5)
dimensions.width = math.floor(dimensions.width * scale + 0.5)
end
if format.max_width > 0 and dimensions.width > format.max_width then
scale = format.max_width / dimensions.width
dimensions.height = math.floor(dimensions.height * scale + 0.5)
dimensions.width = math.floor(dimensions.width * scale + 0.5)
end
extra_data["sizes"][number] = dimensions
end
local function initialize(storage, img_format, images, high_quality, extra_data)
dt.preferences.write('web_gallery', 'title', 'string', title_widget.text)
extra_data["images"] = images -- needed, to preserve images order
extra_data["sizes"] = {};
end
local supported_formats = { "jpg", "tif", "png", "webp" }
local formats_lut = {}
for key,format in pairs(supported_formats) do
formats_lut[format] = true
end
function check_supported(storage, format)
extension = format.extension
if formats_lut[extension] == true then
return true
else
return false
end
end
dt.register_storage("module_webgallery", "website gallery (new)", show_status, build_gallery, check_supported, initialize, gallery_widget)
return script_data

BIN
data/icons/blank20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
data/icons/path20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,56 @@
/*
copyright (c) 2025 Tino Mettler
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 this software. If not, see <http://www.gnu.org/licenses/>.
*/
body {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
.heading h1 {
text-align: center;
display: grid;
}
.gallery {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.gallery img {
cursor: pointer;
object-fit: contain;
}
.thumb {
object-fit: contain;
flex-shrink: 0;
min-width: 100%;
min-height: 100%;
max-width: 100%;
max-height: 100%;
}
.thumb-box {
display: flex;
align-items: center;
object-fit: contain;
justify-content: center;
}

View File

@@ -0,0 +1,99 @@
/*
copyright (c) 2025 Tino Mettler
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 this software. If not, see <http://www.gnu.org/licenses/>.
*/
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.viewer {
position: relative;
width: 100vw;
height: 100vh;
touch-action: none;
overflow: hidden;
display: none;
}
.navgrid {
display: grid;
}
.nav {
height: var(--navigation-bar-height);
justify-content: center;
align-items: center;
display: flex;
z-index: 1001;
}
.slider {
height: calc(100vh - var(--navigation-bar-height));
position: relative;
width: 100vw;
touch-action: none;
overflow: hidden;
}
.slide-container {
position: absolute;
top: 0;
left: 0;
width: 300%;
height: 100%;
display: flex;
transform: translateX(-33.333%);
transition: transform 0.3s ease-out;
will-change: transform;
}
.slide {
width: 33.333%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: #000;
will-change: contents;
overflow: hidden;
position: relative;
touch-action: none;
object-fit: contain;
}
.slide img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
display: block;
transform-origin: 0 0;
cursor: zoom-in;
object-fit: contain;
}
.slide.zoomed img {
cursor: zoom-out;
max-width: none;
max-height: none;
}
.slide img.zooming {
transition: transform 0.2s ease-out;
}

View File

@@ -0,0 +1,66 @@
body {
font-family: system-ui, Arial, Helvetica, sans-serif;
background-color: rgb(238, 240, 242);
}
.heading h1 {
background-color: #cdd;
color: black;
}
.heading h1:after {
content: " ";
border-bottom: 1px solid #888888;
height: 1px;
}
.gallery {
gap: 3vw;
margin: 3vw auto;
padding: 1.5vw;
}
.gallery img {
background: none;
border: 1px outset #737780;
}
.gallery img:hover {
transform: scale(1.08);
}
.thumb-box {
background: none;
box-shadow: 0 1vw 4vw 1vw rgba(0,0,0,0.6);
}
.viewer {
--navigation-bar-height: 5vh;
}
.nav {
background-color: rgba(60,60,60, 0.4);
color: #ccc;
}
.navgrid {
grid-template-columns: 4em 1fr 1.5em 1.5em 1fr 1.5em 1.5em;
font-size: 3.5vh;
grid-column-gap: 1vw;
background-color: black;
}
.button {
cursor: pointer;
}
.button:hover {
cursor: pointer;
color: white;
text-shadow: 0px 0px 10px #ccc;
}
.counter {
font-size: 2.5vh;
}

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
<link rel="stylesheet" href="css/modal.css">
<link rel="stylesheet" href="css/gallery.css">
<link rel="stylesheet" href="css/style.css">
<title id="page-title">Image Gallery</title>
</head>
<body>
<div id="heading1" class="heading">
<h1 id="gallery-title">Image Gallery</h1>
</div>
<div id="gallery" class="gallery"></div>
<div class="viewer" id="viewer">
<div class="navgrid">
<div id="counter" class="nav"></div>
<div class="spacer"></div>
<div class="button nav nav-arrow" id="prevArrow">&#9664;</div>
<div class="button nav nav-arrow" id="nextArrow">&#9654;</div>
<div class="spacer"></div>
<div id="fullscreen" class="button nav">&#x26F6;</div>
<div id="close" class="nav button">&times;</div>
</div>
<div class="slider" id="slider1">
<div class="slide-container">
<div class="slide" id="prev"></div>
<div class="slide" id="current"></div>
<div class="slide" id="next"></div>
</div>
</div>
</div>
<script type='text/javascript' >
var currentIndex = 1;
</script>
<script src="js/images.js"></script>
<script src="js/fullscreen.js"></script>
<script src="js/modal.js"></script>
<script src="js/gallery.js"></script>
</body>
</html>

View File

@@ -0,0 +1,40 @@
var isFullscreen = false;
var toggleFullscreen = function (ele) {
return isFullscreen ? exitFullscreen(ele) : requestFullscreen(ele);
};
var requestFullscreen = function (ele) {
if(isFullscreen == true) return 0 ;
isFullscreen = true;
if (ele.requestFullscreen) {
ele.requestFullscreen();
} else if (ele.webkitRequestFullscreen) {
ele.webkitRequestFullscreen();
} else if (ele.mozRequestFullScreen) {
ele.mozRequestFullScreen();
} else if (ele.msRequestFullscreen) {
ele.msRequestFullscreen();
} else {
console.log('Fullscreen API is not supported.');
}
};
var exitFullscreen = function () {
if(isFullscreen == false) return 0;
isFullscreen = false;
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else {
console.log('Fullscreen API is not supported.');
}
};

View File

@@ -0,0 +1,112 @@
/*
copyright (c) 2025 Tino Mettler
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 this software. If not, see <http://www.gnu.org/licenses/>.
*/
var scrollPosX = 0;
var scrollPosY = 0;
document.addEventListener('DOMContentLoaded', function () {
var imageCount = 0;
const gallery = document.getElementById('gallery');
const viewer = document.getElementById('viewer');
function showModal(e) {
const thumbbox = e.target.parentElement;
const index = [...gallery.children].indexOf(thumbbox);
currentIndex = index;
scrollPosX = document.documentElement.scrollLeft;
scrollPosY = document.documentElement.scrollTop;
gallery.style.display = 'none';
document.getElementById('heading1').style.display = 'none';
viewer.style.display = 'grid';
loadSlides();
updateCounter(currentIndex);
updateNavigationState();
}
function closeModal() {
exitFullscreen(document.documentElement);
viewer.style.display = 'none';
document.getElementById('heading1').style.display = 'grid';
gallery.style.display = 'flex';
document.documentElement.scrollTo({
left: scrollPosX,
top: scrollPosY,
behavior: "instant",
});
};
function createThumbnailElement(imageObj) {
const frame = document.createElement('div');
frame.className = 'thumb-box';
const framesize = 18;
const width = parseInt(imageObj.width);
const height = parseInt(imageObj.height);
const sum = width + height;
const scalefactor = sum / (framesize * 2.0);
frame.style.width = (width / scalefactor) + 'vw';
frame.style.height = (height / scalefactor) + 'vw';
const img = document.createElement('img');
img.className = 'thumb';
img.src = imageObj.filename.replace(/images\/(.*)$/i, 'thumbnails/thumb_$1');
img.alt = imageObj.filename;
img.addEventListener('click', function (e) { e.stopPropagation(); showModal(e); });
frame.appendChild(img);
gallery.appendChild(frame);
}
const images = gallery_data.images;
const title = document.getElementById('gallery-title');
const pageTitle = document.getElementById('page-title');
if (gallery_data.name) {
title.textContent = gallery_data.name;
pageTitle.textContent = gallery_data.name;
}
document.getElementById('close').onclick = function (e) {
e.stopPropagation();
closeModal();
};
// Keyboard navigation using left/right arrow keys
document.onkeyup = function (e) {
e.stopPropagation();
switch(e.key) {
case "Escape":
closeModal();
break;
}
};
document.getElementById('fullscreen').onclick = function (e) {
e.stopPropagation();
toggleFullscreen(document.documentElement);
};
// Populate thumbnail gallery
images.forEach(function (imageObj) {
createThumbnailElement(imageObj);
});
});

View File

@@ -0,0 +1,355 @@
/*
copyright (c) 2025 Tino Mettler
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 this software. If not, see <http://www.gnu.org/licenses/>.
*/
const container = document.querySelector('.slide-container');
const slides = {
prev: document.getElementById('prev'),
current: document.getElementById('current'),
next: document.getElementById('next')
};
const prevArrow = document.getElementById('prevArrow');
const nextArrow = document.getElementById('nextArrow');
let startX = 0;
let startY = 0;
let isDragging = false; // For swipe navigation
let isZoomed = false;
let isPanning = false; // Panning in zoomed state
let hasPanned = false;
let translateX = 0;
let translateY = 0;
let currentScale = 1;
let img;
let baseWidth, baseHeight; // Image dimensions before zoom
let scaledWidth, scaledHeight;
const images = gallery_data.images;
function updateCounter(index) {
const counter = document.getElementById('counter');
counter.textContent = (index + 1) + ' / ' + images.length;
}
function updateBoundaries() {
containerRect = container.getBoundingClientRect();
// Calculate the actual scaled dimensions based on the base (pre-zoom) size
scaledWidth = baseWidth * currentScale;
scaledHeight = baseHeight * currentScale;
}
function limitPanning(proposedX, proposedY) {
const maxX = 0;
const minX = baseWidth - scaledWidth;
const maxY = 0;
const minY = baseHeight - scaledHeight;
let constrainedX = proposedX;
let constrainedY = proposedY;
// Apply constraints
if (scaledWidth > baseWidth) {
// Image wider than original - constrain panning
constrainedX = Math.max(minX, Math.min(maxX, proposedX));
} else {
// Image narrower than original - center it
constrainedX = (baseWidth - scaledWidth) / 2;
}
if (scaledHeight > baseHeight) {
// Image taller than original - constrain panning
constrainedY = Math.max(minY, Math.min(maxY, proposedY));
} else {
// Image shorter than original - center it
constrainedY = (baseHeight - scaledHeight) / 2;
}
return {
x: constrainedX,
y: constrainedY
};
}
function handleZoom(e) {
// If we were actually panning, don't zoom
if (hasPanned) {
isPanning = false;
hasPanned = false;
return;
}
// If we're zoomed and haven't panned, zoom out
if (isZoomed) {
img.classList.add('zooming');
translateX = 0;
translateY = 0;
currentScale = 1;
img.style.transform = 'none';
container.classList.remove('zoomed');
isZoomed = false;
updateNavigationState();
setTimeout(() => {
img.classList.remove('zooming');
}, 200);
return;
}
// Zoom in at clicked/tapped point
if (!isZoomed) {
const rect = img.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Store the base (pre-zoom) dimensions and offset
baseWidth = rect.width;
baseHeight = rect.height;
// Calculate the scale for 1:1 pixel zoom
currentScale = img.naturalWidth / rect.width;
// Calculate the translation needed to keep clicked point under cursor
// After scaling, the point at (x, y) will be at (x * currentScale, y * currentScale)
// We want it to remain at (e.clientX - rect.left, e.clientY - rect.top)
translateX = e.clientX - rect.left - (x * currentScale);
translateY = e.clientY - rect.top - (y * currentScale);
updateBoundaries();
const limited = limitPanning(translateX, translateY);
translateX = limited.x;
translateY = limited.y;
img.classList.add('zooming');
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
container.classList.add('zoomed');
setTimeout(() => {
img.classList.remove('zooming');
}, 200);
}
isZoomed = true;
updateNavigationState();
}
function createImageElement(imageData) {
if (!imageData) return null;
const img = new Image();
img.src = imageData.filename;
img.width = imageData.width;
img.height = imageData.height;
img.addEventListener('dragstart', (e) => e.preventDefault());
return img;
}
function loadSlides() {
slides.prev.innerHTML = '';
slides.current.innerHTML = '';
slides.next.innerHTML = '';
if (currentIndex > 0) {
const prevImg = createImageElement(images[currentIndex - 1]);
if (prevImg) slides.prev.appendChild(prevImg);
}
const currentImg = createImageElement(images[currentIndex]);
if (currentImg) {
img = currentImg;
slides.current.appendChild(currentImg);
}
if (currentIndex < images.length - 1) {
const nextImg = createImageElement(images[currentIndex + 1]);
if (nextImg) slides.next.appendChild(nextImg);
}
updateCounter(currentIndex)
updateNavigationState();
}
function updateNavigationState() {
prevArrow.style.visibility = (currentIndex === 0 || isZoomed) ? 'hidden' : 'visible';
nextArrow.style.visibility = (currentIndex === images.length - 1 || isZoomed) ? 'hidden' : 'visible';
}
async function showPreviousImage() {
if (currentIndex > 0 && !isZoomed) {
currentIndex--;
container.style.transition = 'transform 0.3s ease-out';
container.style.transform = 'translateX(0%)';
await waitForTransition();
container.style.transition = 'none';
container.style.transform = 'translateX(-33.333%)';
loadSlides();
}
}
async function showNextImage() {
if (currentIndex < images.length - 1 && !isZoomed) {
currentIndex++;
container.style.transition = 'transform 0.3s ease-out';
container.style.transform = 'translateX(-66.666%)';
await waitForTransition();
container.style.transition = 'none';
container.style.transform = 'translateX(-33.333%)';
loadSlides();
}
}
function handleTouchStart(e) {
if (isZoomed) return;
startX = e.touches[0].clientX;
isDragging = true;
container.style.transition = 'none';
}
function handleTouchMove(e) {
if (!isDragging || isZoomed) return;
const currentX = e.touches[0].clientX;
const diff = currentX - startX;
const baseOffset = -33.333;
const percentMoved = (diff / window.innerWidth) * 33.333;
container.style.transform = `translateX(${baseOffset + percentMoved}%)`;
}
async function handleTouchEnd(e) {
if (!isDragging)
if(isZoomed) {
handleZoom(e);
return;
}
isDragging = false;
const endX = e.changedTouches[0].clientX;
const diff = endX - startX;
const threshold = window.innerWidth * 0.2;
container.style.transition = 'transform 0.3s ease-out';
if (diff > threshold && currentIndex > 0) {
await showPreviousImage();
} else if (diff < -threshold && currentIndex < images.length - 1) {
await showNextImage();
} else {
container.style.transform = 'translateX(-33.333%)';
}
}
function handleKeyDown(e) {
if (isZoomed) return;
if (e.code === 'Space' || e.code === 'ArrowRight') {
e.preventDefault();
showNextImage();
} else if (e.code === 'Backspace' || e.code === 'ArrowLeft') {
e.preventDefault();
showPreviousImage();
}
}
function waitForTransition() {
return new Promise(resolve => {
container.addEventListener('transitionend', resolve, { once: true });
});
}
// Initialize
loadSlides();
// Navigation event listeners
container.addEventListener('touchstart', handleTouchStart);
container.addEventListener('touchmove', handleTouchMove);
container.addEventListener('touchend', handleTouchEnd);
container.addEventListener('click', handleZoom);
document.addEventListener('keydown', handleKeyDown);
prevArrow.addEventListener('click', showPreviousImage);
nextArrow.addEventListener('click', showNextImage);
// Mouse panning event listeners
container.addEventListener('mousedown', function(e) {
if (isZoomed && e.target.tagName === 'IMG') {
isPanning = true;
hasPanned = false;
updateBoundaries();
startX = e.clientX - translateX;
startY = e.clientY - translateY;
e.preventDefault();
container.style.cursor = 'grabbing';
}
});
window.addEventListener('mousemove', function(e) {
if (isPanning && isZoomed) {
const proposedX = e.clientX - startX;
const proposedY = e.clientY - startY;
const limited = limitPanning(proposedX, proposedY);
translateX = limited.x;
translateY = limited.y;
const img = slides.current.querySelector('img');
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
hasPanned = true;
}
});
window.addEventListener('mouseup', function() {
if (isPanning) {
isPanning = false;
container.style.cursor = isZoomed ? 'zoom-out' : 'zoom-in';
}
});
// Touch panning event listeners
container.addEventListener('touchstart', function(e) {
if (isZoomed) {
isPanning = true;
hasPanned = false;
updateBoundaries();
const touch = e.touches[0];
startX = touch.clientX - translateX;
startY = touch.clientY - translateY;
e.preventDefault();
}
});
container.addEventListener('touchmove', function(e) {
if (isPanning && isZoomed) {
const touch = e.touches[0];
const proposedX = touch.clientX - startX;
const proposedY = touch.clientY - startY;
const limited = limitPanning(proposedX, proposedY);
translateX = limited.x;
translateY = limited.y;
const img = slides.current.querySelector('img');
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
hasPanned = true;
e.preventDefault();
}
});
container.addEventListener('touchend', function(e) {
if (isPanning) {
isPanning = false;
}
});

323
examples/ai_denoise.lua Normal file
View File

@@ -0,0 +1,323 @@
--[[
This file is part of darktable,
copyright (c) 2026 darktable developers.
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 <http://www.gnu.org/licenses/>.
]]
--[[
ai_denoise - example script demonstrating the darktable.ai Lua
API by denoising the selected images with tile-based inference.
Adds an "AI denoise" panel to the lighttable right column with a
"denoise selected" button. Each selected image is loaded through
the develop pipeline (so denoise sees the output of all enabled
IOPs, not the original raw), run tile-by-tile through the active
denoise model, and saved as a 16-bit TIFF grouped with the source.
Demonstrates:
* model lookup via dt.ai.model_for_task
* tensor crop/paste for tile-based inference
* linear <-> sRGB conversion (NR models train on gamma input)
* edge-replicated padding to suppress border artifacts
* background job with progress bar and cancellation
LIMITATIONS
* Image must be at least TILE_SIZE x TILE_SIZE (768 x 768 for
denoise-nind / denoise-nafnet); smaller inputs return an error.
* If a save fails mid-batch the Lua error propagates and aborts
the whole job. Wrap denoise_one in pcall if you need
batch-robust behaviour.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* a denoise model enabled in preferences -> AI (denoise-nind,
denoise-nafnet, ...)
USAGE
* require this script from your luarc
* select one or more images in lighttable
* click the "denoise selected" button in the AI denoise panel
BUGS, COMMENTS, SUGGESTIONS
* file an issue on https://github.com/darktable-org/darktable
CHANGES
]]
local dt = require "darktable"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE_NAME = "ai_denoise"
-- darktable's NR models use static-shape ONNX: each model has a fixed
-- input dimension baked in at training time. The Lua API doesn't
-- expose this, so keep TILE_SIZE in sync with the model's
-- `attributes.input_sizes` in its config.json.
local TILE_SIZE = 768 -- denoise-nind, denoise-nafnet
-- overlap between adjacent tiles. larger values produce smoother
-- seams at the cost of more compute per pixel
local OVERLAP = 64
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
-- Surround the input with `pad` pixels on every side, replicating the
-- outermost rows/columns ("edge clamp" padding). Boundary tiles then
-- see plausible context instead of a literal image edge, so the
-- network's internal zero-padding no longer leaks into the visible
-- output as colour artifacts along the image rim.
local function pad_replicate(input, pad)
local C = input:shape()[2]
local H = input:shape()[3]
local W = input:shape()[4]
local padded = dt.ai.create_tensor({1, C, H + 2 * pad, W + 2 * pad})
-- center: original image
padded:paste(input, pad, pad)
-- left and right strips: replicate first / last column of input
local left_col = input:crop(0, 0, H, 1)
local right_col = input:crop(0, W - 1, H, 1)
for i = 0, pad - 1 do
padded:paste(left_col, pad, i)
padded:paste(right_col, pad, pad + W + i)
end
-- top and bottom strips: replicate first / last row of the already
-- horizontally-padded buffer so the four corners get filled correctly
local top_row = padded:crop(pad, 0, 1, W + 2 * pad)
local bottom_row = padded:crop(pad + H - 1, 0, 1, W + 2 * pad)
for i = 0, pad - 1 do
padded:paste(top_row, i, 0)
padded:paste(bottom_row, pad + H + i, 0)
end
return padded
end
-- Tile a [1,C,H,W] tensor through the model with overlap-blended seams.
-- Each tile is tile_size x tile_size, successive tiles step by
-- (tile_size - 2*overlap), and only the central (non-overlap) region
-- of each tile is pasted into the output. Image-boundary edges keep
-- their full extent; only image-interior seams drop the overlap margin.
--
-- The trailing tile in each axis is pinned to the image boundary so
-- the residual right/bottom strip is never smaller than 2*overlap --
-- otherwise it would have to be skipped (the model has a fixed input
-- size) and those pixels would never be written.
local function run_tiled(ctx, input, tile_size, overlap,
job, base_pct, span_pct)
local C = input:shape()[2]
local H = input:shape()[3]
local W = input:shape()[4]
if H < tile_size or W < tile_size then
return nil, string.format("image too small (%dx%d) for tile %d",
W, H, tile_size)
end
local step = tile_size - 2 * overlap
-- enumerate tile origins along one axis, ending exactly at the boundary
local function origins(extent)
local out, p = {}, 0
while p + tile_size < extent do
out[#out + 1] = p
p = p + step
end
out[#out + 1] = math.max(0, extent - tile_size)
return out
end
local ys, xs = origins(H), origins(W)
local total = #xs * #ys
local output = dt.ai.create_tensor({1, C, H, W})
local done = 0
for _, ty in ipairs(ys) do
for _, tx in ipairs(xs) do
if job and not job.valid then return nil, "cancelled" end
local tile_in = input:crop(ty, tx, tile_size, tile_size)
local tile_out = ctx:run(tile_in)
-- central region to keep: drop overlap on every inner edge;
-- full extent at image-boundary edges
local at_top = (ty == 0)
local at_left = (tx == 0)
local at_bottom = (ty + tile_size >= H)
local at_right = (tx + tile_size >= W)
local cy = at_top and 0 or overlap
local cx = at_left and 0 or overlap
local ch = tile_size - cy - (at_bottom and 0 or overlap)
local cw = tile_size - cx - (at_right and 0 or overlap)
output:paste(tile_out:crop(cy, cx, ch, cw), ty + cy, tx + cx)
done = done + 1
if job then
job.percent = base_pct + span_pct * (done / total)
end
end
end
return output
end
-- Denoise one image: load through the develop pipeline, run tiled
-- inference, save as 16-bit TIFF, group with the source.
local function denoise_one(ctx, img, tile_size, job, base_pct, span_pct)
-- darktable returns the develop-pipeline output as scene-linear RGB;
-- the NR models were trained on sRGB-gamma input, so we re-encode
-- before inference and decode back to linear before saving
local input = dt.ai.load_image(img)
if not input then return false, "load_image failed" end
input:linear_to_srgb()
-- pad so the image's outer rim is no longer at a tile boundary;
-- the padded strip absorbs the network's edge artifacts and gets
-- cropped away after inference
local H = input:shape()[3]
local W = input:shape()[4]
local padded_in = pad_replicate(input, OVERLAP)
local padded_out, err = run_tiled(ctx, padded_in, tile_size, OVERLAP,
job, base_pct, span_pct)
if not padded_out then return false, err end
local output = padded_out:crop(OVERLAP, OVERLAP, H, W)
output:srgb_to_linear()
local base = img.filename:match("(.+)%..+$") or img.filename
local out_path = img.path .. "/" .. base .. "_denoised.tif"
output:save_tiff(out_path, 16, img)
-- import the result and group with the source so they live together
-- in the lighttable
local imported = dt.database.import(out_path)
if imported then imported:group_with(img) end
return true
end
-- Button handler: looks up the active denoise model, fans out the
-- selected images, and reports progress via a cancellable job.
local function process_denoise()
local model_id = dt.ai.model_for_task("denoise")
if not model_id then
dt.print("no denoise model enabled -- pick one in preferences -> AI")
return
end
local images = dt.gui.selection()
if #images == 0 then
dt.print("select at least one image")
return
end
-- progress + cancel job. the cancel callback flips job.valid; the
-- main loop polls it between tiles to stop cleanly
local job = dt.gui.create_job(
string.format("AI denoise (%d image%s)",
#images, #images == 1 and "" or "s"),
true,
function(j) j.valid = false end)
dt.print_log(string.format(
"[ai_denoise] starting: %d image%s with model %s",
#images, #images == 1 and "" or "s", model_id))
local t_start = os.time()
local ctx = dt.ai.load_model(model_id)
if not ctx then
dt.print("failed to load model: " .. model_id)
job.valid = false
return
end
local span = 1.0 / #images
local done = 0
for i, img in ipairs(images) do
if not job.valid then break end
dt.print_log(string.format("[ai_denoise] [%d/%d] %s",
i, #images, img.filename))
local ok, err = denoise_one(ctx, img, TILE_SIZE, job,
(i - 1) * span, span)
if not ok then
dt.print(string.format("[%d/%d] %s: %s",
i, #images, img.filename, tostring(err)))
dt.print_log(string.format("[ai_denoise] [%d/%d] %s FAILED: %s",
i, #images, img.filename, tostring(err)))
else
done = done + 1
end
end
ctx:close()
job.valid = false
local elapsed = os.time() - t_start
dt.print(string.format("denoise complete: %d image%s",
#images, #images == 1 and "" or "s"))
dt.print_log(string.format(
"[ai_denoise] finished: %d/%d in %ds",
done, #images, elapsed))
end
-- script_manager integration: called when the user disables the script.
-- darktable can't remove a registered lib from the UI at runtime, so we
-- only stop any in-flight work; the panel itself remains until restart.
local function destroy()
-- nothing persistent to clean up: each click runs its own job and
-- closes its own model context
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_lib(
MODULE_NAME, -- plugin id
"AI denoise", -- displayed name
true, -- expandable
false, -- no reset button
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
dt.new_widget("box") {
orientation = "vertical",
dt.new_widget("button") {
label = "denoise selected",
clicked_callback = process_denoise,
}
},
nil, nil
)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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.metadata = {
name = "AI denoise",
purpose = "tile-based AI denoise using the darktable.ai Lua API",
author = "Andrii Ryzhkov",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/ai_denoise"
}
script_data.destroy = destroy
return script_data

481
examples/ai_raw_denoise.lua Normal file
View File

@@ -0,0 +1,481 @@
--[[
This file is part of darktable,
copyright (c) 2026 darktable developers.
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 <http://www.gnu.org/licenses/>.
]]
--[[
ai_raw_denoise - example script demonstrating the darktable.ai Lua
API by denoising the selected raw images with tile-based inference
on the raw CFA.
Adds an "AI raw denoise" panel to the lighttable right column with
a "denoise selected" button. Each selected image's raw CFA is
loaded, Bayer-packed to 4 channels, run tile-by-tile through the
currently enabled rawdenoise model (which denoises AND demosaicks
in one pass), and saved as a LinearRaw DNG grouped with the
source image.
Demonstrates:
* model lookup via dt.ai.model_for_task
* loading model variant files via dt.ai.load_model file arg
* raw CFA loading via dt.ai.load_raw
* tensor bayer_pack for 4-channel CFA tiling
* tile loop with non-trivial input->output scale (here 1 -> 2)
* edge-replicated padding to suppress border artifacts
* LinearRaw DNG output via dt.ai.save_dng_linear
* background job with progress bar and cancellation
LIMITATIONS
* Bayer sensors only. The script loads model_bayer.onnx and uses
bayer_pack, which assumes a 2x2 Bayer layout. X-Trans and
Foveon sensors need model_linear.onnx with a different
preprocessing pipeline (no bayer_pack, demosaicked input).
* Image must be at least TILE_SIZE x TILE_SIZE in packed pixels
(1024 x 1024 of CFA for the default 512 packed tile size).
* If a save fails mid-batch the Lua error propagates and aborts
the whole job. Wrap denoise_one in pcall if you need
batch-robust behaviour.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* a rawdenoise model enabled in preferences -> AI
(e.g. rawdenoise-nind)
USAGE
* require this script from your luarc
* select one or more raw images in lighttable
* click the "denoise selected" button in the AI raw denoise panel
BUGS, COMMENTS, SUGGESTIONS
* file an issue on https://github.com/darktable-org/darktable
CHANGES
]]
local dt = require "darktable"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE_NAME = "ai_raw_denoise"
-- The rawdenoise-nind bayer model is a denoise+demosaic combo:
-- input : [1, 4, 512, 512] packed CFA (4 phase planes)
-- output : [1, 3, 1024, 1024] linear RGB at 2x the input resolution
-- So each tile-size pixel of the input covers a 2x2 CFA block, and the
-- model emits two output pixels per input pixel in each axis.
-- TILE_SIZE is in packed pixels (model input); SCALE turns those into
-- output pixels. Keep in sync with `attributes.input_sizes` in the
-- model's config.json.
local TILE_SIZE = 512 -- rawdenoise-nind
local SCALE = 2 -- model output dim / input dim
local OUT_CH = 3 -- model emits linear RGB
-- overlap between adjacent tiles, in packed pixels (input space).
-- larger values produce smoother seams at the cost of more compute
local OVERLAP = 32
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
-- Bayer-only CFA channel lookup. Returns 0/1/2 (R/G/B) for the cell
-- at sensor position (r, c) given the 32-bit `filters` mask from
-- load_raw's meta table. Used to find the R-cell origin so bayer_pack's
-- plane 0 always holds R. X-Trans (filters == 9) uses meta.xtrans
-- instead and is not handled here.
local function fc(r, c, filters)
local shift = ((r % 2) * 4) + ((c % 2) * 2)
return math.floor(filters / (2 ^ shift)) % 4
end
-- daylight WB multipliers (R, G=1, B) derived from the camera's
-- XYZ->camRGB color matrix and the D65 white point. mirrors the C
-- path's _bayer_wb_daylight in restore_raw_bayer.c — the rawdenoise
-- bayer model is trained on daylight-WB'd input, so feeding it
-- un-WB'd camRGB produces wrong colors. Falls back to {1, 1, 1} if
-- the matrix is missing or degenerate.
local function daylight_wb(color_matrix)
local d65 = {0.95047, 1.0, 1.08883}
if not color_matrix then return 1.0, 1.0, 1.0 end
local resp = {0, 0, 0}
for c = 1, 3 do
resp[c] = color_matrix[c][1] * d65[1]
+ color_matrix[c][2] * d65[2]
+ color_matrix[c][3] * d65[3]
end
if resp[1] <= 0 or resp[2] <= 0 or resp[3] <= 0 then
return 1.0, 1.0, 1.0
end
return resp[2] / resp[1], 1.0, resp[2] / resp[3]
end
-- Surround the input with `pad` pixels on every side, replicating the
-- outermost rows/columns ("edge clamp" padding). Boundary tiles then
-- see plausible context instead of a literal image edge, so the
-- network's internal zero-padding no longer leaks into the visible
-- output as colour artifacts along the image rim.
local function pad_replicate(input, pad)
local C = input:shape()[2]
local H = input:shape()[3]
local W = input:shape()[4]
local padded = dt.ai.create_tensor({1, C, H + 2 * pad, W + 2 * pad})
-- center: original image
padded:paste(input, pad, pad)
-- left and right strips: replicate first / last column of input
local left_col = input:crop(0, 0, H, 1)
local right_col = input:crop(0, W - 1, H, 1)
for i = 0, pad - 1 do
padded:paste(left_col, pad, i)
padded:paste(right_col, pad, pad + W + i)
end
-- top and bottom strips: replicate first / last row of the already
-- horizontally-padded buffer so the four corners get filled correctly
local top_row = padded:crop(pad, 0, 1, W + 2 * pad)
local bottom_row = padded:crop(pad + H - 1, 0, 1, W + 2 * pad)
for i = 0, pad - 1 do
padded:paste(top_row, i, 0)
padded:paste(bottom_row, pad + H + i, 0)
end
return padded
end
-- Tile a [1,C_in,H,W] tensor through a model that emits [1,C_out,H*scale,
-- W*scale]. Each tile is tile_size x tile_size in input space; successive
-- tiles step by (tile_size - 2*overlap); only the central (non-overlap)
-- region of each tile's output is pasted into the result, with all
-- offsets multiplied by `scale`. Image-boundary edges keep their full
-- extent; only image-interior seams drop the overlap margin.
--
-- The trailing tile in each axis is pinned to the image boundary so
-- the residual right/bottom strip is never smaller than 2*overlap --
-- otherwise it would have to be skipped (the model has a fixed input
-- size) and those pixels would never be written.
local function run_tiled(ctx, input, tile_size, overlap, scale, c_out,
job, base_pct, span_pct)
local H = input:shape()[3]
local W = input:shape()[4]
if H < tile_size or W < tile_size then
return nil, string.format("image too small (%dx%d) for tile %d",
W, H, tile_size)
end
local step = tile_size - 2 * overlap
-- enumerate tile origins along one axis, ending exactly at the boundary
local function origins(extent)
local out, p = {}, 0
while p + tile_size < extent do
out[#out + 1] = p
p = p + step
end
out[#out + 1] = math.max(0, extent - tile_size)
return out
end
local ys, xs = origins(H), origins(W)
local total = #xs * #ys
-- output is scale-times larger spatially and may have a different
-- channel count than the input
local output = dt.ai.create_tensor({1, c_out, H * scale, W * scale})
local done = 0
for _, ty in ipairs(ys) do
for _, tx in ipairs(xs) do
if job and not job.valid then return nil, "cancelled" end
local tile_in = input:crop(ty, tx, tile_size, tile_size)
local tile_out = ctx:run(tile_in)
-- central region to keep: drop overlap on every inner edge;
-- full extent at image-boundary edges. all dimensions in OUTPUT
-- (post-scale) space, since that's where we paste
local at_top = (ty == 0)
local at_left = (tx == 0)
local at_bottom = (ty + tile_size >= H)
local at_right = (tx + tile_size >= W)
local ov = overlap * scale
local tos = tile_size * scale
local cy = at_top and 0 or ov
local cx = at_left and 0 or ov
local ch = tos - cy - (at_bottom and 0 or ov)
local cw = tos - cx - (at_right and 0 or ov)
output:paste(tile_out:crop(cy, cx, ch, cw),
ty * scale + cy, tx * scale + cx)
done = done + 1
if job then
job.percent = base_pct + span_pct * (done / total)
end
end
end
return output
end
-- Denoise one raw image. The bayer rawdenoise model denoises AND
-- demosaicks in one pass, so its output is linear RGB at the
-- original CFA resolution (not a CFA mosaic). Saved as a LinearRaw
-- DNG, which darktable can re-import as an already-debayered raw.
--
-- Pipeline:
-- load CFA [1,1,H,W] + image metadata
-- -> bayer_pack [1,4,H/2,W/2]
-- -> normalize to [0, 1] using black/white levels (the model is
-- trained on (val - black) / (white - black), not raw ADC)
-- -> pad to [1,4,H/2+2P,W/2+2P]
-- -> tile through model (scale=2, c_out=3) -> [1,3,(H+4P),(W+4P)]
-- -> crop padding back off -> [1,3,H,W]
-- -> save linear DNG -> import + group with source
local function denoise_one(ctx, img, tile_size, job, base_pct, span_pct)
-- load_raw returns (tensor, meta). tensor is [1,1,H,W] in raw ADC
-- units; meta carries black/white/wb/CFA info we need to normalise
-- the input into the [0, 1] range the model was trained on
local cfa, meta = dt.ai.load_raw(img)
if not cfa then return false, "load_raw failed" end
-- this script is Bayer-only: filters == 9 is X-Trans (handled by a
-- different preprocessing pipeline + model_linear.onnx); filters == 0
-- means monochrome / non-CFA, which bayer_pack can't handle either
if not meta.filters or meta.filters == 0 or meta.filters == 9 then
return false, "unsupported CFA pattern (Bayer-only script)"
end
-- Crop to the visible region and shift the origin onto the R cell
-- of the CFA pattern. bayer_pack splits by position only, so to make
-- plane 0 always hold R (which the model expects) the cropped CFA's
-- (0, 0) must sit at an R site. Mirrors the C path's _bayer_origin
-- + RGGB shift in restore_raw_bayer.c.
local cy = 0
local cx = 0
local cw = cfa:shape()[4]
local ch = cfa:shape()[3]
if meta.crop_x and meta.crop_y then
cy = meta.crop_y - (meta.crop_y % 2)
cx = meta.crop_x - (meta.crop_x % 2)
cw = (meta.visible_width or cw) + (meta.crop_x - cx)
ch = (meta.visible_height or ch) + (meta.crop_y - cy)
end
-- find R cell within the 2x2 starting at (cy, cx)
local sy, sx = 0, 0
for ty = 0, 1 do
for tx = 0, 1 do
if fc(cy + ty, cx + tx, meta.filters) == 0 then
sy, sx = ty, tx
end
end
end
cy = cy + sy
cx = cx + sx
ch = ch - sy
cw = cw - sx
-- ensure even dims for bayer_pack
ch = ch - (ch % 2)
cw = cw - (cw % 2)
if cy > 0 or cx > 0
or cw < cfa:shape()[4] or ch < cfa:shape()[3]
then
cfa = cfa:crop(cy, cx, ch, cw)
end
-- uniform-black approximation: most sensors report identical black
-- levels across the 4 CFA sites, in which case a single scalar
-- (val - black) / (white - black) normalisation is exact. cameras
-- with non-uniform per-site black levels (e.g. some PDAF sensors)
-- would need per-plane scaling via scale_add_planes -- left as an
-- exercise
local black = meta.black_level
local range = meta.white_level - black
if range <= 0 then range = 65535 end
-- pack the 1-channel CFA into 4 phase planes so each channel holds
-- one Bayer site type. After the RGGB shift above:
-- plane 0 = R, planes 1,2 = G, plane 3 = B
local packed = cfa:bayer_pack()
packed:scale_add(1.0 / range, -black / range)
-- apply daylight WB so the four planes sit in the same range the
-- model was trained on (R, G, G, B → R*wb_R, G, G, B*wb_B). without
-- this, the model sees unbalanced channels and produces wrong colors
local wb_R, wb_G, wb_B = daylight_wb(meta.color_matrix)
packed:scale_add_planes({wb_R, wb_G, wb_G, wb_B})
local pH = packed:shape()[3]
local pW = packed:shape()[4]
-- save the post-normalisation input mean for gain-matching the
-- model output: the bayer rawdenoise model emits values on its own
-- internal scale, not [0, 1], so we rescale so output mean matches
-- input mean (the per-tile equivalent of dt's C reference path)
local in_mean = packed:mean()
-- pad so the image rim is no longer at a tile boundary; the padded
-- strip absorbs the network's edge artifacts and is cropped away
-- after inference (in output space, where the pad covers 2*OVERLAP
-- pixels per side)
local padded_in = pad_replicate(packed, OVERLAP)
local padded_out, err = run_tiled(ctx, padded_in, tile_size, OVERLAP,
SCALE, OUT_CH,
job, base_pct, span_pct)
if not padded_out then return false, err end
-- crop the padded region (in output coords: pad * scale per side)
-- back to the original-CFA resolution
local out_rgb = padded_out:crop(OVERLAP * SCALE, OVERLAP * SCALE,
pH * SCALE, pW * SCALE)
-- gain-match: rescale the output so its mean matches the input
-- mean, bringing the model's internal-scale output back into the
-- [0, 1] range save_dng_linear expects
local out_mean = out_rgb:mean()
if out_mean > 1e-8 then
out_rgb:scale_add(in_mean / out_mean)
end
-- invert the daylight WB so the saved tensor is un-WB'd camRGB
-- (save_dng_linear's contract — the consumer applies AsShotNeutral
-- on import)
out_rgb:scale_add_planes({1.0 / wb_R, 1.0 / wb_G, 1.0 / wb_B})
local base = img.filename:match("(.+)%..+$") or img.filename
local out_path = img.path .. "/" .. base .. "_rawdenoised.dng"
-- LinearRaw DNG: tensor values must be normalised to [0, 1] camRGB;
-- the model handles black-subtract + scale internally, so we just
-- pass the output through
dt.ai.save_dng_linear(out_rgb, img, out_path)
-- import the result and group with the source so they live together
-- in the lighttable
local imported = dt.database.import(out_path)
if imported then imported:group_with(img) end
return true
end
-- Button handler: looks up the active rawdenoise model, fans out the
-- selected images, and reports progress via a cancellable job.
local function process_raw_denoise()
local model_id = dt.ai.model_for_task("rawdenoise")
if not model_id then
dt.print("no rawdenoise model enabled -- pick one in preferences -> AI")
return
end
local images = dt.gui.selection()
if #images == 0 then
dt.print("select at least one image")
return
end
-- progress + cancel job. the cancel callback flips job.valid; the
-- main loop polls it between tiles to stop cleanly
local job = dt.gui.create_job(
string.format("AI raw denoise (%d image%s)",
#images, #images == 1 and "" or "s"),
true,
function(j) j.valid = false end)
dt.print_log(string.format(
"[ai_raw_denoise] starting: %d image%s with model %s",
#images, #images == 1 and "" or "s", model_id))
local t_start = os.time()
-- rawdenoise packages ship multiple files (one per CFA type); pick
-- the Bayer variant. X-Trans / Foveon would use model_linear.onnx
-- with a different preprocessing pipeline (no bayer_pack)
local ctx = dt.ai.load_model(model_id, nil, "model_bayer.onnx")
if not ctx then
dt.print("failed to load model: " .. model_id)
job.valid = false
return
end
local span = 1.0 / #images
local done = 0
for i, img in ipairs(images) do
if not job.valid then break end
dt.print_log(string.format("[ai_raw_denoise] [%d/%d] %s",
i, #images, img.filename))
local ok, err = denoise_one(ctx, img, TILE_SIZE, job,
(i - 1) * span, span)
if not ok then
dt.print(string.format("[%d/%d] %s: %s",
i, #images, img.filename, tostring(err)))
dt.print_log(string.format("[ai_raw_denoise] [%d/%d] %s FAILED: %s",
i, #images, img.filename, tostring(err)))
else
done = done + 1
end
end
ctx:close()
job.valid = false
local elapsed = os.time() - t_start
dt.print(string.format("raw denoise complete: %d image%s",
#images, #images == 1 and "" or "s"))
dt.print_log(string.format(
"[ai_raw_denoise] finished: %d/%d in %ds",
done, #images, elapsed))
end
-- script_manager integration: called when the user disables the script.
-- darktable can't remove a registered lib from the UI at runtime, so we
-- only stop any in-flight work; the panel itself remains until restart.
local function destroy()
-- nothing persistent to clean up: each click runs its own job and
-- closes its own model context
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_lib(
MODULE_NAME, -- plugin id
"AI raw denoise", -- displayed name
true, -- expandable
false, -- no reset button
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
dt.new_widget("box") {
orientation = "vertical",
dt.new_widget("button") {
label = "denoise selected",
clicked_callback = process_raw_denoise,
}
},
nil, nil
)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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.metadata = {
name = "AI raw denoise",
purpose = "tile-based AI raw denoise using the darktable.ai Lua API",
author = "Andrii Ryzhkov",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/ai_raw_denoise"
}
script_data.destroy = destroy
return script_data

View File

@@ -23,5 +23,36 @@ USAGE
]]
local dt = require "darktable"
-- translation facilities
local gettext = dt.gettext.gettext
local function _(msg)
return gettext(msg)
end
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
local result = dt.configuration.api_version_string
dt.print_error("API Version: "..result)
dt.print_log("API Version: " .. result)
dt.print("API " .. _("version") .. ": " .. result)
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("APIversion"),
purpose = _("display api_version example"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version"
}
script_data.destroy = destroy
return script_data

View File

@@ -40,23 +40,28 @@ local du = require "lib/dtutils"
-- V E R S I O N C H E C K
-- - - - - - - - - - - - - - - - - - - - - - - -
du.check_min_api_version("5.0.2", "darkroom_mode") -- darktable 3.0
du.check_min_api_version("5.0.2", "darkroom_demo") -- darktable 3.0
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE_NAME = "darkroom"
local MODULE_NAME = "darkroom_demo"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- T R A N S L A T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local gettext = dt.gettext
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
return gettext(msgid)
end
-- - - - - - - - - - - - - - - - - - - - - - - -
@@ -70,19 +75,29 @@ local sleep = dt.control.sleep
local current_view = dt.gui.current_view()
-- check that there is an image selected, otherwise we can't activate darkroom viewe
local images = dt.gui.action_images
dt.print_log(#images .. " images selected")
if not images or #images == 0 then
dt.print_log("no images selected, creating selection")
dt.print_log("using image " .. dt.collection[1].filename)
dt.gui.selection({dt.collection[1]})
end
-- enter darkroom view
dt.gui.current_view(dt.gui.views.darkroom)
local max_images = 10
dt.print(_("Showing images, with a pause in between each"))
dt.print(_("showing images, with a pause between each"))
sleep(1500)
-- display first 10 images of collection pausing for a second between each
for i, img in ipairs(dt.collection) do
dt.print(_("Displaying image " .. i))
dt.print(string.format(_("displaying image "), i))
dt.gui.views.darkroom.display_image(img)
sleep(1500)
if i == max_images then
@@ -92,7 +107,22 @@ end
-- return to lighttable view
dt.print(_("Restoring view"))
dt.print(_("restoring view"))
sleep(1500)
dt.gui.current_view(current_view)
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("darkroom demo"),
purpose = _("example demonstrating how to control image display in darkroom mode"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo"
}
script_data.destroy = destroy
return script_data

View File

@@ -58,22 +58,40 @@ local du = require "lib/dtutils"
--check API version
du.check_min_api_version("3.0.0", "gettextExample")
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
-- Not translated Text
dt.print_error("Hello World!")
local gettext = dt.gettext
local gettext = dt.gettext.gettext
-- Translate a string using the darktable textdomain
dt.print_error(gettext.gettext("image"))
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gettextExample",dt.configuration.config_dir.."/lua/locale/")
-- Translate a string using the specified textdomain
dt.print_error(gettext.dgettext("gettextExample", 'Hello World!'))
dt.print_error(gettext("image"))
-- Define a local function called _ to make the code more readable and have it call dgettext
-- with the proper domain.
local function _(msgid)
return gettext.dgettext("gettextExample", msgid)
return gettext(msgid)
end
dt.print_error(_('Hello World!'))
dt.print_error(_("hello world!"))
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("gettext example"),
purpose = _("example of how translations works"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextExample"
}
script_data.destroy = destroy
return script_data

142
examples/gui_action.lua Normal file
View File

@@ -0,0 +1,142 @@
local dt = require "darktable"
local NaN = 0/0
local wg = {}
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = _("gui action"),
purpose = _("example of how to use darktable.gui.action() calls"),
author = "Diederik ter Rahe",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gui_action"
}
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
wg.action = dt.new_widget("entry"){
text = "lib/filter/view",
placeholder = _("action path"),
tooltip = _("enter the full path of an action, for example 'lib/filter/view'")
}
wg.instance = dt.new_widget("combobox"){
label = _("instance"),
tooltip = _("the instance of an image processing module to execute action on"),
"0", "+1", "-1", "+2", "-2", "+3", "-3", "+4", "-4", "+5", "-5", "+6", "-6", "+7", "-7", "+8", "-8", "+9", "-9"
}
wg.element = dt.new_widget("entry"){
text = "",
placeholder = _("action element"),
tooltip = _("enter the element of an action, for example 'selection', or leave empty for default")
}
wg.effect = dt.new_widget("entry"){
text = "next",
placeholder = _("action effect"),
tooltip = _("enter the effect of an action, for example 'next', or leave empty for default")
}
wg.speed = dt.new_widget("entry"){
text = "1",
placeholder = _("action speed"),
tooltip = _("enter the speed to use in action execution, or leave empty to only read state")
}
wg.check = dt.new_widget("check_button"){
label = _('perform action'),
tooltip = _('perform action or only read return'),
clicked_callback = function()
wg.speed.sensitive = wg.check.value
end,
value = true
}
wg.return_value = dt.new_widget("entry"){
text = "",
sensitive = false
}
dt.register_lib(
"execute_action", -- Module name
_("execute gui actions"), -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100},
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}},
dt.new_widget("box")
{
orientation = "vertical",
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label"){label = _("action path"), halign = "start"},
wg.action
},
wg.instance,
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label"){label = _("element"), halign = "start"},
wg.element
},
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label"){label = _("effect"), halign = "start"},
wg.effect
},
wg.check,
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label"){label = _("speed"), halign = "start"},
wg.speed
},
dt.new_widget("button")
{
label = _("execute action"),
tooltip = _("execute the action specified in the fields above"),
clicked_callback = function(_)
local sp = NaN
if wg.check.value then sp = wg.speed.text end
wg.return_value.text = dt.gui.action(wg.action.text, tonumber(wg.instance.value), wg.element.text, wg.effect.text, tonumber(sp))
end
},
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label"){label = "return value:", halign = "start"},
wg.return_value
},
}
)
local function restart()
dt.gui.libs["execute_action"].visible = true
end
local function destroy()
dt.gui.libs["execute_action"].visible = false
end
script_data.destroy = destroy
script_data.destroy_method = "hide"
script_data.restart = restart
script_data.show = restart
return script_data

View File

@@ -29,9 +29,38 @@ USAGE
local dt = require "darktable"
local du = require "lib/dtutils"
-- translation facilities
du.check_min_api_version("2.0.0", "hello_world")
dt.print("hello, world")
local gettext = dt.gettext.gettext
local function _(msg)
return gettext(msg)
end
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
dt.print(_("hello, world"))
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("hello world"),
purpose = _("example of how to print a message to the screen"),
author = "Tobias Ellinghaus",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world"
}
script_data.destroy = destroy
return script_data
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -50,14 +50,21 @@ du.check_min_api_version("5.0.2", "lighttable_demo") -- darktable 3.0
local MODULE_NAME = "lighttable"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- T R A N S L A T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local gettext = dt.gettext
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local gettext = dt.gettext.gettext
dt.gettext.bindtextdomain("lighttable_demo", dt.configuration.config_dir .."/lua/locale/")
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
return gettext(msgid)
end
-- - - - - - - - - - - - - - - - - - - - - - - -
@@ -75,6 +82,7 @@ local layouts = {
"DT_LIGHTTABLE_LAYOUT_ZOOMABLE",
"DT_LIGHTTABLE_LAYOUT_FILEMANAGER",
"DT_LIGHTTABLE_LAYOUT_CULLING",
"DT_LIGHTTABLE_LAYOUT_CULLING_DYNAMIC",
}
local sorts = {
@@ -138,34 +146,41 @@ sleep(2000)
for n, layout in ipairs(layouts) do
dt.gui.libs.lighttable_mode.layout(layout)
dt.print(_("set lighttable layout to " .. layout))
dt.print(string.format(_("set lighttable layout to %s"), layout))
dt.print_log("set lighttable layout to " .. layout)
sleep(1500)
for i = 1, 10 do
dt.gui.libs.lighttable_mode.zoom_level(i)
dt.print(_("Set zoom level to " .. i))
dt.print(string.format(_("set zoom level to %d"), i))
sleep(1500)
end
for i = 9, 1, -1 do
dt.gui.libs.lighttable_mode.zoom_level(i)
dt.print(_("Set zoom level to " .. i))
dt.print(string.format(_("set zoom level to %d"), i))
sleep(1500)
end
end
dt.print_log("finished layout and zoom level testing")
dt.print_log("starting sort demonstration")
-- cycle through sorts
dt.print(_("lighttable sorting demonstration"))
dt.print_log("setting lighttable to filemanager mode")
dt.gui.libs.lighttable_mode.layout("DT_LIGHTTABLE_LAYOUT_FILEMANAGER")
sleep(500)
dt.print_log("setting lighttable to zoom level 5")
dt.gui.libs.lighttable_mode.zoom_level(5)
dt.print_log("starting sorts")
for n, sort in ipairs(sorts) do
dt.gui.libs.filter.sort(sort)
dt.print(_("set lighttable sort to " .. sort))
dt.print(string.format(_("set lighttable sort to %s"), sort))
sleep(1500)
for m, sort_order in ipairs(sort_orders) do
dt.gui.libs.filter.sort_order(sort_order)
dt.print(_("sort order set to " .. sort_order))
dt.print(string.format(_("sort order set to %s"), sort_order))
sleep(1500)
end
end
@@ -176,12 +191,12 @@ dt.print(_("lighttable filtering demonstration"))
for n, rating in ipairs(ratings) do
dt.gui.libs.filter.rating(rating)
dt.print(_("set filter to " .. rating))
dt.print(string.format(_("set filter to %s"), rating))
sleep(1500)
for m, rating_comparator in ipairs(rating_comparators) do
dt.gui.libs.filter.rating_comparator(rating_comparator)
dt.print(_("set rating comparator to " .. rating_comparator))
dt.print(string.format(_("set rating comparator to %s"), rating_comparator))
sleep(1500)
end
end
@@ -196,3 +211,19 @@ current_rating = dt.gui.libs.filter.rating(current_rating)
current_rating_comparator = dt.gui.libs.filter.rating_comparator(current_rating_comparator)
current_sort =dt.gui.libs.filter.sort(current_sort)
current_sort_order = dt.gui.libs.filter.sort_order(current_sort_order)
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("lighttable demo"),
purpose = _("example demonstrating how to control lighttable display modes"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo"
}
script_data.destroy = destroy
return script_data

View File

@@ -33,25 +33,87 @@ https://www.darktable.org/lua-api/index.html.php#darktable_new_widget
local dt = require "darktable"
local du = require "lib/dtutils"
du.check_min_api_version("3.0.0", "moduleExample")
du.check_min_api_version("7.0.0", "moduleExample")
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
local script_data = {}
script_data.metadata = {
name = ("module example"),
purpose = _("example of how to create a lighttable module"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleExample"
}
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, otherwise leave as nil
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
-- translation
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
local gettext = dt.gettext
-- declare a local namespace and a couple of variables we'll need to install the module
local mE = {}
mE.widgets = {}
mE.event_registered = false -- keep track of whether we've added an event callback or not
mE.module_installed = false -- keep track of whether the module is module_installed
gettext.bindtextdomain("moduleExample", dt.configuration.config_dir .. "/lua/locale/")
--[[ We have to create the module in one of two ways depending on which view darktable starts
in. In orker to not repeat code, we wrap the darktable.register_lib in a local function.
]]
local function _(msgid)
return gettext.dgettext("moduleExample", msgid)
local function install_module()
if not mE.module_installed then
-- https://www.darktable.org/lua-api/index.html#darktable_register_lib
dt.register_lib(
"exampleModule", -- Module name
_("example module"), -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
-- https://www.darktable.org/lua-api/types_lua_box.html
dt.new_widget("box") -- widget
{
orientation = "vertical",
dt.new_widget("button")
{
label = _("my ") .. "button",
clicked_callback = function (_)
dt.print(_("button clicked"))
end
},
table.unpack(mE.widgets),
},
nil,-- view_enter
nil -- view_leave
)
mE.module_installed = true
end
end
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
dt.gui.libs["exampleModule"].visible = false -- we haven't figured out how to destroy it yet, so we hide it for now
end
local function restart()
dt.gui.libs["exampleModule"].visible = true -- the user wants to use it again, so we just make it visible and it shows up in the UI
end
-- https://www.darktable.org/lua-api/types_lua_check_button.html
local check_button = dt.new_widget("check_button"){label = _("MyCheck_button"), value = true}
local check_button = dt.new_widget("check_button"){label = _("my ") .. "check_button", value = true}
-- https://www.darktable.org/lua-api/types_lua_combobox.html
local combobox = dt.new_widget("combobox"){label = _("MyCombobox"), value = 2, "8", "16", "32"}
local combobox = dt.new_widget("combobox"){label = _("my ") .. "combobox", value = 2, "8", "16", "32"}
-- https://www.darktable.org/lua-api/types_lua_entry.html
local entry = dt.new_widget("entry")
@@ -60,21 +122,21 @@ local entry = dt.new_widget("entry")
placeholder = _("placeholder"),
is_password = false,
editable = true,
tooltip = _("Tooltip Text"),
tooltip = _("tooltip text"),
reset_callback = function(self) self.text = "text" end
}
-- https://www.darktable.org/lua-api/types_lua_file_chooser_button.html
local file_chooser_button = dt.new_widget("file_chooser_button")
{
title = _("MyFile_chooser_button"), -- The title of the window when choosing a file
title = _("my ") .. "file_chooser_button", -- The title of the window when choosing a file
value = "", -- The currently selected file
is_directory = false -- True if the file chooser button only allows directories to be selecte
}
-- https://www.darktable.org/lua-api/types_lua_label.html
local label = dt.new_widget("label")
label.label = _("MyLabel") -- This is an alternative way to the "{}" syntax to set a property
label.label = _("my ") .. "label" -- This is an alternative way to the "{}" syntax to set a property
-- https://www.darktable.org/lua-api/types_lua_separator.html
local separator = dt.new_widget("separator"){}
@@ -82,7 +144,7 @@ local separator = dt.new_widget("separator"){}
-- https://www.darktable.org/lua-api/types_lua_slider.html
local slider = dt.new_widget("slider")
{
label = _("MySlider"),
label = _("my ") .. "slider",
soft_min = 10, -- The soft minimum value for the slider, the slider can't go beyond this point
soft_max = 100, -- The soft maximum value for the slider, the slider can't go beyond this point
hard_min = 0, -- The hard minimum value for the slider, the user can't manually enter a value beyond this point
@@ -90,35 +152,44 @@ local slider = dt.new_widget("slider")
value = 52 -- The current value of the slider
}
-- https://www.darktable.org/lua-api/index.html#darktable_register_lib
dt.register_lib(
"exampleModule", -- Module name
"exampleModule", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
-- https://www.darktable.org/lua-api/types_lua_box.html
dt.new_widget("box") -- widget
{
orientation = "vertical",
dt.new_widget("button")
{
label = _("MyButton"),
clicked_callback = function (_)
dt.print(_("Button clicked"))
end
},
check_button,
combobox,
entry,
file_chooser_button,
label,
separator,
slider
},
nil,-- view_enter
nil -- view_leave
)
-- pack the widgets in a table for loading in the module
table.insert(mE.widgets, check_button)
table.insert(mE.widgets, combobox)
table.insert(mE.widgets, entry)
table.insert(mE.widgets, file_chooser_button)
table.insert(mE.widgets, label)
table.insert(mE.widgets, separator)
table.insert(mE.widgets, slider)
-- ... and tell dt about it all
if dt.gui.current_view().id == "lighttable" then -- make sure we are in lighttable view
install_module() -- register the lib
else
if not mE.event_registered then -- if we are not in lighttable view then register an event to signal when we might be
-- https://www.darktable.org/lua-api/index.html#darktable_register_event
dt.register_event(
"mdouleExample", "view-changed", -- we want to be informed when the view changes
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then -- if the view changes from darkroom to lighttable
install_module() -- register the lib
end
end
)
mE.event_registered = true -- keep track of whether we have an event handler installed
end
end
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
script_data.destroy = destroy
script_data.restart = restart -- only required for lib modules until we figure out how to destroy them
script_data.destroy_method = "hide" -- tell script_manager that we are hiding the lib so it knows to use the restart function
script_data.show = restart -- if the script was "off" when darktable exited, the module is hidden, so force it to show on start
return script_data
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: hl Lua;

View File

@@ -29,8 +29,7 @@
by hovering over the image and pressing the key combination.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* ufraw-batch - https://ufraw.sourceforge.net
MacOS - install with homebrew
* exiftool - https://exiftool.org
USAGE
* require this script from your main lua file
@@ -44,6 +43,7 @@
* Send to Bill Ferguson, wpferguson@gmail.com
CHANGES
20251205 - replaced ufraw-batch with exiftool
]]
--[[
@@ -70,13 +70,10 @@ local dtsys = require "lib/dtutils.system" -- system utilities
translations, inserting this lays the groundwork for anyone who wants to translate the strings.
]]
local gettext = dt.gettext
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("multi_os",dt.configuration.config_dir.."/lua/locale/")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext("multi_os", msgid)
return gettext(msgid)
end
--[[
@@ -86,7 +83,7 @@ end
screen stating that you couldn't load because the minimum api version wasn't met.
]]
du.check_min_api_version("5.0.0", "multi_os")
du.check_min_api_version("7.0.0", "multi_os")
--[[
copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image
@@ -142,6 +139,36 @@ local function copy_image_attributes(from, to, ...)
end
end
local function check_file_size(img_name)
local result = 0
if dt.configuration.running_os == "windows" then
local path = df.get_path(img_name)
local fname = df.get_filename(img_name)
local p = io.open("forfiles /P " .. path .. " /M " .. fname .. " /C \"cmd /c echo $fsize\"")
if p then
for line in p:lines() do
if line:length() > 5 then
result = tonumber(line)
end
end
p:close()
end
else
local p = io.popen("ls -s " .. img_name)
if p then
local line = p:read("*a")
result = tonumber(du.split(line, " ")[1])
p:close()
end
end
return result
end
--[[
The main function called from the button or the shortcut. It takes one or more raw files, passed
in a table and extracts the embedded jpeg images
@@ -155,11 +182,12 @@ local function extract_embedded_jpeg(images)
in the user's path. When it finds the executable, it returns the command to run it.
]]
local ufraw_executable = df.check_if_bin_exists("ufraw-batch")
if ufraw_executable then
local exiftool_executable = df.check_if_bin_exists("exiftool")
if exiftool_executable then
for _, image in ipairs(images) do
if image.is_raw then
local img_file = du.join({image.path, image.filename}, "/")
local img_file = tostring(image)
local jpg_img_file = string.gsub(img_file, "%....$", ".jpg")
--[[
dtsys.external_command() is operating system aware and formats the command as necessary.
@@ -167,8 +195,12 @@ local function extract_embedded_jpeg(images)
operating system aware and uses the corresponding quotes.
]]
if dtsys.external_command(ufraw_executable .. " --silent --embedded-image " .. df.sanitize_filename(img_file)) then
local jpg_img_file = df.chop_filetype(img_file) .. ".embedded.jpg"
if dtsys.external_command(exiftool_executable .. " -b -JpgFromRaw " .. df.sanitize_filename(img_file) .. " > " .. df.sanitize_filename(jpg_img_file)) then
dt.print_log("created jpg from raw")
if check_file_size(df.sanitize_filename(jpg_img_file)) == 0 then
dt.print_log("using preview")
dtsys.external_command(exiftool_executable .. " -b -PreviewImage " .. df.sanitize_filename(img_file) .. " > " .. df.sanitize_filename(jpg_img_file))
end
dt.print_log("jpg_img_file set to ", jpg_img_file) -- print a debugging message
local myimage = dt.database.import(jpg_img_file)
myimage:group_with(image.group_leader)
@@ -187,15 +219,24 @@ local function extract_embedded_jpeg(images)
end
else
dt.print_error(image.filename .. " is not a raw file. No image can be extracted") -- print debugging error message
dt.print(image.filename .. " is not a raw file. No image can be extracted") -- print the error to the screen
dt.print(string.format(_("%s is not a raw file, no image can be extracted"), image.filename)) -- print the error to the screen
end
end
else
dt.print_error("ufraw-batch not found. Exiting...") -- print debugging error message
dt.print("ufraw-batch not found. Exiting...") -- print the error to the screen
dt.print_error("exiftool not found. Exiting...") -- print debugging error message
dt.print("exiftool not found, exiting...") -- print the error to the screen
end
end
--[[
script_manager integration to allow a script to be removed
without restarting darktable
]]
local function destroy()
dt.destroy_event("multi_os", "shortcut") -- destroy the event since the callback will no longer be present
dt.gui.libs.image.destroy_action("multi_os") -- remove the button from the selected images module
end
--[[
Windows and MacOS don't place executables in the user's path so their location needs to be specified
so that the script can find them. An exception to this is packages installed on MacOS with homebrew. Those
@@ -204,33 +245,35 @@ end
to see if the executable is there.
]]
local executable = "ufraw-batch"
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
title = _("Select ufraw-batch[.exe] executable"),
value = df.get_executable_path_preference(executable),
is_directory = false,
changed_callback = function(self)
if df.check_if_bin_exists(self.value) then
df.set_executable_path_preference(executable, self.value)
if dt.configuration.running_os ~= "linux" then
local executable = "ufraw-batch"
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
title = string.format(_("select %s executable"), "ufraw-batch[.exe]"),
value = df.get_executable_path_preference(executable),
is_directory = false,
changed_callback = function(self)
if df.check_if_bin_exists(self.value) then
df.set_executable_path_preference(executable, self.value)
end
end
end
}
dt.preferences.register("executable_paths", "ufraw-batch", -- name
"file", -- type
_('multi_os: ufraw-batch location'), -- label
_('Installed location of ufraw-batch. Requires restart to take effect.'), -- tooltip
"ufraw-batch", -- default
ufraw_batch_path_widget
)
}
dt.preferences.register("executable_paths", "ufraw-batch", -- name
"file", -- type
'multi_os: ufraw-batch ' .. _('location'), -- label
_('installed location of ufraw-batch, requires restart to take effect.'), -- tooltip
"ufraw-batch", -- default
ufraw_batch_path_widget
)
end
--[[
Add a button to the selected images module in lighttable
]]
dt.gui.libs.image.register_action(
_("extract embedded jpeg"),
"multi_os", _("extract embedded jpeg"),
function(event, images) extract_embedded_jpeg(images) end,
"extract embedded jpeg"
_("extract embedded jpeg")
)
--[[
@@ -238,7 +281,26 @@ dt.gui.libs.image.register_action(
]]
dt.register_event(
"shortcut",
"multi_os", "shortcut",
function(event, shortcut) extract_embedded_jpeg(dt.gui.action_images) end,
"extract embedded jpeg"
_("extract embedded jpeg")
)
--[[
set the destroy routine so that script_manager can call it when
it's time to destroy the script and then return the data to
script_manager
]]
local script_data = {}
script_data.metadata = {
name = _("multi OS"),
purpose = _("example module thet runs on different operating systems"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/multi_os"
}
script_data.destroy = destroy
return script_data

View File

@@ -41,23 +41,28 @@ local du = require "lib/dtutils"
-- V E R S I O N C H E C K
-- - - - - - - - - - - - - - - - - - - - - - - -
du.check_min_api_version("5.0.2", "panels_demo") -- darktable 3.0
du.check_min_api_version("7.0.0", "panels_demo")
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE_NAME = "panels"
local MODULE_NAME = "panels_demo"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- T R A N S L A T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local gettext = dt.gettext
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
return gettext(msgid)
end
-- - - - - - - - - - - - - - - - - - - - - - - -
@@ -88,29 +93,29 @@ dt.gui.panel_show_all()
-- hide center_top, center_bottom, left, top, right, bottom in order
dt.print(_("Hiding all panels, one at a tme"))
dt.print(_("hiding all panels, one at a time"))
sleep(1500)
for i = 1, #panels do
dt.print(_("Hiding " .. panels[i]))
dt.print(string.format(_("hiding %s"), panels[i]))
dt.gui.panel_hide(panels[i])
sleep(1500)
end
-- display left, then top, then right, then bottom
dt.print(_("Make panels visible, one at a time"))
dt.print(_("make panels visible, one at a time"))
sleep(1500)
for i = #panels, 1, -1 do
dt.print(_("Showing " .. panels[i]))
dt.print(string.format(_("showing %s"), panels[i]))
dt.gui.panel_show(panels[i])
sleep(1500)
end
-- hide all
dt.print(_("Hiding all panels"))
dt.print(_("hiding all panels"))
sleep(1500)
dt.gui.panel_hide_all()
@@ -118,7 +123,7 @@ sleep(1500)
-- show all
dt.print(_("Showing all panels"))
dt.print(_("showing all panels"))
sleep(1500)
dt.gui.panel_show_all()
@@ -126,7 +131,7 @@ sleep(1500)
-- restore
dt.print(_("Restoring panels to starting configuration"))
dt.print(_("restoring panels to starting configuration"))
for i = 1, #panels do
if panel_status[i] then
dt.gui.panel_show(panels[i])
@@ -134,3 +139,19 @@ for i = 1, #panels do
dt.gui.panel_hide(panels[i])
end
end
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("panels demo"),
purpose = _("example demonstrating how to contol panel visibility"),
author = "Bill Ferguson <wpferguson@gmail.com>",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo"
}
script_data.destroy = destroy
return script_data

View File

@@ -24,60 +24,86 @@ USAGE
local dt = require "darktable"
local du = require "lib/dtutils"
-- translation facilities
local gettext = dt.gettext.gettext
local function _(msg)
return gettext(msg)
end
local script_data = {}
script_data.metadata = {
name = _("preference examples"),
purpose = _("example to show the different preference types that are possible"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceExamples"
}
du.check_min_api_version("2.0.1", "preferenceExamples")
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"string", -- type
"Example String", -- label
"Example String Tooltip", -- tooltip
_("example") .. " string", -- label
_("example") .. " string " .. _("tooltip"), -- tooltip
"String") -- default
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"preferenceExamplesBool", -- name
"bool", -- type
"Example Boolean", -- label
"Example Boolean Tooltip", -- tooltip
_("example") .. " boolean", -- label
_("example") .. " boolean " .. _("tooltip"), -- tooltip
true) -- default
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"preferenceExamplesInteger", -- name
"integer", -- type
"Example Integer", -- label
"Example Integer Tooltip", -- tooltip
_("example") .. " integer", -- label
_("example") .. " integer " .. _("tooltip"), -- tooltip
2, -- default
1, -- min
99) -- max
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"preferenceExamplesFloat", -- name
"float", -- type
"Example Float", -- label
"Example Float Tooltip", -- tooltip
_("example") .. " float", -- label
_("example") .. " float " .. _("tooltip"), -- tooltip
1.3, -- default
1, -- min
99, -- max
0.5) -- step
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"preferenceExamplesFile", -- name
"file", -- type
"Example File", -- label
"Example File Tooltip", -- tooltip
_("example") .. " file", -- label
_("example") .. " file " .. _("tooltip"), -- tooltip
"") -- default
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"preferenceExamplesDirectory", -- name
"directory", -- type
"Example Directory", -- label
"Example Directory Tooltip", -- tooltip
_("example") .. " directory", -- label
_("example") .. " directory " .. _("tooltip"), -- tooltip
"") -- default
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name
"preferenceExamplesEnum", -- name
"enum", -- type
"Example Enum", -- label
"Example Enum Tooltip", -- tooltip
_("example") .. " enum", -- label
_("example") .. " enum " .. _("tooltip"), -- tooltip
"Enum 1", -- default
"Enum 1", "Enum 2") -- values
local function destroy()
-- nothing to destroy
end
script_data.destroy = destroy
return script_data

View File

@@ -24,9 +24,23 @@ local du = require "lib/dtutils"
du.check_min_api_version("5.0.0", "printExamples")
-- translation facilities
local gettext = dt.gettext.gettext
local function _(msg)
return gettext(msg)
end
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
-- Will print a string to the darktable control log (the long
-- overlayed window that appears over the main panel).
dt.print("print")
dt.print(_("print"))
-- This function will print its parameter if the Lua logdomain is
-- activated. Start darktable with the "-d lua" command line option
@@ -38,5 +52,20 @@ dt.print_error("print error")
-- to enable the Lua logdomain.
dt.print_log("print log")
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("print examples"),
purpose = _("example showing the different types of printing messages"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printExamples"
}
script_data.destroy = destroy
return script_data
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -30,7 +30,37 @@ local du = require "lib/dtutils"
du.check_min_api_version("5.0.0", "running_os")
dt.print("You are running: "..dt.configuration.running_os)
-- translation facilities
local gettext = dt.gettext.gettext
local function _(msg)
return gettext(msg)
end
-- script_manager integration to allow a script to be removed
-- without restarting darktable
local function destroy()
-- nothing to destroy
end
dt.print(string.format(_("you are running: %s"), dt.configuration.running_os))
-- set the destroy routine so that script_manager can call it when
-- it's time to destroy the script and then return the data to
-- script_manager
local script_data = {}
script_data.metadata = {
name = _("running OS"),
purpose = _("example of how to determine the operating system being used"),
author = "Tobias Jakobs",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os"
}
script_data.destroy = destroy
return script_data
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

345
examples/x-touch.lua Normal file
View File

@@ -0,0 +1,345 @@
--[[
This file is part of darktable,
copyright (c) 2023 Diederik ter Rahe
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 <http://www.gnu.org/licenses/>.
]]
--[[
X-Touch Mini flexible encoder shortcuts
This script will create virtual sliders that are mapped dynamically to
the most relevant sliders for the currently focused processing module.
Tailored modules are color zones, tone equalizer, color calibration and
mask manager properties. The script can easily be amended for other
devices or personal preferences. Virtual "toggle" buttons can be created
as well, that dynamically change meaning depending on current status.
USAGE
* require this script from your main lua file
* restart darktable
* create shortcuts for each of the encoders on the x-touch mini
to a virtual slider under lua/x-touch
or import the following shortcutsrc file in the shortcuts dialog/preferences tab:
None;midi:CC1=lua/x-touch/knob 1
None;midi:CC2=lua/x-touch/knob 2
None;midi:CC3=lua/x-touch/knob 3
None;midi:CC4=lua/x-touch/knob 4
None;midi:CC5=lua/x-touch/knob 5
None;midi:CC6=lua/x-touch/knob 6
None;midi:CC7=lua/x-touch/knob 7
None;midi:CC8=lua/x-touch/knob 8
midi:E0=global/modifiers
midi:F0=global/modifiers;ctrl
midi:F#0=global/modifiers;alt
midi:G#-1=iop/blend/tools/show and edit mask elements
midi:A-1=iop/colorzones;focus
midi:A#-1=iop/toneequal;focus
midi:B-1=iop/colorbalancergb;focus
midi:C0=iop/channelmixerrgb;focus
midi:C#0=iop/colorequal;focus
midi:D0=iop/colorequal/page;previous
midi:D#0=iop/colorequal/page;next
USAGE:
To add a new module, simply add an entry to MODULE_PROFILES below.
Get action paths from darktable's shortcut interface.
]]
local dt = require "darktable"
local du = require "lib/dtutils"
du.check_min_api_version("9.2.0", "x-touch")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
end
-- Constants
local NUM_KNOBS = 8
local NAN = 0/0
local darkroom_view = dt.gui.views.darkroom
-- ============================================================================
-- USER CONFIGURATION
-- ============================================================================
-- Module Profiles: Define behavior when each module has focus
-- Structure: [module_name] = { knob1_action, knob2_action, ... }
local MODULE_PROFILES = {
-- Color Zones: Control individual color channels
colorzones = {
{"iop/colorzones/graph", "red"},
{"iop/colorzones/graph", "orange"},
{"iop/colorzones/graph", "yellow"},
{"iop/colorzones/graph", "green"},
{"iop/colorzones/graph", "aqua"},
{"iop/colorzones/graph", "blue"},
{"iop/colorzones/graph", "purple"},
{"iop/colorzones/graph", "magenta"}
},
-- Color Equalizer: Control color channels
colorequal = {
{"iop/colorequal/graph", "red"},
{"iop/colorequal/graph", "orange"},
{"iop/colorequal/graph", "yellow"},
{"iop/colorequal/graph", "green"},
{"iop/colorequal/graph", "cyan"},
{"iop/colorequal/graph", "blue"},
{"iop/colorequal/graph", "lavender"},
{"iop/colorequal/graph", "magenta"}
},
-- Sigmoid: Control primary color adjustments
sigmoid = {
{"iop/sigmoid/primaries/red attenuation"},
{"iop/sigmoid/primaries/red rotation"},
{"iop/sigmoid/primaries/green attenuation"},
{"iop/sigmoid/primaries/green rotation"},
{"iop/sigmoid/primaries/blue attenuation"},
{"iop/sigmoid/primaries/blue rotation"},
{"iop/sigmoid/primaries/recover purity"}
},
-- Color Calibration (Primaries)
primaries = {
{"iop/primaries/red hue"},
{"iop/primaries/red purity"},
{"iop/primaries/green hue"},
{"iop/primaries/green purity"},
{"iop/primaries/blue hue"},
{"iop/primaries/blue purity"},
{"iop/primaries/tint hue"},
{"iop/primaries/tint purity"}
},
-- AgX
agx = {
{"iop/agx/white relative exposure"},
{"iop/agx/black relative exposure"},
{"iop/agx/curve/pivot relative exposure"},
{"iop/agx/curve/pivot target output"},
{"iop/agx/curve/contrast"},
{"iop/agx/curve/shoulder power"},
{"iop/agx/curve/toe power"},
{"iop/agx/curve/curve y gamma"}
},
-- Tone Equalizer: Control EV bands
-- For 8 knobs, using offsets -8 to get bands
toneequal = {
{"iop/toneequal/simple/-8 EV"},
{"iop/toneequal/simple/-7 EV"},
{"iop/toneequal/simple/-6 EV"},
{"iop/toneequal/simple/-5 EV"},
{"iop/toneequal/simple/-4 EV"},
{"iop/toneequal/simple/-3 EV"},
{"iop/toneequal/simple/-2 EV"},
{"iop/toneequal/simple/-1 EV"}
},
-- Color Balance RGB: Selected controls
colorbalancergb = {
nil, -- Knob 1: unused --> default
{"iop/colorbalancergb/global vibrance"},
{"iop/colorbalancergb/contrast"},
{"iop/colorbalancergb/global chroma"},
{"iop/colorbalancergb/global saturation"},
{"iop/colorbalancergb/global brilliance"},
nil, -- Knob 7: --> default
nil -- Knob 8: --> default
},
-- Channel Mixer RGB (Color Calibration): Tab control and sliders
channelmixerrgb = {
nil, -- Knob 1-4: --> default
nil,
nil,
nil,
{"iop/channelmixerrgb/page", "CAT", "tab"}, -- Knob 5: Tab selector
{"iop/focus/sliders", "1st"}, -- Knob 6: First slider
{"iop/focus/sliders", "2nd"}, -- Knob 7: Second slider
{"iop/focus/sliders", "3rd"} -- Knob 8: Third slider
},
}
local DEFAULT_MAPPINGS = {
{"iop/exposure/exposure"},
{"iop/filmicrgb/white relative exposure"},
{"iop/filmicrgb/black relative exposure"},
{"iop/filmicrgb/contrast"},
{"iop/crop/left"},
{"iop/crop/right"},
{"iop/crop/top"},
{"iop/crop/bottom"}
}
-- Mask properties: Always checked first, regardless of module focus
local MASK_PROPERTIES = {
{"lib/masks/properties/opacity"},
{"lib/masks/properties/size"},
{"lib/masks/properties/feather"},
{"lib/masks/properties/hardness"},
{"lib/masks/properties/rotation"},
{"lib/masks/properties/curvature"},
{"lib/masks/properties/compression"}
}
-- Priority order for checking module focus (most commonly used first)
local MODULE_CHECK_ORDER = {
"toneequal",
"colorequal",
"colorbalancergb",
"agx",
"sigmoid",
"primaries",
"channelmixerrgb",
"colorzones"
}
-- ============================================================================
-- CORE LOGIC - No need to edit below unless changing architecture
-- ============================================================================
-- Helper functions
local function is_in_darkroom()
return dt.gui.current_view() == darkroom_view
end
local function is_valid(value)
return value == value -- NaN check
end
local function translate_tab_effect(effect)
if effect == "up" then
return "next"
elseif effect == "down" then
return "previous"
else
return "activate"
end
end
-- Execute an action configuration
local function execute_action(action_config, element, effect, size)
if not action_config then
return nil
end
local path = action_config[1]
local elem = action_config[2] or element
local action_type = action_config[3]
-- Handle special action types
if action_type == "tab" then
effect = translate_tab_effect(effect)
end
return dt.gui.action(path, elem, effect, size)
end
-- Try mask properties first
local function try_mask_properties(knob_idx, element, effect, size)
if knob_idx >= NUM_KNOBS then
return NAN
end
local mask_action = MASK_PROPERTIES[knob_idx]
if not mask_action then
return NAN
end
local value = execute_action(mask_action, element, effect, size)
return value
end
-- Main knob handler
local function handle_knob_action(knob_idx, element, effect, size)
-- Early exit for non-darkroom view
if not is_in_darkroom() then
return NAN
end
-- Try mask properties first (no focus check needed)
local mask_value = try_mask_properties(knob_idx, element, effect, size)
if is_valid(mask_value) then
return mask_value
end
-- Check focused modules in priority order
for _, module_name in ipairs(MODULE_CHECK_ORDER) do
local focus_status = dt.gui.action("iop/" .. module_name, "focus")
if focus_status ~= 0 then
-- Module has focus, get its profile
local profile = MODULE_PROFILES[module_name]
if profile then
local action_config = profile[knob_idx]
-- If profile has explicit mapping for this knob, use it
if action_config then
local result = execute_action(action_config, element, effect, size)
if result then
return result
end
else
-- Profile exists but this knob is nil -> use default
-- This avoids checking other modules when we know the focused one
local default_action = DEFAULT_MAPPINGS[knob_idx]
return execute_action(default_action, element, effect, size) or NAN
end
end
-- If no profile exists for this module, continue checking others
end
end
-- No module has focus or no profile matched, fall back to default
local default_action = DEFAULT_MAPPINGS[knob_idx]
return execute_action(default_action, element, effect, size) or NAN
end
-- Setup mimic sliders
local function setup_knobs()
for k = 1, NUM_KNOBS do
dt.gui.mimic("slider", "knob " .. k,
function(action, element, effect, size)
local knob_idx = tonumber(action:sub(-1))
return handle_knob_action(knob_idx, element, effect, size)
end)
end
end
-- Script metadata
local script_data = {
metadata = {
name = _("x-touch"),
purpose = _("data-driven x-touch midi controller configuration"),
author = "Martin Straeten",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch"
},
destroy = function() end,
destroy_method = nil,
restart = nil,
show = nil
}
-- Initialize
setup_knobs()
return script_data

View File

@@ -61,14 +61,50 @@ dtutils.libdoc.functions["check_min_api_version"] = {
function dtutils.check_min_api_version(min_api, script_name)
local current_api = dt.configuration.api_version_string
if min_api > current_api then
if dtutils.compare_api_versions(min_api, current_api) > 0 then
dt.print_error("This application is written for lua api version " .. min_api .. " or later.")
dt.print_error("The current lua api version is " .. current_api)
dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. min_api .. " or later required.")
dt.control.sleep(2000) -- allow time for the error to display before script_manager writes it's error message
error("Minimum API " .. min_api .. " not met for " .. script_name .. ".", 0)
end
end
dtutils.libdoc.functions["check_max_api_version"] = {
Name = [[check_max_api_version]],
Synopsis = [[check the maximum required api version against the current api version]],
Usage = [[local du = require "lib/dtutils"
local result = du.check_max_api_version(max_api, script_name)
max_api - string - the api version that the application was written for (example: "5.0.0")
script_name - string - the name of the script]],
Description = [[check_max_api_version compares the maximum api required for the appllication to
run against the current api version. This function is used when a part of the Lua API that
the script relies on is removed. If the maximum api version is not met, then an
error message is printed saying the script_name failed to load, then an error is thrown causing the
program to stop executing.]],
Return_Value = [[result - true if the maximum api version is available, false if not.]],
Limitations = [[When using the default handler on a script being executed from the luarc file, the error thrown
will stop the luarc file from executing any remaining statements. This limitation does not apply to script_manger.]],
Example = [[check_max_api_version("9.0.0") does nothing if the api is less than or equal to 9.0.0 otherwise an
error message is printed and an error is thrown stopping execution of the script.]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils.check_max_api_version(max_api, script_name)
local current_api = dt.configuration.api_version_string
if dtutils.compare_api_versions(current_api, max_api) > 0 then
dt.print_error("This application is written for lua api version " .. max_api .. " or earlier.")
dt.print_error("The current lua api version is " .. current_api)
dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. max_api .. " or earlier required.")
dt.control.sleep(2000) -- allow time for the error to display before script_manager writes it's error message
error("Maximum API " .. max_api .. " not met for " .. script_name .. ".", 0)
end
end
dtutils.libdoc.functions["split"] = {
Name = [[split]],
Synopsis = [[split a string on a specified separator]],
@@ -184,7 +220,8 @@ function dtutils.prequire(req_name)
if status then
log.msg(log.info, "Loaded " .. req_name)
else
log.msg(log.info, "Error loading " .. req_name)
log.msg(log.error, "Error loading " .. req_name)
log.msg(log.error, "Error returned is " .. lib)
end
return status, lib
end
@@ -283,4 +320,148 @@ function dtutils.check_os(operating_systems)
return false
end
dtutils.libdoc.functions["find_image_by_id"] = {
Name = [[find_image_by_id]],
Synopsis = [[look up an image by ID in the database]],
Usage = [[local du = require "lib/dtutils"
local img = du.find_image_by_id(imgid)
id - int - the ID to look up
]],
Description = [[find_image_by_id looks up an image by ID in the database.]],
Return_Value = [[result - dt_lua_image_t - image with the given ID if found, nil if not]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils.find_image_by_id(imgid)
if #dt.database == 0 or imgid > dt.database[#dt.database].id then
return nil
end
if dt.configuration.api_version_string >= "6.2.0" then
return dt.database.get_image(imgid)
else
local min = 1
local max = #dt.database
while (max-min)//2 > 0 do
local mid = min + (max-min)//2
local midID = dt.database[mid].id
if imgid == midID then
return dt.database[mid]
elseif imgid < midID then
max = mid-1
else
min = mid+1
end
end
if dt.database[min].id == imgid then
return dt.database[min]
elseif dt.database[max].id == imgid then
return dt.database[max]
else
return nil
end
end
end
dtutils.libdoc.functions["deprecated"] = {
Name = [[deprecated]],
Synopsis = [[print deprecation warning]],
Usage = [[local du = require "lib/dtutils"
du.deprecated(script_name, removal_string)
script_name - name of the script being deprecated
removal_string - a string explaining when the script will be removed]],
Description = [[deprecated prints an error message saying the script is deprecated and when it will be removed]],
Return_Value = [[]],
Limitations = [[]],
Example = [[local du = require "lib/dtutils"
du.deprecated("contrib/rename-tags.lua", "darktable release 4.0")]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils.deprecated(script_name, removal_string)
dt.print_toast("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
dt.print_error("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
end
dtutils.libdoc.functions["gen_uuid"] = {
Name = [[gen_uuid]],
Synopsis = [[generate a UUID string]],
Usage = [[local du = require "lib/dtutils"
uuid = du.gen_uuid(case)
case - "upper" or "lower" to specify the case of the UUID string]],
Description = [[gen_uuid prints an error message saying the script is gen_uuid and when it will be removed]],
Return_Value = [[uuid - string - a hexidecimal string representing the UUID in the requested case]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[https://gist.github.com/jrus/3197011]],
License = [[]],
Copyright = [[]],
}
function dtutils.gen_uuid(case)
local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
-- seed with os.time in seconds and add an extra degree of random for multiple calls in the same second
math.randomseed(os.time(), math.random(0, 65536))
local uuid = string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
return string.format('%x', v)
end
)
if case and case == "upper" then
uuid = string.upper(uuid)
end
return uuid
end
dtutils.libdoc.functions["compare_api_versions"] = {
Name = [[compare_api_versions]],
Synopsis = [[compare two API version strings]],
Usage = [[local du = require "lib/dtutils"
local result = du.compare_api_versions(version1, version2)
version1 - string - the first version string to compare (example: "5.0.0")
version2 - string - the second version string to compare (example: "5.1.0")]],
Description = [[compare_api_versions compares two version strings and returns 1 if version1 is greater,
-1 if version2 is greater, and 0 if they are equal.]],
Return_Value = [[result - 1 if version1 is greater, -1 if version2 is greater, 0 if they are equal.]],
Limitations = [[]],
Example = [[compare_api_versions("5.0.0", "5.1.0") returns -1]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils.compare_api_versions(version1, version2)
local v1 = {}
for num in version1:gmatch("%d+") do table.insert(v1, tonumber(num)) end
local v2 = {}
for num in version2:gmatch("%d+") do table.insert(v2, tonumber(num)) end
for i = 1, math.max(#v1, #v2) do
local num1 = v1[i] or 0
local num2 = v2[i] or 0
if num1 > num2 then
return 1
elseif num1 < num2 then
return -1
end
end
return 0
end
return dtutils

View File

@@ -24,25 +24,62 @@ dtutils_file.libdoc = {
functions = {}
}
local gettext = dt.gettext
local gettext = dt.gettext.gettext
du.check_min_api_version("5.0.0", "dtutils.file")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("dtutils.file",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("dtutils.file", msgid)
return gettext(msgid)
end
dtutils_file.libdoc.functions["is_dir"] = {
Name = [[is_dir]],
Synopsis = [[check if a path is a directory]],
--[[
local function to run a test command on windows and return a true if it succeeds
instead of returning true if it runs
]]
local function _win_os_execute(cmd)
local result = nil
local p = io.popen(cmd)
local output = p:read("*a")
p:close()
if string.match(output, "true") then
result = true
else
result = false
end
return result
end
--[[
local function to determine if a path name is a windows executable
]]
local function _is_windows_executable(path)
local result = false
if dtutils_file.test_file(path, "f") then
if string.match(path, ".exe$") or string.match(path, ".EXE$") or
string.match(path, ".com$") or string.match(path, ".COM$") or
string.match(path, ".bat$") or string.match(path, ".BAT$") or
string.match(path, ".cmd$") or string.match(path, ".CMD$") then
result = true
end
end
return result
end
dtutils_file.libdoc.functions["test_file"] = {
Name = [[test_file]],
Synopsis = [[test a file to see what it is]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.is_dir(path)
path - string - the path to check]],
Description = [[is_dir checks a path to see if it is a directory]],
local result = df.test_file(path, test)
path - string - the path to check
test - one of d, e, f, x where
d - directory
e - exists
f - file
x - executable]],
Description = [[test_file checks a path to see if it is a directory]],
Return_Value = [[result - boolean - true if path is a directory, nil if not]],
Limitations = [[]],
Example = [[]],
@@ -52,125 +89,71 @@ dtutils_file.libdoc.functions["is_dir"] = {
Copyright = [[]],
}
function dtutils_file.is_dir(path)
local cmd = nil
function dtutils_file.test_file(path, test)
local cmd = "test -"
local engine = os.execute
local cmdstring = ""
if dt.configuration.running_os == "windows" then
cmd = "if exist " .. ds.sanitize(path .. "\\*")
else
cmd = "test -d " .. ds.sanitize(path)
cmd = "if exist "
engine = _win_os_execute
end
return os.execute(cmd)
end
dtutils_file.libdoc.functions["is_file"] = {
Name = [[is_file]],
Synopsis = [[check if a path is a file]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.is_file(path)
path - string - the path to check]],
Description = [[is_file checks a path to see if it is a file]],
Return_Value = [[result - boolean - true if path is a file, nil if not]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.is_file(path)
local cmd = nil
local result = false
if dt.configuration.running_os == "windows" then
if not dtutils_file.is_dir(path) then
cmd = "if exist " .. ds.sanitize(path) .. " echo true"
local p = io.popen(cmd)
output = p:read("*a")
p:close()
if string.match(output, "true") then
result = true
if test == "d" then
-- test if directory
if dt.configuration.running_os == "windows" then
cmdstring = cmd .. dtutils_file.sanitize_filename(path .. "\\*") .. " echo true"
else
cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path)
end
elseif test == "e" then
-- test exists
if dt.configuration.running_os == "windows" then
cmdstring = cmd .. dtutils_file.sanitize_filename(path) .. " echo true"
else
cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path)
end
elseif test == "f" then
-- test if file
if dt.configuration.running_os == "windows" then
if not dtutils_file.test_file(path, "d") then -- make sure it's not a directory
cmdstring = cmd .. dtutils_file.sanitize_filename(path) .. " echo true"
else
result = false
return false
end
else
return false
cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path)
end
elseif test == "x" then
-- test executable
if dt.configuration.running_os == "windows" then
return _is_windows_executable(path)
else
cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path)
end
else
result = os.execute("test -f " .. ds.sanitize(path))
dt.print_error("[test_file] unknown test " .. test)
return false
end
return result
return engine(cmdstring)
end
dtutils_file.libdoc.functions["is_executable"] = {
Name = [[is_executable]],
Synopsis = [[check if a path is a executable]],
Usage = [[local df = require "lib/dtutils.file"
--[[
local function to return a case insensitive pattern for matching
i.e. gimp becomes [Gg][Ii][Mm][Pp] which should match any capitalization
of gimp.
]]
local result = df.is_executable(path)
path - string - the path to check]],
Description = [[is_executable checks a path to see if it is an executable]],
Return_Value = [[result - boolean - true if path is an executable, nil if not]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
local function _is_windows_executable(path)
local result = nil
dt.print_log("checking " .. path)
if string.match(path, ".exe$") or string.match(path, ".EXE$") or
string.match(path, ".com$") or string.match(path, ".COM$") or
string.match(path, ".bat$") or string.match(path, ".BAT$") or
string.match(path, ".cmd$") or string.match(path, ".CMD$") then
result = true
end
return result
local function _case_insensitive_pattern(pattern)
return pattern:gsub("(.)", function(letter)
return string.format("[%s$s]", letter:lower(), letter:upper())
end)
end
function dtutils_file.is_executable(path)
local result = nil
if dt.configuration.running_os == "windows" then
if _is_windows_executable(path) then
result = true
end
else
result = os.execute("test -x " .. ds.sanitize(path))
end
return result
end
dtutils_file.libdoc.functions["check_if_bin_exists"] = {
Name = [[check_if_bin_exists]],
Synopsis = [[check if an executable exists]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.check_if_bin_exists(bin)
bin - string - the binary to check for]],
Description = [[check_if_bin_exists checks to see if the specified binary exists.
check_if_bin_exists first checks to see if a preference for the binary has been
registered and uses that if found. The presence of the file is verified, then
quoted and returned. If no preference is specified and the operating system is
linux then the which command is used to check for a binary in the path. If found
that path is returned. If no binary is found, false is returned.]],
Return_Value = [[result - string - the sanitized path of the binary, false if not found]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
--[[
local function to search windows for an executable
]]
local function _search_for_bin_windows(bin)
local result = false
@@ -185,9 +168,11 @@ local function _search_for_bin_windows(bin)
local output = p:read("*a")
p:close()
local lines = du.split(output, "\n")
local cibin = _case_insensitive_pattern(bin)
for _,line in ipairs(lines) do
if string.match(line, bin) then
if dtutils_file.is_file(line) and dtutils_file.is_executable(line) then
if string.match(line, cibin) then
dt.print_log("found win search match " .. line)
if dtutils_file.test_file(line, "f") and dtutils_file.test_file(line, "x") then
dtutils_file.set_executable_path_preference(bin, line) -- save it so we don't have to search again
return line
end
@@ -197,24 +182,33 @@ local function _search_for_bin_windows(bin)
return result
end
--[[
local function to search *nix systems for an executable
]]
local function _search_for_bin_nix(bin)
local result = false
local p = io.popen("which " .. bin)
local p = io.popen("command -v " .. bin)
local output = p:read("*a")
p:close()
if string.len(output) > 0 then
local spath = dtutils_file.sanitize_filename(output:sub(1,-2))
if dtutils_file.is_file(spath) and dtutils_file.is_executable(spath) then
local spath = dtutils_file.sanitize_filename(output:sub(1, -2))
if dtutils_file.test_file(spath, "f") and dtutils_file.test_file(spath, "x") then
dtutils_file.set_executable_path_preference(bin, spath)
result = spath
end
end
return result
end
--[[
local function to search macos systems for an executable
]]
local function _search_for_bin_macos(bin)
local result = false
result = _search_for_bin_nix(bin)
result = _search_for_bin_nix(bin) -- see if it's in the path
if not result then
local search_start = "/Applications"
@@ -229,9 +223,9 @@ local function _search_for_bin_macos(bin)
local lines = du.split(output, "\n")
for _,line in ipairs(lines) do
local spath = dtutils_file.sanitize_filename(line:sub(1, -2))
if dtutils_file.is_executable(spath) then
dtutils_file.set_executable_path_preference(bin, spath)
local spath = dtutils_file.sanitize_filename(line:sub(1, -1))
if dtutils_file.test_file(spath, "x") then
dtutils_file.set_executable_path_preference(bin, spath) -- save it so we don't have to search again
result = spath
end
end
@@ -240,11 +234,19 @@ local function _search_for_bin_macos(bin)
return result
end
--[[
local function to provide a generic search call that can be
split into operating system specific calls
]]
local function _search_for_bin(bin)
local result = false
if dt.configuration.running_os == "windows" then
result = _search_for_bin_windows(bin)
if result then
result = dtutils_file.sanitize_filename(result)
end
elseif dt.configuration.running_os == "macos" then
result = _search_for_bin_macos(bin)
else
@@ -254,6 +256,11 @@ local function _search_for_bin(bin)
return result
end
--[[
local function to check if an executable path is
a windows executable on linux or macos, thus requiring wine to run
]]
local function _check_path_for_wine_bin(path)
local result = false
@@ -268,6 +275,12 @@ local function _check_path_for_wine_bin(path)
return result
end
--[[
local function to check if an executable path is
a valid executable. Some generic checks are done before
system specific checks are done.
]]
local function _check_path_for_bin(bin)
local result = false
local path = nil
@@ -279,13 +292,13 @@ local function _check_path_for_bin(bin)
else
path = dtutils_file.get_executable_path_preference(bin)
-- reset path preference is the returned preference is a directory
if dtutils_file.is_dir(path) then
if dtutils_file.test_file(path, "d") then
dtutils_file.set_executable_path_preference(bin, "")
path = nil
end
end
if path and dtutils_file.is_dir(path) then
if path and dtutils_file.test_file(path, "d") then
path = nil
end
@@ -294,7 +307,7 @@ local function _check_path_for_bin(bin)
end
if path and not result then
if dtutils_file.is_executable(path) then
if dtutils_file.test_file(path, "x") then
result = dtutils_file.sanitize_filename(path)
end
end
@@ -302,137 +315,108 @@ local function _check_path_for_bin(bin)
return result
end
--[[
local function to the old check_if_bin_exists functionality
on windows in order to decrease the amount of windows being
created and destroyed by system calls.
]]
local function _old_check_if_bin_exists(bin) -- only run on windows if preference checked
local result = false
local path = nil
if string.match(bin, "\\") then
path = bin
else
path = dtutils_file.get_executable_path_preference(bin)
end
if string.len(path) > 0 then
if dtutils_file.check_if_file_exists(path) then
if (string.match(path, ".exe$") or string.match(path, ".EXE$")) then
result = dtutils_file.sanitize_filename(path)
end
end
end
return result
end
dtutils_file.libdoc.functions["check_if_bin_exists"] = {
Name = [[check_if_bin_exists]],
Synopsis = [[check if an executable exists]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.check_if_bin_exists(bin)
bin - string - the binary to check for]],
Description = [[check_if_bin_exists checks to see if the specified binary exists.
check_if_bin_exists first checks to see if a preference for the binary has been
registered and uses that if found, after it's verified to be an executable and
exist. If no preference exissts, the user's path is checked for the executable.
If the executable is not found in the users path, then a search of the operating
system is conducted to see if the executable can be found.
If an executalble is found, it's verified to exist and be an executable. Once
the executable is verified, the path is saved as a preference to speed up
subsequent checks. The executable path is sanitized and returned.
If no executable is found, false is returned.]],
Return_Value = [[result - string - the sanitized path of the binary, false if not found]],
Limitations = [[If more than one executable that satisfies the search results is found, the
wrong one may be returned. If the wrong value is returned, the user can still specify the
correct execuable using tools/executable_manager. Most packages are well behaved with the
notiable exception being GIMP on windows. Depending on the packager there are multiple
gimp executables, often with version numbers. In this case, the user needs to specify
the location of the correct executable using executable_manager.]],
Example = [[]],
See_Also = [[executable_manager]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.check_if_bin_exists(bin)
local result = false
result = _check_path_for_bin(bin)
if dt.configuration.running_os == "windows" and dt.preferences.read("dtutils.file", "use_old_check_if_bin_exists", "bool") then
result = _old_check_if_bin_exists(bin)
else
if not result then
result = _search_for_bin(bin)
result = _check_path_for_bin(bin)
if not result then
result = _search_for_bin(bin)
end
end
return result
end
dtutils_file.libdoc.functions["split_filepath"] = {
Name = [[split_filepath]],
Synopsis = [[split a filepath into parts]],
Usage = [[local df = require "lib/dtutils.file"
-- the following path, filename, etc functions have
-- moved to the string library since they are string
-- manipulation functions, and to prevent circular
-- library inclusiion.
local result = df.split_filepath(filepath)
filepath - string - path and filename]],
Description = [[split_filepath splits a filepath into the path, filename, basename and filetype and puts
that in a table]],
Return_Value = [[result - table - a table containing the path, filename, basename, and filetype]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
-- these functions are left here for compatibility
-- with older scripts
function dtutils_file.split_filepath(str)
-- strip out single quotes from quoted pathnames
str = string.gsub(str, "'", "")
str = string.gsub(str, '"', '')
local result = {}
-- Thank you Tobias Jakobs for the awesome regular expression, which I tweaked a little
result["path"], result["filename"], result["basename"], result["filetype"] = string.match(str, "(.-)(([^\\/]-)%.?([^%.\\/]*))$")
if result["basename"] == "" and result["filetype"]:len() > 1 then
result["basename"] = result["filetype"]
result["filetype"] = ""
end
return result
return ds.split_filepath(str)
end
dtutils_file.libdoc.functions["get_path"] = {
Name = [[get_path]],
Synopsis = [[get the path from a file path]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.get_path(filepath)
filepath - string - path and filename]],
Description = [[get_path strips the filename and filetype from a path and returns the path]],
Return_Value = [[result - string - the path]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.get_path(str)
local parts = dtutils_file.split_filepath(str)
return parts["path"]
return ds.get_path(str)
end
dtutils_file.libdoc.functions["get_filename"] = {
Name = [[get_filename]],
Synopsis = [[get the filename and extension from a file path]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.get_filename(filepath)
filepath - string - path and filename]],
Description = [[get_filename strips the path from a filepath and returns the filename]],
Return_Value = [[result - string - the file name and type]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.get_filename(str)
local parts = dtutils_file.split_filepath(str)
return parts["filename"]
return ds.get_filename(str)
end
dtutils_file.libdoc.functions["get_basename"] = {
Name = [[get_basename]],
Synopsis = [[get the filename without the path or extension]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.get_basename(filepath)
filepath - string - path and filename]],
Description = [[get_basename returns the name of the file without the path or filetype
]],
Return_Value = [[result - string - the basename of the file]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.get_basename(str)
local parts = dtutils_file.split_filepath(str)
return parts["basename"]
return ds.get_basename(str)
end
dtutils_file.libdoc.functions["get_filetype"] = {
Name = [[get_filetype]],
Synopsis = [[get the filetype from a filename]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.get_filetype(filepath)
filepath - string - path and filename]],
Description = [[get_filetype returns the filetype from the supplied filepath]],
Return_Value = [[result - string - the filetype]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.get_filetype(str)
local parts = dtutils_file.split_filepath(str)
return parts["filetype"]
return ds.get_filetype(str)
end
@@ -535,9 +519,9 @@ function dtutils_file.file_copy(fromFile, toFile)
local result = nil
-- if cp exists, use it
if dt.configuration.running_os == "windows" then
result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"')
result = os.execute('copy ' .. dtutils_file.sanitize_filename(fromFile) .. ' ' .. dtutils_file.sanitize_filename(toFile))
elseif dtutils_file.check_if_bin_exists("cp") then
result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'")
result = os.execute("cp " .. dtutils_file.sanitize_filename(fromFile) .. ' ' .. dtutils_file.sanitize_filename(toFile))
end
-- if cp was not present, or if cp failed, then a pure lua solution
@@ -588,7 +572,7 @@ function dtutils_file.file_move(fromFile, toFile)
if not success then
-- an error occurred, so let's try using the operating system function
if dtutils_file.check_if_bin_exists("mv") then
success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'")
success = os.execute("mv " .. dtutils_file.sanitize_filename(fromFile) .. ' ' .. dtutils_file.sanitize_filename(toFile))
end
-- if the mv didn't exist or succeed, then...
if not success then
@@ -828,7 +812,7 @@ dtutils_file.libdoc.functions["mkdir"] = {
function dtutils_file.mkdir(path)
if not dtutils_file.check_if_file_exists(path) then
local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p"
return dsys.external_command(mkdir_cmd.." "..path)
return dsys.external_command(mkdir_cmd.." "..dtutils_file.sanitize_filename(path))
else
return 0
end
@@ -853,8 +837,54 @@ dtutils_file.libdoc.functions["rmdir"] = {
function dtutils_file.rmdir(path)
local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r"
return dsys.external_command(rm_cmd.." "..path)
return dsys.external_command(rm_cmd.." "..dtutils_file.sanitize_filename(path))
end
dtutils_file.libdoc.functions["create_tmp_file"] = {
Name = [[create_tmp_file]],
Synopsis = [[creates a temporary file]],
Usage = [[local df = require "lib/dtutils.file
local result = df.create_tmp_file()]],
Description = [[create_tmp_file can be used to create temporary files]],
Return_Value = [[result - string - path to the created temporary file.]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.create_tmp_file()
local tmp_file = os.tmpname()
local f = io.open(tmp_file, "w")
if not f then
log.msg(log.error, string.format("Error writing to `%s`", tmp_file))
os.remove(tmp_file)
return nil
end
return tmp_file
end
--[[
The new check_if_bin_exists() does multiple calls to the operating system to check
if the file exists and is an executable. On windows, each call to the operating system
causes a window to open in order to run the command, then the window closes when the
command exits. If the user gets annoyed by the "flickering windows", then they can
enable this preference to use the old check_if_bin_exists() that relys on the
executable path preferences and doesn't do as many checks.
]]
if dt.configuration.running_os == "windows" then
dt.preferences.register("dtutils.file", "use_old_check_if_bin_exists", "bool",
"lua scripts use old check_if_bin_exists()",
"lessen flickering windows effect when scripts run",
false)
end
return dtutils_file

View File

@@ -17,11 +17,11 @@ dtutils_log.libdoc = {
print out warning, error and success messages as code is running
log.log_level(debug)
log.log_level(log.debug)
print out debugging messages too because this isnt working
log.log_level(info)
log.log_level(log.info)
I want to make sure this is working ok
@@ -51,54 +51,63 @@ local dt_print_error = dt.print_error
local dt_print_log = dt.print_log
local dt_print = dt.print
-- set the default log levels
dtutils_log.debug = {
label = "DEBUG:",
label = "DEBUG: ",
enabled = false,
engine = dt_print_log,
caller_info = 3,
level = 1,
}
dtutils_log.info = {
label = "INFO:",
label = "INFO: ",
enable = false,
engine = dt_print_log,
caller_info = 1,
level = 2,
}
dtutils_log.warn = {
label = "WARN:",
label = "WARN: ",
enabled = false,
engine = dt_print_log,
caller_info = 2,
level = 3,
}
dtutils_log.error = {
label = "ERROR:",
label = "ERROR: ",
enabled = true,
engine = dt_print_error,
engine = dt_print_log,
caller_info = 3,
level = 4,
}
dtutils_log.success = {
label = "SUCCESS:",
label = "SUCCESS: ",
enabled = true,
engine = dt_print_log,
caller_info = 2,
level = 5,
}
dtutils_log.screen = {
label = "",
enabled = true,
engine = dt_print,
caller_info = 0,
level = 9,
}
dtutils_log.always = {
label = "",
enabled = true,
engine = dt_print_log,
caller_info = 3,
level = 9,
}
dtutils_log.critical = {
label = "CRITICAL:",
label = "CRITICAL: ",
enabled = true,
engine = print,
caller_info = 3,
level = 9,
}
@@ -120,7 +129,7 @@ result = log.caller(level)
Copyright = [[]],
}
function dtutils_log.caller(level)
function dtutils_log.caller(level, info)
local name = debug.getinfo(level).name
local lineno = nil
local source = nil
@@ -131,7 +140,16 @@ function dtutils_log.caller(level)
-- we just need the filename, so grab it from the string
-- Thanks, Tobias Jakobs :-)
source = string.match(source, "@.-([^\\/]-%.?[^%.\\/]*)$")
return name .. ": " .. source .. ": " .. lineno .. ":"
if info == 0 then
return ""
elseif info == 1 then
return source .. ": "
elseif info == 2 then
return source .. ": " .. name .. ": "
else
return source .. ": " .. name .. ": " .. lineno .. ":"
end
else
return "callback:"
end
@@ -181,8 +199,8 @@ function dtutils_log.msg(level, ...)
table.remove(args, 1)
end
local log_msg = level.label
if level.engine ~= dt_screen and call_level ~= 0 then
log_msg = log_msg .. dtutils_log.caller(call_level) .. " "
if level.engine ~= dt_print and call_level ~= 0 then
log_msg = log_msg .. dtutils_log.caller(call_level, level.caller_info) .. " "
elseif log_msg:len() > 2 then
log_msg = log_msg .. " "
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
local dtutils_system = {}
local dt = require "darktable"
require "official/yield" -- necessary for dt.control.execute()
local ds = require "lib/dtutils.string"
dtutils_system.libdoc = {
Name = [[dtutils.system]],
@@ -86,8 +86,15 @@ function dtutils_system.windows_command(command)
local file = io.open(fname, "w")
if file then
dt.print_log("opened file")
command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell
file:write(command)
file:write("@echo off\n")
file:write('for /f "tokens=2 delims=:." %%x in (\'chcp\') do set cp=%%x\n')
file:write("chcp 65001>nul\n") -- change the encoding of the terminal to handle non-english characters in path
file:write("\n")
file:write(command .. "\n")
file:write("set COMMAND_RC=%ERRORLEVEL%")
file:write("\n")
file:write("chcp %cp%>nul\n")
file:write("exit /b %COMMAND_RC%") -- otherwise, when the real command fails, script will still return "good" error code of chcp
file:close()
result = dt.control.execute(fname)
@@ -119,6 +126,7 @@ dtutils_system.libdoc.functions["launch_default_app"] = {
License = [[]],
Copyright = [[]],
}
function dtutils_system.launch_default_app(path)
local open_cmd = "xdg-open "
if (dt.configuration.running_os == "windows") then
@@ -129,5 +137,4 @@ function dtutils_system.launch_default_app(path)
return dtutils_system.external_command(open_cmd .. path)
end
return dtutils_system

View File

@@ -1,39 +1,78 @@
# OpenInExplorer Darktable Plug-In.
# Copyright (C) 2020 Volker Lenhardt
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Volker Lenhardt <volker.lenhardt@unitybox.de>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: OpenInExplorer\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-19 14:43+0200\n"
"POT-Creation-Date: 2020-06-11 19:07+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Volker Lenhardt <volker.lenhardt@unitybox.de>\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: OpenInExplorer.lua:64
#: OpenInExplorer.lua:71
msgid ""
"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"
msgstr "OpenInExplorer-Plug-in kann nur auf macOS, Linux oder Windows laufen"
"OpenInExplorer plug-in only supports Linux and macOS, and Windows at this time"
msgstr "OpenInExplorer-Plug-in unterstützt zur Zeit nur Linux, macOS oder Windows"
#: OpenInExplorer.lua:79
#: OpenInExplorer.lua:87
msgid ""
"No links directory selected.\n"
"Please check the dt preferences (lua options)"
msgstr "Kein Verknüpfungs-Verzeichnis ausgewählt.\nBitte die dt-Voreinstellungen überprüfen (Lua-Optionen)"
#: OpenInExplorer.lua:91
#, lua-format
msgid ""
"Links directory '%s' not found.\n"
"Please check the dt preferences (lua options)"
msgstr "Das Verknüpfungs-Verzeichnis '%s' konnte nicht gefunden werden.\nBitte die dt-Voreinstellungen überprüfen (Lua-Optionen)"
#: OpenInExplorer.lua:148
msgid "Failed to create links. Missing rights?"
msgstr "Die Verknüpfungen konnten nicht erstellt werden. Fehlende Rechte?"
#: OpenInExplorer.lua:164
msgid "Please select an image"
msgstr "Es wurde kein Bild ausgewählt"
#: OpenInExplorer.lua:98
msgid "Please select fewer images (max 15)"
#: OpenInExplorer.lua:170
msgid "Please select fewer images (max. 15)"
msgstr "Bitte nicht mehr als 15 Bilder auswählen"
#: OpenInExplorer.lua:105
#: OpenInExplorer.lua:186
msgid "show in file explorer"
msgstr "im Dateimanager anzeigen"
#: OpenInExplorer.lua:107
msgid "Opens the file manager at the selected image's location"
#: OpenInExplorer.lua:188
msgid "Open the file manager at the selected image's location"
msgstr "Öffnet den Dateimanager an der Position des ausgewählten Bildes"
#: OpenInExplorer.lua:193
msgid "OpenInExplorer: linked files directory"
msgstr "OpenInExplorer: Verknüpfungs-Verzeichnis"
#: OpenInExplorer.lua:194
msgid ""
"Directory to store the links to the file names. Requires restart to take "
"effect"
msgstr "Verzeichnis für die Verknüpfungen zu den Dateinamen. Erfordert dt-Neustart"
#: OpenInExplorer.lua:197
msgid "Select directory"
msgstr "Verzeichnis auswählen"
#: OpenInExplorer.lua:203
msgid "OpenInExplorer: use links"
msgstr "OpenInExplorer: Verknüpfungen nutzen"
#: OpenInExplorer.lua:204
msgid "Use links instead of multiple windows. Requires restart to take effect"
msgstr "Verknüpfungen statt einzelner Fenster nutzen. Erfordert dt-Neustart"

View File

@@ -14,7 +14,7 @@ msgstr ""
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n"
"Language: de_DE\n"

View File

@@ -0,0 +1,73 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Lua photils\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-12 13:12+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "get tags"
msgstr "hole stichwörter"
msgid "requires a restart to be applied"
msgstr "erfordert einen Neustart um angewendet zu werden"
msgid "min confidence value"
msgstr "min vertrauenswert"
msgid ""
"The suggested tags were not generated\n"
" for the currently selected image!"
msgstr ""
"Die vorgeschlagenen Stichwörter wurden für das\n"
"aktuell ausgewählte Bild nicht generiert!"
#, lua-format
msgid " page %s of %s "
msgstr " seite %s von %s "
msgid "Tags successfully attached to image"
msgstr "Stichwörter erfolgreich an Bild angefügt"
#, lua-format
msgid "Error writing to `%s`"
msgstr "Fehler beim Schreiben in `%s`"
#, lua-format
msgid "%s failed, see terminal output for details"
msgstr "%s fehlgeschlagen, siehe Terminal-Ausgabe für Details"
#, lua-format
msgid "%s found %d tags for your image"
msgstr "%s gefunden %d-Stichwörter für Ihr Bild"
msgid "No image selected."
msgstr "Kein Bild ausgewählt."
msgid "This plugin can only handle a single image."
msgstr "Dieses Plugin kann nur ein einziges Bild verarbeiten."
#, lua-format
msgid "attach %d tags"
msgstr "%d stichwörter anhängen"
msgid "photils-cli not found"
msgstr "photils-cli nicht gefunden"
msgid ""
"Select an image, click \"Get Tags\" and get \n"
"suggestions for tags."
msgstr ""
"Wählen Sie ein Bild aus, drücken Sie \"Hole Stichwörter\"\n"
" und erhalten Sie Vorschläge für Stichwörter."

View File

@@ -0,0 +1,12 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
msgid "select non existing images"
msgstr "nicht vorhandene Bilder auswählen"
msgid "select non existing"
msgstr "nicht vorhandene auswählen"
msgid "select all non-existing images in the current images"
msgstr "alle nicht vorhandenen Bilder in den aktuellen Bildern auswählen"

View File

@@ -0,0 +1,85 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2022-06-27 17:22+0200\n"
"PO-Revision-Date: 2022-06-27 17:36+0200\n"
"Last-Translator: Manuel PINTOR <manu@lepassepartout.fr>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"X-Poedit-Basepath: ../../../contrib\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SearchPath-0: OpenInExplorer.lua\n"
#: OpenInExplorer.lua:79
msgid ""
"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"
msgstr ""
"Le script OpenInExplorer ne fonctionne que sur Linux, macOS et Windows pour "
"le moment"
#: OpenInExplorer.lua:95
msgid ""
"No links directory selected.\n"
"Please check the dt preferences (lua options)"
msgstr ""
"Aucun répertoire de liens sélectionné.\n"
"Voir dans les préférences dt > options lua"
#: OpenInExplorer.lua:99
#, lua-format
msgid ""
"Links directory '%s' not found.\n"
"Please check the dt preferences (lua options)"
msgstr ""
"Répertoire de liens '%s' inexistant.\n"
"Voir dans les préférences dt > options lua"
#: OpenInExplorer.lua:156
msgid "Failed to create links. Missing rights?"
msgstr "Erreur de création de liens. Droits insuffisants ?"
#: OpenInExplorer.lua:172
msgid "Please select an image"
msgstr "Sélectionner une image SVP"
#: OpenInExplorer.lua:178
msgid "Please select fewer images (max. 15)"
msgstr "Sélectionner moins d'images SVP (max. 15)"
#: OpenInExplorer.lua:203
msgid "show in file explorer"
msgstr "afficher dans le gestionnaire de fichiers"
#: OpenInExplorer.lua:205
msgid "Open the file manager at the selected image's location"
msgstr "Ouvre le gestionnaire de fichiers à l'endroit de l'image sélectionnée"
#: OpenInExplorer.lua:212
msgid "OpenInExplorer: linked files directory"
msgstr "OpenInExplorer : répertoire des fichiers liés"
#: OpenInExplorer.lua:213
msgid ""
"Directory to store the links to the file names. Requires restart to take "
"effect"
msgstr ""
"Répertoire dans lequel enregistrer les liens sur les fichiers. Nécessite un "
"redémarrage de dt"
#: OpenInExplorer.lua:216
msgid "Select directory"
msgstr "Sélectionner un répertoire"
#: OpenInExplorer.lua:222
msgid "OpenInExplorer: use links"
msgstr "OpenInExplorer : utiliser des liens"
#: OpenInExplorer.lua:223
msgid "Use links instead of multiple windows. Requires restart to take effect"
msgstr ""
"Utiliser des liens plutôt que de multiples fenêtres. Nécessite un "
"redémarrage de dt"

View File

@@ -7,21 +7,29 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-07 22:25-0400\n"
"PO-Revision-Date: 2016-10-03 14:55+0200\n"
"Last-Translator: Pascal Obry <pascal@obry.net>\n"
"POT-Creation-Date: 2020-12-27 17:55+0100\n"
"PO-Revision-Date: 2020-12-27 17:55+0100\n"
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.9\n"
"Language: fr_FR\n"
"X-Generator: Poedit 2.3\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: gettext;dgettext:2;dcgettext:2ngettext:1,2;"
"X-Poedit-KeywordsList: _;gettext;dgettext:2;dcgettext:2ngettext:1,2;"
"dngettext:2,3\n"
"X-Poedit-Basepath: .\n"
"Language-Team: \n"
"X-Poedit-Basepath: ../../../contrib\n"
"X-Poedit-SearchPath-0: clear_GPS.lua\n"
#: clear_GPS.lua:62
#: clear_GPS.lua:67
msgid "clear GPS data"
msgstr "effacer données GPS"
#: clear_GPS.lua:69
msgid "Clear GPS data from selected images"
msgstr "Effacer les données GPS des images sélectionnées"
#: clear_GPS.lua:75
msgid "Clear GPS data"
msgstr "Effacer les données GPS"

View File

@@ -0,0 +1,78 @@
# French messages for copy_attach_detach_tags.lua
# Christophe Agathon <christophe.agathon@gmail.com>, 2021
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2021-01-10 19:39+0100\n"
"PO-Revision-Date: 2021-01-10 19:43+0100\n"
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"X-Poedit-Basepath: ../../../contrib\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-KeywordsList: _\n"
"X-Poedit-SearchPath-0: copy_attach_detach_tags.lua\n"
#: copy_attach_detach_tags.lua:93
msgid "Image tags copied ..."
msgstr "Mots-clés de l'image copiés ..."
#: copy_attach_detach_tags.lua:115
msgid "No tag to attach, please copy tags first."
msgstr "Pas de mot-clé à attacher, veuillez d'abord copier les mots-clés."
#: copy_attach_detach_tags.lua:139
msgid "Tags attached ..."
msgstr "Mots-clés attachés ..."
#: copy_attach_detach_tags.lua:155
msgid "Tags removed from image(s)."
msgstr "Mots-clés supprimés des images."
#: copy_attach_detach_tags.lua:161
msgid "Tags replaced"
msgstr "Mots-clés remplacés"
#: copy_attach_detach_tags.lua:166
msgid "tagging addon"
msgstr "mots-clés extra"
#: copy_attach_detach_tags.lua:189
msgid "tag clipboard"
msgstr "presse-papier des mots-clés"
#: copy_attach_detach_tags.lua:197
msgid "multi copy tags"
msgstr "copier mots-clés"
#: copy_attach_detach_tags.lua:198 copy_attach_detach_tags.lua:252
msgid "copy tags from selected image(s)"
msgstr "copier les mots-clés des images sélectionnnées"
#: copy_attach_detach_tags.lua:201 copy_attach_detach_tags.lua:257
msgid "paste tags to selected image(s)"
msgstr "coller les mots clés dans les images sélectionnées"
#: copy_attach_detach_tags.lua:202
msgid "paste tags"
msgstr "coller mots-clés"
#: copy_attach_detach_tags.lua:210
msgid "replace tags"
msgstr "remplacer mots-clés"
#: copy_attach_detach_tags.lua:211 copy_attach_detach_tags.lua:267
msgid "replace tags from selected image(s)"
msgstr "remplacer les mots-clés des images sélectionnées"
#: copy_attach_detach_tags.lua:214
msgid "remove all tags"
msgstr "supprimer tous mots-clés"
#: copy_attach_detach_tags.lua:215 copy_attach_detach_tags.lua:262
msgid "remove tags from selected image(s)"
msgstr "supprimer les mots-clés des images sélectionnées"

View File

@@ -0,0 +1,35 @@
# French messages for copy_paste_metadata.lua
# Christophe Agathon <christophe.agathon@gmail.com>, 2020
msgid ""
msgstr ""
"Project-Id-Version: copy_paste_metadata\n"
"POT-Creation-Date: 2020-12-26 12:49+0100\n"
"PO-Revision-Date: 2020-12-26 12:52+0100\n"
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"X-Poedit-Basepath: ../../../official\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-KeywordsList: _\n"
"X-Poedit-SearchPath-0: copy_paste_metadata.lua\n"
#: copy_paste_metadata.lua:115
msgid "copy metadata"
msgstr "copier les métadonnées"
#: copy_paste_metadata.lua:117
msgid "copy metadata of the first selected image"
msgstr "copier les métadonnées de la première image sélectionnée"
#: copy_paste_metadata.lua:121
msgid "paste metadata"
msgstr "coller les métadonnées"
#: copy_paste_metadata.lua:123
msgid "paste metadata to the selected images"
msgstr "coller les métadonnées vers les images sélectionnées"

View File

@@ -0,0 +1,117 @@
# French messages for photils.lua
# Christophe Agathon <christophe.agathon@gmail.com>, 2021
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2021-08-13 09:04+0200\n"
"PO-Revision-Date: 2021-08-13 09:49+0200\n"
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.1\n"
"X-Poedit-Basepath: ../../../contrib\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SearchPath-0: photils.lua\n"
#: photils.lua:117
msgid "get tags"
msgstr "trouver des mots-clés"
#: photils.lua:148
msgid "requires a restart to be applied"
msgstr "nécessite un redémarrage pour être pris en compte"
#: photils.lua:163
msgid "min confidence value"
msgstr "niveau de confiance minimum"
#: photils.lua:180
msgid ""
"The suggested tags were not generated\n"
" for the currently selected image!"
msgstr ""
"La proposition de mots-clés n'a pas été générée\n"
" pour l'image sélectionnée actuellement !"
#: photils.lua:186
#, lua-format
msgid " page %s of %s "
msgstr " page %s de %s "
#: photils.lua:237
msgid "Apply tag to image"
msgstr "Appliquer les mots-clés à l'image"
#: photils.lua:249
msgid "Tags successfully attached to image"
msgstr "Les mots-clés ont bien été ajoutés à l'image"
#: photils.lua:299
#, lua-format
msgid "%s found %d tags for your image"
msgstr "%s a trouvé %d mots-clés pour votre image"
#: photils.lua:315
msgid "No image selected."
msgstr "Pas d'image sélectionnée."
#: photils.lua:319
msgid "This plugin can only handle a single image."
msgstr "Ce plugin ne peut gérer qu'une seule image."
#: photils.lua:326
#, lua-format
msgid "%s failed, see terminal output for details"
msgstr ""
"%s a échoué, consultez les messages sur le terminal pour plus de détails"
#: photils.lua:334
#, lua-format
msgid "no tags where found"
msgstr "aucun mot-clé n'a été trouvé"
#: photils.lua:365
#, lua-format
msgid "attach %d tags"
msgstr "ajouter %d mots-clés"
#: photils.lua:429 photils.lua:430
msgid "photils-cli not found"
msgstr "photils-cli non trouvé"
#: photils.lua:432
msgid ""
"Select an image, click \"get tags\" and get \n"
"suggestions for tags."
msgstr ""
"Sélectionnez une image, cliquez sur « trouver des mots-clés » \n"
"pour obtenir une proposition de mots-clés."
#: photils.lua:467
msgid "photils: show confidence value"
msgstr "photils: afficher le niveau de confiance"
# Original message should begin with capital I
#: photils.lua:468
msgid "if enabled, the confidence value for each tag is displayed"
msgstr "Si activé, le niveau de confiance de chaque mot-clé est affiché"
#: photils.lua:474
msgid "photils: use exported image for tag request"
msgstr "photils: utiliser l'image exportée pour la recherche de mots-clés"
#: photils.lua:475
msgid ""
"If enabled, the image passed to photils for tag suggestion is based on the "
"exported, already edited image. Otherwise, the embedded thumbnail of the RAW "
"file will be used for tag suggestion.The embedded thumbnail could speedup "
"the tag suggestion but can fail if the RAW file is not supported."
msgstr ""
"Si activé, l'image passée à photils pour la proposition de mots-clés est "
"basée sur l'export de l'image déjà éditée. Sinon, c'est la vignette "
"encapsulée dans le fichier RAW qui sera utilisée. La vignette encapsulée "
"pourra accélérer la proposition des mots-clés mais la recherche échouera si "
"le fichier RAW n'est pas supporté."

View File

@@ -0,0 +1,188 @@
# French messages for script_manager.lua
# Christophe Agathon <christophe.agathon@gmail.com>, 2020
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2020-12-28 10:22+0100\n"
"PO-Revision-Date: 2020-12-28 10:24+0100\n"
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"X-Poedit-Basepath: ../../../tools\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-KeywordsList: _\n"
"X-Poedit-SearchPath-0: script_manager.lua\n"
#: script_manager.lua:190
msgid "Cant read from "
msgstr "Ne peut pas lire "
#: script_manager.lua:204
msgid "Loaded "
msgstr "Script chargé : "
#: script_manager.lua:207
msgid " failed to load"
msgstr " non chargé"
#: script_manager.lua:223
msgid " will not start when darktable is restarted"
msgstr " ne démarrera pas lorsque darktable sera redémarré"
#: script_manager.lua:281 script_manager.lua:345
msgid "find command is "
msgstr "la commande find est "
#: script_manager.lua:321
msgid "lua scripts successfully updated"
msgstr "mise à jour des scripts lua réussie"
#: script_manager.lua:381
msgid "category "
msgstr "la catégorie "
#: script_manager.lua:381
msgid " is already in use. Please specify a different category name."
msgstr " est déjà utilisée. Indiquez un autre nom de catégorie SVP."
#: script_manager.lua:410
msgid "scripts successfully installed into category "
msgstr "les scripts ont été installés dans la catégorie "
#: script_manager.lua:426
msgid "No scripts found to install"
msgstr "Nous n'avons pas trouvé de script à installer"
#: script_manager.lua:430
msgid "failed to download scripts"
msgstr "impossible de télécharger les scripts"
# Peut-être que « activé » serait mieux, mais ce n'est pas la traduction de « started »
#: script_manager.lua:458
msgid " started"
msgstr " démarré"
# Peut-être que « désactivé » serait mieux, mais ce n'est pas la traduction de « stopped »
#: script_manager.lua:460
msgid " stopped"
msgstr " arrêté"
#: script_manager.lua:545
msgid "Page "
msgstr "Page "
#: script_manager.lua:545
msgid " of "
msgstr " sur "
#: script_manager.lua:674
msgid "scripts to update"
msgstr "scripts à mettre à jour"
#: script_manager.lua:675
msgid "select the scripts installation to update"
msgstr "sélectionnez les scripts à mettre à jour"
#: script_manager.lua:684 script_manager.lua:742
msgid "update scripts"
msgstr "mettre à jour les scripts"
#: script_manager.lua:685
msgid "update the lua scripts from the repository"
msgstr "metre à jour les scripts depuis le dépôt"
#: script_manager.lua:696
msgid "enter the URL of the git repository containing the scripts you wish to add"
msgstr "indiquez l'URL du dépôt git contenant les scripts que vous souhaitez ajouter"
#: script_manager.lua:701
msgid "name of new category"
msgstr "nom de la nouvelle catégorie"
#: script_manager.lua:702
msgid "enter a category name for the additional scripts"
msgstr "indiquez un nom de catégorie pour les scripts supplémentaires"
#: script_manager.lua:707
msgid "URL to download additional scripts from"
msgstr "URL d'où seront téléchargés les scripts"
#: script_manager.lua:709
msgid "new category to place scripts in"
msgstr "nouvelle catégorie où seront placés les scripts"
#: script_manager.lua:712
msgid "install additional scripts"
msgstr "installer les scripts supplémentaires"
#: script_manager.lua:720
msgid "Enable \"Disable Scripts\" button"
msgstr "Activer le bouton « Désactiver les Scripts »"
#: script_manager.lua:730
msgid "Disable Scripts"
msgstr "Désactiver les scripts"
#: script_manager.lua:736
msgid "lua scripts will not run the next time darktable is started"
msgstr "les scripts lua ne seront pas lancés au prochain démarrage de darktable"
#: script_manager.lua:745
msgid "add more scripts"
msgstr "ajouter des scripts"
#: script_manager.lua:747
msgid "disable scripts"
msgstr "désactiver les scripts"
#: script_manager.lua:755
msgid "category"
msgstr "catégorie"
#: script_manager.lua:756
msgid "select the script category"
msgstr "sélectionner la catégorie"
#: script_manager.lua:780
msgid "Page:"
msgstr "Page :"
#: script_manager.lua:808
msgid "Scripts"
msgstr "Scripts"
#: script_manager.lua:817
msgid "scripts per page"
msgstr "scripts par page"
#: script_manager.lua:818
msgid "select number of start/stop buttons to display"
msgstr "sélectionner le nombre de boutons marche/arrêt à afficher"
#: script_manager.lua:829
msgid "change number of buttons"
msgstr "changer le nombre de boutons"
#: script_manager.lua:837
msgid "Configuration"
msgstr "Configuration"
#: script_manager.lua:858
msgid "action"
msgstr "action"
#: script_manager.lua:864
msgid "install/update scripts"
msgstr "installer/mettre à jour des scripts"
#: script_manager.lua:864
msgid "configure"
msgstr "configurer"
#: script_manager.lua:864
msgid "start/stop scripts"
msgstr "démarrer/arrêter des scripts"

View File

@@ -0,0 +1,62 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Maurizio Paglia <mpaglia0@gmail.com>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-11 22:19+0200\n"
"PO-Revision-Date: 2022-07-11 22:21+0200\n"
"Last-Translator: Maurizio Paglia <mpaglia0@gmail.com>\n"
"Language-Team: Italian <>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 21.12.3\n"
#: executable_manager.lua:144
msgid "No executable paths found, exiting..."
msgstr "Nessun percorso con eseguibili, esco..."
#: executable_manager.lua:160
msgid "select an executable"
msgstr "seleziona un eseguibile"
#: executable_manager.lua:160
msgid "search path for executable"
msgstr "cerca il percorso dell'eseguibile"
#: executable_manager.lua:169
msgid "select "
msgstr "seleziona"
#: executable_manager.lua:169
msgid " executable"
msgstr " eseguibile"
#: executable_manager.lua:190
msgid "select executable to modify"
msgstr "Seleziona l'eseguibile da modificare"
#: executable_manager.lua:205
msgid "current"
msgstr "Attuale"
#: executable_manager.lua:207
msgid "select"
msgstr "Seleziona"
#: executable_manager.lua:209
msgid "reset"
msgstr "Cancella"
#: executable_manager.lua:212
msgid "Clear path for "
msgstr "Cancella il percorso di "
#: executable_manager.lua:211
msgid "clear"
msgstr "Pulisci"

View File

@@ -0,0 +1,190 @@
# French messages for script_manager.lua
# Christophe Agathon <christophe.agathon@gmail.com>, 2020.
# Maurizio Paglia <mpaglia0@gmail.com>, 2021, 2022.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2020-12-28 10:22+0100\n"
"PO-Revision-Date: 2022-07-11 22:03+0200\n"
"Last-Translator: Maurizio Paglia <mpaglia0@gmail.com>\n"
"Language-Team: Italian <>\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Lokalize 21.12.3\n"
"X-Poedit-Basepath: ../../../tools\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-KeywordsList: _\n"
"X-Poedit-SearchPath-0: script_manager.lua\n"
#: script_manager.lua:190
msgid "Cant read from "
msgstr "Non posso leggere da "
#: script_manager.lua:204
msgid "Loaded "
msgstr "Script caricato "
#: script_manager.lua:207
msgid " failed to load"
msgstr "Non riesco a caricare lo script"
#: script_manager.lua:223
msgid " will not start when darktable is restarted"
msgstr " non verrà avviato al riavvio di darktable"
#: script_manager.lua:281 script_manager.lua:345
msgid "find command is "
msgstr "Il comando find è "
#: script_manager.lua:321
msgid "lua scripts successfully updated"
msgstr "Gli script lua sono stati correttamente aggiornati"
#: script_manager.lua:381
msgid "category "
msgstr "Categoria"
#: script_manager.lua:381
msgid " is already in use. Please specify a different category name."
msgstr " è già utilizzato. Prego specificare un nome differente."
#: script_manager.lua:410
msgid "scripts successfully installed into category "
msgstr "Gli script sono stati installati correttamente nella categoria "
#: script_manager.lua:426
msgid "No scripts found to install"
msgstr "Non trovo script da installare"
#: script_manager.lua:430
msgid "failed to download scripts"
msgstr "Non riesco a scaricare gli script"
# Peut-être que « activé » serait mieux, mais ce n'est pas la traduction de « started »
#: script_manager.lua:458
msgid " started"
msgstr " avviato"
# Peut-être que « désactivé » serait mieux, mais ce n'est pas la traduction de « stopped »
#: script_manager.lua:460
msgid " stopped"
msgstr " bloccato"
#: script_manager.lua:545
msgid "Page "
msgstr "Pag."
#: script_manager.lua:545
msgid " of "
msgstr " di "
#: script_manager.lua:674
msgid "scripts to update"
msgstr "Script da aggiornare"
#: script_manager.lua:675
msgid "select the scripts installation to update"
msgstr "Selezionare l'installazione di script da aggiornare"
#: script_manager.lua:684 script_manager.lua:742
msgid "update scripts"
msgstr "Aggiornare gli script"
#: script_manager.lua:685
msgid "update the lua scripts from the repository"
msgstr "Aggiornare gli script lua del repo"
#: script_manager.lua:696
msgid ""
"enter the URL of the git repository containing the scripts you wish to add"
msgstr "Immettere l'URL del repo git che contiene gli script da aggiungere"
#: script_manager.lua:701
msgid "name of new category"
msgstr "Nome della nuova categoria"
#: script_manager.lua:702
msgid "enter a category name for the additional scripts"
msgstr "Immettere il nome della categoria per altri script"
#: script_manager.lua:707
msgid "URL to download additional scripts from"
msgstr "URL dal quale scaricare ulteriori script"
#: script_manager.lua:709
msgid "new category to place scripts in"
msgstr "Nuova categoria che dovrà contenere gli script"
#: script_manager.lua:712
msgid "install additional scripts"
msgstr "Installare gli script aggiuntivi"
#: script_manager.lua:720
msgid "Enable \"Disable Scripts\" button"
msgstr "Abilita il pulsante \"Disabilita Script\""
#: script_manager.lua:730
msgid "Disable Scripts"
msgstr "Disabilita Script"
#: script_manager.lua:736
msgid "lua scripts will not run the next time darktable is started"
msgstr "Gli script lua non verranno avviati al prossimo riavvio di darktable"
#: script_manager.lua:745
msgid "add more scripts"
msgstr "Aggiungi altri script"
#: script_manager.lua:747
msgid "disable scripts"
msgstr "Disabilita script"
#: script_manager.lua:755
msgid "category"
msgstr "categoria"
#: script_manager.lua:756
msgid "select the script category"
msgstr "Seleziona la categoria"
#: script_manager.lua:780
msgid "Page:"
msgstr "Pag.:"
#: script_manager.lua:808
msgid "Scripts"
msgstr "Scripts"
#: script_manager.lua:817
msgid "scripts per page"
msgstr "Script per pagina"
#: script_manager.lua:818
msgid "select number of start/stop buttons to display"
msgstr "Selezionare il numero di pulsanti da visualizzare"
#: script_manager.lua:829
msgid "change number of buttons"
msgstr "Modifica la quantità di pulsanti"
#: script_manager.lua:837
msgid "Configuration"
msgstr "Configurazione"
#: script_manager.lua:858
msgid "action"
msgstr "Azione"
#: script_manager.lua:864
msgid "install/update scripts"
msgstr "Installa/aggiorna script"
#: script_manager.lua:864
msgid "configure"
msgstr "Configura"
#: script_manager.lua:864
msgid "start/stop scripts"
msgstr "Avvia/blocca script"

View File

@@ -0,0 +1,46 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-09-28 19:54-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/AutoGrouper.lua:144
msgid "auto group"
msgstr "grupo automático"
#: ../contrib/AutoGrouper.lua:173
msgid "group gap [sec.]"
msgstr "intervalo de agrupamento [seg]"
#: ../contrib/AutoGrouper.lua:174
msgid "minimum gap, in seconds, between groups"
msgstr "intervalo mínimo, em segundos, entre os grupos"
#: ../contrib/AutoGrouper.lua:187
msgid "auto group: selected"
msgstr "grupo automático: selecionado"
#: ../contrib/AutoGrouper.lua:188
msgid "auto group selected images"
msgstr "agrupa automaticamente as imagens selecionadas"
#: ../contrib/AutoGrouper.lua:192
msgid "auto group: collection"
msgstr "grupo automático: coleção"
#: ../contrib/AutoGrouper.lua:193
msgid "auto group the entire collection"
msgstr "agrupa automaticamente toda a coleção"

View File

@@ -0,0 +1,97 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-09-28 20:02-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/CollectHelper.lua:74
msgid "Please select a single image"
msgstr "Por favor, selecione uma única imagem"
#: ../contrib/CollectHelper.lua:124
msgid "select an image with an active color label"
msgstr "selecione uma imagem com uma etiqueta de cor ativa"
#: ../contrib/CollectHelper.lua:205
msgid "collect: previous"
msgstr "colecionar: anterior"
#: ../contrib/CollectHelper.lua:207
msgid "Sets the Collect parameters to be the previously active parameters"
msgstr "Define os parâmetros da coleção para os ativos anteriormente"
#: ../contrib/CollectHelper.lua:211
msgid "collect: folder"
msgstr "colecionar: pasta"
#: ../contrib/CollectHelper.lua:213
msgid "Sets the Collect parameters to be the selected images's folder"
msgstr "Define os parâmetros da coleção para a pasta de imagens selecionada"
#: ../contrib/CollectHelper.lua:218
msgid "collect: color label(s)"
msgstr "colecionar: etiqueta(s) de cor"
#: ../contrib/CollectHelper.lua:220
msgid "Sets the Collect parameters to be the selected images's color label(s)"
msgstr ""
"Define os parâmetros da coleção para a(s) etiqueta(s) de cor das imagens"
" selecionadas"
#: ../contrib/CollectHelper.lua:225
msgid "collect: all (AND)"
msgstr "colecionar: tudo (E)"
#: ../contrib/CollectHelper.lua:227
msgid ""
"Sets the Collect parameters based on all activated CollectHelper options"
msgstr "Define os parâmetros da coleção baseado em todas as opções do script"
#: ../contrib/CollectHelper.lua:234
msgid "CollectHelper: All"
msgstr "CollectHelper: Tudo"
#: ../contrib/CollectHelper.lua:235
msgid ""
"Will create a collect parameter set that utilizes all enabled CollectHelper "
"types (AND)"
msgstr ""
"Criará um conjunto de parâmetros de coleção que utilizará todos os tipos "
"habilitados do CollectHelper (E)"
#: ../contrib/CollectHelper.lua:240
msgid "CollectHelper: Color Label(s)"
msgstr "CollectHelper: Etiqueta(s) de Cor"
#: ../contrib/CollectHelper.lua:241
msgid ""
"Enable the button that allows you to swap to a collection based on selected "
"image's COLOR LABEL(S)"
msgstr ""
"Habilita o botão que permite que você alterna para uma coleção baseada na "
"ETIQUETA(S) DE COR da imagem selecionada"
#: ../contrib/CollectHelper.lua:246
msgid "CollectHelper: Folder"
msgstr "CollectHelper: Pasta"
#: ../contrib/CollectHelper.lua:247
msgid ""
"Enable the button that allows you to swap to a collection based on selected "
"image's FOLDER location"
msgstr ""
"Habilita o botão que permite que você alterne para uma coleção baseada na "
"PASTA da imagem selecionada"

View File

@@ -0,0 +1,163 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-10-01 19:40-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/HDRMerge.lua:175
msgid "please update you binary location"
msgstr "por favor, atualize a localização do binário"
#: ../contrib/HDRMerge.lua:194
msgid "update successful"
msgstr "atualização bem sucedida"
#: ../contrib/HDRMerge.lua:196
msgid "update unsuccessful, please try again"
msgstr "atualização mau sucedida, tente de novo"
#: ../contrib/HDRMerge.lua:210
msgid "HDRMerge install issue"
msgstr "problema com a instalação do HDRMerge"
#: ../contrib/HDRMerge.lua:211
msgid "HDRMerge install issue, please ensure the binary path is proper"
msgstr ""
"problema com a instalação do HDRMerge, certifique-se de que o caminho do"
" binário esteja correto"
#: ../contrib/HDRMerge.lua:216
msgid "not enough images selected, select at least 2 images to merge"
msgstr ""
"imagens selecionadas insuficientes, selecione apenas 2 imagens para mesclar"
#: ../contrib/HDRMerge.lua:277
msgid "HDRMerge completed successfully"
msgstr "HDRMerge completado com sucesso"
#: ../contrib/HDRMerge.lua:279 ../contrib/HDRMerge.lua:280
msgid "HDRMerge failed"
msgstr "HDRMerge falhou"
#: ../contrib/HDRMerge.lua:289
msgid "HDRMerge"
msgstr "HDRMerge"
#: ../contrib/HDRMerge.lua:312
msgid "HDRMerge options"
msgstr "opções do HDRMerge"
#: ../contrib/HDRMerge.lua:317
msgid "bits per sample"
msgstr "bits por amostra"
#: ../contrib/HDRMerge.lua:318
msgid "number of bits per sample in the output image"
msgstr "número de bits por amostra na imagem de saída"
#: ../contrib/HDRMerge.lua:334
msgid "embedded preview size"
msgstr "tamanho da pré-visualização embutida"
#: ../contrib/HDRMerge.lua:335
msgid "size of the embedded preview in output image"
msgstr "tamanho da pré-visualização embutida na imagem de saída"
#: ../contrib/HDRMerge.lua:337 ../contrib/HDRMerge.lua:382
msgid "none"
msgstr "nenhum"
#: ../contrib/HDRMerge.lua:337
msgid "half"
msgstr "metade"
#: ../contrib/HDRMerge.lua:337
msgid "full"
msgstr "completo"
#: ../contrib/HDRMerge.lua:349
msgid "batch mode"
msgstr "modo em lote"
#: ../contrib/HDRMerge.lua:351
msgid ""
"enable batch mode operation \n"
"NOTE: resultant files will NOT be auto-imported"
msgstr ""
"ativar a operação em modo em lote\n"
"NOTA: arquivos resultantes NÃO serão importados automaticamente"
#: ../contrib/HDRMerge.lua:361
msgid "batch gap [sec.]"
msgstr "intervalo do lote [seg.]"
#: ../contrib/HDRMerge.lua:362
msgid "gap, in seconds, between batch mode groups"
msgstr "intervalo, em segundos, entre os grupos do modo em lote"
#: ../contrib/HDRMerge.lua:376
msgid "import options"
msgstr "opções de importação"
#: ../contrib/HDRMerge.lua:379
msgid "apply style on import"
msgstr "aplicar estilo ao importar"
#: ../contrib/HDRMerge.lua:380
msgid "Apply selected style on auto-import to newly created image"
msgstr ""
"Aplica o estilo selecionado ao importar automaticamente para novas imagens"
" criadas"
#: ../contrib/HDRMerge.lua:400
msgid "copy tags"
msgstr "copiar etiquetas"
#: ../contrib/HDRMerge.lua:402
msgid "copy tags from first source image"
msgstr "copia etiquetas da primeira imagem fonte"
#: ../contrib/HDRMerge.lua:409
msgid ""
"Additional tags to be added on import. Seperate with commas, all spaces will "
"be removed"
msgstr ""
"Etiquetas adicionais a serem anexadas ao importar. Separar com vírgulas,"
" todos os espaços serão removidos"
#: ../contrib/HDRMerge.lua:411
msgid "Enter tags, seperated by commas"
msgstr "Insira as etiquetas, separadas por vírgulas"
#: ../contrib/HDRMerge.lua:415
msgid "merge"
msgstr "mesclar"
#: ../contrib/HDRMerge.lua:416
msgid "run HDRMerge with the above specified settings"
msgstr "rodar o HDRMerge com as configurações especificadas acima"
#: ../contrib/HDRMerge.lua:420
msgid "Select HDRmerge executable"
msgstr "Selecionar o executável do HDRmerge"
#: ../contrib/HDRMerge.lua:425
msgid "update"
msgstr "atualizar"
#: ../contrib/HDRMerge.lua:426
msgid "update the binary path with current value"
msgstr "atualiza o caminho do binário com o valor atual"

View File

@@ -0,0 +1,86 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-10-01 23:40-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/OpenInExplorer.lua:79
msgid ""
"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"
msgstr ""
"Atualmente, o plugin OpenInExplorer somente suporta Linux, macOS e Windows"
#: ../contrib/OpenInExplorer.lua:95
msgid ""
"No links directory selected.\n"
"Please check the dt preferences (lua options)"
msgstr ""
"Nenhum link para pasta selecionado.\n"
"Verifique as preferências do darktable (opções lua)"
#: ../contrib/OpenInExplorer.lua:99
#, lua-format
msgid ""
"Links directory '%s' not found.\n"
"Please check the dt preferences (lua options)"
msgstr ""
"Links para pasta '%s' não encontrados.\n"
"Verifique as preferências do darktable (opções lua)"
#: ../contrib/OpenInExplorer.lua:156
msgid "Failed to create links. Missing rights?"
msgstr "Falha ao criar links. Possui permissões adequadas?"
#: ../contrib/OpenInExplorer.lua:172
msgid "Please select an image"
msgstr "Por favor, selecione uma imagem"
#: ../contrib/OpenInExplorer.lua:178
msgid "Please select fewer images (max. 15)"
msgstr "Por favor, selecione menos imagens (máx 15)"
#: ../contrib/OpenInExplorer.lua:203
msgid "show in file explorer"
msgstr "mostrar no navegador de arquivos"
#: ../contrib/OpenInExplorer.lua:205
msgid "Open the file manager at the selected image's location"
msgstr "Abre o gerenciador de arquivos na localização das imagens selecionadas"
#: ../contrib/OpenInExplorer.lua:212
msgid "OpenInExplorer: linked files directory"
msgstr "OpenInExplorer: pasta de arquivos linkada"
#: ../contrib/OpenInExplorer.lua:213
msgid ""
"Directory to store the links to the file names. Requires restart to take "
"effect"
msgstr ""
"Pasta para armazenar os links para os nomes de arquivo. Precisa reiniciar"
" para ter efeito"
#: ../contrib/OpenInExplorer.lua:216
msgid "Select directory"
msgstr "Selecione a pasta"
#: ../contrib/OpenInExplorer.lua:222
msgid "OpenInExplorer: use links"
msgstr "OpenInExplorer: usar links"
#: ../contrib/OpenInExplorer.lua:223
msgid "Use links instead of multiple windows. Requires restart to take effect"
msgstr ""
"Usa links ao invés de janelas múltiplas. Precisa reiniciar para ter efeito"

View File

@@ -0,0 +1,74 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-10-02 05:31-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/RL_out_sharp.lua:119
msgid "GMic executable not configured"
msgstr "executável GMic não configurado"
#: ../contrib/RL_out_sharp.lua:139
msgid "sharpening image "
msgstr "melhorar nitidez da imagem "
#: ../contrib/RL_out_sharp.lua:154
msgid "sharpening error"
msgstr "erro ao melhorar nitidez"
#: ../contrib/RL_out_sharp.lua:163
msgid "finished exporting"
msgstr "exportação finalizada"
#: ../contrib/RL_out_sharp.lua:175 ../contrib/RL_out_sharp.lua:176
msgid "select output folder"
msgstr "selecionar pasta de saída"
#: ../contrib/RL_out_sharp.lua:185
msgid "sigma"
msgstr "sigma"
#: ../contrib/RL_out_sharp.lua:186
msgid "controls the width of the blur that's applied"
msgstr "controla a largura do desfoque que é aplicada"
#: ../contrib/RL_out_sharp.lua:197
msgid "iterations"
msgstr "iterações"
#: ../contrib/RL_out_sharp.lua:198
msgid "increase for better sharpening, but slower"
msgstr "aumente para melhor efeito, mas diminui velocidade"
#: ../contrib/RL_out_sharp.lua:209
msgid "output jpg quality"
msgstr "qualidade do jpg de saída"
#: ../contrib/RL_out_sharp.lua:210
msgid "quality of the output jpg file"
msgstr "qualidade do arquivo jpg de saída"
#: ../contrib/RL_out_sharp.lua:229
msgid "RL output sharpen"
msgstr "melhoria de nitidez na saída RL"
#: ../contrib/RL_out_sharp.lua:233
msgid "executable for GMic CLI"
msgstr "executável para o GMic CLI"
#: ../contrib/RL_out_sharp.lua:234
msgid "select executable for GMic command line version"
msgstr "selecione o executável para a versão de linha de comando do GMic"

View File

@@ -0,0 +1,30 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-09-28 19:53-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/clear_GPS.lua:81
msgid "clear GPS data"
msgstr "limpar dados de GPS"
#: ../contrib/clear_GPS.lua:83
msgid "Clear GPS data from selected images"
msgstr "Limpa os dados de GPS das imagens selecionadas"
#: ../contrib/clear_GPS.lua:89
msgid "Clear GPS data"
msgstr "Limpar dados de GPS"

View File

@@ -0,0 +1,90 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Marcus Gama <marcus.gama@gmail.com>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
"PO-Revision-Date: 2021-09-30 19:05-0300\n"
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Lokalize 21.08.1\n"
#: ../contrib/color_profile_manager.lua:122
msgid "added color profile "
msgstr "perfil de cor adicionado "
#: ../contrib/color_profile_manager.lua:128
msgid "removed color profile "
msgstr "perfil de cor removido "
#: ../contrib/color_profile_manager.lua:146
msgid "ERROR: color profile must be an icc or icm file"
msgstr "ERRO: perfil de cor deve ser um arquivo icm ou icc"
#: ../contrib/color_profile_manager.lua:229
msgid "color profile manager"
msgstr "gerenciador de perfil de cor"
#: ../contrib/color_profile_manager.lua:270
msgid "initialize color profiles"
msgstr "inicializar perfis de cor"
#: ../contrib/color_profile_manager.lua:271
msgid "create the directory structure to contain the color profiles"
msgstr "cria a estrutura de pasta para conter os perfis de cor"
#: ../contrib/color_profile_manager.lua:286
msgid "select profile set"
msgstr "selecionar definição de perfil"
#: ../contrib/color_profile_manager.lua:287
msgid "select input or output profiles"
msgstr "selecionar perfis de entrada ou saída"
#: ../contrib/color_profile_manager.lua:294
msgid "input"
msgstr "entrada"
#: ../contrib/color_profile_manager.lua:294
msgid "output"
msgstr "saída"
#: ../contrib/color_profile_manager.lua:298
msgid "select color profile to add"
msgstr "selecionar perfil de cor a adicionar"
#: ../contrib/color_profile_manager.lua:299
msgid "select the .icc or .icm file to add"
msgstr "selecionar arquivo icc ou icm a adicionar"
#: ../contrib/color_profile_manager.lua:306
msgid "add profile"
msgstr "adicionar perfil"
#: ../contrib/color_profile_manager.lua:309
msgid "add selected color profile"
msgstr "adicionar perfil de cor selecionado"
#: ../contrib/color_profile_manager.lua:310
msgid "add selected file to profiles"
msgstr "adicionar arquivo selecionado aos perfis"
#: ../contrib/color_profile_manager.lua:320
msgid "remove profile"
msgstr "remover perfil"
#: ../contrib/color_profile_manager.lua:323
msgid "remove selected profile(s)"
msgstr "remover perfil(s) selecionado(s)"
#: ../contrib/color_profile_manager.lua:324
msgid "remove the checked profile(s)"
msgstr "remover o(s) perfil(s) marcado(s)"

Some files were not shown because too many files have changed in this diff Show More