Compare commits
297 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1629ee719 | ||
|
|
b32216d94e | ||
|
|
a55de740e5 | ||
|
|
dc31b221ef | ||
|
|
34b88490c2 | ||
|
|
d9b77bb3a2 | ||
|
|
cc198d6ca1 | ||
|
|
84582cbf29 | ||
|
|
9e7a18e290 | ||
|
|
8ff81fcfa4 | ||
|
|
b649e7a840 | ||
|
|
efa06ee34c | ||
|
|
77959422e0 | ||
|
|
0bff346b05 | ||
|
|
d05c494147 | ||
|
|
9cbbae9621 | ||
|
|
fb2aade198 | ||
|
|
7ffd8bdc27 | ||
|
|
b689dbc7ee | ||
|
|
027135a257 | ||
|
|
643057deaf | ||
|
|
a00f71f533 | ||
|
|
36d6db24e7 | ||
|
|
f62a5a817f | ||
|
|
44b40b305b | ||
|
|
97dade06cf | ||
|
|
30167631e7 | ||
|
|
8235de1f0a | ||
|
|
d67ada8300 | ||
|
|
c6788e4370 | ||
|
|
d238fcee61 | ||
|
|
28d71c989f | ||
|
|
74f020c809 | ||
|
|
80769a753a | ||
|
|
427359769e | ||
|
|
5e5b073cec | ||
|
|
c771c5fc1b | ||
|
|
7cdf1d4c7f | ||
|
|
b6ea6211db | ||
|
|
4d3924939b | ||
|
|
80ff1ef0aa | ||
|
|
af362315e8 | ||
|
|
28f3e9f6a2 | ||
|
|
4f05672809 | ||
|
|
8d8f295c77 | ||
|
|
a81ab437cf | ||
|
|
a94a0c2c90 | ||
|
|
0eda1b6ac5 | ||
|
|
58bc3a69df | ||
|
|
35398c55e5 | ||
|
|
1762b372b1 | ||
|
|
1907f9a1f5 | ||
|
|
b1dd3eca5a | ||
|
|
732d2788ca | ||
|
|
04908e3f0b | ||
|
|
3ba3facad2 | ||
|
|
cb209d1d56 | ||
|
|
36533b1004 | ||
|
|
cbbf0f413f | ||
|
|
181aea5bc2 | ||
|
|
94c6dff245 | ||
|
|
7a61d77c27 | ||
|
|
2635e261a0 | ||
|
|
d197851478 | ||
|
|
6f519b3ce8 | ||
|
|
03eb79e83a | ||
|
|
fb03b49a93 | ||
|
|
c0865c37bb | ||
|
|
182a2c96c3 | ||
|
|
06522802d8 | ||
|
|
ad263fed11 | ||
|
|
ced60016fb | ||
|
|
1d6d0695fe | ||
|
|
fbe504b47a | ||
|
|
7da38b3665 | ||
|
|
08fe1ba410 | ||
|
|
088c79e140 | ||
|
|
f06eebe316 | ||
|
|
d6a1cec9fa | ||
|
|
e194ad2bc7 | ||
|
|
22e3c59d69 | ||
|
|
eb743e4b3b | ||
|
|
7a8f3afc40 | ||
|
|
2b3910c698 | ||
|
|
c584ee05c9 | ||
|
|
bb3a74feac | ||
|
|
90d2764a5a | ||
|
|
b0373f7166 | ||
|
|
c6002e7eb7 | ||
|
|
a47de82afd | ||
|
|
7f09e55525 | ||
|
|
f83ac1366a | ||
|
|
6cda27172e | ||
|
|
df78522a79 | ||
|
|
6e64d6db6f | ||
|
|
7f00cb8d6c | ||
|
|
dfeff73fcd | ||
|
|
d75f00daa0 | ||
|
|
3e430ee2be | ||
|
|
3856ae3449 | ||
|
|
d54a9f8b7e | ||
|
|
8f6cc850fb | ||
|
|
e9f18a3d36 | ||
|
|
f3a89a89bd | ||
|
|
d075fb7128 | ||
|
|
7f4bae4234 | ||
|
|
071a3527c1 | ||
|
|
22c9a7cd6e | ||
|
|
12ccefcc3b | ||
|
|
fa6014fefa | ||
|
|
e831f377bf | ||
|
|
d696e6e3f2 | ||
|
|
db505f1e0a | ||
|
|
57d15a3fa6 | ||
|
|
3c131c41d1 | ||
|
|
4d54b5eaba | ||
|
|
ffa125b134 | ||
|
|
dbf0c5d853 | ||
|
|
193a615772 | ||
|
|
056f616283 | ||
|
|
447980ddc4 | ||
|
|
8af5476f8d | ||
|
|
c95547caa7 | ||
|
|
b4d3b2901f | ||
|
|
cb6b3161d8 | ||
|
|
2d82eea237 | ||
|
|
3beee93af4 | ||
|
|
68291414f3 | ||
|
|
5662519314 | ||
|
|
28ab065293 | ||
|
|
aed3275943 | ||
|
|
d7aeb43340 | ||
|
|
ea79d10b11 | ||
|
|
e4001f984f | ||
|
|
453d744b69 | ||
|
|
b9cc0dccce | ||
|
|
84fea20ee5 | ||
|
|
e054bfc59d | ||
|
|
8cd10dce68 | ||
|
|
f8d49ccd01 | ||
|
|
25afb41924 | ||
|
|
3001a0dc1a | ||
|
|
62e932990e | ||
|
|
980fe3962c | ||
|
|
0043769946 | ||
|
|
0cb9a8872e | ||
|
|
5309ff1726 | ||
|
|
c6eee15d75 | ||
|
|
8c2d0ddf2e | ||
|
|
87d18549a6 | ||
|
|
6fdd78114f | ||
|
|
f20721373a | ||
|
|
9c0f8d63e6 | ||
|
|
ea2596ae13 | ||
|
|
a9191cf084 | ||
|
|
59b5039579 | ||
|
|
e070a1462c | ||
|
|
faa3302a37 | ||
|
|
115dbbec32 | ||
|
|
9bf7efe71f | ||
|
|
3ec9dc4963 | ||
|
|
cddb86ded0 | ||
|
|
2624338100 | ||
|
|
067fe29f76 | ||
|
|
926272b1cd | ||
|
|
363524fbaf | ||
|
|
edeceb268e | ||
|
|
252cbc96f2 | ||
|
|
b70fdc0ecf | ||
|
|
96457bc598 | ||
|
|
cd31d18075 | ||
|
|
4e140f10d1 | ||
|
|
593f8049a0 | ||
|
|
89973b3f9a | ||
|
|
deca366ce4 | ||
|
|
914a17fb4e | ||
|
|
1e33ea4192 | ||
|
|
9f3fb16161 | ||
|
|
3f064783b7 | ||
|
|
0ab4b0cd6d | ||
|
|
e2f5c993b8 | ||
|
|
3def8b43d9 | ||
|
|
7b2942ca29 | ||
|
|
bfed38f1db | ||
|
|
6d16a8778b | ||
|
|
3c67c0c693 | ||
|
|
1ba488d073 | ||
|
|
3bdbfd12bf | ||
|
|
77c405882d | ||
|
|
ecc3fb29f5 | ||
|
|
a09140c03c | ||
|
|
95e2e4924e | ||
|
|
72ce0b0f93 | ||
|
|
e4da226661 | ||
|
|
a3404b64de | ||
|
|
13ae486ab3 | ||
|
|
268aa7df42 | ||
|
|
ea7ac8e474 | ||
|
|
2a4452cd20 | ||
|
|
749db97dd5 | ||
|
|
84a1c1ddfe | ||
|
|
3791b47914 | ||
|
|
f6a1d1c20b | ||
|
|
118bbad5cb | ||
|
|
44e442e7eb | ||
|
|
08726427aa | ||
|
|
39f63cc8c5 | ||
|
|
99c3548950 | ||
|
|
7e8340c526 | ||
|
|
2e95c4837b | ||
|
|
fe6d262d9b | ||
|
|
794b12305c | ||
|
|
1a3a7f17ff | ||
|
|
496f8e704d | ||
|
|
28f87566e1 | ||
|
|
865d29fb40 | ||
|
|
1986f4e148 | ||
|
|
b963eaa38c | ||
|
|
3b520d6291 | ||
|
|
87ca8bde27 | ||
|
|
d27df0c0ed | ||
|
|
9bceb79a15 | ||
|
|
ed9f2bb0d0 | ||
|
|
b5c93e55d1 | ||
|
|
9bfda93b51 | ||
|
|
daa0877b4c | ||
|
|
da275c2a66 | ||
|
|
3fa913d68a | ||
|
|
e4e194b0ec | ||
|
|
c70ebf4297 | ||
|
|
7e3c6471ff | ||
|
|
a39f6feba7 | ||
|
|
ee7e92019a | ||
|
|
96b7ba24bc | ||
|
|
8581af29d2 | ||
|
|
ab532a724c | ||
|
|
b1e6bb8daa | ||
|
|
26e797d5e5 | ||
|
|
3941c7ab52 | ||
|
|
b7c8759af3 | ||
|
|
c5711880d3 | ||
|
|
e7dbd6e369 | ||
|
|
1b5cb1ae6e | ||
|
|
2953cf7e91 | ||
|
|
5e4ba9da5e | ||
|
|
3a1f6bbc7a | ||
|
|
21ec2883fe | ||
|
|
a0af01884d | ||
|
|
8e129d3825 | ||
|
|
d123634679 | ||
|
|
13f210bec9 | ||
|
|
8e4767c42a | ||
|
|
300bb9d443 | ||
|
|
4f8df88fec | ||
|
|
44da339202 | ||
|
|
0e37a287ca | ||
|
|
2af2530e45 | ||
|
|
99eb595f4c | ||
|
|
31cdd3f24a | ||
|
|
631d89e581 | ||
|
|
526e97a4e1 | ||
|
|
a835ab89bc | ||
|
|
da7674ca51 | ||
|
|
0e535b850c | ||
|
|
64840089e9 | ||
|
|
d754e37cf4 | ||
|
|
f6cb8091e7 | ||
|
|
e359567620 | ||
|
|
36a6a54364 | ||
|
|
ecff920537 | ||
|
|
842f2fca66 | ||
|
|
ee9384b9a7 | ||
|
|
272c35499a | ||
|
|
4165d46b33 | ||
|
|
5bed7c914c | ||
|
|
684178b27e | ||
|
|
a72abe0ad0 | ||
|
|
8863f6ade7 | ||
|
|
e0463eba83 | ||
|
|
3a2ff7a7b9 | ||
|
|
ce7ddfa472 | ||
|
|
3fb00586c3 | ||
|
|
167620fadf | ||
|
|
f0bdc890c1 | ||
|
|
abdfc9de0b | ||
|
|
c65e070a76 | ||
|
|
05c57c0247 | ||
|
|
5be3d6986e | ||
|
|
f0e3627800 | ||
|
|
ea08557a0e | ||
|
|
ac43afd8c9 | ||
|
|
ee98f96a4f | ||
|
|
a33eeddbd6 | ||
|
|
746c6a07c4 | ||
|
|
96fee48ef2 | ||
|
|
6c686128d2 | ||
|
|
6ca8f0034e |
138
README.md
138
README.md
@@ -15,17 +15,17 @@ These scripts are written primarily by the darktable developers and maintained b
|
||||
|
||||
Name|Standalone|OS |Purpose
|
||||
----|:--------:|:---:|-------
|
||||
[check_for_updates](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/check_for_updates)|Yes|LMW|Check for updates to darktable
|
||||
[copy_paste_metadata](https://darktable-org.github.io/luadocs/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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length
|
||||
[delete_unused_tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images
|
||||
[enfuse](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR)
|
||||
[generate_image_txt](https://darktable-org.github.io/luadocs/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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path
|
||||
[import_filter_manager](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters
|
||||
[import_filters](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager
|
||||
[save_selection](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers
|
||||
[selection_to_pdf](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/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
|
||||
@@ -34,50 +34,52 @@ These scripts are contributed by users. They are meant to have an "owner", i.e.
|
||||
|
||||
Name|Standalone|OS |Purpose
|
||||
----|:--------:|:---:|-------
|
||||
[AutoGrouper](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time
|
||||
[autostyle](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images
|
||||
[CollectHelper](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images
|
||||
[cr2hdr](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images
|
||||
[exportLUT](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it
|
||||
[ext_editor](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors
|
||||
[face_recognition](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition
|
||||
[fujifilm_dynamic_range](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting
|
||||
[fujifilm_ratings](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
|
||||
[geoJSON_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ...
|
||||
[geoToolbox](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions
|
||||
[gimp](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result
|
||||
[gpx_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result
|
||||
[hugin](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result
|
||||
[image_stack](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects
|
||||
[image_time](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth
|
||||
[LabelsToTags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings
|
||||
[OpenInExplorer](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager
|
||||
[passport_guide](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow
|
||||
[photils](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images
|
||||
[quicktag](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags
|
||||
[rate_group](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images
|
||||
[rename_images](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images
|
||||
[rename-tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name
|
||||
[RL_out_sharp](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
|
||||
[select_non_existing](https://darktable-org.github.io/luadocs/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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images
|
||||
[slideshowMusic](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow
|
||||
[transfer_hierarchy](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy
|
||||
[video_ffmpeg](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable
|
||||
[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
|
||||
|
||||
@@ -85,18 +87,18 @@ These scripts provide examples of how to use specific portions of the API. They
|
||||
|
||||
Name|Standalone|OS |Purpose
|
||||
----|:--------:|:---:|-------
|
||||
[api_version](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version
|
||||
[darkroom_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom
|
||||
[gettextExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation
|
||||
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts
|
||||
[lighttable_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering
|
||||
[moduleExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module
|
||||
[multi_os](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/multi_os)|No|LMW|How to create a cross platform script that calls an external executable
|
||||
[panels_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels
|
||||
[preferenceExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script
|
||||
[printExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script
|
||||
[running_os](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system
|
||||
[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
|
||||
@@ -105,11 +107,11 @@ Tool scripts perform functions relating to the repository, such as generating do
|
||||
|
||||
Name|Standalone|OS |Purpose
|
||||
----|:--------:|:---:|-------
|
||||
[executable_manager](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/executable_manager)|Yes|LMW|Manage the external executables used by the lua scripts
|
||||
[gen_i18n_mo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po)
|
||||
[get_lib_manpages](https://darktable-org.github.io/luadocs/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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text
|
||||
[script_manager](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/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
|
||||
|
||||
@@ -178,7 +180,7 @@ 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 module. To use script manager do the following:
|
||||
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
|
||||
|
||||
@@ -237,18 +239,18 @@ To update the script repository, open a terminal or command prompt and do the fo
|
||||
|
||||
## Documentation
|
||||
|
||||
The [Lua Scripts Manual](https://darktable-org.github.io/luadocs/lua.scripts.manual/) provides documentation
|
||||
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.
|
||||
|
||||
The [Lua Scripts Library API Manual](https://darktable-org.github.io/luadocs/lua.scripts.api.manual/) provides
|
||||
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:
|
||||
[Scripting with Lua](https://darktable-org.github.io/dtdocs/lua/)
|
||||
[Scripting with Lua](https://darktable.org.github.io/dtdocs/lua/)
|
||||
|
||||
The [Lua API Manual](https://darktable-org.github.io/luadocs/lua.api.manual/) provides docuemntation of the
|
||||
The [Lua API Manual](https://docs.darktable.org/lua/stable/lua.api.manual/) provides docuemntation of the
|
||||
darktable Lua API.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -43,11 +43,10 @@ du.check_min_api_version("7.0.0", "AutoGrouper")
|
||||
|
||||
local MOD = 'autogrouper'
|
||||
|
||||
local gettext = dt.gettext
|
||||
gettext.bindtextdomain(MOD, dt.configuration.config_dir .."/lua/locale/")
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
local function _(msgid)
|
||||
return gettext.gettext(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
-- return data structure for script_manager
|
||||
@@ -55,7 +54,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "AutoGrouper",
|
||||
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/"
|
||||
@@ -133,10 +132,11 @@ local function main(on_collection)
|
||||
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])
|
||||
images[i]:group_with(images[i-1].group_leader)
|
||||
end
|
||||
prev_image = curr_image
|
||||
end
|
||||
|
||||
@@ -53,8 +53,6 @@ local all_active = false
|
||||
|
||||
du.check_min_api_version("7.0.0", "CollectHelper")
|
||||
|
||||
dt.gettext.bindtextdomain("CollectHelper", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -64,7 +62,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "CollectHelper",
|
||||
name = _("collection helper"),
|
||||
purpose = _("add collection helper buttons"),
|
||||
author = "Kevin Ertel",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/CollectHelper/"
|
||||
@@ -239,7 +237,7 @@ end
|
||||
dt.preferences.register("module_CollectHelper", "all_and", -- name
|
||||
"bool", -- type
|
||||
_('CollectHelper: all'), -- label
|
||||
_('will create a collect parameter set that utilizes all enabled CollectHelper types (and)'), -- tooltip
|
||||
_('creates a collect parameter set that utilizes all enabled CollectHelper types (and)'), -- tooltip
|
||||
true -- default
|
||||
)
|
||||
dt.preferences.register("module_CollectHelper", "colors", -- name
|
||||
|
||||
@@ -54,8 +54,6 @@ 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
|
||||
|
||||
dt.gettext.bindtextdomain("HDRMerge", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -65,7 +63,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "HDRmerge",
|
||||
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"
|
||||
@@ -218,7 +216,7 @@ 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'))
|
||||
dt.print(_('HDRMerge install issue, please ensure the binary path is correct'))
|
||||
return
|
||||
end
|
||||
images = dt.gui.selection() --get selected images
|
||||
@@ -416,14 +414,14 @@ GUI.Target.copy_tags = dt.new_widget('check_button'){
|
||||
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, separate with commas, all spaces will be removed'),
|
||||
text = temp,
|
||||
placeholder = _('enter tags, seperated by commas'),
|
||||
placeholder = _('enter tags, separated by commas'),
|
||||
editable = true
|
||||
}
|
||||
GUI.run = dt.new_widget('button'){
|
||||
label = _('merge'),
|
||||
tooltip =_('run HDRMerge with the above specified settings'),
|
||||
tooltip =_('run HDRMerge with the above settings'),
|
||||
clicked_callback = function() main() end
|
||||
}
|
||||
GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){
|
||||
|
||||
@@ -54,8 +54,6 @@ du.check_min_api_version("7.0.0", "LabelsToTags")
|
||||
|
||||
local gettext = darktable.gettext.gettext
|
||||
|
||||
darktable.gettext.bindtextdomain("LabelsToTags", darktable.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -65,7 +63,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "LabelsToTags",
|
||||
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"
|
||||
@@ -216,8 +214,8 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -247,7 +245,7 @@ end
|
||||
|
||||
local function install_module()
|
||||
if not ltt.module_installed then
|
||||
darktable.register_lib(LIB_ID,"labels to tags",true,true,{
|
||||
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
|
||||
|
||||
@@ -56,8 +56,6 @@ local gettext = dt.gettext.gettext
|
||||
--Check API version
|
||||
du.check_min_api_version("7.0.0", "OpenInExplorer")
|
||||
|
||||
dt.gettext.bindtextdomain("OpenInExplorer", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -67,7 +65,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "OpenInExplorer",
|
||||
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"
|
||||
|
||||
@@ -68,8 +68,6 @@ du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||
-- translation
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("RL_out_sharp", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -79,7 +77,7 @@ local function _(msgid)
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "RL_out_sharp",
|
||||
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"
|
||||
@@ -108,7 +106,7 @@ local function preserve_metadata(original, sharpened)
|
||||
if exiftool then
|
||||
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
|
||||
else
|
||||
dt.print_log(MODULE .. " exiftool not found, metadata not preserved")
|
||||
dt.print_log(MODULE_NAME .. " exiftool not found, metadata not preserved")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -59,10 +59,8 @@ du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that c
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(MODULE, msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
|
||||
@@ -78,10 +76,10 @@ script_data.restart = nil -- how to restart the (lib) script after it'
|
||||
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||
|
||||
script_data.metadata = {
|
||||
name = "auto_snapshot", -- name of script
|
||||
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 = "" -- URL to help/documentation
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_snapshot/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
@@ -101,8 +99,8 @@ local auto_snapshot = {}
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "always_create_snapshot", "bool", "always automatically create_snapshot",
|
||||
"auto_snapshot - create a snapshot even if the image is altered", false)
|
||||
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
|
||||
|
||||
129
contrib/auto_tag_modified.lua
Normal file
129
contrib/auto_tag_modified.lua
Normal 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
|
||||
@@ -44,7 +44,6 @@ local syslib = require "lib/dtutils.system"
|
||||
du.check_min_api_version("7.0.0", "autostyle")
|
||||
|
||||
local gettext = darktable.gettext.gettext
|
||||
darktable.gettext.bindtextdomain("autostyle", darktable.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
@@ -54,8 +53,10 @@ end
|
||||
|
||||
local script_data = {}
|
||||
|
||||
local have_not_printed_config_message = true
|
||||
|
||||
script_data.metadata = {
|
||||
name = "autostyle",
|
||||
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/"
|
||||
@@ -102,56 +103,62 @@ end
|
||||
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+)")
|
||||
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
|
||||
-- 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
|
||||
-- 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
|
||||
-- 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
|
||||
|
||||
@@ -181,7 +188,9 @@ end
|
||||
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("autostyle", "post-import-image",
|
||||
autostyle_apply_one_image_event)
|
||||
|
||||
@@ -40,8 +40,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
local MODULE = "change_group_leader"
|
||||
|
||||
dt.gettext.bindtextdomain(MODULE, dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
du.check_min_api_version("3.0.0", MODULE)
|
||||
|
||||
local function _(msgid)
|
||||
@@ -53,7 +51,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "change_group_leader",
|
||||
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"
|
||||
@@ -65,7 +63,7 @@ script_data.restart = nil -- how to restart the (lib) script after it's been hid
|
||||
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||
|
||||
-- create a namespace to contain persistent data and widgets
|
||||
chg_grp_ldr = {}
|
||||
local chg_grp_ldr = {}
|
||||
|
||||
local cgl = chg_grp_ldr
|
||||
|
||||
@@ -82,7 +80,7 @@ local function install_module()
|
||||
if not cgl.module_installed then
|
||||
dt.register_lib(
|
||||
MODULE, -- Module name
|
||||
_("change_group_leader"), -- Visible name
|
||||
_("change group leader"), -- Visible name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700}}, -- containers
|
||||
|
||||
@@ -40,7 +40,6 @@ local dt = require "darktable"
|
||||
local du = require "lib/dtutils"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
dt.gettext.bindtextdomain("clear_GPS", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
@@ -51,7 +50,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "clear_GPS",
|
||||
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/"
|
||||
@@ -92,7 +91,7 @@ dt.gui.libs.image.register_action(
|
||||
dt.register_event(
|
||||
"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
|
||||
|
||||
@@ -55,8 +55,6 @@ du.check_min_api_version("7.0.0", "color_profile_manager")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("color_profile_manager", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -365,7 +363,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "color_profile_manager",
|
||||
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"
|
||||
|
||||
@@ -44,8 +44,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "copy_attach_detach_tags")
|
||||
|
||||
dt.gettext.bindtextdomain("copy_attach_detach_tags", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -55,7 +53,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "copy_attach_detach_tags",
|
||||
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"
|
||||
|
||||
@@ -39,8 +39,6 @@ du.check_min_api_version("7.0.0", "cr2hdr")
|
||||
|
||||
local gettext = darktable.gettext.gettext
|
||||
|
||||
darktable.gettext.bindtextdomain("cr2hdr", darktable.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -50,7 +48,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "cr2hdr",
|
||||
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"
|
||||
|
||||
@@ -60,8 +60,6 @@ du.check_min_api_version("7.0.0", MODULE)
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("cycle_group_leader", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -73,7 +71,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "cycle_group_leader",
|
||||
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"
|
||||
@@ -106,7 +104,7 @@ end
|
||||
local function cycle_group_leader(image)
|
||||
local group_images = image:get_group_members()
|
||||
if #group_images < 2 then
|
||||
hinter_msg(_("no images to cycle to in group"))
|
||||
hinter_msg(_("no images to cycle through in group"))
|
||||
return
|
||||
else
|
||||
local position = nil
|
||||
|
||||
@@ -63,10 +63,8 @@ du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that c
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(MODULE, msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
|
||||
@@ -82,7 +80,7 @@ script_data.restart = nil -- how to restart the (lib) script after it'
|
||||
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||
|
||||
script_data.metadata = {
|
||||
name = "dbmaint",
|
||||
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/"
|
||||
@@ -117,6 +115,20 @@ 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 = {}
|
||||
|
||||
@@ -130,19 +142,21 @@ local function scan_film_rolls()
|
||||
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.log_msg(log.debug, "checking " .. image.filename)
|
||||
log.msg(log.debug, "checking " .. image.filename)
|
||||
if not df.check_if_file_exists(image.path .. PS .. image.filename) then
|
||||
log.log_msg(log.info, image.filename .. " not found")
|
||||
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
|
||||
|
||||
@@ -195,21 +209,23 @@ dbmaint.list_widget = dt.new_widget("text_view"){
|
||||
}
|
||||
|
||||
dbmaint.chooser = dt.new_widget("combobox"){
|
||||
label = "scan for",
|
||||
label = _("scan for"),
|
||||
selected = 1,
|
||||
"film rolls", "images",
|
||||
_("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",
|
||||
label = _("scan"),
|
||||
tooltip = _("click to scan for missing film rolls/files"),
|
||||
clicked_callback = function(this)
|
||||
local found = nil
|
||||
local found_text = ""
|
||||
if dbmaint.chooser.value == "film rolls" then
|
||||
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
|
||||
@@ -218,7 +234,7 @@ dbmaint.scan_button = dt.new_widget("button"){
|
||||
end
|
||||
end
|
||||
else
|
||||
log.log_msg(log.debug, "checking path " .. dt.collection[1].path .. " for missing files")
|
||||
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
|
||||
@@ -227,10 +243,14 @@ dbmaint.scan_button = dt.new_widget("button"){
|
||||
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
|
||||
@@ -238,11 +258,11 @@ dbmaint.scan_button = dt.new_widget("button"){
|
||||
}
|
||||
|
||||
dbmaint.remove_button = dt.new_widget("button"){
|
||||
label = "remove",
|
||||
tooltip = "remove missing film rolls/images",
|
||||
label = _("remove"),
|
||||
tooltip = _("remove missing film rolls/images"),
|
||||
sensitive = false,
|
||||
clicked_callback = function(this)
|
||||
if dbmaint.chooser.value == "film rolls" then
|
||||
if dbmaint.chooser.selected == 1 then -- film rolls
|
||||
remove_missing_film_rolls(dbmaint.found)
|
||||
else
|
||||
remove_missing_images(dbmaint.found)
|
||||
@@ -258,7 +278,7 @@ dbmaint.remove_button = dt.new_widget("button"){
|
||||
|
||||
dbmaint.main_widget = dt.new_widget("box"){
|
||||
orientation = "vertical",
|
||||
dt.new_widget("section_label"){label = "missing items"},
|
||||
dt.new_widget("section_label"){label = _("missing items")},
|
||||
dbmaint.list_widget,
|
||||
dt.new_widget("label"){label = ""},
|
||||
dbmaint.chooser,
|
||||
|
||||
@@ -70,8 +70,6 @@ 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
|
||||
|
||||
dt.gettext.bindtextdomain("enfuseAdvanced", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -81,7 +79,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "enfuseAdvanced",
|
||||
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"
|
||||
@@ -290,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)
|
||||
@@ -347,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
|
||||
@@ -445,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 = ''
|
||||
@@ -494,6 +498,7 @@ local function main(storage, image_table, extra_data)
|
||||
remove_temp_files(images_to_remove)
|
||||
dt.print_error(ENF.name..' failed')
|
||||
dt.print(string.format(_("%s failed"), ENF.name))
|
||||
job.valid = false
|
||||
return
|
||||
end
|
||||
|
||||
@@ -956,7 +961,7 @@ 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'),
|
||||
placeholder = _('enter tags, separated by commas'),
|
||||
editable = true
|
||||
}
|
||||
temp = dt.preferences.read(mod, 'active_current_preset_ind', 'integer')
|
||||
@@ -1023,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,
|
||||
@@ -1031,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,
|
||||
@@ -1039,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,
|
||||
|
||||
@@ -35,8 +35,6 @@ du.check_min_api_version("7.0.0", "exportLUT")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("exportLUT", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -46,7 +44,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "exportLUT",
|
||||
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"
|
||||
@@ -71,13 +69,13 @@ 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 = _("identity_file_chooser"),
|
||||
title = _("choose the identity file"),
|
||||
value = "",
|
||||
is_directory = false
|
||||
}
|
||||
|
||||
local export_chooser_button = dt.new_widget("file_chooser_button"){
|
||||
title = _("export_location_chooser"),
|
||||
title = _("choose the export location"),
|
||||
value = "",
|
||||
is_directory = true
|
||||
}
|
||||
|
||||
@@ -78,8 +78,6 @@ du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||
-- translation
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("ext_editor", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -89,7 +87,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "ext_editor",
|
||||
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"
|
||||
|
||||
@@ -54,8 +54,6 @@ local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt"
|
||||
|
||||
du.check_min_api_version("7.0.0", MODULE)
|
||||
|
||||
dt.gettext.bindtextdomain("face_recognition", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -65,7 +63,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "face_recognition",
|
||||
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"
|
||||
|
||||
@@ -65,8 +65,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "fujifilm_dynamic_range")
|
||||
|
||||
dt.gettext.bindtextdomain("fujifilm_dynamic_range", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -76,7 +74,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "fujifilm_dynamic_range",
|
||||
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"
|
||||
|
||||
@@ -31,8 +31,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "fujifilm_ratings")
|
||||
|
||||
dt.gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -42,7 +40,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "fujifilm_ratings",
|
||||
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"
|
||||
@@ -58,7 +56,7 @@ local function detect_rating(event, image)
|
||||
return
|
||||
end
|
||||
if not df.check_if_bin_exists("exiftool") then
|
||||
dt.print_error(_("exiftool not found"))
|
||||
dt.print_error("exiftool not found")
|
||||
return
|
||||
end
|
||||
local RAF_filename = df.sanitize_filename(tostring(image))
|
||||
|
||||
@@ -40,8 +40,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "geoJSON_export")
|
||||
|
||||
dt.gettext.bindtextdomain("geoJSON_export", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -51,7 +49,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "geoJSON_export",
|
||||
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"
|
||||
@@ -92,19 +90,19 @@ 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
|
||||
|
||||
@@ -295,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
|
||||
@@ -315,19 +313,19 @@ end
|
||||
dt.preferences.register("geoJSON_export",
|
||||
"CreateMapBoxHTMLFile",
|
||||
"bool",
|
||||
_("geoJSON export: Create an additional HTML file"),
|
||||
"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"),
|
||||
"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"),
|
||||
"geoJSON export: " .. _("open geoJSON file after export"),
|
||||
_("opens the geoJSON file after the export with the standard program for geoJSON files"),
|
||||
false )
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -33,8 +34,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "geoToolbox")
|
||||
|
||||
dt.gettext.bindtextdomain("geoToolbox", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -44,7 +43,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "geoToolbox",
|
||||
name = _("geo toolbox"),
|
||||
purpose = _("geodata tools"),
|
||||
author = "Tobias Jakobs",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoToolbox"
|
||||
@@ -65,21 +64,33 @@ gT.event_registered = false
|
||||
local labelDistance = dt.new_widget("label")
|
||||
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()
|
||||
@@ -169,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
|
||||
@@ -208,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
|
||||
@@ -244,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
|
||||
@@ -291,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 = string.format(_("latitude: "), copy_gps_latitude)
|
||||
label_copy_gps_lon.label = string.format(_("longitude: "),copy_gps_longitude)
|
||||
label_copy_gps_ele.label = string.format(_("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
|
||||
@@ -318,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
|
||||
@@ -345,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
|
||||
@@ -372,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
|
||||
|
||||
@@ -388,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
|
||||
@@ -463,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
|
||||
@@ -547,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
|
||||
@@ -597,7 +608,7 @@ local function install_module()
|
||||
if not gT.module_installed then
|
||||
dt.register_lib(
|
||||
"geoToolbox", -- Module name
|
||||
"geo toolbox", -- name
|
||||
_("geo toolbox"), -- name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||
@@ -665,13 +676,25 @@ gT.widget = dt.new_widget("box")
|
||||
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,--------------------------------------------------------
|
||||
|
||||
@@ -74,8 +74,6 @@ local gimp_widget = nil
|
||||
|
||||
du.check_min_api_version("7.0.0", "gimp")
|
||||
|
||||
dt.gettext.bindtextdomain("gimp", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -85,7 +83,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "gimp",
|
||||
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"
|
||||
|
||||
@@ -25,14 +25,12 @@ 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("7.0.0", "gpx_export")
|
||||
|
||||
gettext.bindtextdomain("gpx_export", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext.dgettext("gpx_export", msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
-- return data structure for script_manager
|
||||
@@ -40,7 +38,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "gpx_export",
|
||||
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"
|
||||
@@ -150,7 +148,7 @@ local function install_module()
|
||||
if not gpx.module_installed then
|
||||
dt.register_lib(
|
||||
"gpx_exporter",
|
||||
"gpx export",
|
||||
_("gpx export"),
|
||||
true, -- expandable
|
||||
true, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||
|
||||
@@ -36,8 +36,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("2.0.0", "harmonic_armature_guide")
|
||||
|
||||
dt.gettext.bindtextdomain("harmonic_armature_guide", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -45,7 +43,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "harmonic_armature_guide",
|
||||
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"
|
||||
|
||||
@@ -64,8 +64,6 @@ du.check_min_api_version("7.0.0", MODULE)
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("hif_group_leader", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -77,7 +75,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "hif_group_leader",
|
||||
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"
|
||||
|
||||
@@ -56,8 +56,6 @@ local PQ = dt.configuration.running_os == "windows" and '"' or "'"
|
||||
-- works with darktable API version from 5.0.0 on
|
||||
du.check_min_api_version("7.0.0", "hugin")
|
||||
|
||||
dt.gettext.bindtextdomain("hugin", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -67,7 +65,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "hugin",
|
||||
name = _("hugin"),
|
||||
purpose = _("stitch images into a panorama"),
|
||||
author = "Wolfgang Goetz",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin"
|
||||
@@ -85,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
|
||||
@@ -146,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
|
||||
|
||||
|
||||
@@ -73,8 +73,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
-- works with LUA API version 5.0.0
|
||||
du.check_min_api_version("7.0.0", "image_stack")
|
||||
|
||||
dt.gettext.bindtextdomain("image_stack", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -84,7 +82,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "image_stack",
|
||||
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"
|
||||
@@ -141,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"){
|
||||
@@ -506,7 +504,7 @@ local function image_stack(storage, image_table, extra_data)
|
||||
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
|
||||
|
||||
@@ -116,8 +116,6 @@ img_time.event_registered = false
|
||||
|
||||
du.check_min_api_version("7.0.0", "image_time")
|
||||
|
||||
dt.gettext.bindtextdomain("image_time", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -127,7 +125,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "image_time",
|
||||
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"
|
||||
@@ -156,12 +154,12 @@ local function systime2exiftime(systime)
|
||||
end
|
||||
|
||||
local function vars2exiftime(year, month, day, hour, min, sec)
|
||||
local y = tonumber(year) and string.format("%4d", year) or " "
|
||||
local mo = tonumber(month) and string.format("%02d", month) or " "
|
||||
local d = tonumber(day) and string.format("%02d", day) or " "
|
||||
local h = tonumber(hour) and string.format("%02d", hour) or " "
|
||||
local m = tonumber(min) and string.format("%02d", min) or " "
|
||||
local s = tonumber(sec) and string.format("%02d", sec) or " "
|
||||
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
|
||||
|
||||
@@ -195,7 +193,7 @@ end
|
||||
|
||||
local function synchronize_time(images)
|
||||
local sign = 1
|
||||
if img_time.sdir.value == "subtract" then
|
||||
if img_time.sdir.value == _("subtract") then
|
||||
sign = -1
|
||||
end
|
||||
synchronize_times(images, tonumber(img_time.diff_entry.text) * sign)
|
||||
@@ -213,7 +211,6 @@ local function year_month2months(year, month)
|
||||
end
|
||||
|
||||
local function months2year_month(months)
|
||||
dt.print_log("months is " .. months)
|
||||
local year = math.floor(months / 12)
|
||||
local month = months - (year * 12)
|
||||
|
||||
@@ -226,7 +223,7 @@ local function get_image_taken_time(image)
|
||||
|
||||
local exiv2 = df.check_if_bin_exists("exiv2")
|
||||
if exiv2 then
|
||||
p = io.popen(exiv2 .. " -K Exif.Image.DateTime " .. image.path .. PS .. image.filename)
|
||||
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
|
||||
@@ -265,7 +262,7 @@ end
|
||||
|
||||
local function _get_nix_image_file_creation_time(image)
|
||||
local datetime = nil
|
||||
local p = io.popen("ls -lL --time-style=full-iso " .. image.path .. PS .. image.filename)
|
||||
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
|
||||
@@ -511,7 +508,7 @@ img_time.stack = dt.new_widget("stack"){
|
||||
dt.new_widget("box"){
|
||||
orientation = "vertical",
|
||||
dt.new_widget("label"){label = _("set time")},
|
||||
dt.new_widget("section_label"){label = _("date: ")},
|
||||
dt.new_widget("section_label"){label = _("date:")},
|
||||
img_time.sdy,
|
||||
img_time.smo,
|
||||
img_time.syr,
|
||||
|
||||
@@ -64,8 +64,6 @@ du.check_min_api_version("7.0.0", MODULE)
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("jpg_group_leader", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -77,7 +75,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "jpg_group_leader",
|
||||
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"
|
||||
|
||||
@@ -45,8 +45,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
|
||||
du.check_min_api_version("7.0.0", "kml_export")
|
||||
|
||||
dt.gettext.bindtextdomain("kml_export", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -56,7 +54,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "kml_export",
|
||||
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"
|
||||
@@ -95,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
|
||||
@@ -110,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
|
||||
|
||||
@@ -41,8 +41,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("2.0.0", "passport_guide")
|
||||
|
||||
dt.gettext.bindtextdomain("passport_guide", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -50,7 +48,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "passport_guide",
|
||||
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"
|
||||
|
||||
@@ -40,21 +40,19 @@ 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_germany")
|
||||
|
||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||
dt.gettext.bindtextdomain("passport_guide_germany",dt.configuration.config_dir.."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext.dgettext("passport_guide_germany", msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "passport_guide_germany",
|
||||
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"
|
||||
|
||||
@@ -44,8 +44,6 @@ local df = require "lib/dtutils.file"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("pdf_slideshow", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -62,7 +60,7 @@ du.check_min_api_version("7.0.0", "pdf_slideshow")
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "pdf_slideshow",
|
||||
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"
|
||||
|
||||
@@ -45,7 +45,6 @@ local MODULE_NAME = "photils"
|
||||
du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
dt.gettext.bindtextdomain("photils", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
@@ -56,7 +55,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "photils",
|
||||
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"
|
||||
@@ -265,10 +264,6 @@ function PHOTILS.get_tags(image, with_export)
|
||||
local out_arg = df.sanitize_filename(tmp_file)
|
||||
local executable = photils_installed
|
||||
|
||||
if dt.configuration.running_os == "macos" then
|
||||
executable = executable .. "/Contents/MacOS/photils-cli"
|
||||
end
|
||||
|
||||
if with_export then
|
||||
dt.print_log("use export to for prediction")
|
||||
local export_file = df.create_tmp_file()
|
||||
@@ -339,7 +334,7 @@ function PHOTILS.on_tags_clicked()
|
||||
end
|
||||
|
||||
if #PHOTILS.tags == 0 then
|
||||
local msg = string.format(_("no tags where found"), MODULE_NAME)
|
||||
local msg = string.format(_("no tags were found"), MODULE_NAME)
|
||||
GUI.warning_label.label = msg
|
||||
GUI.stack.active = GUI.error_view
|
||||
return
|
||||
@@ -394,7 +389,7 @@ end
|
||||
local function install_module()
|
||||
if not PHOTILS.module_installed then
|
||||
dt.register_lib(MODULE_NAME,
|
||||
"photils autotagger",
|
||||
_("photils auto-tagger"),
|
||||
true,
|
||||
true,
|
||||
PHOTILS.plugin_display_views,
|
||||
|
||||
@@ -51,8 +51,6 @@ du.check_min_api_version("7.0.0", "quicktag")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("quicktag", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -62,7 +60,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "quicktag",
|
||||
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"
|
||||
@@ -199,7 +197,7 @@ local function install_module()
|
||||
if not qt.module_installed then
|
||||
dt.register_lib(
|
||||
"quicktag", -- Module name
|
||||
"quicktag", -- name
|
||||
_("quick tag"), -- name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
|
||||
@@ -306,7 +304,7 @@ end
|
||||
for i=1,qnr do
|
||||
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
|
||||
|
||||
@@ -46,8 +46,6 @@ du.check_min_api_version("7.0.0", "rate_group")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("rate_group", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -57,7 +55,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "rate_group",
|
||||
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"
|
||||
@@ -93,8 +91,6 @@ local function destroy()
|
||||
dt.destroy_event("rg5", "shortcut")
|
||||
end
|
||||
|
||||
local rate_group = _("rate group")
|
||||
|
||||
dt.register_event("rg_reject", "shortcut",
|
||||
function(event, shortcut)
|
||||
apply_rating(-1)
|
||||
@@ -103,32 +99,33 @@ end, _("reject group"))
|
||||
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("rg1", "shortcut",
|
||||
function(event, shortcut)
|
||||
apply_rating(1)
|
||||
end, rate group .. " 1")
|
||||
end, string.format(_("rate group %d"), 1))
|
||||
|
||||
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("rg3", "shortcut",
|
||||
function(event, shortcut)
|
||||
apply_rating(3)
|
||||
end, rate group .. " 3")
|
||||
end, string.format(_("rate group %d"), 3))
|
||||
|
||||
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("rg5", "shortcut",
|
||||
function(event, shortcut)
|
||||
apply_rating(5)
|
||||
end, rate group .. " 5")
|
||||
end, string.format(_("rate group %d"), 5))
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
|
||||
@@ -38,8 +38,6 @@ du.deprecated("contrib/rename-tags.lua","darktable release 4.0")
|
||||
|
||||
local gettext = darktable.gettext.gettext
|
||||
|
||||
darktable.gettext.bindtextdomain("rename-tags", darktable.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -49,7 +47,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "rename-tags",
|
||||
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"
|
||||
|
||||
@@ -43,13 +43,12 @@
|
||||
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
|
||||
|
||||
dt.gettext.bindtextdomain("rename_images", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -57,12 +56,6 @@ end
|
||||
-- namespace variable
|
||||
local rename = {
|
||||
presets = {},
|
||||
substitutes = {},
|
||||
placeholders = {"ROLL_NAME","FILE_FOLDER","FILE_NAME","FILE_EXTENSION","ID","VERSION","SEQUENCE","YEAR","MONTH","DAY",
|
||||
"HOUR","MINUTE","SECOND","EXIF_YEAR","EXIF_MONTH","EXIF_DAY","EXIF_HOUR","EXIF_MINUTE","EXIF_SECOND",
|
||||
"STARS","LABELS","MAKER","MODEL","TITLE","CREATOR","PUBLISHER","RIGHTS","USERNAME","PICTURES_FOLDER",
|
||||
"HOME","DESKTOP","EXIF_ISO","EXIF_EXPOSURE","EXIF_EXPOSURE_BIAS","EXIF_APERTURE","EXIF_FOCUS_DISTANCE",
|
||||
"EXIF_FOCAL_LENGTH","LONGITUDE","LATITUDE","ELEVATION","LENS","DESCRIPTION","EXIF_CROP"},
|
||||
widgets = {},
|
||||
}
|
||||
rename.module_installed = false
|
||||
@@ -72,7 +65,7 @@ rename.event_registered = false
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "rename_images",
|
||||
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"
|
||||
@@ -99,83 +92,6 @@ local DESKTOP = HOME .. PS .. "Desktop"
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function build_substitution_list(image, sequence, datetime, username, pic_folder, home, desktop)
|
||||
-- build the argument substitution list from each image
|
||||
-- local datetime = os.date("*t")
|
||||
local colorlabels = {}
|
||||
if image.red then table.insert(colorlabels, "red") end
|
||||
if image.yellow then table.insert(colorlabels, "yellow") end
|
||||
if image.green then table.insert(colorlabels, "green") end
|
||||
if image.blue then table.insert(colorlabels, "blue") end
|
||||
if image.purple then table.insert(colorlabels, "purple") end
|
||||
local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, ",")
|
||||
local eyear,emon,eday,ehour,emin,esec = string.match(image.exif_datetime_taken, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d-)$")
|
||||
local replacements = {image.film,
|
||||
image.path,
|
||||
df.get_filename(image.filename),
|
||||
string.upper(df.get_filetype(image.filename)),
|
||||
image.id,image.duplicate_index,
|
||||
string.format("%04d", sequence),
|
||||
datetime.year,
|
||||
string.format("%02d", datetime.month),
|
||||
string.format("%02d", datetime.day),
|
||||
string.format("%02d", datetime.hour),
|
||||
string.format("%02d", datetime.min),
|
||||
string.format("%02d", datetime.sec),
|
||||
eyear,
|
||||
emon,
|
||||
eday,
|
||||
ehour,
|
||||
emin,
|
||||
esec,
|
||||
image.rating,
|
||||
labels,
|
||||
image.exif_maker,
|
||||
image.exif_model,
|
||||
image.title,
|
||||
image.creator,
|
||||
image.publisher,
|
||||
image.rights,
|
||||
username,
|
||||
pic_folder,
|
||||
home,
|
||||
desktop,
|
||||
image.exif_iso,
|
||||
image.exif_exposure,
|
||||
image.exif_exposure_bias,
|
||||
image.exif_aperture,
|
||||
image.exif_focus_distance,
|
||||
image.exif_focal_length,
|
||||
image.longitude,
|
||||
image.latitude,
|
||||
image.elevation,
|
||||
image.exif_lens,
|
||||
image.description,
|
||||
image.exif_crop
|
||||
}
|
||||
|
||||
for i=1,#rename.placeholders,1 do rename.substitutes[rename.placeholders[i]] = replacements[i] end
|
||||
end
|
||||
|
||||
local function substitute_list(str)
|
||||
-- replace the substitution variables in a string
|
||||
for match in string.gmatch(str, "%$%(.-%)") do
|
||||
local var = string.match(match, "%$%((.-)%)")
|
||||
if rename.substitutes[var] then
|
||||
str = string.gsub(str, "%$%("..var.."%)", rename.substitutes[var])
|
||||
else
|
||||
dt.print_error("unrecognized variable " .. var)
|
||||
dt.print(string.format(_("unknown variable %s, aborting..."), var))
|
||||
return -1
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
local function clear_substitute_list()
|
||||
for i=1,#rename.placeholders,1 do rename.substitutes[rename.placeholders[i]] = nil end
|
||||
end
|
||||
|
||||
local function stop_job(job)
|
||||
job.valid = false
|
||||
end
|
||||
@@ -225,14 +141,14 @@ local function do_rename(images)
|
||||
for i, image in ipairs(images) do
|
||||
if job.valid then
|
||||
job.percent = i / #images
|
||||
build_substitution_list(image, i, datetime, USER, PICTURES, HOME, DESKTOP)
|
||||
local new_name = substitute_list(pattern)
|
||||
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
|
||||
clear_substitute_list()
|
||||
ds.clear_substitute_list()
|
||||
local args = {}
|
||||
local path = string.sub(df.get_path(new_name), 1, -2)
|
||||
if string.len(path) == 0 then
|
||||
@@ -269,60 +185,19 @@ local function do_rename(images)
|
||||
end
|
||||
end
|
||||
|
||||
local function reset_callback()
|
||||
rename.widgets.pattern.text = ""
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- W I D G E T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
rename.widgets.pattern = dt.new_widget("entry"){
|
||||
tooltip = _("$(ROLL_NAME) - film roll name\n") ..
|
||||
_("$(FILE_FOLDER) - image file folder\n") ..
|
||||
_("$(FILE_NAME) - image file name\n") ..
|
||||
_("$(FILE_EXTENSION) - image file extension\n") ..
|
||||
_("$(ID) - image id\n") ..
|
||||
_("$(VERSION) - version number\n") ..
|
||||
_("$(SEQUENCE) - sequence number of selection\n") ..
|
||||
_("$(YEAR) - current year\n") ..
|
||||
_("$(MONTH) - current month\n") ..
|
||||
_("$(DAY) - current day\n") ..
|
||||
_("$(HOUR) - current hour\n") ..
|
||||
_("$(MINUTE) - current minute\n") ..
|
||||
_("$(SECOND) - current second\n") ..
|
||||
_("$(EXIF_YEAR) - EXIF year\n") ..
|
||||
_("$(EXIF_MONTH) - EXIF month\n") ..
|
||||
_("$(EXIF_DAY) - EXIF day\n") ..
|
||||
_("$(EXIF_HOUR) - EXIF hour\n") ..
|
||||
_("$(EXIF_MINUTE) - EXIF minute\n") ..
|
||||
_("$(EXIF_SECOND) - EXIF seconds\n") ..
|
||||
_("$(EXIF_ISO) - EXIF ISO\n") ..
|
||||
_("$(EXIF_EXPOSURE) - EXIF exposure\n") ..
|
||||
_("$(EXIF_EXPOSURE_BIAS) - EXIF exposure bias\n") ..
|
||||
_("$(EXIF_APERTURE) - EXIF aperture\n") ..
|
||||
_("$(EXIF_FOCAL_LENGTH) - EXIF focal length\n") ..
|
||||
_("$(EXIF_FOCUS_DISTANCE) - EXIF focus distance\n") ..
|
||||
_("$(EXIF_CROP) - EXIF crop\n") ..
|
||||
_("$(LONGITUDE) - longitude\n") ..
|
||||
_("$(LATITUDE) - latitude\n") ..
|
||||
_("$(ELEVATION) - elevation\n") ..
|
||||
_("$(STARS) - star rating\n") ..
|
||||
_("$(LABELS) - color labels\n") ..
|
||||
_("$(MAKER) - camera maker\n") ..
|
||||
_("$(MODEL) - camera model\n") ..
|
||||
_("$(LENS) - lens\n") ..
|
||||
_("$(TITLE) - title from metadata\n") ..
|
||||
_("$(DESCRIPTION) - description from metadata\n") ..
|
||||
_("$(CREATOR) - creator from metadata\n") ..
|
||||
_("$(PUBLISHER) - publisher from metadata\n") ..
|
||||
_("$(RIGHTS) - rights from metadata\n") ..
|
||||
_("$(USERNAME) - username\n") ..
|
||||
_("$(PICTURES_FOLDER) - pictures folder\n") ..
|
||||
_("$(HOME) - user's home directory\n") ..
|
||||
_("$(DESKTOP) - desktop directory"),
|
||||
placeholder = _("enter pattern $(FILE_FOLDER)/$(FILE_NAME)"),
|
||||
text = ""
|
||||
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")
|
||||
|
||||
@@ -20,19 +20,22 @@ Enable selection of non-existing images in the the currently worked on images, e
|
||||
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 = "select_non_existing"
|
||||
local MODULE <const> = "select_non_existing"
|
||||
|
||||
du.check_min_api_version("9.1.0", MODULE)
|
||||
|
||||
-- figure out the path separator
|
||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
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
|
||||
|
||||
dt.gettext.bindtextdomain("select_non_existing", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -41,26 +44,61 @@ local function stop_job(job)
|
||||
job.valid = false
|
||||
end
|
||||
|
||||
local function select_nonexisting_images(event, images)
|
||||
local selection = {}
|
||||
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)
|
||||
|
||||
local job = dt.gui.create_job(_("select non existing images"), true, stop_job)
|
||||
for key,image in ipairs(images) do
|
||||
if(job.valid) then
|
||||
job.percent = (key - 1)/#images
|
||||
local filepath = image.path..PS..image.filename
|
||||
local file_exists = df.test_file(filepath, "e")
|
||||
dt.print_log(filepath.." exists? => "..tostring(file_exists))
|
||||
if (not file_exists) then
|
||||
table.insert(selection, image)
|
||||
end
|
||||
else
|
||||
break
|
||||
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
|
||||
stop_job(job)
|
||||
p:close()
|
||||
end
|
||||
|
||||
return selection
|
||||
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()
|
||||
@@ -76,7 +114,7 @@ dt.gui.libs.select.register_selection(
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "select_non_existing",
|
||||
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"
|
||||
|
||||
@@ -24,8 +24,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "select_untagged")
|
||||
|
||||
dt.gettext.bindtextdomain("select_untagged", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -35,7 +33,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "select_untagged",
|
||||
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"
|
||||
|
||||
199
contrib/selected_image_visible.lua
Normal file
199
contrib/selected_image_visible.lua
Normal 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
|
||||
@@ -31,8 +31,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "slideshowMusic")
|
||||
|
||||
dt.gettext.bindtextdomain("slideshowMusic", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -42,7 +40,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "slideshowMusic",
|
||||
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"
|
||||
|
||||
313
contrib/toggle_group_view.lua
Normal file
313
contrib/toggle_group_view.lua
Normal 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
|
||||
@@ -76,14 +76,13 @@ 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("7.0.0", LIB_ID)
|
||||
|
||||
darktable.gettext.bindtextdomain("transfer_hierarchy", darktable.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return darktable.gettext.gettext(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
-- return data structure for script_manager
|
||||
@@ -91,7 +90,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "transfer_hierarchy",
|
||||
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"
|
||||
@@ -145,7 +144,7 @@ end
|
||||
local function install_module()
|
||||
if not th.module_installed then
|
||||
darktable.register_lib(LIB_ID,
|
||||
"transfer hierarchy", true, true, {
|
||||
_("transfer hierarchy"), true, true, {
|
||||
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
|
||||
}, th.transfer_widget, nil, nil)
|
||||
th.module_installed = true
|
||||
@@ -271,7 +270,7 @@ 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
|
||||
|
||||
1066
contrib/ultrahdr.lua
Normal file
1066
contrib/ultrahdr.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -41,8 +41,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "video_ffmpeg")
|
||||
|
||||
dt.gettext.bindtextdomain("video_ffmpeg", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -52,7 +50,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "video_ffmpeg",
|
||||
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"
|
||||
@@ -248,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)
|
||||
@@ -418,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"]
|
||||
@@ -450,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"))
|
||||
|
||||
294
contrib/website_gallery_export.lua
Normal file
294
contrib/website_gallery_export.lua
Normal 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
|
||||
56
data/website_gallery/css/gallery.css
Normal file
56
data/website_gallery/css/gallery.css
Normal 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;
|
||||
}
|
||||
99
data/website_gallery/css/modal.css
Normal file
99
data/website_gallery/css/modal.css
Normal 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;
|
||||
}
|
||||
|
||||
66
data/website_gallery/css/style.css
Normal file
66
data/website_gallery/css/style.css
Normal 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;
|
||||
}
|
||||
46
data/website_gallery/index.html
Normal file
46
data/website_gallery/index.html
Normal 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">◀</div>
|
||||
<div class="button nav nav-arrow" id="nextArrow">▶</div>
|
||||
<div class="spacer"></div>
|
||||
<div id="fullscreen" class="button nav">⛶</div>
|
||||
<div id="close" class="nav button">×</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>
|
||||
40
data/website_gallery/js/fullscreen.js
Normal file
40
data/website_gallery/js/fullscreen.js
Normal 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.');
|
||||
}
|
||||
};
|
||||
|
||||
112
data/website_gallery/js/gallery.js
Normal file
112
data/website_gallery/js/gallery.js
Normal 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);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
355
data/website_gallery/js/modal.js
Normal file
355
data/website_gallery/js/modal.js
Normal 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;
|
||||
}
|
||||
});
|
||||
@@ -27,8 +27,6 @@ local dt = require "darktable"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("api_version", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -41,7 +39,7 @@ end
|
||||
|
||||
local result = dt.configuration.api_version_string
|
||||
dt.print_log("API Version: " .. result)
|
||||
dt.print(string.format(_("API version: %s"), 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
|
||||
@@ -49,7 +47,7 @@ dt.print(string.format(_("API version: %s"), result))
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "api_version",
|
||||
name = _("APIversion"),
|
||||
purpose = _("display api_version example"),
|
||||
author = "Tobias Jakobs",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version"
|
||||
|
||||
@@ -60,8 +60,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("darkroom_demo", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -93,7 +91,7 @@ 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
|
||||
@@ -119,7 +117,7 @@ dt.gui.current_view(current_view)
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "darkroom_demo",
|
||||
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"
|
||||
|
||||
@@ -69,8 +69,6 @@ dt.print_error("Hello World!")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("gettextExample", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
-- Translate a string using the darktable textdomain
|
||||
dt.print_error(gettext("image"))
|
||||
|
||||
@@ -88,7 +86,7 @@ dt.print_error(_("hello world!"))
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "gettextExample",
|
||||
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"
|
||||
|
||||
@@ -5,7 +5,6 @@ local NaN = 0/0
|
||||
local wg = {}
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
dt.gettext.bindtextdomain("gui_action", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
@@ -16,7 +15,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "gui_action",
|
||||
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"
|
||||
@@ -29,37 +28,37 @@ script_data.show = nil -- only required for libs since the destroy_method only h
|
||||
|
||||
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'"
|
||||
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",
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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',
|
||||
label = _('perform action'),
|
||||
tooltip = _('perform action or only read return'),
|
||||
clicked_callback = function()
|
||||
wg.speed.sensitive = wg.check.value
|
||||
end,
|
||||
@@ -73,7 +72,7 @@ wg.return_value = dt.new_widget("entry"){
|
||||
|
||||
dt.register_lib(
|
||||
"execute_action", -- Module name
|
||||
"execute gui actions", -- name
|
||||
_("execute gui actions"), -- name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100},
|
||||
@@ -85,37 +84,37 @@ dt.register_lib(
|
||||
dt.new_widget("box")
|
||||
{
|
||||
orientation = "horizontal",
|
||||
dt.new_widget("label"){label = "action path", halign = "start"},
|
||||
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"},
|
||||
dt.new_widget("label"){label = _("element"), halign = "start"},
|
||||
wg.element
|
||||
},
|
||||
dt.new_widget("box")
|
||||
{
|
||||
orientation = "horizontal",
|
||||
dt.new_widget("label"){label = "effect", halign = "start"},
|
||||
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"},
|
||||
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",
|
||||
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, wg.instance.value, wg.element.text, wg.effect.text, sp)
|
||||
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")
|
||||
|
||||
@@ -35,8 +35,6 @@ du.check_min_api_version("2.0.0", "hello_world")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("hello_world", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -55,7 +53,7 @@ dt.print(_("hello, world"))
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "hello_world",
|
||||
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"
|
||||
|
||||
@@ -218,7 +218,7 @@ current_sort_order = dt.gui.libs.filter.sort_order(current_sort_order)
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "lighttable_demo",
|
||||
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"
|
||||
|
||||
@@ -38,8 +38,6 @@ du.check_min_api_version("7.0.0", "moduleExample")
|
||||
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("moduleExample", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -49,7 +47,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "moduleExample",
|
||||
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"
|
||||
@@ -77,7 +75,7 @@ local function install_module()
|
||||
-- https://www.darktable.org/lua-api/index.html#darktable_register_lib
|
||||
dt.register_lib(
|
||||
"exampleModule", -- Module name
|
||||
"exampleModule", -- name
|
||||
_("example module"), -- name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||
@@ -87,7 +85,7 @@ local function install_module()
|
||||
orientation = "vertical",
|
||||
dt.new_widget("button")
|
||||
{
|
||||
label = _("my button"),
|
||||
label = _("my ") .. "button",
|
||||
clicked_callback = function (_)
|
||||
dt.print(_("button clicked"))
|
||||
end
|
||||
@@ -112,10 +110,10 @@ local function restart()
|
||||
end
|
||||
|
||||
-- https://www.darktable.org/lua-api/types_lua_check_button.html
|
||||
local check_button = dt.new_widget("check_button"){label = _("my check_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 = _("my combobox"), 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")
|
||||
@@ -131,14 +129,14 @@ local entry = dt.new_widget("entry")
|
||||
-- https://www.darktable.org/lua-api/types_lua_file_chooser_button.html
|
||||
local file_chooser_button = dt.new_widget("file_chooser_button")
|
||||
{
|
||||
title = _("my file_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 = _("my label") -- 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"){}
|
||||
@@ -146,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 = _("my slider"),
|
||||
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
|
||||
|
||||
@@ -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
|
||||
]]
|
||||
|
||||
--[[
|
||||
@@ -72,8 +72,6 @@ local dtsys = require "lib/dtutils.system" -- system utilities
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("multi_os", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -141,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
|
||||
@@ -154,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.
|
||||
@@ -166,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)
|
||||
@@ -190,8 +223,8 @@ local function extract_embedded_jpeg(images)
|
||||
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
|
||||
|
||||
@@ -215,7 +248,7 @@ end
|
||||
if dt.configuration.running_os ~= "linux" then
|
||||
local executable = "ufraw-batch"
|
||||
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
|
||||
title = _("select ufraw-batch[.exe] executable"),
|
||||
title = string.format(_("select %s executable"), "ufraw-batch[.exe]"),
|
||||
value = df.get_executable_path_preference(executable),
|
||||
is_directory = false,
|
||||
changed_callback = function(self)
|
||||
@@ -226,7 +259,7 @@ if dt.configuration.running_os ~= "linux" then
|
||||
}
|
||||
dt.preferences.register("executable_paths", "ufraw-batch", -- name
|
||||
"file", -- type
|
||||
_('multi_os: ufraw-batch location'), -- label
|
||||
'multi_os: ufraw-batch ' .. _('location'), -- label
|
||||
_('installed location of ufraw-batch, requires restart to take effect.'), -- tooltip
|
||||
"ufraw-batch", -- default
|
||||
ufraw_batch_path_widget
|
||||
@@ -262,7 +295,7 @@ dt.register_event(
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "multi_os",
|
||||
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"
|
||||
|
||||
@@ -61,8 +61,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("panels_demo", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -95,11 +93,11 @@ 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
|
||||
@@ -110,7 +108,7 @@ end
|
||||
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
|
||||
@@ -148,7 +146,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "panels_demo",
|
||||
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"
|
||||
|
||||
@@ -28,8 +28,6 @@ local du = require "lib/dtutils"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("preferenceExamples", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -37,7 +35,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "preferenceExamples",
|
||||
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"
|
||||
@@ -49,22 +47,22 @@ 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.
|
||||
"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.
|
||||
"preferenceExamplesInteger", -- name
|
||||
"integer", -- type
|
||||
_("example integer"), -- label
|
||||
_("example integer tooltip"), -- tooltip
|
||||
_("example") .. " integer", -- label
|
||||
_("example") .. " integer " .. _("tooltip"), -- tooltip
|
||||
2, -- default
|
||||
1, -- min
|
||||
99) -- max
|
||||
@@ -72,8 +70,8 @@ dt.preferences.register("preferenceExamples", -- script: This is a string
|
||||
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.
|
||||
"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
|
||||
@@ -82,22 +80,22 @@ dt.preferences.register("preferenceExamples", -- script: This is a string
|
||||
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.
|
||||
"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.
|
||||
"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.
|
||||
"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
|
||||
|
||||
|
||||
@@ -28,8 +28,6 @@ du.check_min_api_version("5.0.0", "printExamples")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("printExamples", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -60,7 +58,7 @@ dt.print_log("print log")
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "printExamples",
|
||||
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"
|
||||
|
||||
@@ -34,8 +34,6 @@ du.check_min_api_version("5.0.0", "running_os")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("running_os", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -54,7 +52,7 @@ dt.print(string.format(_("you are running: %s"), dt.configuration.running_os))
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "running_os",
|
||||
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"
|
||||
|
||||
@@ -48,6 +48,13 @@ 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"
|
||||
@@ -56,139 +63,283 @@ local du = require "lib/dtutils"
|
||||
du.check_min_api_version("9.2.0", "x-touch")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
dt.gettext.bindtextdomain("gui_action", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
-- return data structure for script_manager
|
||||
-- Constants
|
||||
local NUM_KNOBS = 8
|
||||
local NAN = 0/0
|
||||
local darkroom_view = dt.gui.views.darkroom
|
||||
|
||||
local script_data = {}
|
||||
-- ============================================================================
|
||||
-- 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
|
||||
},
|
||||
|
||||
script_data.metadata = {
|
||||
name = "x-touch",
|
||||
purpose = _("example of how to control an x-touch midi device"),
|
||||
author = "Diederik ter Rahe",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch"
|
||||
}
|
||||
|
||||
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
|
||||
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"}
|
||||
}
|
||||
|
||||
-- set up 8 mimic sliders with the same function
|
||||
for k = 1,8 do
|
||||
dt.gui.mimic("slider", "knob ".. k,
|
||||
function(action, element, effect, size)
|
||||
-- take the number from the mimic name
|
||||
local k = tonumber(action:sub(-1))
|
||||
-- Priority order for checking module focus (most commonly used first)
|
||||
local MODULE_CHECK_ORDER = {
|
||||
"toneequal",
|
||||
"colorequal",
|
||||
"colorbalancergb",
|
||||
"agx",
|
||||
"sigmoid",
|
||||
"primaries",
|
||||
"channelmixerrgb",
|
||||
"colorzones"
|
||||
}
|
||||
|
||||
-- only operate in darkroom; return NAN otherwise
|
||||
if dt.gui.current_view() ~= dt.gui.views.darkroom then
|
||||
return 0/0
|
||||
end
|
||||
-- ============================================================================
|
||||
-- CORE LOGIC - No need to edit below unless changing architecture
|
||||
-- ============================================================================
|
||||
|
||||
local maskval = 0/0
|
||||
if k < 8 then
|
||||
-- first try if the mask slider at that position is active
|
||||
local s = { "opacity",
|
||||
"size",
|
||||
"feather",
|
||||
"hardness",
|
||||
"rotation",
|
||||
"curvature",
|
||||
"compression" }
|
||||
maskval = dt.gui.action("lib/masks/properties/" .. s[k],
|
||||
element, effect, size)
|
||||
end
|
||||
-- if a value different from NAN is returned, the slider was active
|
||||
if maskval == maskval then
|
||||
return maskval
|
||||
-- Helper functions
|
||||
local function is_in_darkroom()
|
||||
return dt.gui.current_view() == darkroom_view
|
||||
end
|
||||
|
||||
-- try if colorzones module is focused; if so select element of graph
|
||||
elseif dt.gui.action("iop/colorzones", "focus") ~= 0 then
|
||||
which = "iop/colorzones/graph"
|
||||
local e = { "red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"aqua",
|
||||
"blue",
|
||||
"purple",
|
||||
"magenta" }
|
||||
element = e[k]
|
||||
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
|
||||
|
||||
-- if the sigmoid rgb primaries is focused,
|
||||
-- check sliders
|
||||
elseif dt.gui.action("iop/sigmoid", "focus") ~= 0 and k <8 then
|
||||
local e = { "red attenuation", "red rotation", "green attenuation", "green rotation", "blue attenuation", "blue rotation", "recover purity" }
|
||||
which = "iop/sigmoid/primaries/"..e[k]
|
||||
-- Execute an action configuration
|
||||
local function execute_action(action_config, element, effect, size)
|
||||
if not action_config then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- if the rgb primaries is focused,
|
||||
-- check sliders
|
||||
elseif dt.gui.action("iop/primaries", "focus") ~= 0 and k >=1 then
|
||||
local e = { "red hue", "red purity", "green hue", "green purity", "blue hue", "blue purity", "tint hue", "tint purity" }
|
||||
which = "iop/primaries/" ..e[k]
|
||||
local path = action_config[1]
|
||||
local elem = action_config[2] or element
|
||||
local action_type = action_config[3]
|
||||
|
||||
-- if the tone equalizer is focused,
|
||||
-- select one of the sliders in the "simple" tab
|
||||
elseif dt.gui.action("iop/toneequal", "focus") ~= 0 then
|
||||
which ="iop/toneequal/simple/"..(k-9).." EV"
|
||||
-- Handle special action types
|
||||
if action_type == "tab" then
|
||||
effect = translate_tab_effect(effect)
|
||||
end
|
||||
|
||||
-- if color calibration is focused,
|
||||
-- the last 4 knobs are sent there
|
||||
elseif dt.gui.action("iop/channelmixerrgb", "focus") ~= 0
|
||||
and k >= 5 then
|
||||
-- knob 5 selects the active tab; pressing it resets to CAT
|
||||
if k == 5 then
|
||||
which = "iop/channelmixerrgb/page"
|
||||
element = "CAT"
|
||||
-- since the tab action is not a slider,
|
||||
-- the effects need to be translated
|
||||
if effect == "up" then effect = "next"
|
||||
elseif effect == "down" then effect = "previous"
|
||||
else effect = "activate"
|
||||
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
|
||||
-- knobs 6, 7 and 8 are for the three color sliders on each tab
|
||||
which = "iop/focus/sliders"
|
||||
local e = { "1st",
|
||||
"2nd",
|
||||
"3rd" }
|
||||
element = e[k - 5]
|
||||
-- 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
|
||||
|
||||
-- the 4th knob is contrast;
|
||||
-- either colorbalance if it is focused, or filmic
|
||||
elseif dt.gui.action("iop/colorbalancergb", "focus") ~= 0
|
||||
and k == 4 then
|
||||
which = "iop/colorbalancergb/contrast"
|
||||
|
||||
-- in all other cases use a default selection
|
||||
else
|
||||
local s = { "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" }
|
||||
which = s[k]
|
||||
end
|
||||
|
||||
-- now pass the element/effect/size to the selected slider
|
||||
return dt.gui.action(which, element, effect, size)
|
||||
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
|
||||
|
||||
local function destroy()
|
||||
-- nothing to destroy
|
||||
-- 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_data.destroy = destroy
|
||||
-- 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
|
||||
|
||||
@@ -61,7 +61,7 @@ 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.")
|
||||
@@ -96,7 +96,7 @@ dtutils.libdoc.functions["check_max_api_version"] = {
|
||||
|
||||
function dtutils.check_max_api_version(max_api, script_name)
|
||||
local current_api = dt.configuration.api_version_string
|
||||
if current_api > max_api then
|
||||
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.")
|
||||
@@ -374,7 +374,7 @@ dtutils.libdoc.functions["deprecated"] = {
|
||||
|
||||
du.deprecated(script_name, removal_string)
|
||||
script_name - name of the script being deprecated
|
||||
removal_strubg - a string explaining when the script will be removed]],
|
||||
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 = [[]],
|
||||
@@ -391,5 +391,77 @@ function dtutils.deprecated(script_name, 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
|
||||
|
||||
@@ -24,15 +24,12 @@ 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
|
||||
|
||||
--[[
|
||||
@@ -522,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
|
||||
@@ -575,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
|
||||
@@ -815,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
|
||||
@@ -840,7 +837,7 @@ 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"] = {
|
||||
|
||||
@@ -3,9 +3,14 @@ local dtutils_string = {}
|
||||
local dt = require "darktable"
|
||||
local du = require "lib/dtutils"
|
||||
local log = require "lib/dtutils.log"
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
local DEFAULT_LOG_LEVEL = log.error
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
|
||||
dtutils_string.log_level = DEFAULT_LOG_LEVEL
|
||||
|
||||
dtutils_string.libdoc = {
|
||||
@@ -301,8 +306,16 @@ end
|
||||
local function _should_be_sanitized(str)
|
||||
local old_log_level = log.log_level()
|
||||
local result = false
|
||||
local UNSAFE_POSIX_FILENAME_CHARS <const> = "[^%w/._%-]+"
|
||||
local UNSAFE_WIN_FILENAME_CHARS <const> = "[^%w\\._%-:]+"
|
||||
|
||||
local pattern = UNSAFE_POSIX_FILENAME_CHARS
|
||||
if dt.configuration.running_os == "windows" then
|
||||
pattern = UNSAFE_WIN_FILENAME_CHARS
|
||||
end
|
||||
|
||||
log.log_level(dtutils_string.log_level)
|
||||
if string.match(str, "[^%g]") then
|
||||
if string.match(str, pattern) then
|
||||
result = true
|
||||
end
|
||||
log.log_level(old_log_level)
|
||||
@@ -530,10 +543,13 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
||||
"FILE.NAME",
|
||||
"FILE.EXTENSION",
|
||||
"ID",
|
||||
"IMAGE.ID",
|
||||
"IMAGE.ID.NEXT",
|
||||
"VERSION",
|
||||
"VERSION.IF.MULTI",
|
||||
"VERSION.NAME",
|
||||
"DARKTABLE.VERSION",
|
||||
"Xmp.darktable.version",
|
||||
"DARKTABLE.NAME", -- Not Implemented
|
||||
"SEQUENCE",
|
||||
"WIDTH.SENSOR",
|
||||
@@ -553,6 +569,7 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
||||
"MONTH.SHORT",
|
||||
"DAY",
|
||||
"HOUR",
|
||||
"HOUR.AMPM", -- Not Implemented
|
||||
"MINUTE",
|
||||
"SECOND",
|
||||
"MSEC",
|
||||
@@ -563,6 +580,7 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
||||
"EXIF.MONTH.SHORT",
|
||||
"EXIF.DAY",
|
||||
"EXIF.HOUR",
|
||||
"EXIF.HOUR.AMPM", -- Not Implemented
|
||||
"EXIF.MINUTE",
|
||||
"EXIF.SECOND",
|
||||
"EXIF.MSEC",
|
||||
@@ -571,12 +589,23 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
||||
"EXIF.ISO",
|
||||
"EXIF.EXPOSURE",
|
||||
"EXIF.EXPOSURE.BIAS",
|
||||
"EXIF.EXPOSURE.PROGRAM", -- Not Implemented
|
||||
"EXIF.APERTURE",
|
||||
"EXIF.CROP.FACTOR",
|
||||
"EXIF.FOCAL.LENGTH",
|
||||
"EXIF.FOCAL.LENGTH.EQUIV", -- Not Implemented
|
||||
"EXIF.FOCUS.DISTANCE",
|
||||
"IMAGE.EXIF", -- Not Implemented
|
||||
"EXIF.MAKER",
|
||||
"EXIF.MODEL",
|
||||
"EXIF.WHTIEBALANCE", -- Not Implemented
|
||||
"EXIF.METERING", -- Not Implemented
|
||||
"EXIF.LENS",
|
||||
"EXIF.FLASH.ICON", -- Not Implemented
|
||||
"EXIF.FLASH", -- Not Implemented
|
||||
"GPS.LONGITUDE", -- Not Implemented
|
||||
"GPS.LATITUDE", -- Not Implemented
|
||||
"GPS.ELEVATION", -- Not Implemented
|
||||
"GPS.LOCATION.ICON", -- Not Implemented
|
||||
"LONGITUDE",
|
||||
"LATITUDE",
|
||||
"ELEVATION",
|
||||
@@ -585,14 +614,16 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
||||
"RATING.ICONS", -- Not Implemented
|
||||
"LABELS",
|
||||
"LABELS.ICONS", -- Not Implemented
|
||||
"MAKER",
|
||||
"MODEL",
|
||||
"LENS",
|
||||
"TITLE",
|
||||
"Xmp.dc.title",
|
||||
"DESCRIPTION",
|
||||
"Xmp.dc.description",
|
||||
"CREATOR",
|
||||
"Xmp.dc.creator",
|
||||
"PUBLISHER",
|
||||
"Xmp.dc.publisher",
|
||||
"RIGHTS",
|
||||
"Xmp.dc.rights",
|
||||
"TAGS", -- Not Implemented
|
||||
"SIDECAR.TXT", -- Not Implemented
|
||||
"FOLDER.PICTURES",
|
||||
@@ -629,6 +660,37 @@ local function get_colorlabels(image)
|
||||
return labels
|
||||
end
|
||||
|
||||
-- handle the ROLL.NAME[n] case
|
||||
|
||||
local function build_rollname_substitution_list(image, variable_string)
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(dtutils_string.log_level)
|
||||
|
||||
for match in string.gmatch(variable_string, "%$%(.-%)?%)") do -- grab each complete variable
|
||||
log.msg(log.info, "match is " .. match)
|
||||
|
||||
local var = string.match(match, "%$%((.-%)?)%)") -- strip of the leading $( and trailing )
|
||||
log.msg(log.info, "var is " .. var)
|
||||
|
||||
local element = nil
|
||||
|
||||
if string.match(var, "ROLL.NAME%[") then
|
||||
element = string.match(var, "%[(%d)%]")
|
||||
|
||||
local path = du.split(image.film.path, dt.configuration.running_os ~= "windows" and "/" or "\\")
|
||||
|
||||
element = tonumber(element) + 1 -- add one because c arrays are 0 based and lua are 1 based
|
||||
|
||||
if element > #path then
|
||||
log.msg(log.warn, "ROLL.NAME element requested was more than number of elements in film roll")
|
||||
substitutes[var] = ""
|
||||
else
|
||||
substitutes[var] = path[(#path + 1) - element]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- find the $CATEGORYn and $CATEGORY[n,m] requests and add them to the substitute list
|
||||
|
||||
local function build_category_substitution_list(image, variable_string)
|
||||
@@ -688,7 +750,7 @@ end
|
||||
|
||||
-- build the argument substitution list from each image
|
||||
|
||||
function dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop)
|
||||
function dtutils_string.build_substitute_list(image, sequence, variable_string, username, pic_folder, home, desktop)
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(dtutils_string.log_level)
|
||||
|
||||
@@ -699,6 +761,8 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
||||
is_api_9_1 = false
|
||||
end
|
||||
|
||||
local is_api_9_4 = dt.configuration.api_version_string >= "9.4.0" and true or false
|
||||
|
||||
local datetime = os.date("*t")
|
||||
local long_month = os.date("%B")
|
||||
local short_month = os.date("%b")
|
||||
@@ -709,27 +773,43 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
||||
|
||||
local labels = get_colorlabels(image)
|
||||
|
||||
local emon_long, emon_short
|
||||
local eyear, emon, eday, ehour, emin, esec, emsec
|
||||
local use_millisecs = false
|
||||
if dt.preferences.read("darktable", "lighttable/ui/milliseconds", "bool") and is_api_9_1 then
|
||||
eyear, emon, eday, ehour, emin, esec, emsec =
|
||||
string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)%.(%d+)$")
|
||||
else
|
||||
emsec = "0"
|
||||
eyear, emon, eday, ehour, emin, esec =
|
||||
string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$")
|
||||
use_millisecs = true
|
||||
end
|
||||
|
||||
local version_multi = #image:get_group_members() > 1 and image.version or ""
|
||||
if image.exif_datetime_taken and image.exif_datetime_taken ~= "" then
|
||||
emon_long = os.date("%B", exiftime2systime(image.exif_datetime_taken))
|
||||
emon_short = os.date("%b", exiftime2systime(image.exif_datetime_taken))
|
||||
if use_millisecs then
|
||||
eyear, emon, eday, ehour, emin, esec, emsec =
|
||||
string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)%.(%d+)$")
|
||||
else
|
||||
emsec = "0"
|
||||
eyear, emon, eday, ehour, emin, esec =
|
||||
string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$")
|
||||
end
|
||||
else
|
||||
log.msg(log.warn, "no capture date and time found in EXIF data")
|
||||
emon_long, emon_short, eyear, emon, eday, ehour, emin, esec, emsec = "", "", "", "", "", "", "", "", ""
|
||||
end
|
||||
|
||||
local replacements = {image.film.path, -- ROLL.NAME
|
||||
local version_multi = #image:get_group_members() > 1 and image.duplicate_index or ""
|
||||
|
||||
local replacements = {dtutils_string.get_basename(image.film.path),-- ROLL.NAME
|
||||
image.path, -- FILE.FOLDER
|
||||
image.filename, -- FILE.NAME
|
||||
dtutils_string.get_basename(image.filename),-- FILE.NAME
|
||||
dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION
|
||||
image.id, -- ID
|
||||
image.id, -- IMAGE.ID
|
||||
dt.database[#dt.database].id + 1, -- IMAGE.ID.NEXT
|
||||
image.duplicate_index, -- VERSION
|
||||
version_multi, -- VERSION.IF_MULTI
|
||||
image.version_name, -- VERSION.NAME
|
||||
image.version_name and image.version_name or "", -- VERSION.NAME
|
||||
dt.configuration.version, -- DARKTABLE.VERSION
|
||||
dt.configuration.version, -- Xmp.darktable.version
|
||||
"", -- DARKTABLE.NAME
|
||||
string.format("%04d", sequence), -- SEQUENCE
|
||||
image.width, -- WIDTH.SENSOR
|
||||
@@ -749,16 +829,18 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
||||
short_month, -- MONTH.SHORT
|
||||
string.format("%02d", datetime.day), -- DAY
|
||||
string.format("%02d", datetime.hour), -- HOUR
|
||||
"", -- HOUR.AMPM
|
||||
string.format("%02d", datetime.min), -- MINUTE
|
||||
string.format("%02d", datetime.sec), -- SECOND
|
||||
0, -- MSEC
|
||||
eyear, -- EXIF.YEAR
|
||||
string.sub(eyear, 3), -- EXIF.YEAR.SHORT
|
||||
(eyear ~= nil) and string.sub(eyear, 3) or "", -- EXIF.YEAR.SHORT
|
||||
emon, -- EXIF.MONTH
|
||||
os.date("%B", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.LONG
|
||||
os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT
|
||||
emon_long, -- EXIF.MONTH.LONG
|
||||
emon_short, -- EXIF.MONTH.SHORT
|
||||
eday, -- EXIF.DAY
|
||||
ehour, -- EXIF.HOUR
|
||||
"", -- EXIF.HOUR.AMPM
|
||||
emin, -- EXIF.MINUTE
|
||||
esec, -- EXIF.SECOND
|
||||
emsec, -- EXIF.MSEC
|
||||
@@ -767,12 +849,23 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
||||
string.format("%d", image.exif_iso), -- EXIF.ISO
|
||||
string.format("%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE
|
||||
image.exif_exposure_bias, -- EXIF.EXPOSURE.BIAS
|
||||
"", -- EXIF.EXPOSURE.PROGRAM
|
||||
string.format("%.01f", image.exif_aperture), -- EXIF.APERTURE
|
||||
string.format("%.01f", image.exif_crop),-- EXIF.CROP_FACTOR
|
||||
string.format("%.0f", image.exif_focal_length), -- EXIF.FOCAL.LENGTH
|
||||
string.format("%.0f", image.exif_focal_length * image.exif_crop), -- EXIF.FOCAL.LENGTH.EQUIV
|
||||
image.exif_focus_distance, -- EXIF.FOCUS.DISTANCE
|
||||
"", -- IMAGE.EXIF
|
||||
image.exif_maker, -- EXIF.MAKER
|
||||
image.exif_model, -- EXIF.MODEL
|
||||
is_api_9_4 and image.exif_whitebalance or "", -- EXIF.WHITEBALANCE
|
||||
is_api_9_4 and image.exif_metering_mode or "", -- EXIF.METERING
|
||||
image.exif_lens, -- LENS
|
||||
"", -- EXIF.FLASH.ICON
|
||||
is_api_9_4 and image.exif_flash or "", -- EXIF.FLASH
|
||||
"", -- GPS.LONGITUDE
|
||||
"", -- GPS.LATITUDE
|
||||
"", -- GPS.ELEVATION
|
||||
"", -- GPS.LOCATION.ICON
|
||||
image.longitude or "", -- LONGITUDE
|
||||
image.latitude or "", -- LATITUDE
|
||||
image.elevation or "", -- ELEVATION
|
||||
@@ -781,21 +874,23 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
||||
"", -- RATING.ICONS - wont be implemented
|
||||
labels, -- LABELS
|
||||
"", -- LABELS.ICONS - wont be implemented
|
||||
image.exif_maker, -- MAKER
|
||||
image.exif_model, -- MODEL
|
||||
image.exif_lens, -- LENS
|
||||
image.title, -- TITLE
|
||||
image.description, -- DESCRIPTION
|
||||
image.creator, -- CREATOR
|
||||
image.publisher, -- PUBLISHER
|
||||
image.rights, -- RIGHTS
|
||||
image.title and image.title or "", -- TITLE
|
||||
image.title and image.title or "", -- Xmp.dc.title
|
||||
image.description and image.description or "", -- DESCRIPTION
|
||||
image.description and image.description or "", -- Xmp.dc.description
|
||||
image.creator and image.creator or "", -- CREATOR
|
||||
image.creator and image.creator or "", -- Xmp.dc.creator
|
||||
image.publisher and image.publisher or "", -- PUBLISHER
|
||||
image.publisher and image.publisher or "", -- Xmp.dc.publisher
|
||||
image.rights and image.rights or "", -- RIGHTS
|
||||
image.rights and image.rights or "", -- Xmp.dc.rights
|
||||
"", -- TAGS - wont be implemented
|
||||
"", -- SIDECAR.TXT - wont be implemented
|
||||
pictures_folder, -- FOLDER.PICTURES
|
||||
home_folder, -- FOLDER.HOME
|
||||
desktop_folder, -- FOLDER.DESKTOP
|
||||
"", -- OPENCL.ACTIVATED - wont be implemented
|
||||
user_name, -- USERNAME
|
||||
(user_name ~= nil) and user_name or "",-- USERNAME
|
||||
"", -- NL - wont be implemented
|
||||
"" -- JOBCODE - wont be implemented
|
||||
}
|
||||
@@ -810,10 +905,127 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
||||
-- do category substitutions separately
|
||||
|
||||
build_category_substitution_list(image, variable_string)
|
||||
build_rollname_substitution_list(image, variable_string)
|
||||
|
||||
log.log_level(old_log_level)
|
||||
end
|
||||
|
||||
dtutils_string.libdoc.functions["get_substitution_tooltip"] = {
|
||||
Name = [[get_substitution_tooltip]],
|
||||
Synopsis = [[get a tooltip that lists the substitution variables]],
|
||||
Usage = [[local ds = require "lib/dtutils.string"
|
||||
ds.get_substitution_tooltip()
|
||||
Description = [[get_substitution_tooltip lists the variables with brief explanations]],
|
||||
Return_Value = [[string - the tooltip]],
|
||||
Limitations = [[]],
|
||||
Example = [[]],
|
||||
See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]],
|
||||
Reference = [[]],
|
||||
License = [[]],
|
||||
Copyright = [[]],
|
||||
}
|
||||
|
||||
function dtutils_string.get_substitution_tooltip()
|
||||
|
||||
return table.concat({
|
||||
_("$(ROLL.NAME) - roll of the input image"),
|
||||
_("$(FILE.FOLDER) - folder containing the input image"),
|
||||
_("$(FILE.NAME) - basename of the input image"),
|
||||
_("$(FILE.EXTENSION) - extension of the input image"),
|
||||
_("$(ID) - image ID"),
|
||||
_("$(IMAGE.ID) - image ID"),
|
||||
_("$(IMAGE.ID.NEXT) - next image ID to be assigned on import"),
|
||||
_("$(VERSION) - duplicate version"),
|
||||
_("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists"),
|
||||
_("$(VERSION.NAME) - version name from metadata"),
|
||||
_("$(DARKTABLE.VERSION) - current darktable version"),
|
||||
_("$(Xmp.darktable.version) - current darktable version"),
|
||||
-- _("$(DARKTABLE.NAME) - darktable name"), -- not implemented
|
||||
_("$(SEQUENCE[n,m]) - sequence number, n: number of digits, m: start number"),
|
||||
_("$(WIDTH.SENSOR) - image sensor width"),
|
||||
_("$(HEIGHT.SENSOR) - image sensor height"),
|
||||
_("$(WIDTH.RAW) - RAW image width"),
|
||||
_("$(HEIGHT.RAW) - RAW image height"),
|
||||
_("$(WIDTH.CROP) - image width after crop"),
|
||||
_("$(HEIGHT.CROP) - image height after crop"),
|
||||
_("$(WIDTH.EXPORT) - exported image width"),
|
||||
_("$(HEIGHT.EXPORT) - exported image height"),
|
||||
-- _("$(WIDTH.MAX) - maximum image export width"), -- not implemented
|
||||
-- _("$(HEIGHT.MAX) - maximum image export height"), -- not implemented
|
||||
_("$(YEAR) - year"),
|
||||
_("$(YEAR.SHORT) - year without century"),
|
||||
_("$(MONTH) - month"),
|
||||
_("$(MONTH.LONG) - full month name according to the current locale"),
|
||||
_("$(MONTH.SHORT) - abbreviated month name according to the current locale"),
|
||||
_("$(DAY) - day"),
|
||||
_("$(HOUR) - hour"),
|
||||
-- _("$(HOUR.AMPM) - hour, 12-hour clock"), -- not implemented
|
||||
_("$(MINUTE) - minute"),
|
||||
_("$(SECOND) - second"),
|
||||
_("$(MSEC) - millisecond"),
|
||||
_("$(EXIF.YEAR) - EXIF year"),
|
||||
_("$(EXIF.YEAR.SHORT) - EXIF year without century"),
|
||||
_("$(EXIF.MONTH) - EXIF month"),
|
||||
_("$(EXIF.MONTH.LONG) - full EXIF month name according to the current locale"),
|
||||
_("$(EXIF.MONTH.SHORT) - abbreviated EXIF month name according to the current locale"),
|
||||
_("$(EXIF.DAY) - EXIF day"),
|
||||
_("$(EXIF.HOUR) - EXIF hour"),
|
||||
-- _("$(EXIF.HOUR.AMPM) - EXIF hour, 12-hour clock") .. "\n" .. -- not implemented
|
||||
_("$(EXIF.MINUTE) - EXIF minute"),
|
||||
_("$(EXIF.SECOND) - EXIF second"),
|
||||
_("$(EXIF.MSEC) - EXIF millisecond"),
|
||||
-- _("$(EXIF.DATE.REGIONAL) - localized EXIF date"), -- not implemented
|
||||
-- _("$(EXIF.TIME.REGIONAL) - localized EXIF time"), -- not implemented
|
||||
_("$(EXIF.ISO) - ISO value"),
|
||||
_("$(EXIF.EXPOSURE) - EXIF exposure"),
|
||||
_("$(EXIF.EXPOSURE.BIAS) - EXIF exposure bias"),
|
||||
-- _("$(EXIF.EXPOSURE.PROGRAM) - EXIF exposure program"), -- not implemented
|
||||
_("$(EXIF.APERTURE) - EXIF aperture"),
|
||||
_("$(EXIF.CROP_FACTOR) - EXIF crop factor"),
|
||||
_("$(EXIF.FOCAL.LENGTH) - EXIF focal length"),
|
||||
_("$(EXIF.FOCAL.LENGTH.EQUIV) - EXIF 35 mm equivalent focal length"),
|
||||
_("$(EXIF.FOCUS.DISTANCE) - EXIF focal distance"),
|
||||
_("$(EXIF.MAKER) - camera maker") ..
|
||||
_("$(EXIF.MODEL) - camera model") ..
|
||||
_("$(EXIF.WHITEBALANCE) - EXIF selected white balance") .. -- not implemented
|
||||
_("$(EXIF.METERING) - EXIF exposure metering mode") .. -- not implemented
|
||||
_("$(EXIF.LENS) - lens") ..
|
||||
-- _("$(EXIF.FLASH.ICON) - icon indicating whether flash was used") .. -- not implemented
|
||||
_("$(EXIF.FLASH) - was flash used (yes/no/--)") .. -- not implemented
|
||||
-- _("$(GPS.LONGITUDE) - longitude"),-- not implemented
|
||||
-- _("$(GPS.LATITUDE) - latitude"),-- not implemented
|
||||
-- _("$(GPS.ELEVATION) - elevation"),-- not implemented
|
||||
-- _("$(GPS.LOCATION.ICON) - icon indicating whether GPS location is known"),-- not implemented
|
||||
_("$(LONGITUDE) - longitude"),
|
||||
_("$(LATITUDE) - latitude"),
|
||||
_("$(ELEVATION) - elevation"),
|
||||
_("$(STARS) - star rating as number (-1 for rejected)"),
|
||||
-- _("$(RATING.ICONS) - star/reject rating in icon form"),-- not implemented
|
||||
_("$(LABELS) - color labels as text"),
|
||||
-- _("$(LABELS.ICONS) - color labels as icons"),-- not implemented
|
||||
_("$(TITLE) - title from metadata"),
|
||||
_("$(Xmp.dc.title) - title from metadata"),
|
||||
_("$(DESCRIPTION) - description from metadata"),
|
||||
_("$(Xmp.dc.description) - description from metadata"),
|
||||
_("$(CREATOR) - creator from metadata"),
|
||||
_("$(Xmp.dc.creator) - creator from metadata"),
|
||||
_("$(PUBLISHER) - publisher from metadata"),
|
||||
_("$(Xmp.dc.publisher) - publisher from metadata"),
|
||||
_("$(RIGHTS) - rights from metadata"),
|
||||
_("$(Xmp.dc.rights) - rights from metadata"),
|
||||
--_("$(TAGS) - tags as set in metadata settings"),
|
||||
_("$(CATEGORY[n,category]) - subtag of level n in hierarchical tags"),
|
||||
_("$(SIDECAR_TXT) - contents of .txt sidecar file, if present"),
|
||||
_("$(FOLDER.PICTURES) - pictures folder"),
|
||||
_("$(FOLDER.HOME) - home folder"),
|
||||
_("$(FOLDER.DESKTOP) - desktop folder"),
|
||||
-- _("$(OPENCL.ACTIVATED) - whether OpenCL is activated"),
|
||||
_("$(USERNAME) - login name"),
|
||||
-- _("$(NL) - newline"),
|
||||
-- _("$(JOBCODE) - job code for import"),
|
||||
""}, "\n")
|
||||
end
|
||||
|
||||
-- handle different versions of names
|
||||
|
||||
local function check_legacy_vars(var_name)
|
||||
@@ -847,10 +1059,16 @@ local function treat(var_string)
|
||||
if string.match(var_string, "CATEGORY%d") or string.match(var_string, "CATEGORY%[") then
|
||||
log.msg(log.info, "substituting for " .. var_string)
|
||||
ret_val = substitutes[var_string]
|
||||
if not ret_val then ret_val = "" end
|
||||
log.msg(log.info, "ret_val is " .. ret_val)
|
||||
|
||||
elseif string.match(var_string, "ROLL.NAME%[") then
|
||||
ret_val = substitutes[var_string]
|
||||
if not ret_val then ret_val = "" end
|
||||
log.msg(log.info, "ret_val is " .. ret_val)
|
||||
|
||||
elseif string.match(var_string, "SEQUENCE%[") then
|
||||
local start, width = string.match(var_string, "(%d+),(%d)")
|
||||
local width, start = string.match(var_string, "(%d+),(%d+)")
|
||||
local seq_val = tonumber(substitutes[var])
|
||||
local pat = "%0" .. width .. "d"
|
||||
substitutes[var_string] = string.format(pat, start + (seq_val - 1))
|
||||
@@ -1082,6 +1300,49 @@ function dtutils_string.substitute(image, sequence, variable_string, username, p
|
||||
return str
|
||||
end
|
||||
|
||||
dtutils_string.libdoc.functions["format_exposure"] = {
|
||||
Name = [[format_exposure]],
|
||||
Synopsis = [[format exif_exposure from dt_lua_image_t to an exposure time]],
|
||||
Usage = [[local ds = require "lib/dtutils.string"
|
||||
ds.format_exposure(image.exif_exposure)
|
||||
image.exif_exposure - dt_lua_image_t exif exposure field
|
||||
Description = [[format_exposure takes the EXIF exposure field and formats it into an exposure string]],
|
||||
Return_Value = [[result - string - the EXIF exposure formatted as an exposure string]],
|
||||
Limitations = [[]],
|
||||
Example = [[]],
|
||||
See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]],
|
||||
Reference = [[]],
|
||||
License = [[]],
|
||||
Copyright = [[]],
|
||||
}
|
||||
|
||||
local function _nearbyintf(v)
|
||||
return math.floor(v + .5)
|
||||
end
|
||||
|
||||
function dtutils_string.format_exposure(exif_exposure)
|
||||
result = nil
|
||||
|
||||
if exif_exposure > 1.0 then
|
||||
if _nearbyintf(exif_exposure) == exif_exposure then
|
||||
result = string.format("%.0f″", exif_exposure)
|
||||
else
|
||||
result = string.format("%.1f″", exif_exposure)
|
||||
end
|
||||
-- catch everything below 0.3 seconds
|
||||
elseif exif_exposure < 0.2 then
|
||||
result = string.format("1/%.0f", 1.0 / exif_exposure)
|
||||
-- catch 1/2, 1/3, ...
|
||||
elseif _nearbyintf(1.0 / exif_exposure) == (1.0 / exif_exposure) then
|
||||
result = string.format("1/%.0f", 1.0 / exif_exposure)
|
||||
-- catch 1/1.3, 1/1.6, ...
|
||||
elseif (10 * _nearbyintf(10.0 / exif_exposure)) == _nearbyintf(100.0 / exif_exposure) then
|
||||
result = string.format("1/%.1f", 1.0 / exif_exposure)
|
||||
else
|
||||
result = string.format("%.1f″", exif_exposure)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return dtutils_string
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
514
official/apply_camera_style.lua
Normal file
514
official/apply_camera_style.lua
Normal file
@@ -0,0 +1,514 @@
|
||||
--[[
|
||||
|
||||
apply_camera_style.lua - apply camera style to matching images
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
apply_camera_style - apply darktable camera style to matching images
|
||||
|
||||
apply a camera style corresponding to the camera used to
|
||||
take the image to provide a starting point for editing that
|
||||
is similar to the SOOC jpeg.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
none
|
||||
|
||||
USAGE
|
||||
start the script from script_manager
|
||||
|
||||
BUGS, COMMENTS, SUGGESTIONS
|
||||
Bill Ferguson <wpferguson@gmail.com>
|
||||
|
||||
CHANGES
|
||||
|
||||
20251202 - fixed pattern matching bugs affecting Canon EOS R? series
|
||||
styles and styles being applied twice if images are reimported
|
||||
|
||||
Added more debugging statements to pattern matching functions
|
||||
]]
|
||||
|
||||
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"
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "apply_camera_style"
|
||||
local DEFAULT_LOG_LEVEL <const> = log.warn
|
||||
local TMP_DIR <const> = dt.configuration.tmp_dir
|
||||
local STYLE_PREFIX <const> = "_l10n_darktable|_l10n_camera styles|"
|
||||
local MAKER = 3
|
||||
local STYLE = 4
|
||||
|
||||
-- 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("9.4.0", MODULE) -- camera styles added to darktable 5.0
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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 = _("apply camera style"), -- name of script
|
||||
purpose = _("apply darktable camera style to matching images"), -- purpose of script
|
||||
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
|
||||
help = "https://docs.darktable.org/lua/development/lua.scripts.manual/scripts/official/apply_camera_style/" -- 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 apply_camera_style = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
apply_camera_style.imported_images = {}
|
||||
apply_camera_style.styles = {}
|
||||
apply_camera_style.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 = apply_camera_style
|
||||
local acs = apply_camera_style
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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
|
||||
|
||||
-------------------
|
||||
-- script functions
|
||||
-------------------
|
||||
|
||||
local function process_pattern(pattern)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
log.msg(log.debug, "input pattern is " .. pattern)
|
||||
|
||||
pattern = string.lower(pattern)
|
||||
log.msg(log.debug, "lower case pattern is " .. pattern)
|
||||
-- strip off series
|
||||
pattern = string.gsub(pattern, " series$", "?")
|
||||
log.msg(log.debug, "series stripped off pattern is " .. pattern)
|
||||
-- match a character
|
||||
if string.match(pattern, "r%?%?$") then
|
||||
-- handle EOS R case
|
||||
pattern = string.gsub(pattern, "%?%?", ".?")
|
||||
log.msg(log.debug, "matched EOS R pattern is " .. pattern)
|
||||
else
|
||||
pattern = string.gsub(pattern, "?", ".")
|
||||
log.msg(log.debug, "didn't match EOS R pattern is " .. pattern)
|
||||
end
|
||||
-- escape dashes
|
||||
pattern = string.gsub(pattern, "%-", "%%-")
|
||||
log.msg(log.debug, "escape dashes conditional pattern is " .. pattern)
|
||||
-- make spaces optional
|
||||
pattern = string.gsub(pattern, " ", " ?")
|
||||
-- until we end up with a set, I'll defer set processing, i.e. [...]
|
||||
--log.msg(log.debug, "make spaces conditional again? pattern is " .. pattern)
|
||||
-- anchor the pattern to ensure we don't short match
|
||||
pattern = "^" .. pattern .. "$"
|
||||
log.msg(log.debug, "anchored pattern is " .. pattern)
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
return pattern
|
||||
end
|
||||
|
||||
local function process_set(pattern_set)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
local to_process = {}
|
||||
local processed = {}
|
||||
|
||||
local base, set, tail
|
||||
|
||||
if string.match(pattern_set, "]$") then
|
||||
base, set = string.match(pattern_set, "(.+)%[(.+)%]")
|
||||
else
|
||||
base, set, tail = string.match(pattern_set, "(.+)%[(.+)%](.+)")
|
||||
end
|
||||
|
||||
log.msg(log.debug, "base is " .. base .. " and set is " .. set)
|
||||
|
||||
to_process = du.split(set, ",")
|
||||
|
||||
for _, item in ipairs(to_process) do
|
||||
local pat = base .. item
|
||||
if tail then
|
||||
pat = pat .. tail
|
||||
end
|
||||
table.insert(processed, process_pattern(pat))
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
return processed
|
||||
end
|
||||
|
||||
local function get_camera_styles()
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
-- separate the styles into
|
||||
--
|
||||
-- acs.styles -
|
||||
-- maker -
|
||||
-- styles {}
|
||||
-- patterns {}
|
||||
|
||||
for _, style in ipairs(dt.styles) do
|
||||
|
||||
if string.match(style.name, STYLE_PREFIX) then
|
||||
log.msg(log.debug, "got " .. style.name)
|
||||
|
||||
local parts = du.split(style.name, "|")
|
||||
parts[MAKER] = string.lower(parts[MAKER])
|
||||
log.msg(log.debug, "maker is " .. parts[MAKER])
|
||||
|
||||
if not acs.styles[parts[MAKER]] then
|
||||
acs.styles[parts[MAKER]] = {}
|
||||
acs.styles[parts[MAKER]]["styles"] = {}
|
||||
acs.styles[parts[MAKER]]["patterns"] = {}
|
||||
end
|
||||
if parts[STYLE] then
|
||||
if not string.match(parts[STYLE], "]") then
|
||||
table.insert(acs.styles[parts[MAKER]].styles, style)
|
||||
local processed_pattern = process_pattern(parts[#parts])
|
||||
table.insert(acs.styles[parts[MAKER]].patterns, processed_pattern)
|
||||
log.msg(log.debug, "pattern for " .. style.name .. " is " .. processed_pattern)
|
||||
else
|
||||
local processed_patterns = process_set(parts[STYLE])
|
||||
for _, pat in ipairs(processed_patterns) do
|
||||
table.insert(acs.styles[parts[MAKER]].styles, style)
|
||||
table.insert(acs.styles[parts[MAKER]].patterns, pat)
|
||||
log.msg(log.debug, "pattern for " .. style.name .. " is " .. pat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
end
|
||||
|
||||
local function normalize_model(maker, model)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
model = string.lower(model)
|
||||
|
||||
-- strip off the maker name
|
||||
if maker == "canon" then
|
||||
model = string.gsub(model, "canon ", "")
|
||||
elseif maker == "hasselblad" then
|
||||
model = string.gsub(model, "hasselblad ", "")
|
||||
elseif maker == "leica" then
|
||||
model = string.gsub(model, "leica ", "")
|
||||
elseif maker == "lg" then
|
||||
model = string.gsub(model, "lg ", "")
|
||||
elseif maker == "nikon" then
|
||||
model = string.gsub(model, "nikon ", "")
|
||||
elseif maker == "nokia" then
|
||||
model = string.gsub(model, "nokia ", "")
|
||||
elseif maker == "oneplus" then
|
||||
model = string.gsub(model, "oneplus ", "")
|
||||
elseif maker == "pentax" then
|
||||
model = string.gsub(model, "pentax ", "")
|
||||
model = string.gsub(model, "ricoh ", "")
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
return model
|
||||
end
|
||||
|
||||
local function normalize_maker(maker)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
maker = string.lower(maker)
|
||||
|
||||
if string.match(maker, "^fujifilm") then
|
||||
maker = "fujifilm"
|
||||
elseif string.match(maker, "^hmd ") then
|
||||
maker = "nokia"
|
||||
elseif string.match(maker, "^leica") then
|
||||
maker = "leica"
|
||||
elseif string.match(maker, "^minolta") then
|
||||
maker = "minolta"
|
||||
elseif string.match(maker, "^nikon") then
|
||||
maker = "nikon"
|
||||
elseif string.match(maker, "^om ") then
|
||||
maker = "om system"
|
||||
elseif string.match(maker, "^olympus") then
|
||||
maker = "olympus"
|
||||
elseif string.match(maker, "^pentax") or string.match(maker, "^ricoh") then
|
||||
maker = "pentax"
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
return maker
|
||||
end
|
||||
|
||||
local function has_style_tag(image, tag_name)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
local result = false
|
||||
|
||||
log.msg(log.debug, "looking for tag " .. tag_name)
|
||||
|
||||
for _, tag in ipairs(image:get_tags()) do
|
||||
log.msg(log.debug, "got attached tag " .. tag.name)
|
||||
local cleaned_tag_name = string.gsub(tag_name, "_l10n_", "")
|
||||
log.msg(log.debug, "checking against " .. cleaned_tag_name)
|
||||
if ds.sanitize_lua(tag.name) == cleaned_tag_name then
|
||||
log.msg(log.debug, "matched tag " .. cleaned_tag_name)
|
||||
result = true
|
||||
end
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function mangle_model(model)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
if string.match(model, "eos") then
|
||||
log.msg(log.debug, "mangle model got " .. model)
|
||||
model = string.gsub(model, "r6m2", "r6 mark ii")
|
||||
model = string.gsub(model, "eos 350d digital", "eos kiss digital n")
|
||||
model = string.gsub(model, "eos 500d", "eos rebel t1")
|
||||
model = string.gsub(model, "eos 550d", "eos rebel t2")
|
||||
model = string.gsub(model, "eos 600d", "eos rebel t3i")
|
||||
model = string.gsub(model, "eos 650d", "eos rebel t4i")
|
||||
model = string.gsub(model, "eos 700d", "eos rebel t5")
|
||||
model = string.gsub(model, "eos 750d", "eos rebel t6i")
|
||||
model = string.gsub(model, "eos 760d", "eos rebel t6s")
|
||||
model = string.gsub(model, "eos 100d", "eos rebel t6")
|
||||
model = string.gsub(model, "eos 1100d", "eos rebel t3")
|
||||
model = string.gsub(model, "eos 1200d", "eos rebel t5")
|
||||
model = string.gsub(model, "eos 1300d", "eos rebel t6")
|
||||
model = string.gsub(model, "eos 2000d", "eos rebel t7")
|
||||
log.msg(log.debug, "mangle model returning " .. model)
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
return model
|
||||
end
|
||||
|
||||
local function stop_job()
|
||||
if acs.job then
|
||||
if acs.job.valid then
|
||||
acs.job.valid = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function apply_style_to_images(images)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
acs.job = dt.gui.create_job(_("applying camera styles to images"), true, stop_job)
|
||||
|
||||
for count, image in ipairs(images) do
|
||||
local maker = normalize_maker(image.exif_maker)
|
||||
local model = normalize_model(maker, image.exif_model)
|
||||
model = mangle_model(model)
|
||||
log.msg(log.debug, "got maker " .. maker .. " and model " .. model .. " from image " .. image.filename)
|
||||
|
||||
if acs.styles[maker] then
|
||||
local no_match = true
|
||||
for i, pattern in ipairs(acs.styles[maker].patterns) do
|
||||
log.msg(log.debug, "pattern is " .. pattern)
|
||||
if string.match(model, pattern) or
|
||||
(i == #acs.styles[maker].patterns and string.match(pattern, "generic")) then
|
||||
local tag_name = "darktable|style|" .. acs.styles[maker].styles[i].name
|
||||
if not has_style_tag(image, tag_name) then
|
||||
image:apply_style(acs.styles[maker].styles[i])
|
||||
log.msg(log.info, "applied style " .. acs.styles[maker].styles[i].name .. " to " .. image.filename)
|
||||
end
|
||||
no_match = false
|
||||
log.log_level(loglevel)
|
||||
break
|
||||
end
|
||||
end
|
||||
if no_match then
|
||||
log.msg(log.info, "no style found for " .. maker .. " " .. model)
|
||||
end
|
||||
else
|
||||
log.msg(log.info, "no maker found for " .. image.filename)
|
||||
end
|
||||
if count % 10 == 0 then
|
||||
acs.job.percent = count / #images
|
||||
end
|
||||
if dt.control.ending then
|
||||
stop_job()
|
||||
end
|
||||
end
|
||||
|
||||
stop_job()
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
end
|
||||
|
||||
local function apply_camera_style(collection)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
local images = nil
|
||||
|
||||
if collection == true then
|
||||
images = dt.collection
|
||||
log.msg(log.info, "applying camera styles to collection")
|
||||
elseif collection == false then
|
||||
images = dt.gui.selection()
|
||||
if #images == 0 then
|
||||
images = dt.gui.action_images
|
||||
end
|
||||
log.msg(log.info, "applying camera styles to selection")
|
||||
end
|
||||
apply_style_to_images(images)
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
get_camera_styles()
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "post-import-image")
|
||||
dt.destroy_event(MODULE, "post-import-film")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
apply_camera_style(true)
|
||||
end, _("apply darktable camera styles to collection")
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
apply_camera_style(false)
|
||||
end, _("apply darktable camera styles to selection")
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "post-import-image",
|
||||
function(event, image)
|
||||
if image.is_raw then
|
||||
table.insert(acs.imported_images, image)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "post-import-film",
|
||||
function(event, film)
|
||||
apply_style_to_images(acs.imported_images)
|
||||
acs.imported_images = {}
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
283
official/auto_straighten.lua
Normal file
283
official/auto_straighten.lua
Normal file
@@ -0,0 +1,283 @@
|
||||
--[[
|
||||
|
||||
auto_straighten.lua - straighten an image in darkroom using image metadata
|
||||
|
||||
Copyright (C) 2025 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/>.
|
||||
]]
|
||||
--[[
|
||||
auto_straighten - straighten an image in darkroom using image metadata
|
||||
|
||||
auto_straighten reads the roll and pitch angle imformation captured when
|
||||
an image is captured. A correction is computed and applied to correct the
|
||||
roll to the nearest vertical or horizontal axis using the rotate and
|
||||
perspective module.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
exiftool - https://exiftool.org
|
||||
|
||||
USAGE
|
||||
* add it sowewhere in your scripts directory
|
||||
* ensure exiftool is installed and accessible
|
||||
* enable it using script manager
|
||||
|
||||
BUGS, COMMENTS, SUGGESTIONS
|
||||
Bill Ferguson <wpferguson@gmail.com>
|
||||
|
||||
CHANGES
|
||||
|
||||
LIMITATIONS
|
||||
* Roll and pitch angles are taken when an image is captured. If you shoot
|
||||
in burst mode, the roll and pitch angles of the first image are used in
|
||||
the rest of the burst (at least for Canon).
|
||||
* If you are running multiple scripts that trigger on an image being opened
|
||||
in darkroom then the auto crop may not work. There is a shortcut included
|
||||
that can have a key sequence assigned so that the auto crop will be reapplied.
|
||||
* Currently oply roll angle is corrected
|
||||
|
||||
LIEENSE
|
||||
GPL V2
|
||||
]]
|
||||
|
||||
local dt = require "darktable"
|
||||
local du = require "lib/dtutils"
|
||||
-- local da = require "lib/dtutils.action"
|
||||
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"
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "auto_straighten"
|
||||
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 ";"
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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_straighten"), -- visible name of script
|
||||
purpose = _("straighten an image in darkroom using image metadata"), -- 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/official/auto_straighten/" -- 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_straighten = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
auto_straighten.pixelpipe_busy = false
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "crop_area", "enum",
|
||||
_("automatic_cropping"),
|
||||
_("what automatic cropping to perform"),
|
||||
_("original format"),
|
||||
_("off"), _("largest area"), _("original format"))
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = auto_straighten
|
||||
local as = auto_straighten
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function determine_correction(roll_angle)
|
||||
|
||||
--[[
|
||||
Canon camera roll angle is as foillows
|
||||
* landscape orientation camera upright = 0
|
||||
* camera rolled to the left (shutter button up) portrait = -90
|
||||
* camera inverted landscape = +/- 180
|
||||
* camera rolled to the right (shutter button down) portrait = 90
|
||||
|
||||
rotate and perspective corrections
|
||||
* rotate the image to the right is a negative correction
|
||||
* rotate the image left is a positive correction
|
||||
]]
|
||||
|
||||
if roll_angle == 0 or -- landscape
|
||||
roll_angle == -90 or -- roll left portrait
|
||||
math.abs(roll_angle) == 180 or -- inverted landscape
|
||||
roll_angle == 90 then -- roll right portrait
|
||||
dt.print_log("on axis, no correction required")
|
||||
return 0
|
||||
elseif roll_angle > 0 and roll_angle <= 45 then
|
||||
return (roll_angle * -1.0)
|
||||
elseif roll_angle > 45 and roll_angle < 90 then
|
||||
return (90 - roll_angle)
|
||||
elseif roll_angle > 90 and roll_angle <= 135 then
|
||||
return (roll_angle - 90) * -1.0
|
||||
elseif roll_angle > 135 and roll_angle < 180 then
|
||||
return (180 - roll_angle)
|
||||
elseif roll_angle > -180 and roll_angle <= -135 then
|
||||
return (-180 - roll_angle)
|
||||
elseif roll_angle > -135 and roll_angle <= -90 then
|
||||
return (math.abs(roll_angle) - 90)
|
||||
elseif roll_angle > -90 and roll_angle <= -45 then
|
||||
return (-90 - roll_angle)
|
||||
else
|
||||
return roll_angle * -1.0
|
||||
end
|
||||
end
|
||||
|
||||
local function toggle_auto_crop()
|
||||
local crop = dt.preferences.read(MODULE, "crop_area", "enum")
|
||||
dt.gui.action("iop/ashift/automatic cropping", "selection", "item:off", 1.000)
|
||||
dt.control.sleep(100)
|
||||
dt.gui.action("iop/ashift/automatic cropping", "selection", "item:" .. crop, 1.000)
|
||||
end
|
||||
|
||||
local function straighten_image(image)
|
||||
|
||||
local roll = nil
|
||||
local pitch = nil
|
||||
|
||||
-- Roll Angle 0 is landscape, -90 is rolled to the left in portrait, +- 180 is landscape inverted, 90 is portrait rolled to the right
|
||||
|
||||
local crop = dt.preferences.read(MODULE, "crop_area", "enum")
|
||||
|
||||
local pipe = io.popen(as.exiftool .. " -RollAngle -PitchAngle " .. image.path .. PS .. image.filename)
|
||||
if pipe then
|
||||
for line in pipe:lines() do
|
||||
local pr, val = string.match(line, "(.).+: (.+)")
|
||||
if pr == "R" then
|
||||
dt.print_log("roll angle is " .. val)
|
||||
log.msg(log.debug, "roll angle is " .. val)
|
||||
roll = determine_correction(tonumber(val))
|
||||
dt.print_log("corrected roll angle is " .. roll)
|
||||
log.msg(log.debug, "corrected roll angle is " .. roll)
|
||||
elseif pr == "P" then
|
||||
pitch = tonumber(val)
|
||||
end
|
||||
end
|
||||
pipe:close()
|
||||
end
|
||||
|
||||
if roll and dt.gui.action("iop/ashift", "enable", "", "") == 0.0 then
|
||||
dt.print_log("straightening image")
|
||||
dt.gui.action("iop/ashift/rotation", "value", "set", roll)
|
||||
-- dt.gui.action("iop/ashift/automatic cropping", "selection", "item:" .. crop, 1.000)
|
||||
-- da.wait_until_pixelpipe_complete()
|
||||
toggle_auto_crop()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
as.exiftool = df.check_if_bin_exists("exiftool")
|
||||
|
||||
if not as.exiftool then
|
||||
log.msg(log.error, "exiftool not found, exiting...")
|
||||
log.msg(log.screen, _("exiftool not found, exiting..."))
|
||||
return
|
||||
end
|
||||
|
||||
-- da.register_events(MODULE, straighten_image)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "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)
|
||||
if clean then
|
||||
straighten_image(image)
|
||||
end
|
||||
end
|
||||
)
|
||||
--]]
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
toggle_auto_crop()
|
||||
end, "toggle auto crop off and on"
|
||||
)
|
||||
|
||||
return script_data
|
||||
@@ -36,8 +36,6 @@ du.check_min_api_version("2.0.0", "check_for_updates")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("check_for_updates", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -47,7 +45,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "check_for_updates",
|
||||
name = _("check for updates"),
|
||||
purpose = _("check for newer darktable releases"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates"
|
||||
|
||||
@@ -31,8 +31,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "copy_paste_metadata")
|
||||
|
||||
dt.gettext.bindtextdomain("copy_paste_metadata", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -42,7 +40,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "copy_paste_metadata",
|
||||
name = _("copy paste metadata"),
|
||||
purpose = _("adds keyboard shortcuts and buttons to copy/paste metadata between images"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata"
|
||||
|
||||
@@ -35,8 +35,6 @@ du.check_min_api_version("2.0.0", "delete_long_tags")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("delete_long_tags", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -46,7 +44,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "delete_long_tags",
|
||||
name = _("delete long tags"),
|
||||
purpose = _("delete all tags longer than a set length"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags"
|
||||
|
||||
@@ -39,8 +39,6 @@ end
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("delete_unused_tags", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -50,7 +48,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "delete_unused_tags",
|
||||
name = _("delete unused tags"),
|
||||
purpose = _("delete unused tags"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags"
|
||||
|
||||
@@ -43,8 +43,6 @@ local gettext = dt.gettext.gettext
|
||||
|
||||
du.check_min_api_version("7.0.0", "enfuse")
|
||||
|
||||
dt.gettext.bindtextdomain("enfuse", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -54,7 +52,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "enfuse",
|
||||
name = _("enfuse"),
|
||||
purpose = _("exposure blend images"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/enfuse"
|
||||
@@ -74,7 +72,7 @@ local function install_module()
|
||||
if not enf.module_installed then
|
||||
dt.register_lib(
|
||||
"enfuse", -- plugin name
|
||||
"enfuse", -- name
|
||||
_("enfuse"), -- name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||
@@ -137,7 +135,7 @@ if enfuse_installed then
|
||||
if version < "4.2" then
|
||||
exposure_mu = dt.new_widget("slider")
|
||||
{
|
||||
label = "exposure mu",
|
||||
label = _("exposure mu"),
|
||||
tooltip = _("center also known as mean of gaussian weighting function (0 <= mean <= 1); default: 0.5"),
|
||||
hard_min = 0,
|
||||
hard_max = 1,
|
||||
@@ -146,7 +144,7 @@ if enfuse_installed then
|
||||
else
|
||||
exposure_mu = dt.new_widget("slider")
|
||||
{
|
||||
label = "exposure optimum",
|
||||
label = _("exposure optimum"),
|
||||
tooltip = _("optimum exposure value, usually the maximum of the weighting function (0 <= optimum <=1); default 0.5"),
|
||||
hard_min = 0,
|
||||
hard_max = 1,
|
||||
@@ -156,7 +154,7 @@ if enfuse_installed then
|
||||
|
||||
local depth = dt.new_widget("combobox")
|
||||
{
|
||||
label = "depth",
|
||||
label = _("depth"),
|
||||
tooltip = _("the number of bits per channel of the output image"),
|
||||
value = dt.preferences.read("enfuse", "depth", "integer"),
|
||||
changed_callback = function(w) dt.preferences.write("enfuse", "depth", "integer", w.selected) end,
|
||||
@@ -165,7 +163,7 @@ if enfuse_installed then
|
||||
|
||||
local blend_colorspace = dt.new_widget("combobox")
|
||||
{
|
||||
label = "blend colorspace",
|
||||
label = _("blend colorspace"),
|
||||
tooltip = _("force blending in selected colorspace"),
|
||||
changed_callback = function(w) dt.preferences.write("enfuse", "blend_colorspace", "string", w.selected) end,
|
||||
"", "identity", "ciecam"
|
||||
|
||||
459
official/extract_burst_roll_images.lua
Normal file
459
official/extract_burst_roll_images.lua
Normal file
@@ -0,0 +1,459 @@
|
||||
--[[
|
||||
|
||||
extract_burst_roll_images.lua - extract burst images from a burst roll file
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
extract_burst_roll_images - extract burst images from a burst roll file
|
||||
|
||||
Canon cameras are capable of shooting in burst mode. When the shutter
|
||||
button is half pressed the camera starts "recording". When the shutter is
|
||||
fully pressed the pervious 1/2 second of imformation is recorded as individual
|
||||
images stored in one file as well as images captured at 30 frames/sec until
|
||||
the buffer fills or the shutter button is released. So a Canon R7 burst file
|
||||
could contain ~90 images.
|
||||
|
||||
Extracting the images from this file required use of Canon proprietary software
|
||||
until recently when dnglab got the capability to extract the embedded images as
|
||||
DNG raws.
|
||||
|
||||
This script can be set to run on import, scanning for burst roll containers,
|
||||
extracting the embedded images and grouping them with the burst roll container.
|
||||
The script can alsu be utilized via a short cut and applied to selected images.
|
||||
|
||||
When set to on import the script scans each image after import using exiv2 or exiftool
|
||||
to check the image EXIF information to see if the file is a busrt roll container.
|
||||
Once the import images are scanned, the detected burst roll containers are processed,
|
||||
the embedded images extracted as DNSs, and grouped with the container image.
|
||||
|
||||
In shortcut mode the user selects the burst roll containers manually and uses the shortcut
|
||||
to extract the embedded images as DNGs
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
- exiv2 - https://exiv2.org
|
||||
- exiftool - https://exiftool.org
|
||||
- dnglab - https://github.com/dnglab/dnglab
|
||||
|
||||
USAGE
|
||||
- add the script to your lua scripts
|
||||
- enable in script manager
|
||||
- set the preferences in perferences->Lua options
|
||||
|
||||
LIMITATIONS
|
||||
extract_burst_roll_images makes heavy use of external programs to identify burst roll
|
||||
containters and to extract the images. Windows users may want to use it in shortcut mode
|
||||
to lessen the number of windows popping up.
|
||||
|
||||
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 = _("extract burst roll images"), -- visible name of script
|
||||
purpose = _("extract burst images from a burst roll file"), -- 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/official/extract_burst_roll_images/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "extract_burst_roll_images"
|
||||
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 ";"
|
||||
|
||||
local EXIFTOOL <const> = df.check_if_bin_exists("exiftool")
|
||||
local EXIV2 <const> = df.check_if_bin_exists("exiv2")
|
||||
local DNGLAB <const> = df.check_if_bin_exists("dnglab")
|
||||
|
||||
local DNGLAB_ARGS <const> = " convert --image-index all --embed-raw false "
|
||||
local EXIV2_ARGS <const> = " -K Exif.Canon.RawBurstModeRoll -pt "
|
||||
local EXIFTOOL_ARGS <const> = " -T -RawBurstImageCount "
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- L O G L E V E L
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- N A M E S P A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local extract_burst_roll_images = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
extract_burst_roll_images.preferred_tag_reader = nil
|
||||
extract_burst_roll_images.on_import = true
|
||||
extract_burst_roll_images.imported_images = {}
|
||||
extract_burst_roll_images.log_level = DEFAULT_LOG_LEVEL
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- exiv2 or exiftool
|
||||
dt.preferences.register(MODULE, -- 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.
|
||||
"tag_reader", -- name
|
||||
"enum", -- type
|
||||
_("preferred exif tag reader"), -- label
|
||||
_("use exiftool or exiv2 to read exif tags"), -- tooltip
|
||||
"exiftool", -- default
|
||||
"exiftool", "exiv2") -- values
|
||||
|
||||
-- run on import
|
||||
dt.preferences.register(MODULE, -- 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.
|
||||
"on_import", -- name
|
||||
"bool", -- type
|
||||
_("extract burst roll images on import"), -- label
|
||||
_("extract burst roll images on import"), -- tooltip
|
||||
true) -- default
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = extract_burst_roll_images
|
||||
local ebri = extract_burst_roll_images
|
||||
|
||||
local sleep = dt.control.sleep
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-------------------
|
||||
-- helper functions
|
||||
-------------------
|
||||
|
||||
local function set_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(level)
|
||||
|
||||
return old_log_level
|
||||
end
|
||||
|
||||
local function restore_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
|
||||
log.log_level(level)
|
||||
end
|
||||
|
||||
local function reset_log_level()
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
namespace.log_level = DEFAULT_LOG_LEVEL
|
||||
end
|
||||
|
||||
local function pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(ebri.log_level)
|
||||
|
||||
log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, "read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(ebri.log_level)
|
||||
|
||||
log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function get_last_value(valuestr)
|
||||
local old_log_level = set_log_level()
|
||||
local parts = du.split(valuestr, " +")
|
||||
|
||||
if parts then
|
||||
log.msg(log.debug, "last value of parts is " .. tostring(parts[#parts]))
|
||||
return parts[#parts]
|
||||
else
|
||||
log.msg(log.debug, "last part not detected, returning nil")
|
||||
return nil
|
||||
end
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function is_burst_roll_image(image)
|
||||
local old_log_level = set_log_level()
|
||||
local result = false
|
||||
local cmd = EXIFTOOL .. EXIFTOOL_ARGS
|
||||
|
||||
if ebri.preferred_tag_reader == "exiv2" then
|
||||
cmd = EXIV2 .. EXIV2_ARGS
|
||||
end
|
||||
|
||||
local p = io.popen(cmd .. ds.sanitize(image.path) .. PS .. ds.sanitize(image.filename))
|
||||
if p then
|
||||
for line in p:lines() do
|
||||
if ebri.preferred_tag_reader == "exiv2" then
|
||||
line = get_last_value(line)
|
||||
end
|
||||
line = tonumber(line)
|
||||
if type(line) == "number" then
|
||||
if tonumber(line) > 0 then
|
||||
log.msg(log.info, image.filename .. " is a burst roll container image")
|
||||
result = true
|
||||
end
|
||||
end
|
||||
end
|
||||
p:close()
|
||||
end
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return result
|
||||
end
|
||||
|
||||
local function group_extracted_dngs(image)
|
||||
local old_log_level = set_log_level()
|
||||
local count = 0
|
||||
|
||||
local cmd = "ls -1 " .. ds.sanitize(image.path) .. PS .. ds.sanitize(ds.get_basename(image.filename)) .. "*.dng"
|
||||
|
||||
if dt.configuration.running_os == "windows" then
|
||||
cmd = "forfiles /P " .. ds.sanitize(image.path) .. " /M " .. ds.sanitize(ds.get_basename(image.filename)) .. "*.dng" .. " /C \"cmd /c echo @path\\@fname\""
|
||||
end
|
||||
|
||||
local p = io.popen(cmd)
|
||||
if p then
|
||||
for line in p:lines() do
|
||||
if line:length() > 5 then -- skip blank lines created by forfiles
|
||||
local dng_image = dt.database.import(line)
|
||||
dng_image:group_with(image)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
p:close()
|
||||
log.msg(log.screen, string.format(_("extracted %d dng files from %s"), count, image.filename))
|
||||
end
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function extract_burst(image)
|
||||
local old_log_level = set_log_level()
|
||||
|
||||
local cmd = DNGLAB .. DNGLAB_ARGS .. ds.sanitize(image.path) .. PS .. ds.sanitize(image.filename) .. " " .. ds.sanitize(image.path)
|
||||
|
||||
local result = dtsys.external_command(cmd)
|
||||
|
||||
if result == 0 then
|
||||
group_extracted_dngs(image)
|
||||
end
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function process_image(image)
|
||||
local old_log_level = set_log_level()
|
||||
if is_burst_roll_image(image) then
|
||||
extract_burst(image)
|
||||
end
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function stop_job(job)
|
||||
job.valid = false
|
||||
end
|
||||
|
||||
local function process_images(images)
|
||||
local old_log_level = set_log_level()
|
||||
local job = dt.gui.create_job(_("scanning for burst roll images"), true, stop_job)
|
||||
|
||||
for i, image in ipairs(images) do
|
||||
if ebri.job.valid then
|
||||
extract_burst(image)
|
||||
ebri.job.percent = i / #images
|
||||
sleep(10)
|
||||
end
|
||||
end
|
||||
|
||||
stop_job(job)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function process_import(images)
|
||||
local old_log_level = set_log_level()
|
||||
local burst_roll_images = {}
|
||||
local job = dt.gui.create_job(_("scanning for burst roll images"), true, stop_job)
|
||||
|
||||
for i, image in ipairs(images) do
|
||||
if job.valid then
|
||||
if is_burst_roll_image(image) then
|
||||
table.insert(burst_roll_images, image)
|
||||
end
|
||||
if i % 10 == 0 then
|
||||
job.percent = i / #images
|
||||
end
|
||||
sleep(10)
|
||||
end
|
||||
end
|
||||
|
||||
stop_job(job)
|
||||
|
||||
if #burst_roll_images > 0 then
|
||||
job = dt.gui.create_job(_("extracting burst roll images"), true, stop_job)
|
||||
for j, image in ipairs(burst_roll_images) do
|
||||
if job.valid then
|
||||
extract_burst(image)
|
||||
job.percent = j / #burst_roll_images
|
||||
sleep(10)
|
||||
end
|
||||
end
|
||||
stop_job(job)
|
||||
end
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
if not DNGLAB then
|
||||
log.msg(log.error, MODULE .. _("dnglab executable not found"))
|
||||
log.msg(log.screen, script_data.metadata.name .. _(": dnglab executable not found, exiting..."))
|
||||
return
|
||||
end
|
||||
|
||||
if not EXIFTOOL and not EXIV2 then
|
||||
log.msg(log.error, MODULE .. _("no exif tag reader available"))
|
||||
log.msg(log.screen, script_data.metadata.name .. _(" no exif tag reader available, exiting..."))
|
||||
return
|
||||
end
|
||||
|
||||
ebri.preferred_tag_reader = pref_read("tag_reader", "enum")
|
||||
|
||||
if not string.match(EXIFTOOL, ebri.preferred_tag_reader) and not string.match(EXIV2, ebri.preferred_tag_reader) then
|
||||
log.msg(log.error, MODULE .. " selected tag " .. ebri.preferred_tag_reader .." not available")
|
||||
log.msg(log.screen, script_data.metadata.name .. string.format(_(" selected tag reader %s not available"), ebri.preferred_tag_reader))
|
||||
return
|
||||
end
|
||||
|
||||
ebri.on_import = dt.preferences.read(MODULE, "on_import", "bool")
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "post-import-film")
|
||||
dt.destroy_event(MODULE, "post-import-image")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "post-import-film",
|
||||
function(event, film)
|
||||
process_import(ebri.imported_images)
|
||||
ebri.imported_images = {}
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "post-import-image",
|
||||
function(event, image)
|
||||
if ebri.on_import then
|
||||
table.insert(ebri.imported_images, image)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
if not dt.query_event(MODULE, "shortcut") then
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
if #dt.gui.action_images > 1 then
|
||||
process_images(dt.gui.action_images)
|
||||
else
|
||||
process_image(dt.gui.action_images[1])
|
||||
end
|
||||
end, "extract roll burst images"
|
||||
)
|
||||
end
|
||||
|
||||
return script_data
|
||||
@@ -40,8 +40,6 @@ require "darktable.debug"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("generate_image_txt", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -53,7 +51,7 @@ du.check_min_api_version("7.0.0", "generate_image_txt")
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "generate_image_txt",
|
||||
name = _("generate image text"),
|
||||
purpose = _("overlay metadata on the selected image(s)"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt"
|
||||
|
||||
694
official/group_persistence.lua
Normal file
694
official/group_persistence.lua
Normal file
@@ -0,0 +1,694 @@
|
||||
--[[
|
||||
|
||||
group_persistence.lua - preserve groups between darktable instances
|
||||
|
||||
Copyright (C) 2024-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/>.
|
||||
]]
|
||||
--[[
|
||||
group_persistence - preserve groups between darktable instances
|
||||
|
||||
group_persistence uses "functional" tagging to maintain image groups
|
||||
across database instances, allowing image grouping to be restored when
|
||||
the database is rebuilt from the XMP sidecar files.
|
||||
|
||||
Once the script is started it "listens" for changes in image grouping and
|
||||
applies or removes the necessary tags automatically.
|
||||
|
||||
A shortcut can be assigned to read a collection and apply the necessary
|
||||
grouping tags so that existing collections can be maintained.
|
||||
|
||||
If the database is lost and needs to be rebuilt then start the script before
|
||||
importing any images. As images are imported the tags will be read and the
|
||||
grouping applied in the database.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
|
||||
None
|
||||
|
||||
USAGE
|
||||
|
||||
* Enable the script using script manager
|
||||
* Optionally, create a keystroke combination for the shortcut
|
||||
|
||||
LIMITATIONS
|
||||
|
||||
In normal use you wont notice that the script is running.
|
||||
|
||||
However if you do something like import 4000+ images, then
|
||||
run the autogrouper script to create groups whenever there is
|
||||
a 5 second difference between 2 images, your system will be busy
|
||||
for awhile. If for some reason you need to do something like this
|
||||
turn off group_persistence, run the autogrouper, turn on group_persistence
|
||||
and use the shortcut to read all the grouping information and apply
|
||||
the necessary tags. It will still be an impact, but less than trying
|
||||
to do both at once. Learned from experience :-)
|
||||
|
||||
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"
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "group_persistence"
|
||||
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("9.4.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(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 = "group_persistence", -- name of script
|
||||
purpose = _("preserve groups between darktable instances"), -- purpose of script
|
||||
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
|
||||
help = "" -- 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 group_persistence = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = group_persistence
|
||||
|
||||
local gp = group_persistence
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- table of imported images that need grouping rebuilt
|
||||
gp.imported_images = {}
|
||||
|
||||
-- uuid to group leader mapping
|
||||
gp.uuids = {}
|
||||
|
||||
-- group leader to uuid mapping
|
||||
gp.leaders = {}
|
||||
|
||||
-- uuid to group tag mapping
|
||||
gp.group_tags = {}
|
||||
|
||||
-- uuid to group leader tag mapping
|
||||
gp.leader_tags = {}
|
||||
|
||||
-- when the groups are rebuilt after import they
|
||||
-- cause the group change events to fire but the
|
||||
-- events are queued until after rebuilding completes
|
||||
-- so we need to build a table of images to ignore when
|
||||
-- the queued events catch up. As the event fires for an
|
||||
-- image, the image is removed.
|
||||
gp.rebuilt_images = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- utilities for building/debugging
|
||||
|
||||
local function dump_image_tags(image)
|
||||
log.msg(log.debug, "")
|
||||
|
||||
for _, tag in ipairs(image:get_tags()) do
|
||||
log.msg(log.debug, "tag for image " .. image.id .. " is " .. tag.name)
|
||||
end
|
||||
|
||||
log.msg(log.debug, "group_leader is " .. image.group_leader.id .. " and group has " .. #image:get_group_members() .. " members")
|
||||
|
||||
log.msg(log.debug, "")
|
||||
end
|
||||
|
||||
local function dump_group_tags(image)
|
||||
local leader = image.group_leader
|
||||
|
||||
for _, tag in ipairs(image:get_tags()) do
|
||||
if string.match(tag.name, "darktable|group") then
|
||||
log.msg(log.debug, "group leader image " .. image.id .. " has tag " .. tag.name)
|
||||
end
|
||||
end
|
||||
|
||||
for _, img in ipairs(image:get_group_members()) do
|
||||
for _, tag in ipairs(img:get_tags()) do
|
||||
if string.match(tag.name, "darktable|group") then
|
||||
log.msg(log.debug, "group member image " .. img.id .. " has tag " .. tag.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- rebuild grouping on import
|
||||
|
||||
local function save_group_leader(image, uuid)
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
log.msg(log.debug, "input image is " .. image.id .. " and uuid is " .. uuid)
|
||||
|
||||
gp.uuids[image] = uuid
|
||||
gp.leaders[uuid] = image
|
||||
|
||||
log.msg(log.debug, "after saving group leader uuid is " .. gp.leaders[uuid].id .. " and image is " .. gp.uuids[image])
|
||||
end
|
||||
|
||||
local function get_group_and_uuid(tags)
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
local group_type = nil
|
||||
local uuid = nil
|
||||
|
||||
for _, tag in ipairs(tags) do
|
||||
|
||||
if string.match(tag.name, "darktable|group") then
|
||||
|
||||
log.msg(log.debug, "found tag darktable|group")
|
||||
|
||||
local parts = du.split(tag.name, "|")
|
||||
|
||||
group_type = parts[2]
|
||||
uuid = parts[3]
|
||||
|
||||
log.msg(log.info, "found type " .. group_type .. " with uuid " .. uuid)
|
||||
end
|
||||
end
|
||||
|
||||
return group_type, uuid
|
||||
end
|
||||
|
||||
local function get_uuid_from_tag(tag)
|
||||
local parts = du.split(tag.name, "|")
|
||||
return parts[#parts]
|
||||
end
|
||||
|
||||
local function find_group_leader(uuid)
|
||||
|
||||
local group_leader = nil
|
||||
|
||||
local tag_name = "darktable|group_leader|" .. uuid
|
||||
|
||||
local found_tag = dt.tags.find(tag_name)
|
||||
|
||||
if found_tag then
|
||||
|
||||
group_leader = dt.tags.get_tagged_images(found_tag)[1]
|
||||
|
||||
end
|
||||
|
||||
return group_leader
|
||||
end
|
||||
|
||||
local function rebuild_groups()
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
for _, image in ipairs(gp.imported_images) do
|
||||
|
||||
log.msg(log.info, "processing image " .. image.id)
|
||||
|
||||
local tags = image:get_tags()
|
||||
|
||||
local group_type, uuid = get_group_and_uuid(tags)
|
||||
|
||||
if group_type =="group_leader" then
|
||||
|
||||
table.insert(gp.rebuilt_images, image)
|
||||
|
||||
log.msg(log.debug, "took group_leader branch")
|
||||
|
||||
save_group_leader(image, uuid)
|
||||
|
||||
elseif group_type == "group" then
|
||||
|
||||
table.insert(gp.rebuilt_images, image)
|
||||
|
||||
log.msg(log.debug, "took group branch")
|
||||
|
||||
local leader = gp.uuids[uuid]
|
||||
|
||||
if leader then
|
||||
|
||||
image:group_with(leader)
|
||||
|
||||
else
|
||||
|
||||
local group_leader = find_group_leader(uuid)
|
||||
|
||||
if group_leader then
|
||||
|
||||
save_group_leader(group_leader, uuid)
|
||||
image:group_with(group_leader)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
gp.imported_images = {}
|
||||
end
|
||||
|
||||
-- rebuild grouping on command (shortcut)
|
||||
|
||||
local function is_group_leader(image)
|
||||
result = false
|
||||
|
||||
if image.group_leader == image and #image:get_group_members() > 1 then
|
||||
result = true
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function has_group_tag(image)
|
||||
local result = false
|
||||
|
||||
for _, tag in ipairs(image:get_tags()) do
|
||||
if string.match(tag.name, "darktable|group|") then
|
||||
result = tag
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function has_group_leader_tag(image)
|
||||
local result = false
|
||||
|
||||
for _, tag in ipairs(image:get_tags()) do
|
||||
if string.match(tag.name, "darktable|group_leader") then
|
||||
result = tag
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- handle grouping changes in the UI
|
||||
|
||||
local function group_exists(group_leader)
|
||||
local exists = false
|
||||
|
||||
local tags = group_leader:get_tags()
|
||||
|
||||
for _, tag in ipairs(tags) do
|
||||
if string.match(tag.name, "group_leader") then
|
||||
exists = true
|
||||
break
|
||||
end
|
||||
end
|
||||
return exists
|
||||
end
|
||||
|
||||
local function add_group_leader(image, group)
|
||||
-- tag image as group leader
|
||||
-- create the uuid and leader/group tags if necessary
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
local uuid = du.gen_uuid()
|
||||
|
||||
if uuid then
|
||||
log.msg(log.debug, "in add_group_leader and uuid is " .. uuid)
|
||||
else
|
||||
log.msg(log.debug, "uuid not created")
|
||||
end
|
||||
|
||||
local tag = dt.tags.create("darktable|group_leader|" .. uuid)
|
||||
|
||||
local group_tag = dt.tags.create("darktable|group|" .. uuid)
|
||||
|
||||
log.msg(log.info, "created group leader tag " .. tag.name)
|
||||
log.msg(log.info, "created group tag " .. group_tag.name)
|
||||
|
||||
gp.group_tags[uuid] = group_tag
|
||||
gp.leader_tags[uuid] = tag
|
||||
|
||||
gp.uuids[uuid] = image
|
||||
gp.leaders[image] = uuid
|
||||
|
||||
image:attach_tag(tag)
|
||||
log.msg(log.info, "attached tag " .. tag.name .. " to image " .. image.id)
|
||||
end
|
||||
|
||||
local function add_to_group(image, group_leader)
|
||||
-- add an image to a group
|
||||
-- create the group if necessary
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
if image == group_leader then
|
||||
|
||||
add_group_leader(image, group_leader)
|
||||
|
||||
else
|
||||
|
||||
if not group_exists(group_leader) then
|
||||
add_group_leader(group_leader, group_leader)
|
||||
end
|
||||
|
||||
local group_tag = nil
|
||||
|
||||
log.msg(log.debug, "image is " .. image.id .. " and group_leader is " .. image.group_leader.id)
|
||||
|
||||
local group, uuid = get_group_and_uuid(image.group_leader:get_tags())
|
||||
|
||||
group_tag = gp.group_tags[uuid]
|
||||
|
||||
if not group_tag then
|
||||
log.msg(log.info, "creating group tag darktable|group|" .. uuid)
|
||||
group_tag = dt.tags.create("darktable|group|" .. uuid)
|
||||
gp.group_tags[uuid] = group_tag
|
||||
end
|
||||
|
||||
image:attach_tag(group_tag)
|
||||
log.msg(log.info, "group tag attached to image " .. image.id)
|
||||
end
|
||||
end
|
||||
|
||||
local function build_group(leader)
|
||||
|
||||
if not has_group_leader_tag(leader) then
|
||||
add_group_leader(leader)
|
||||
end
|
||||
|
||||
for _, image in ipairs(leader:get_group_members()) do
|
||||
if not has_group_tag(image) then
|
||||
add_to_group(image, leader)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function process(image_set)
|
||||
local images
|
||||
|
||||
if image_set == "collection" then
|
||||
images = dt.collection
|
||||
else
|
||||
images = dt.gui.selection()
|
||||
if #images == 0 then
|
||||
images = dt.gui.action_images
|
||||
end
|
||||
end
|
||||
|
||||
for _, image in ipairs(images) do
|
||||
if is_group_leader(image) then
|
||||
build_group(image)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function check_and_remove_empty_group_data(group_tag)
|
||||
if not group_tag then
|
||||
return -- handle duplicate getting deleted
|
||||
end
|
||||
|
||||
local group_leader_tag = nil
|
||||
|
||||
group_leader_tag = dt.tags.find(string.gsub(group_tag.name, "|group|", "|group_leader|"))
|
||||
|
||||
local group_leader_count = 0
|
||||
local group_count = #dt.tags.get_tagged_images(group_tag)
|
||||
|
||||
if group_leader_tag then
|
||||
group_leader_count = #dt.tags.get_tagged_images(group_leader_tag)
|
||||
end
|
||||
|
||||
if group_leader_count + group_count < 2 then -- there's either 1 or 0, so no group
|
||||
local uuid = get_uuid_from_tag(group_tag)
|
||||
|
||||
if gp.group_tags[uuid] then
|
||||
gp.group_tags[uuid] = nil
|
||||
end
|
||||
|
||||
if gp.uuids[image] then
|
||||
gp.uuids[image] = nil
|
||||
end
|
||||
|
||||
if gp.leaders[uuid] then
|
||||
gp.leaders[uuid] = nil
|
||||
end
|
||||
|
||||
group_tag:delete()
|
||||
if group_leader_tag then
|
||||
group_leader_tag:delete()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function remove_group_leader(image, new_group_leader)
|
||||
-- remove image group leader tag
|
||||
-- if it's the last image, then remove the leader/group tags
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
dump_image_tags(image)
|
||||
|
||||
local group, uuid = get_group_and_uuid(image:get_tags())
|
||||
|
||||
local leader_tag = dt.tags.find("darktable|group_leader|" .. uuid)
|
||||
local group_tag = dt.tags.find("darktable|group|" .. uuid)
|
||||
|
||||
image:detach_tag(leader_tag)
|
||||
gp.leaders[image] = nil
|
||||
|
||||
log.msg(log.info, "removed image " .. image.id .. " as group leader for uuid " .. uuid)
|
||||
|
||||
new_group_leader:detach_tag(group_tag)
|
||||
new_group_leader:attach_tag(leader_tag)
|
||||
|
||||
log.msg(log.info, "added image " .. new_group_leader.id .. " as group leader for uuid " .. uuid)
|
||||
|
||||
gp.leaders[new_group_leader] = uuid
|
||||
gp.uuids[uuid] = new_group_leader
|
||||
|
||||
check_and_remove_empty_group_data(group_tag)
|
||||
end
|
||||
|
||||
local function remove_from_group(image, group_leader)
|
||||
-- remove an image from a group
|
||||
-- if it's the last image then remove the tags
|
||||
|
||||
-- image.id > group_leder.id - simple remove
|
||||
-- image.id < group_leader.id - deleting old group_leader (image) with new group_leader (group_leader)
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
if image.id < group_leader.id then
|
||||
|
||||
remove_group_leader(image, group_leader)
|
||||
|
||||
else
|
||||
|
||||
local group_tag = has_group_tag(image)
|
||||
|
||||
if group_tag then
|
||||
|
||||
image:detach_tag(group_tag)
|
||||
|
||||
log.msg(log.info, "image " .. image.filename .. " with id " .. image.id .. " removed from group " .. group_tag.name)
|
||||
end
|
||||
|
||||
check_and_remove_empty_group_data(group_tag)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function change_group_leader(image)
|
||||
-- remove the group leader tag from the old leader
|
||||
-- replace the old group leader tag with a group tag
|
||||
-- add the group leader tag to the new image
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
dump_image_tags(image)
|
||||
|
||||
local group, uuid = get_group_and_uuid(image:get_tags())
|
||||
|
||||
local leader_tag = dt.tags.find("darktable|group_leader|" .. uuid)
|
||||
local group_tag = dt.tags.find("darktable|group|" .. uuid)
|
||||
|
||||
old_leader = dt.tags.get_tagged_images(leader_tag)[1]
|
||||
|
||||
old_leader:detach_tag(leader_tag)
|
||||
log.msg(log.info, "remove leader tag from image " .. old_leader.id)
|
||||
old_leader:attach_tag(group_tag)
|
||||
log.msg(log.info, "attached group tag to image " .. old_leader.id)
|
||||
image:detach_tag(group_tag)
|
||||
log.msg(log.info, "removed group tag from new leader image " .. image.id)
|
||||
image:attach_tag(leader_tag)
|
||||
log.msg(log.info, "attached leader tag to new leader image " .. image.id)
|
||||
gp.leaders[uuid] = image
|
||||
gp.uuids[image] = uuid
|
||||
end
|
||||
|
||||
-- event handling
|
||||
|
||||
local function add_group_event()
|
||||
dt.register_event(MODULE, "image-group-information-changed",
|
||||
function(event, reason, image, other_image)
|
||||
log.msg(log.debug, "")
|
||||
log.msg(log.debug, "caught event with reason " .. reason .. " and image " .. image.id .. " and other image " .. other_image.id)
|
||||
log.msg(log.debug, "")
|
||||
for pos, rimage in ipairs(gp.rebuilt_images) do
|
||||
if rimage == image then
|
||||
table.remove(gp.rebuilt_images, pos)
|
||||
return
|
||||
end
|
||||
end
|
||||
if reason == "add" then
|
||||
log.msg(log.debug, "in add, image is " .. image.id .. " and group is " .. other_image.id)
|
||||
if image == other_image then
|
||||
add_group_leader(image, other_image)
|
||||
else
|
||||
add_to_group(image, other_image)
|
||||
end
|
||||
elseif reason == "remove" then
|
||||
log.msg(log.debug, "in remove with image " .. image.id .. " and group_id " .. other_image.id)
|
||||
remove_from_group(image, other_image)
|
||||
elseif reason == "remove-leader" then
|
||||
remove_group_leader(image, other_image)
|
||||
log.msg(log.debug, "in remove-leader with image " .. image.id .. " and new group id " .. other_image.id)
|
||||
else
|
||||
if image ~= other_image then
|
||||
log.msg(log.debug, "in change-leader with image " .. image.id .. " and other_id " .. other_image.id)
|
||||
change_group_leader(image)
|
||||
log.msg(log.debug, "in change-leader with image " .. image.id .. " and other_id " .. other_image.id)
|
||||
else
|
||||
log.msg(log.debug, "in change-leader with image " .. image.id .. " and other_id " .. other_image.id)
|
||||
log.msg(log.debug, "calling add_group_leader")
|
||||
add_group_leader(image, other_image)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.util.message(MODULE, "autogrouper", "running")
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "post-import-image")
|
||||
dt.destroy_event(MODULE, "post-import-film")
|
||||
dt.destroy_event(MODULE, "image-group-information-changed")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "post-import-image",
|
||||
function(event, image)
|
||||
table.insert(gp.imported_images, image)
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "post-import-film",
|
||||
function(event, filmroll)
|
||||
rebuild_groups()
|
||||
end
|
||||
)
|
||||
|
||||
add_group_event()
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, string)
|
||||
dump_image_tags(dt.gui.action_images[1])
|
||||
end, "dump image tags"
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, string)
|
||||
dump_group_tags(dt.gui.action_images[1])
|
||||
end, "dump group_tags"
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, string)
|
||||
process("collection")
|
||||
end, "group collection"
|
||||
)
|
||||
|
||||
|
||||
return script_data
|
||||
@@ -35,8 +35,6 @@ du.check_min_api_version("7.0.0", "image_path_in_ui")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("image_path_in_ui", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
@@ -46,7 +44,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "image_path_in_ui",
|
||||
name = _("image path in UI"),
|
||||
purpose = _("print the image path in the UI"),
|
||||
author = "Jérémy Rosen",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui"
|
||||
@@ -65,7 +63,7 @@ local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle
|
||||
|
||||
local function install_module()
|
||||
if not ipiu.module_installed then
|
||||
dt.register_lib("image_path_no_ui","selected images path",true,false,{
|
||||
dt.register_lib("image_path_no_ui",_("selected images path"),true,false,{
|
||||
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER",300}
|
||||
}, main_label
|
||||
)
|
||||
|
||||
@@ -34,8 +34,6 @@ local dt = require "darktable"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("import_filter_manager", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -43,7 +41,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "import_filter_manager",
|
||||
name = _("import filter manager"),
|
||||
purpose = _("manage import filters"),
|
||||
author = "Tobias Ellinghaus",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager"
|
||||
|
||||
@@ -32,8 +32,6 @@ local dt = require "darktable"
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("import_filters", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -41,7 +39,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "import_filters",
|
||||
name = _("import filters"),
|
||||
purpose = _("import filtering"),
|
||||
author = "Tobias Ellinghaus & Christian Mandel",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filters"
|
||||
|
||||
343
official/recent_bookmarks.lua
Normal file
343
official/recent_bookmarks.lua
Normal file
@@ -0,0 +1,343 @@
|
||||
--[[
|
||||
|
||||
recent_bookmarks.lua - add recently edited and exported images to system recent bookmarks
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
recent_bookmarks - add recently edited and exported images to system recently used files
|
||||
|
||||
recent_bookmarks adds images edited in darktable and the directory of images exported from
|
||||
darktable to the reccently-used.xbel file so that they show up in the recently used system
|
||||
shortcuts.
|
||||
|
||||
Currently recent_bookmarks only works on Linux
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
|
||||
file - linux system file utility to get mime-type
|
||||
|
||||
USAGE
|
||||
|
||||
eanble from 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("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 = _("recent_bookmarks"), -- visible name of script
|
||||
purpose = _("add recently edited and exported images to system recent bookmarks"), -- purpose of script
|
||||
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
|
||||
help = "" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "recent_bookmarks"
|
||||
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 ";"
|
||||
|
||||
-- format strings for writing the bookmarks
|
||||
|
||||
local BOOKMARK_FORMAT <const> = " <bookmark href=\"file://%s\" added=\"%s\" modified=\"%s\" visited=\"%s\">\n"
|
||||
local MIME_FORMAT <const> = " <mime:mime-type type=\"%s\"/>\n"
|
||||
local EXPORT_FORMAT <const> = " <bookmark:application name=\"xdg-open\" exec=\"'xdg-open %%u'\" modified=\"%s\" count=\"1\"/>\n"
|
||||
local EDIT_FORMAT <const> = " <bookmark:application name=\"darktable\" exec=\"'darktable %%u'\" modified=\"%s\" count=\"1\"/>\n"
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- L O G L E V E L
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- N A M E S P A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local recent_bookmarks = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
recent_bookmarks.log_level = DEFAULT_LOG_LEVEL
|
||||
recent_bookmarks.bookmark_file = nil
|
||||
recent_bookmarks.current_view = nil
|
||||
recent_bookmarks.in_darkroom = false
|
||||
recent_bookmarks.current_image = nil
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = recent_bookmarks
|
||||
local rb = recent_bookmarks
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-------------------
|
||||
-- helper functions
|
||||
-------------------
|
||||
|
||||
local function set_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(level)
|
||||
|
||||
return old_log_level
|
||||
end
|
||||
|
||||
local function restore_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
log.log_level(level)
|
||||
end
|
||||
|
||||
local function reset_log_level()
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
namespace.log_level = DEFAULT_LOG_LEVEL
|
||||
end
|
||||
|
||||
local function pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, "read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- program functions
|
||||
--------------------
|
||||
|
||||
local function zulu_timestamp()
|
||||
return os.date("!%Y-%m-%dT%H:%M:%SZ")
|
||||
end
|
||||
|
||||
local function get_mimetype(filepath)
|
||||
local mimetype = "application/octet-stream"
|
||||
local p = io.popen("file -L --mime-type " .. df.sanitize_filename(filepath))
|
||||
if p then
|
||||
for line in p:lines() do
|
||||
local result = string.match(line, ".-: (.+)$")
|
||||
if result then
|
||||
mimetype = result
|
||||
end
|
||||
end
|
||||
end
|
||||
return mimetype
|
||||
end
|
||||
|
||||
local function find_bookmark_file()
|
||||
local file = nil
|
||||
local home = os.getenv("HOME")
|
||||
if home then
|
||||
if df.test_file(home .. PS ..".local/share/recently-used.xbel", "e") then
|
||||
file = home .. PS ..".local/share/recently-used.xbel"
|
||||
end
|
||||
end
|
||||
return file
|
||||
end
|
||||
|
||||
local function write_bookmark_file(bookmark, mimetype, application)
|
||||
local file = io.open(rb.bookmark_file, "r+")
|
||||
if file then
|
||||
local pos = file:seek("end", -7)
|
||||
if pos > 0 then
|
||||
file:write(bookmark)
|
||||
file:write(" <info>\n <metadata owner=\"http://freedesktop.org\">\n")
|
||||
file:write(mimetype)
|
||||
file:write(" <bookmark:applications>\n")
|
||||
file:write(application)
|
||||
file:write(" </bookmark:applications>\n </metadata>\n </info>\n </bookmark>\n</xbel>")
|
||||
file:close()
|
||||
else
|
||||
log.msg(log.screen, _("failed to open bookmark file"))
|
||||
end
|
||||
else
|
||||
log.msg(log.screen, _("failed to open bookmark file"))
|
||||
end
|
||||
end
|
||||
|
||||
local function create_bookmark_string(filename, zulu)
|
||||
return string.format(BOOKMARK_FORMAT, filename, zulu, zulu, zulu)
|
||||
end
|
||||
|
||||
local function create_mimetype_string(mimetype)
|
||||
return string.format(MIME_FORMAT, mimetype)
|
||||
end
|
||||
|
||||
local function process_bookmark(image, filename)
|
||||
|
||||
local zulu = zulu_timestamp()
|
||||
local filepath = filename and filename or (image.path .. PS .. image.filename)
|
||||
local format = filename and EXPORT_FORMAT or EDIT_FORMAT
|
||||
local mimetype = get_mimetype(filepath)
|
||||
local bookmark_string = create_bookmark_string(filepath, zulu)
|
||||
local mime_string = create_mimetype_string(mimetype)
|
||||
local app_string = string.format(format, zulu)
|
||||
write_bookmark_file(bookmark_string, mime_string, app_string)
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
if dt.configuration.running_os ~= "linux" then
|
||||
log.msg(log.screen, script_data.metadata.name .. _(" only runs on Linux"))
|
||||
return
|
||||
end
|
||||
|
||||
rb.bookmark_file = find_bookmark_file()
|
||||
if not rb.bookmark_file then
|
||||
log.msg(log.screen, _("recent bookmarks file not found"))
|
||||
return
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "intermediate-export-image")
|
||||
dt.destroy_event(MODULE, "darkroom-image-loaded")
|
||||
dt.destroy_event(MODULE, "darkroom-image-history-changed")
|
||||
dt.destroy_event(MODULE, "view-changed")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "intermediate-export-image",
|
||||
function(event, image, filename, format, storage)
|
||||
process_bookmark(image, filename)
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "darkroom-image-loaded",
|
||||
function(event, clean, image)
|
||||
if clean then
|
||||
rb.in_darkroom = true
|
||||
rb.current_view = "darkroom"
|
||||
rb.current_image = image
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "darkroom-image-history-changed",
|
||||
function(event, image)
|
||||
if image == rb.current_image then
|
||||
process_bookmark(image)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "view-changed",
|
||||
function(event, old_view, new_view)
|
||||
rb.current_view = new_view.id
|
||||
if rb.current_view == "darkroom" then
|
||||
rb.in_darkroom = true
|
||||
else
|
||||
if rb.in_darkroom then
|
||||
rb.in_darkroom = false
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
267
official/regenerate_thumbnails.lua
Normal file
267
official/regenerate_thumbnails.lua
Normal file
@@ -0,0 +1,267 @@
|
||||
--[[
|
||||
|
||||
regenerate_thumbnails.lua - regenerate mipmap cache for selected images
|
||||
|
||||
Copyright (C) 2025 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/>.
|
||||
]]
|
||||
--[[
|
||||
regenerate_thumbnails - regenerate mipmap cache for selected images
|
||||
|
||||
regenerate_thumbnails drops the cached thumbnail for each selected image
|
||||
and generates a new thumbnail.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
None
|
||||
|
||||
USAGE
|
||||
* enable the script in script_manager
|
||||
* assign a shortcut, if desired, to apply the script by hovering
|
||||
over a skull and using the shortcut to regenerate the thumbnail
|
||||
|
||||
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("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 = _("regenerate_thumbnails"), -- visible name of script
|
||||
purpose = _("regenerate mipmap cache for selected images"), -- purpose of script
|
||||
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
|
||||
help = "https://docs.darktable.org/lua/development/lua.scripts.manual/scripts/official/regenerate_thumbnails" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "regenerate_thumbnails"
|
||||
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 regenerate_thumbnails = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = regenerate_thumbnails
|
||||
local rt = regenerate_thumbnails
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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 pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(sm.log_level)
|
||||
|
||||
log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, "read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(sm.log_level)
|
||||
|
||||
log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function stop_job()
|
||||
rt.job.valid = false
|
||||
end
|
||||
|
||||
local function generate_thumbnails(images)
|
||||
local has_job = false
|
||||
|
||||
if #images > 50 then
|
||||
rt.job = dt.gui.create_job("regenerating thumbnails", true, stop_job)
|
||||
has_job = true
|
||||
end
|
||||
|
||||
for count, image in ipairs(images) do
|
||||
image:drop_cache()
|
||||
image:generate_cache(true, 1, 3)
|
||||
if has_job then
|
||||
if not rt.job.valid then
|
||||
return
|
||||
end
|
||||
if count % 10 == 0 then
|
||||
rt.job.percent = count / #images
|
||||
end
|
||||
end
|
||||
end
|
||||
if has_job then
|
||||
rt.job.valid = false
|
||||
end
|
||||
end
|
||||
|
||||
local function update_button_sensitivity()
|
||||
if #dt.gui.action_images > 0 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,
|
||||
_("generate thumbnails"),
|
||||
function(event, images)
|
||||
generate_thumbnails(images)
|
||||
end,
|
||||
_("generate thumbnails")
|
||||
)
|
||||
|
||||
update_button_sensitivity()
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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, "selection-changed")
|
||||
dt.destroy_event(MODULE, "mouse-over-image-changed")
|
||||
dt.gui.libs.image.destroy_action(MODULE, "regenerate thumbnails")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
local images = dt.gui.action_images
|
||||
if images then
|
||||
generate_thumbnails(images)
|
||||
end
|
||||
end, "regenerate thumbnails"
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "selection-changed",
|
||||
function(event)
|
||||
update_button_sensitivity()
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "mouse-over-image-changed",
|
||||
function(event, image)
|
||||
if #dt.gui.selection() < 1 then
|
||||
if image then
|
||||
dt.gui.libs.image.set_sensitive(MODULE, true)
|
||||
else
|
||||
dt.gui.libs.image.set_sensitive(MODULE, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
@@ -40,8 +40,6 @@ du.check_min_api_version("7.0.0", "save_selection")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("save_selection", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -51,7 +49,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "save_selection",
|
||||
name = _("save selection"),
|
||||
purpose = _("shortcuts providing multiple selection buffers"),
|
||||
author = "Jérémy Rosen",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/save_selection"
|
||||
|
||||
224
official/scheduler.lua
Normal file
224
official/scheduler.lua
Normal file
@@ -0,0 +1,224 @@
|
||||
--[[
|
||||
|
||||
scheduler.lua - provide scheduler services for multitasking
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
scheduler - provide scheduler services for multitasking
|
||||
|
||||
scheduler implements a sinple first in first out (FIFO) scheduler
|
||||
necessary for lua script multitasking.
|
||||
|
||||
Scripts voluntarily yield to the scheduler and they are placed at
|
||||
the end of the run queue. The first item in the run queue is told
|
||||
to resume.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
None
|
||||
|
||||
USAGE
|
||||
start script from 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"
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "scheduler"
|
||||
local DEFAULT_LOG_LEVEL <const> = log.info
|
||||
local TMP_DIR <const> = dt.configuration.tmp_dir
|
||||
local BROADCAST_MSG <const> = "broadcast"
|
||||
|
||||
-- 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("9.4.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(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 = "scheduler", -- name of script
|
||||
purpose = _("provide scheduler services for multitasking"), -- 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/official/scheduler/" -- 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 scheduler = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
scheduler.run_queue = {}
|
||||
scheduler.message_queue = {}
|
||||
scheduler.log_level = log.debug
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "target_time_slice_seconds", "float", "scheduler preferred time slice", "target time slice before interrupt", 1.0, 0.1, 1.0, 0.1)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local sch = scheduler
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function remove_from_run_queue()
|
||||
|
||||
log.log_level(sch.log_level)
|
||||
|
||||
if #sch.run_queue > 0 then
|
||||
local module = sch.run_queue[1]
|
||||
local message = sch.message_queue[1]
|
||||
table.remove(sch.run_queue, 1)
|
||||
table.remove(sch.message_queue, 1)
|
||||
log.msg(log.info, "run queue length is " .. #sch.run_queue)
|
||||
log.msg(log.debug, "sending " .. message .. " to " .. module)
|
||||
dt.util.message(MODULE, module, message)
|
||||
else
|
||||
log.msg(log.warn, "run queue empty, nothing to resume")
|
||||
end
|
||||
end
|
||||
|
||||
local function add_to_run_queue(module, message)
|
||||
|
||||
log.log_level(sch.log_level)
|
||||
|
||||
table.insert(sch.run_queue, module)
|
||||
table.insert(sch.message_queue, message)
|
||||
|
||||
log.msg(log.info, "run queue length is " .. #sch.run_queue)
|
||||
|
||||
dt.control.sleep(500) -- allow time for waiting scripts to start
|
||||
|
||||
remove_from_run_queue()
|
||||
end
|
||||
|
||||
local function send_status(sender)
|
||||
dt.util.message(MODULE, sender, "running, queue length is " .. #sch.run_queue)
|
||||
end
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.util.message(MODULE, BROADCAST_MSG, "starting")
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
-- put things to destroy (events, storages, etc) here
|
||||
dt.util.message(MODULE, BROADCAST_MSG, "stopping")
|
||||
dt.destroy_event(MODULE, "inter-script-communication")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "inter-script-communication",
|
||||
function(event, sender,receiver, message)
|
||||
|
||||
log.log_level(sch.log_level)
|
||||
|
||||
if receiver == "scheduler" then
|
||||
log.msg(log.debug, "got message from " .. sender .. " to " .. receiver .. " with message " .. message)
|
||||
if string.match(message, "^yield") then
|
||||
log.msg(log.debug, "got message " .. message .. " from " .. sender)
|
||||
add_to_run_queue(sender, string.gsub(message, "yield", "resume"))
|
||||
elseif message == "status" then
|
||||
send_status(sender)
|
||||
elseif messages == "done" then
|
||||
-- do nothing
|
||||
else
|
||||
log.msg(log.warn, "unrecognized message " .. message)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
266
official/select_duplicates.lua
Normal file
266
official/select_duplicates.lua
Normal file
@@ -0,0 +1,266 @@
|
||||
--[[
|
||||
|
||||
select_duplicates.lua - select duplicate images from a collection
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
select_duplicates - select duplicate images from a collection
|
||||
|
||||
select_duplicates adds a button to the select module to select
|
||||
duplicate images from a collection. If the selection2collection
|
||||
script is also active an option is available to have the selected
|
||||
duplicates appear as a temporary collection.
|
||||
|
||||
A shortcut is available to select the duplicates from the collection
|
||||
but it is somewhat slower because it doesn't have access to the current
|
||||
image cache and therfore has to open each image separately.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
|
||||
None
|
||||
|
||||
USAGE
|
||||
|
||||
* enable in script manager
|
||||
* Press the button
|
||||
|
||||
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("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 = _("select duplicates"), -- visible name of script
|
||||
purpose = _("select duplicate images from a collection"), -- 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/official/select_duplicates/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "select_duplicates"
|
||||
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 select_duplicates = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
select_duplicates.log_level = DEFAULT_LOG_LEVEL
|
||||
select_duplicates.create_collection = false
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "create_collection", "bool",
|
||||
script_data.metadata.name .. _(" create collection from selection"),
|
||||
_("create collection of selected duplicates"), false)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = select_duplicates
|
||||
local sd = select_duplicates
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-------------------
|
||||
-- helper functions
|
||||
-------------------
|
||||
|
||||
local function set_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(level)
|
||||
|
||||
return old_log_level
|
||||
end
|
||||
|
||||
local function restore_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
log.log_level(level)
|
||||
end
|
||||
|
||||
local function reset_log_level()
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
namespace.log_level = DEFAULT_LOG_LEVEL
|
||||
end
|
||||
|
||||
local function pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, "read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function select_duplicate_files(event, images)
|
||||
local duplicates = {}
|
||||
local sorted_files = {}
|
||||
sd.create_collection = pref_read("create_collection", "bool")
|
||||
|
||||
for _, img in ipairs(images) do
|
||||
local rawname = img.path .. PS .. img.filename
|
||||
if not sorted_files[rawname] then
|
||||
sorted_files[rawname] = {}
|
||||
end
|
||||
table.insert(sorted_files[rawname], img)
|
||||
end
|
||||
for k,v in pairs(sorted_files) do
|
||||
if #v > 1 then
|
||||
local old_image = nil
|
||||
for _,i in ipairs(v) do
|
||||
if not old_image then
|
||||
old_image = i
|
||||
else
|
||||
if i.duplicate_index > old_image.duplicate_index then
|
||||
table.insert(duplicates, i)
|
||||
else
|
||||
table.insert(duplicates, old_image)
|
||||
old_image = i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if sd.create_collection then
|
||||
dt.util.message(MODULE, "selection2collection", "create")
|
||||
end
|
||||
return duplicates
|
||||
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.select.register_selection(MODULE, _("select duplicates"), select_duplicate_files,
|
||||
_("select duplicated images"))
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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.select.destroy_selection(MODULE)
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
if not dt.query_event(MODULE, "shortcut") then -- skip if shortcut already registered
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
local duplicates = select_duplicate_files(event, dt.collection)
|
||||
if duplicates then
|
||||
dt.gui.selection(duplicates)
|
||||
end
|
||||
end, _("select duplicates")
|
||||
)
|
||||
end
|
||||
|
||||
return script_data
|
||||
180
official/select_raw_non_raw.lua
Normal file
180
official/select_raw_non_raw.lua
Normal file
@@ -0,0 +1,180 @@
|
||||
--[[
|
||||
|
||||
select_raw_non_raw.lua - select raw or non raw files from a collection
|
||||
|
||||
Copyright (C) 2025 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/>.
|
||||
]]
|
||||
--[[
|
||||
select_raw_non_raw - select raw or non raw files from a collection
|
||||
|
||||
select_raw_non_raw adds two selection buttons, one to select raw
|
||||
images and one to select non raw images.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
None
|
||||
|
||||
USAGE
|
||||
* enable the script with 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"
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "select_raw_non_raw"
|
||||
local DEFAULT_LOG_LEVEL <const> = log.warn
|
||||
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
|
||||
|
||||
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 = "select_raw_non_raw", -- name of script
|
||||
purpose = _("select raw or non raw files from collection"), -- 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/official/select_raw_non_raw" -- 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 select_group_followers = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = select_group_followers
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function select_raw_files(event, images)
|
||||
local raws = {}
|
||||
|
||||
for _, img in ipairs(images) do
|
||||
if img.is_raw then
|
||||
table.insert(raws, img)
|
||||
end
|
||||
end
|
||||
|
||||
return raws
|
||||
end
|
||||
|
||||
local function select_non_raw_files(event, images)
|
||||
local non_raws = {}
|
||||
|
||||
for _, img in ipairs(images) do
|
||||
if not img.is_raw then
|
||||
table.insert(non_raws, img)
|
||||
end
|
||||
end
|
||||
|
||||
return non_raws
|
||||
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.select.register_selection(MODULE .. "_raws", _("select raw"), select_raw_files,
|
||||
_("select raw files"))
|
||||
dt.gui.libs.select.register_selection(MODULE .. "_non_raws", _("select non raw"), select_non_raw_files,
|
||||
_("select non raw files"))
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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.gui.libs.select.destroy_selection(MODULE .. "_raws")
|
||||
dt.gui.libs.select.destroy_selection(MODULE .. "_non_raws")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
return script_data
|
||||
168
official/select_unaltered.lua
Normal file
168
official/select_unaltered.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
--[[
|
||||
|
||||
select_unaltered.lua - select images that have not been altered in any way
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
select_unaltered - select images that have not been altered in any way
|
||||
|
||||
select_unaltered selects images from the current collection that have not
|
||||
been altered in any way. In other words just imported into darktable, not
|
||||
loaded into darkroom nor having any style applied.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
None
|
||||
|
||||
USAGE
|
||||
* enable with 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("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 = _("select_unaltered"), -- visible name of script
|
||||
purpose = _("select images that have not been altered in any way"), -- 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/official/select_unaltered/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "select_unaltered"
|
||||
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 select_unaltered = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
select_unaltered.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 = select_unaltered
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function select_unaltered_files(event, images)
|
||||
local unaltered = {}
|
||||
|
||||
for _, img in ipairs(images) do
|
||||
if not img.is_altered then
|
||||
table.insert(unaltered, img)
|
||||
end
|
||||
end
|
||||
|
||||
return unaltered
|
||||
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.select.register_selection(MODULE, _("select unaltered"), select_unaltered_files,
|
||||
_("select unaltered files"))
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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.select.destroy_selection(MODULE)
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
return script_data
|
||||
354
official/selection2collection.lua
Normal file
354
official/selection2collection.lua
Normal file
@@ -0,0 +1,354 @@
|
||||
--[[
|
||||
|
||||
selection2collection.lua - create a temporary collection from a selection
|
||||
|
||||
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/>.
|
||||
]]
|
||||
--[[
|
||||
selection2collection - create a temporary collection from a selection
|
||||
|
||||
selection2collection creates a temporary collection, using functional
|
||||
tagging, from a selection. The collection lasts until darktable exits
|
||||
and then the collections are removed, but not the images.
|
||||
|
||||
To create the temporary collection, a function tag (darktable|functional|s2c)
|
||||
tag with a date and a time is applied to the selected images. The collection
|
||||
module filters on the tag to display the connection. More than one temporary
|
||||
collection can be created and collections can be changed by selecting the
|
||||
appropriate tag.
|
||||
|
||||
The tags are autmatically destroyed when darktable exits, thus removing the
|
||||
collections. There is a preference in the Lua options to keep the collections
|
||||
across darktable runs.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
|
||||
None
|
||||
|
||||
USAGE
|
||||
|
||||
* start from script_manager
|
||||
* make a selection
|
||||
* click the create temporary collection button the the actions on selected images
|
||||
module
|
||||
|
||||
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 = _("selection2collection"), -- visible name of script
|
||||
purpose = _("create a temporary collection from a selection"), -- 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/official/selection2collection/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "selection2collection"
|
||||
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 ";"
|
||||
|
||||
-- script specific
|
||||
|
||||
local TAG_HEADER <const> = "darktable|functional|s2c|"
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- L O G L E V E L
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- N A M E S P A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local selection2collection = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
selection2collection.log_level = DEFAULT_LOG_LEVEL
|
||||
selection2collection.keep_collections = false
|
||||
selection2collection.collection_rules = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "keep_collections", "bool",
|
||||
script_data.metadata.name .. _(" keep collections created from selection"),
|
||||
_("keep collections created from selections"), false)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = selection2collection
|
||||
local s2c = selection2collection
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-------------------
|
||||
-- helper functions
|
||||
-------------------
|
||||
|
||||
local function set_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(level)
|
||||
|
||||
return old_log_level
|
||||
end
|
||||
|
||||
local function restore_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
log.log_level(level)
|
||||
end
|
||||
|
||||
local function reset_log_level()
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
namespace.log_level = DEFAULT_LOG_LEVEL
|
||||
end
|
||||
|
||||
local function pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, "read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- program functions
|
||||
-------------------
|
||||
|
||||
local function clear_collections()
|
||||
s2c.keep_collections = pref_read("keep_collections", "bool")
|
||||
|
||||
if not s2c.keep_collections then
|
||||
for _, tag in ipairs(dt.tags) do
|
||||
if string.match(tag.name, TAG_HEADER) then
|
||||
tag:delete()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function create_collection_tag()
|
||||
|
||||
local t = os.date("*t")
|
||||
|
||||
local ymd = string.format("%4d:%02d:%02d", t.year, t.month, t.day)
|
||||
local hms = string.format("%02d:%02d:%02d", t.hour, t.min, t.sec)
|
||||
|
||||
local tag_string = TAG_HEADER .. ymd .. "|" .. hms
|
||||
dt.print_log("tag_string is " .. tag_string)
|
||||
|
||||
local tag = dt.tags.create(tag_string)
|
||||
|
||||
return tag
|
||||
end
|
||||
|
||||
local function display_collection(tag_name)
|
||||
local collection_rules = dt.gui.libs.collect.filter()
|
||||
|
||||
local rule = dt.gui.libs.collect.new_rule()
|
||||
|
||||
rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||
rule.data = tostring(tag_name)
|
||||
rule.item = "DT_COLLECTION_PROP_TAG"
|
||||
|
||||
table.insert(collection_rules, rule)
|
||||
|
||||
-- dt.gui.libs.collect.filter returns the previous rule set when you install another
|
||||
s2c.collection_rules = dt.gui.libs.collect.filter(collection_rules)
|
||||
end
|
||||
|
||||
local function create_collection()
|
||||
local images = dt.gui.selection()
|
||||
|
||||
if #images > 1 then
|
||||
|
||||
local tag = create_collection_tag()
|
||||
|
||||
if tag then
|
||||
for _, image in ipairs(images) do
|
||||
image:attach_tag(tag)
|
||||
end
|
||||
end
|
||||
|
||||
display_collection(tag.name)
|
||||
else
|
||||
log.msg(log.screen, _("not enough images to create a collection"))
|
||||
end
|
||||
end
|
||||
|
||||
local function communications_handler(sender, receiver, message)
|
||||
if receiver == MODULE then
|
||||
if message == "create" then
|
||||
create_collection()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function set_button_sensitive(images)
|
||||
if #images > 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
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
s2c.keep_collections = pref_read("keep_collections", "bool")
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.gui.libs.image.register_action(
|
||||
MODULE, _("create temporary collection"),
|
||||
function(event, images)
|
||||
create_collection()
|
||||
end,
|
||||
_("convert selection to collection")
|
||||
)
|
||||
|
||||
set_button_sensitive(dt.gui.selection())
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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, "inter-script-communication")
|
||||
dt.destroy_event(MODULE, "exit")
|
||||
dt.destroy_event(MODULE, "selection-changed")
|
||||
dt.gui.libs.image.destroy_action(MODULE, "create_collection")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "inter-script-communication",
|
||||
function(event, sender, receiver, message)
|
||||
communications_handler(sender, receiver, message)
|
||||
end
|
||||
)
|
||||
|
||||
if not dt.query_event(MODULE, "shortcut") then -- skip if shortcut already registered
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
create_collection()
|
||||
end, _("convert selection to collection")
|
||||
)
|
||||
end
|
||||
|
||||
dt.register_event(MODULE, "exit",
|
||||
function(event)
|
||||
if not s2c.keep_collections then
|
||||
dt.gui.libs.collect.filter(s2c.collection_rules)
|
||||
clear_collections()
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "selection-changed",
|
||||
function(event)
|
||||
set_button_sensitive(dt.gui.selection())
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
@@ -40,18 +40,20 @@ du.check_min_api_version("7.0.0", "selection_to_pdf")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("selection_to_pdf", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
|
||||
local NIX_OPEN <const> = "xdg-open "
|
||||
local MAC_OPEN <const> = "open "
|
||||
local WIN_OPEN <const> = "start "
|
||||
|
||||
-- return data structure for script_manager
|
||||
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "selection_to_pdf",
|
||||
name = _("selection to PDF"),
|
||||
purpose = _("generate a pdf file of selected images"),
|
||||
author = "Jérémy Rosen & Pascal Obry",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf"
|
||||
@@ -65,8 +67,8 @@ script_data.show = nil -- only required for libs since the destroy_method only h
|
||||
dt.preferences.register
|
||||
("selection_to_pdf","Open with","string",
|
||||
_("a pdf viewer"),
|
||||
_("can be an absolute pathname or the tool may be in the PATH"),
|
||||
"xdg-open")
|
||||
_("can be an absolute pathname or the tool may be in the PATH\nif not specified the system default will be used to open the pdf"),
|
||||
"")
|
||||
|
||||
local title_widget = dt.new_widget("entry") {
|
||||
placeholder = _("title")
|
||||
@@ -117,8 +119,9 @@ local function thumbnail(latexfile,i,image,file)
|
||||
-- fact is that latex will get confused if the filename has multiple dots.
|
||||
-- so \includegraphics{file.01.jpg} wont work. We need to output the filename
|
||||
-- and extention separated, e.g: \includegraphics{{file.01}.jpg}
|
||||
local filenoext=string.gsub(file, "(.*)(%..*)", "%1")
|
||||
local ext=string.gsub(file, "(.*)(%..*)", "%2")
|
||||
|
||||
local filenoext = string.gsub(file, "(.*)(%..*)", "%1")
|
||||
local ext = string.gsub(file, "(.*)(%..*)", "%2")
|
||||
my_write(latexfile,"\\begin{minipage}[b]{"..width.."\\textwidth}\n")
|
||||
my_write(latexfile,"\\includegraphics[width=\\textwidth]{{"..filenoext.."}"..ext.."}\\newline\n")
|
||||
my_write(latexfile,"\\centering{"..i..": \\verb|"..title.."|}\n")
|
||||
@@ -131,7 +134,7 @@ local function destroy()
|
||||
dt.print_log("done destroying")
|
||||
end
|
||||
|
||||
dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
||||
dt.register_storage("export_pdf", _("export thumbnails to pdf"),
|
||||
nil,
|
||||
function(storage,image_table)
|
||||
local my_title = title_widget.text
|
||||
@@ -140,7 +143,7 @@ dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
||||
end
|
||||
local thumbs_per_line = no_of_thumbs_widget.value
|
||||
thumbs_per_line = tonumber(thumbs_per_line)
|
||||
width = (1/thumbs_per_line) - 0.01
|
||||
width = (1 / thumbs_per_line) - 0.01
|
||||
|
||||
local preamble = [[
|
||||
\documentclass[a4paper,10pt]{article}
|
||||
@@ -150,31 +153,31 @@ dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
||||
\usepackage{geometry}
|
||||
\geometry{a4paper,left=5mm,right=5mm, top=5mm, bottom=5mm}
|
||||
\begin{document}
|
||||
{\Large\bfseries ]]..my_title..[[} \\
|
||||
{\Large\bfseries ]] .. my_title .. [[} \\
|
||||
\bigskip\bigskip
|
||||
]]
|
||||
|
||||
local errmsg
|
||||
local latexfile
|
||||
latexfile,errmsg=io.open(filename,"w")
|
||||
latexfile,errmsg = io.open(filename, "w")
|
||||
if not latexfile then
|
||||
error(errmsg)
|
||||
end
|
||||
my_write(latexfile,preamble)
|
||||
my_write(latexfile, preamble)
|
||||
local i = 1
|
||||
for img,file in pairs(image_table) do
|
||||
thumbnail(latexfile,i,img,file)
|
||||
for img, file in pairs(image_table) do
|
||||
thumbnail(latexfile, i, img, file)
|
||||
if i % thumbs_per_line == 0 then
|
||||
my_write(latexfile,"\n\\bigskip\n")
|
||||
my_write(latexfile, "\n\\bigskip\n")
|
||||
end
|
||||
i = i+1
|
||||
i = i + 1
|
||||
end
|
||||
my_write(latexfile,ending)
|
||||
latexfile:close()
|
||||
|
||||
-- convert to PDF
|
||||
local dir=string.gsub(filename, "(.*/)(.*)", "%1")
|
||||
local locfile=string.gsub(filename, "(.*/)(.*)", "%2")
|
||||
local dir = string.gsub(filename, "(.*/)(.*)", "%1")
|
||||
local locfile = string.gsub(filename, "(.*/)(.*)", "%2")
|
||||
local command = "pdflatex -halt-on-error -output-directory "..dir.." "..locfile
|
||||
local result = dt.control.execute(command)
|
||||
if result ~= 0 then
|
||||
@@ -183,9 +186,18 @@ dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
||||
end
|
||||
|
||||
-- open the PDF
|
||||
local pdffile=string.gsub(filename, ".tex", ".pdf")
|
||||
local pdffile = string.gsub(filename, ".tex", ".pdf")
|
||||
command = dt.preferences.read("selection_to_pdf","Open with","string")
|
||||
command = command.." "..pdffile
|
||||
if command == "" then
|
||||
if dt.configuration.running_os == "windows" then
|
||||
command = WIN_OPEN
|
||||
elseif dt.configuration.running_os == "macos" then
|
||||
command = MAC_OPEN
|
||||
else
|
||||
command = NIX_OPEN
|
||||
end
|
||||
end
|
||||
command = command .. " " .. pdffile
|
||||
local result = dt.control.execute(command)
|
||||
if result ~= 0 then
|
||||
dt.print(_("problem running pdf viewer")) -- this one is probably usefull to the user
|
||||
|
||||
337
official/use_paired_jpg_as_mipmap.lua
Normal file
337
official/use_paired_jpg_as_mipmap.lua
Normal file
@@ -0,0 +1,337 @@
|
||||
--[[
|
||||
|
||||
use_paired_jpg_as_mipmap.lua - Use the JPG from the RAW JPG pair as the full size mipmap
|
||||
|
||||
Copyright (C) 2026 Bill Ferguson <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/>.
|
||||
]]
|
||||
--[[
|
||||
use_paired_jpg_as_mipmap - Use the JPG from the RAW JPG pair as the full size mipmap
|
||||
|
||||
use_paired_jpg_as_mipmap looks for RAW+JPEG image pairs as images are imported. After
|
||||
import the JPEG image is copied to the mipmap cache as the full resolution mipmap. Requests
|
||||
for smaller mipmap sizes can be satisfied by down sampling the full resolution mipmap. User's can
|
||||
decide whether or not to keep the paired JPEG files. The default is to keep them. If the
|
||||
user doesn't want them, they are deleted after being copied to the mipmap cache.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
|
||||
None
|
||||
|
||||
USAGE
|
||||
|
||||
- Add the script to the lua-scripts
|
||||
- Enable the script
|
||||
- Turn off the keep_jpgs preference if the JPEGS are not wanted
|
||||
|
||||
BUGS, COMMENTS, SUGGESTIONS
|
||||
Bill Ferguson <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("7.0.0", MODULE) -- choose the minimum version that contains the features you need
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- I 1 8 N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local script_data = {}
|
||||
|
||||
script_data.destroy = nil -- function to destory the script
|
||||
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||
|
||||
script_data.metadata = {
|
||||
name = _("use paired jpg as mipmap"), -- visible name of script
|
||||
purpose = _("Use the JPG from the RAW JPG pair as the full size mipmap"), -- purpose of script
|
||||
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/use_paired_jpg_as_mipmap/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "use_paired_jpg_as_mipmap"
|
||||
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 ";"
|
||||
|
||||
-- max mipmap size - 5.4.x = 8, after is 10
|
||||
local MAX_MIPMAP_SIZE <const> = (dt.configuration.version > "5.4.1") and "10" or "8"
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- L O G L E V E L
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- N A M E S P A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local use_paired_jpg_as_mipmap = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
use_paired_jpg_as_mipmap.log_level = DEFAULT_LOG_LEVEL
|
||||
use_paired_jpg_as_mipmap.imported_images = {}
|
||||
use_paired_jpg_as_mipmap.image_count = 0
|
||||
use_paired_jpg_as_mipmap.mipmap_dir = nil
|
||||
use_paired_jpg_as_mipmap.keep_jpgs = true
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "keep_jpgs", "bool", script_data.metadata.name .. " - " .. _("keep JPEG files"),
|
||||
_("don't delete JPEGs after copying them to mipmap folder"), true)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = use_paired_jpg_as_mipmap
|
||||
local pmj = use_paired_jpg_as_mipmap
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-------------------
|
||||
-- helper functions
|
||||
-------------------
|
||||
|
||||
local function set_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(level)
|
||||
|
||||
return old_log_level
|
||||
end
|
||||
|
||||
local function restore_log_level(level)
|
||||
if not level then
|
||||
level = namespace.log_level
|
||||
end
|
||||
log.log_level(level)
|
||||
end
|
||||
|
||||
local function reset_log_level()
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
namespace.log_level = DEFAULT_LOG_LEVEL
|
||||
end
|
||||
|
||||
local function pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, MODULE .. ": name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, MODULE .. ": read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(namespace.log_level)
|
||||
|
||||
log.msg(log.debug, MODULE .. ": writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function refresh_collection()
|
||||
local rules = dt.gui.libs.collect.filter()
|
||||
dt.gui.libs.collect.filter(rules)
|
||||
end
|
||||
|
||||
local function get_mipmap_dir()
|
||||
local old_log_level = set_log_level(pmj.log_level)
|
||||
local mipmap_dir = nil
|
||||
local cachedir = dt.configuration.cache_dir
|
||||
|
||||
local cmd = "cd " .. cachedir .. "; ls -d mip*"
|
||||
|
||||
if dt.configuration.running_os == "windows" then
|
||||
cmd = "forfiles /P \"" .. cachedir .. "\" /M mip* /C \"cmd /c echo @file\""
|
||||
end
|
||||
|
||||
local p = io.popen(cmd)
|
||||
if p then
|
||||
for line in p:lines() do
|
||||
if line:len() > 4 then
|
||||
mipmap_dir = cachedir .. PS .. line
|
||||
log.msg(log.info, MODULE .. ": mipmap_dir is " .. mipmap_dir)
|
||||
end
|
||||
end
|
||||
p:close()
|
||||
end
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return mipmap_dir
|
||||
end
|
||||
|
||||
local function stop_job(job)
|
||||
job.valid = false
|
||||
end
|
||||
|
||||
local function process_images(images, count)
|
||||
local old_log_level = set_log_level(pmj.log_level)
|
||||
|
||||
-- if dt.collection is equal to count there are no
|
||||
-- RAW+JPEG pairs
|
||||
|
||||
if count == #dt.collection then
|
||||
log.msg(log.screen, _("No RAW+JPEG pairs to process"))
|
||||
return
|
||||
end
|
||||
|
||||
local job = dt.gui.create_job(_("Generating cache from jpgs"), true, stop_job)
|
||||
local processed_count = 0
|
||||
local mipmap_dir = get_mipmap_dir() .. PS .. MAX_MIPMAP_SIZE .. PS
|
||||
|
||||
local rc = df.mkdir(mipmap_dir)
|
||||
log.msg(log.debug, MODULE .. ": rc from mkdir is " .. rc)
|
||||
|
||||
for k,v in pairs(images) do
|
||||
if job.valid then
|
||||
if v["raw"] and v["jpg"] then
|
||||
local raw_image_id = v["raw"].id
|
||||
local fname = v["jpg"].path .. PS .. v["jpg"].filename
|
||||
df.file_copy(fname, mipmap_dir .. raw_image_id .. ".jpg")
|
||||
if not pmj.keep_jpgs then
|
||||
log.msg(log.debug, MODULE .. ": removing jpg")
|
||||
v["jpg"]:delete()
|
||||
local success, msg = os.remove(v["jpg"].path .. PS .. v["jpg"].filename)
|
||||
if not success then
|
||||
log.msg(log.warn, MODULE .. ": unable to remove jpg - reason: " .. msg)
|
||||
end
|
||||
success, msg = os.remove(v["jpg"].path .. PS .. v["jpg"].filename .. ".xmp")
|
||||
if not success then
|
||||
log.msg(log.warn, MODULE .. ": unable to remove jpg xmp - reason: " .. msg)
|
||||
end
|
||||
end
|
||||
-- v["raw"]:generate_cache(true, 6, 6)
|
||||
processed_count = processed_count + 1
|
||||
end
|
||||
job.percent = processed_count / count
|
||||
end
|
||||
dt.control.sleep(5)
|
||||
end
|
||||
stop_job(job)
|
||||
if not pmj.keep_jpgs then
|
||||
refresh_collection()
|
||||
end
|
||||
dt.util.message(MODULE, "broadcast", "finished")
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
use_paired_jpg_as_mipmap.keep_jpgs = pref_read("keep_jpgs", "bool")
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "post-import-film")
|
||||
dt.destroy_event(MODULE, "post-import-image")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "post-import-film",
|
||||
function(event, film)
|
||||
process_images(pmj.imported_images, pmj.image_count)
|
||||
pmj.imported_images = {}
|
||||
pmj.image_count = 0
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "post-import-image",
|
||||
function(event, image)
|
||||
local basename = df.get_basename(image.filename)
|
||||
local extension = string.lower(df.get_filetype(image.filename))
|
||||
|
||||
if not string.match(extension, "jpg") then
|
||||
extension = "raw"
|
||||
end
|
||||
|
||||
if not pmj.imported_images[basename] then
|
||||
pmj.imported_images[basename] = {}
|
||||
end
|
||||
|
||||
pmj.imported_images[basename][extension] = image
|
||||
|
||||
if not string.match(extension, "jpg") then
|
||||
pmj.image_count = pmj.image_count + 1
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
@@ -37,8 +37,6 @@ du.check_min_api_version("7.0.0", "executable_manager")
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
dt.gettext.bindtextdomain("executable_manager", dt.configuration.config_dir .."/lua/locale/")
|
||||
|
||||
local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
@@ -48,7 +46,7 @@ end
|
||||
local script_data = {}
|
||||
|
||||
script_data.metadata = {
|
||||
name = "executable_manager",
|
||||
name = _("executable manager"),
|
||||
purpose = _("manage the list of external executables used by the lua scripts"),
|
||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/executable_manager"
|
||||
@@ -117,7 +115,7 @@ local function install_module()
|
||||
if not exec_man.module_installed then
|
||||
dt.register_lib(
|
||||
"executable_manager", -- Module name
|
||||
"executable manager", -- Visible name
|
||||
_("executables"), -- Visible name
|
||||
true, -- expandable
|
||||
false, -- resetable
|
||||
{[dt.gui.views.lighttable] = {panel, panel_pos}}, -- containers
|
||||
@@ -201,7 +199,7 @@ exec_man.stack = dt.new_widget("stack"){}
|
||||
-- create a combobox to for indexing into the stack of widgets
|
||||
|
||||
exec_man.selector = dt.new_widget("combobox"){
|
||||
label = "executable",
|
||||
label = _("executable"),
|
||||
tooltip = _("select executable to modify"),
|
||||
value = 1, "placeholder",
|
||||
changed_callback = function(self)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user