Compare commits
323 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d109612045 | ||
|
|
0f47f46b35 | ||
|
|
1a44df7d3c | ||
|
|
aa62473cbf | ||
|
|
f7faad0962 | ||
|
|
53549f16b5 | ||
|
|
7531a29235 | ||
|
|
a4dce23ef8 | ||
|
|
8abc350d68 | ||
|
|
078011d6b4 | ||
|
|
2a16599f91 | ||
|
|
14646cf8e6 | ||
|
|
5823d015ac | ||
|
|
30e6b9459a | ||
|
|
d3e15065d2 | ||
|
|
fe722f8409 | ||
|
|
b91baebdeb | ||
|
|
5ebadd83ea | ||
|
|
01c3b7d2f6 | ||
|
|
bfee6b12dc | ||
|
|
503a59d590 | ||
|
|
78176e32e8 | ||
|
|
4d8d076d8e | ||
|
|
72f31d6208 | ||
|
|
784a6ea85d | ||
|
|
bbfd9d12e3 | ||
|
|
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 | ||
|
|
79ac6fc31e | ||
|
|
7a61d77c27 | ||
|
|
2635e261a0 | ||
|
|
d197851478 | ||
|
|
6f519b3ce8 | ||
|
|
03eb79e83a | ||
|
|
fb03b49a93 | ||
|
|
c0865c37bb | ||
|
|
6bbb619513 | ||
|
|
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
|
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
|
[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://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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR)
|
[enfuse](https://docs.darktable.org/lua/stable/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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/selection_to_pdf)|No|L|Generate a PDF file from the selected images
|
[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
|
### 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
|
Name|Standalone|OS |Purpose
|
||||||
----|:--------:|:---:|-------
|
----|:--------:|:---:|-------
|
||||||
[AutoGrouper](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time
|
[AutoGrouper](https://docs.darktable.org/lua/stable/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
|
[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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection
|
[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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO
|
[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
|
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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it
|
[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://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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition
|
[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://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_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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
|
[fujifilm_ratings](https://docs.darktable.org/lua/stable/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 ...
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions
|
[geoToolbox](https://docs.darktable.org/lua/stable/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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data
|
[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
|
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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama 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://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_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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time
|
[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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool
|
[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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images
|
[photils](https://docs.darktable.org/lua/stable/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
|
[quicktag](https://docs.darktable.org/lua/stable/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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple 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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
|
[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://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_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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged 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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow
|
[slideshowMusic](https://docs.darktable.org/lua/stable/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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable
|
[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
|
### Example Scripts
|
||||||
|
|
||||||
@@ -85,18 +87,18 @@ These scripts provide examples of how to use specific portions of the API. They
|
|||||||
|
|
||||||
Name|Standalone|OS |Purpose
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom
|
[darkroom_demo](https://docs.darktable.org/lua/stable/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
|
[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
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module
|
[moduleExample](https://docs.darktable.org/lua/stable/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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script
|
[preferenceExamples](https://docs.darktable.org/lua/stable/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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system
|
[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
|
x-touch|Yes|LMW|demonstrate how to use an x-touch mini MIDI controller to control the darktable GUI
|
||||||
|
|
||||||
### Tools
|
### Tools
|
||||||
@@ -105,11 +107,11 @@ Tool scripts perform functions relating to the repository, such as generating do
|
|||||||
|
|
||||||
Name|Standalone|OS |Purpose
|
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
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po)
|
[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://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_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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text
|
[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://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/script_manager)|No|LMW|Manage (install, update, enable, disable) the lua scripts
|
[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
|
### 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"`.
|
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
|
### Linux or MacOS
|
||||||
|
|
||||||
@@ -237,18 +239,18 @@ To update the script repository, open a terminal or command prompt and do the fo
|
|||||||
|
|
||||||
## Documentation
|
## 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
|
for the scripts transcribed from the header comments. Each script also contains comments and usage instructions
|
||||||
in the header comments.
|
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
|
documentation of the libraries and functions. Lua-script libraries documentation may also be generated using
|
||||||
the tools in the tools/ directory.
|
the tools in the tools/ directory.
|
||||||
|
|
||||||
More information about the scripting with Lua can be found in the darktable user manual:
|
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.
|
darktable Lua API.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ du.check_min_api_version("7.0.0", "AutoGrouper")
|
|||||||
|
|
||||||
local MOD = 'autogrouper'
|
local MOD = 'autogrouper'
|
||||||
|
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
gettext.bindtextdomain(MOD, dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return data structure for script_manager
|
-- return data structure for script_manager
|
||||||
@@ -55,7 +54,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "AutoGrouper",
|
name = _("auto group"),
|
||||||
purpose = _("automatically group images by time interval"),
|
purpose = _("automatically group images by time interval"),
|
||||||
author = "Kevin Ertel",
|
author = "Kevin Ertel",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/AutoGrouper/"
|
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
|
for i, image in ipairs(images) do
|
||||||
if i == 1 then
|
if i == 1 then
|
||||||
prev_image = image
|
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
|
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
|
local curr_image = image
|
||||||
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
|
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
|
||||||
images[i]:group_with(images[i-1])
|
images[i]:group_with(images[i-1].group_leader)
|
||||||
end
|
end
|
||||||
prev_image = curr_image
|
prev_image = curr_image
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ local all_active = false
|
|||||||
|
|
||||||
du.check_min_api_version("7.0.0", "CollectHelper")
|
du.check_min_api_version("7.0.0", "CollectHelper")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("CollectHelper", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -64,7 +62,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "CollectHelper",
|
name = _("collection helper"),
|
||||||
purpose = _("add collection helper buttons"),
|
purpose = _("add collection helper buttons"),
|
||||||
author = "Kevin Ertel",
|
author = "Kevin Ertel",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/CollectHelper/"
|
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
|
dt.preferences.register("module_CollectHelper", "all_and", -- name
|
||||||
"bool", -- type
|
"bool", -- type
|
||||||
_('CollectHelper: all'), -- label
|
_('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
|
true -- default
|
||||||
)
|
)
|
||||||
dt.preferences.register("module_CollectHelper", "colors", -- name
|
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
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("HDRMerge", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -65,7 +63,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "HDRmerge",
|
name = _("HDR merge"),
|
||||||
purpose = _("merge bracketed images into an HDR DNG image"),
|
purpose = _("merge bracketed images into an HDR DNG image"),
|
||||||
author = "Kevin Ertel",
|
author = "Kevin Ertel",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/HDRmerge"
|
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
|
PreCall({HDRM}) --check if furst run then check if install OK
|
||||||
if HDRM.install_error then
|
if HDRM.install_error then
|
||||||
dt.print_error('HDRMerge install issue')
|
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
|
return
|
||||||
end
|
end
|
||||||
images = dt.gui.selection() --get selected images
|
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')
|
temp = dt.preferences.read(mod, 'active_add_tags', 'string')
|
||||||
if temp == '' then temp = nil end
|
if temp == '' then temp = nil end
|
||||||
GUI.Target.add_tags = dt.new_widget('entry'){
|
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,
|
text = temp,
|
||||||
placeholder = _('enter tags, seperated by commas'),
|
placeholder = _('enter tags, separated by commas'),
|
||||||
editable = true
|
editable = true
|
||||||
}
|
}
|
||||||
GUI.run = dt.new_widget('button'){
|
GUI.run = dt.new_widget('button'){
|
||||||
label = _('merge'),
|
label = _('merge'),
|
||||||
tooltip =_('run HDRMerge with the above specified settings'),
|
tooltip =_('run HDRMerge with the above settings'),
|
||||||
clicked_callback = function() main() end
|
clicked_callback = function() main() end
|
||||||
}
|
}
|
||||||
GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){
|
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
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
darktable.gettext.bindtextdomain("LabelsToTags", darktable.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -65,7 +63,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "LabelsToTags",
|
name = _("labels to tags"),
|
||||||
purpose = _("allows the mass-application of tags using color labels and ratings as a guide"),
|
purpose = _("allows the mass-application of tags using color labels and ratings as a guide"),
|
||||||
author = "August Schwerdfeger (august@schwerdfeger.name)",
|
author = "August Schwerdfeger (august@schwerdfeger.name)",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/LabelsToTags"
|
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",
|
orientation = "vertical",
|
||||||
mappingComboBox,
|
mappingComboBox,
|
||||||
darktable.new_widget("button") {
|
darktable.new_widget("button") {
|
||||||
label = "start",
|
label = _("start"),
|
||||||
tooltip = "Tag all selected images",
|
tooltip = _("tag all selected images"),
|
||||||
clicked_callback = doTagging
|
clicked_callback = doTagging
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,7 +245,7 @@ end
|
|||||||
|
|
||||||
local function install_module()
|
local function install_module()
|
||||||
if not ltt.module_installed then
|
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},
|
[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20},
|
||||||
},ltt.my_widget,nil,nil)
|
},ltt.my_widget,nil,nil)
|
||||||
ltt.module_installed = true
|
ltt.module_installed = true
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ local gettext = dt.gettext.gettext
|
|||||||
--Check API version
|
--Check API version
|
||||||
du.check_min_api_version("7.0.0", "OpenInExplorer")
|
du.check_min_api_version("7.0.0", "OpenInExplorer")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("OpenInExplorer", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -67,7 +65,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "OpenInExplorer",
|
name = _("open in explorer"),
|
||||||
purpose = _("open a selected file in the system file manager"),
|
purpose = _("open a selected file in the system file manager"),
|
||||||
author = "Kevin Ertel",
|
author = "Kevin Ertel",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/OpenInExplorer"
|
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
|
-- translation
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("RL_out_sharp", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -79,7 +77,7 @@ local function _(msgid)
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "RL_out_sharp",
|
name = _("RL output sharpening"),
|
||||||
purpose = _("Richardson-Lucy output sharpening using GMic"),
|
purpose = _("Richardson-Lucy output sharpening using GMic"),
|
||||||
author = "Marco Carrarini <marco.carrarini@gmail.com>",
|
author = "Marco Carrarini <marco.carrarini@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/RL_out_sharp"
|
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
|
if exiftool then
|
||||||
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
|
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
|
||||||
else
|
else
|
||||||
dt.print_log(MODULE .. " exiftool not found, metadata not preserved")
|
dt.print_log(MODULE_NAME .. " exiftool not found, metadata not preserved")
|
||||||
end
|
end
|
||||||
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(MODULE, msgid)
|
return gettext(msgid)
|
||||||
end
|
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.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
script_data.metadata = {
|
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
|
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
|
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
|
-- P R E F E R E N C E S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
dt.preferences.register(MODULE, "always_create_snapshot", "bool", "always automatically create_snapshot",
|
dt.preferences.register(MODULE, "always_create_snapshot", "bool", "auto_snapshot - " .. _("always automatically create_snapshot"),
|
||||||
"auto_snapshot - create a snapshot even if the image is altered", false)
|
_("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
|
-- 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")
|
du.check_min_api_version("7.0.0", "autostyle")
|
||||||
|
|
||||||
local gettext = darktable.gettext.gettext
|
local gettext = darktable.gettext.gettext
|
||||||
darktable.gettext.bindtextdomain("autostyle", darktable.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
@@ -54,8 +53,10 @@ end
|
|||||||
|
|
||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
|
local have_not_printed_config_message = true
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "autostyle",
|
name = _("auto style"),
|
||||||
purpose = _("automatically apply a style based on image EXIF tag"),
|
purpose = _("automatically apply a style based on image EXIF tag"),
|
||||||
author = "Marc Cousin <cousinmarc@gmail.com>",
|
author = "Marc Cousin <cousinmarc@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle/"
|
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 function autostyle_apply_one_image (image)
|
||||||
|
|
||||||
local pref = darktable.preferences.read("autostyle", "exif_tag", "string")
|
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
|
-- 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)
|
-- check they all exist (correct syntax)
|
||||||
if (not tag) then
|
if (not tag) then
|
||||||
darktable.print(string.format(_("EXIF tag not found in %s"), pref))
|
darktable.print(string.format(_("EXIF tag not found in %s"), pref))
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
if (not value) then
|
if (not value) then
|
||||||
darktable.print(string.format(_("value to match not found in %s"), pref))
|
darktable.print(string.format(_("value to match not found in %s"), pref))
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
if (not style_name) then
|
if (not style_name) then
|
||||||
darktable.print(string.format(_("style name not found in %s"), pref))
|
darktable.print(string.format(_("style name not found in %s"), pref))
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
if not filelib.check_if_bin_exists("exiftool") then
|
if not filelib.check_if_bin_exists("exiftool") then
|
||||||
darktable.print(_("can't find exiftool"))
|
darktable.print(_("can't find exiftool"))
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- First find the style (we have its name)
|
-- First find the style (we have its name)
|
||||||
local styles = darktable.styles
|
local styles = darktable.styles
|
||||||
local style
|
local style
|
||||||
for _, s in ipairs(styles) do
|
for _, s in ipairs(styles) do
|
||||||
if s.name == style_name then
|
if s.name == style_name then
|
||||||
style = s
|
style = s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if (not style) then
|
if (not style) then
|
||||||
darktable.print(string.format(_("style not found for autostyle: %s"), style_name))
|
darktable.print(string.format(_("style not found for autostyle: %s"), style_name))
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply the style to image, if it is tagged
|
-- Apply the style to image, if it is tagged
|
||||||
local ok, auto_dr_attr = pcall(exiftool_attribute, image.path .. '/' .. image.filename,tag)
|
local ok, auto_dr_attr = pcall(exiftool_attribute, image.path .. '/' .. image.filename,tag)
|
||||||
--darktable.print_error("dr_attr:" .. auto_dr_attr)
|
--darktable.print_error("dr_attr:" .. auto_dr_attr)
|
||||||
-- If the lookup fails, stop here
|
-- If the lookup fails, stop here
|
||||||
if (not ok) then
|
if (not ok) then
|
||||||
darktable.print(string.format(_("couldn't get attribute %s from exiftool's output"), auto_dr_attr))
|
darktable.print(string.format(_("couldn't get attribute %s from exiftool's output"), auto_dr_attr))
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
if auto_dr_attr == value then
|
if auto_dr_attr == value then
|
||||||
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. pref)
|
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. pref)
|
||||||
darktable.styles.apply(style,image)
|
darktable.styles.apply(style,image)
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. pref .. " not matched: " .. auto_dr_attr)
|
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. pref .. " not matched: " .. auto_dr_attr)
|
||||||
return 0
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -181,7 +188,9 @@ end
|
|||||||
darktable.register_event("autostyle", "shortcut", autostyle_apply,
|
darktable.register_event("autostyle", "shortcut", autostyle_apply,
|
||||||
_("apply your chosen style from exiftool tags"))
|
_("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",
|
darktable.register_event("autostyle", "post-import-image",
|
||||||
autostyle_apply_one_image_event)
|
autostyle_apply_one_image_event)
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ local gettext = dt.gettext.gettext
|
|||||||
|
|
||||||
local MODULE = "change_group_leader"
|
local MODULE = "change_group_leader"
|
||||||
|
|
||||||
dt.gettext.bindtextdomain(MODULE, dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", MODULE)
|
du.check_min_api_version("3.0.0", MODULE)
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
@@ -53,7 +51,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "change_group_leader",
|
name = _("change group leader"),
|
||||||
purpose = _("automatically change the leader of raw+jpg paired image groups"),
|
purpose = _("automatically change the leader of raw+jpg paired image groups"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/change_group_leader"
|
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
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- create a namespace to contain persistent data and widgets
|
-- create a namespace to contain persistent data and widgets
|
||||||
chg_grp_ldr = {}
|
local chg_grp_ldr = {}
|
||||||
|
|
||||||
local cgl = chg_grp_ldr
|
local cgl = chg_grp_ldr
|
||||||
|
|
||||||
@@ -82,7 +80,7 @@ local function install_module()
|
|||||||
if not cgl.module_installed then
|
if not cgl.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
MODULE, -- Module name
|
MODULE, -- Module name
|
||||||
_("change_group_leader"), -- Visible name
|
_("change group leader"), -- Visible name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
false, -- resetable
|
false, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700}}, -- containers
|
{[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 du = require "lib/dtutils"
|
||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
dt.gettext.bindtextdomain("clear_GPS", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
@@ -51,7 +50,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "clear_GPS",
|
name = _("clear GPS info"),
|
||||||
purpose = _("remove GPS data from selected image(s)"),
|
purpose = _("remove GPS data from selected image(s)"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps/"
|
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(
|
dt.register_event(
|
||||||
"clear_GPS", "shortcut",
|
"clear_GPS", "shortcut",
|
||||||
function(event, shortcut) clear_GPS(dt.gui.action_images) end,
|
function(event, shortcut) clear_GPS(dt.gui.action_images) end,
|
||||||
_("clear GPS data")
|
_("clear GPS data from selected images")
|
||||||
)
|
)
|
||||||
|
|
||||||
return script_data
|
return script_data
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ du.check_min_api_version("7.0.0", "color_profile_manager")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("color_profile_manager", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -365,7 +363,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "color_profile_manager",
|
name = _("color profile manager"),
|
||||||
purpose = _("manage external darktable color profiles"),
|
purpose = _("manage external darktable color profiles"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/color_profile_manager"
|
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")
|
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)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -55,7 +53,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "copy_attach_detach_tags",
|
name = _("copy attach detach tags"),
|
||||||
purpose = _("shortcuts to copy, paste, replace, or remove tags from images"),
|
purpose = _("shortcuts to copy, paste, replace, or remove tags from images"),
|
||||||
author = "Christian Kanzian",
|
author = "Christian Kanzian",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags"
|
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
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
darktable.gettext.bindtextdomain("cr2hdr", darktable.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -50,7 +48,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "cr2hdr",
|
name = _("cr2hdr"),
|
||||||
purpose = _("process Magic Lantern dual ISO images"),
|
purpose = _("process Magic Lantern dual ISO images"),
|
||||||
author = "Till Theato <theato@ttill.de>",
|
author = "Till Theato <theato@ttill.de>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("cycle_group_leader", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -73,7 +71,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "cycle_group_leader",
|
name = _("cycle group leader"),
|
||||||
purpose = _("change image group leader"),
|
purpose = _("change image group leader"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cycle_group_leader"
|
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 function cycle_group_leader(image)
|
||||||
local group_images = image:get_group_members()
|
local group_images = image:get_group_members()
|
||||||
if #group_images < 2 then
|
if #group_images < 2 then
|
||||||
hinter_msg(_("no images to cycle to in group"))
|
hinter_msg(_("no images to cycle through in group"))
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
local position = nil
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(MODULE, msgid)
|
return gettext(msgid)
|
||||||
end
|
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.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "dbmaint",
|
name = _("db maintenance"),
|
||||||
purpose = _("perform database maintenance"),
|
purpose = _("perform database maintenance"),
|
||||||
author = "Bill Ferguson <wpferguson@gamil.com>",
|
author = "Bill Ferguson <wpferguson@gamil.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/"
|
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
|
-- 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 function scan_film_rolls()
|
||||||
local missing_films = {}
|
local missing_films = {}
|
||||||
|
|
||||||
@@ -130,19 +142,21 @@ local function scan_film_rolls()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function scan_images(film)
|
local function scan_images(film)
|
||||||
|
local old_log_level = set_log_level(DEFAULT_LOG_LEVEL)
|
||||||
local missing_images = {}
|
local missing_images = {}
|
||||||
|
|
||||||
if film then
|
if film then
|
||||||
for i = 1, #film do
|
for i = 1, #film do
|
||||||
local image = film[i]
|
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
|
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)
|
table.insert(missing_images, image)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
restore_log_level(old_log_level)
|
||||||
return missing_images
|
return missing_images
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -195,21 +209,23 @@ dbmaint.list_widget = dt.new_widget("text_view"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbmaint.chooser = dt.new_widget("combobox"){
|
dbmaint.chooser = dt.new_widget("combobox"){
|
||||||
label = "scan for",
|
label = _("scan for"),
|
||||||
selected = 1,
|
selected = 1,
|
||||||
"film rolls", "images",
|
_("film rolls"), _("images"),
|
||||||
reset_callback = function(this)
|
reset_callback = function(this)
|
||||||
this.selected = 1
|
this.selected = 1
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
dbmaint.scan_button = dt.new_widget("button"){
|
dbmaint.scan_button = dt.new_widget("button"){
|
||||||
label = "scan",
|
label = _("scan"),
|
||||||
tooltip = "click to scan for missing film rolls/files",
|
tooltip = _("click to scan for missing film rolls/files"),
|
||||||
clicked_callback = function(this)
|
clicked_callback = function(this)
|
||||||
local found = nil
|
local found = nil
|
||||||
local found_text = ""
|
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()
|
found = scan_film_rolls()
|
||||||
if #found > 0 then
|
if #found > 0 then
|
||||||
for _, film in ipairs(found) do
|
for _, film in ipairs(found) do
|
||||||
@@ -218,7 +234,7 @@ dbmaint.scan_button = dt.new_widget("button"){
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
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)
|
found = scan_images(dt.collection[1].film)
|
||||||
if #found > 0 then
|
if #found > 0 then
|
||||||
for _, image in ipairs(found) do
|
for _, image in ipairs(found) do
|
||||||
@@ -227,10 +243,14 @@ dbmaint.scan_button = dt.new_widget("button"){
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #found > 0 then
|
if #found > 0 then
|
||||||
|
log.msg(log.debug, "found " .. #found .. " missing items")
|
||||||
dbmaint.list_widget.text = found_text
|
dbmaint.list_widget.text = found_text
|
||||||
dbmaint.found = found
|
dbmaint.found = found
|
||||||
dbmaint.remove_button.sensitive = true
|
dbmaint.remove_button.sensitive = true
|
||||||
|
else
|
||||||
|
log.msg(log.debug, "no missing items found")
|
||||||
end
|
end
|
||||||
|
restore_log_level(old_log_level)
|
||||||
end,
|
end,
|
||||||
reset_callback = function(this)
|
reset_callback = function(this)
|
||||||
dbmaint.found = nil
|
dbmaint.found = nil
|
||||||
@@ -238,11 +258,11 @@ dbmaint.scan_button = dt.new_widget("button"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbmaint.remove_button = dt.new_widget("button"){
|
dbmaint.remove_button = dt.new_widget("button"){
|
||||||
label = "remove",
|
label = _("remove"),
|
||||||
tooltip = "remove missing film rolls/images",
|
tooltip = _("remove missing film rolls/images"),
|
||||||
sensitive = false,
|
sensitive = false,
|
||||||
clicked_callback = function(this)
|
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)
|
remove_missing_film_rolls(dbmaint.found)
|
||||||
else
|
else
|
||||||
remove_missing_images(dbmaint.found)
|
remove_missing_images(dbmaint.found)
|
||||||
@@ -258,7 +278,7 @@ dbmaint.remove_button = dt.new_widget("button"){
|
|||||||
|
|
||||||
dbmaint.main_widget = dt.new_widget("box"){
|
dbmaint.main_widget = dt.new_widget("box"){
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
dt.new_widget("section_label"){label = "missing items"},
|
dt.new_widget("section_label"){label = _("missing items")},
|
||||||
dbmaint.list_widget,
|
dbmaint.list_widget,
|
||||||
dt.new_widget("label"){label = ""},
|
dt.new_widget("label"){label = ""},
|
||||||
dbmaint.chooser,
|
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
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("enfuseAdvanced", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -81,7 +79,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "enfuseAdvanced",
|
name = _("enfuse advanced"),
|
||||||
purpose = _("focus stack or exposure blend images"),
|
purpose = _("focus stack or exposure blend images"),
|
||||||
author = "Kevin Ertel",
|
author = "Kevin Ertel",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseAdvanced"
|
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)
|
local temp = dt.preferences.read(mod, prefix..argument, arg_data.style)
|
||||||
if arg_data.style == 'bool' and temp then
|
if arg_data.style == 'bool' and temp then
|
||||||
prog_table.arg_string = prog_table.arg_string..arg_data.text..' '
|
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..' '
|
prog_table.arg_string = prog_table.arg_string..arg_data.text..temp..' '
|
||||||
elseif arg_data.style == 'float' then
|
elseif arg_data.style == 'float' then
|
||||||
temp = string.sub(tostring(temp),1,3)
|
temp = string.sub(tostring(temp),1,3)
|
||||||
@@ -347,7 +351,7 @@ local function UpdateENFargs(image_table, prefix) --updates the Enfuse arguments
|
|||||||
end
|
end
|
||||||
ENF.arg_string = ENF.arg_string..' --depth='..GUI.Target.depth.value..' '
|
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..' '
|
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
|
end
|
||||||
if not GUI.Target.source_location.value then out_path = GUI.Target.output_directory.value end
|
if not GUI.Target.source_location.value then out_path = GUI.Target.output_directory.value end
|
||||||
out_name = smallest_name..'-'..largest_id
|
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'))
|
dt.print(_('too few images selected, please select at least 2 images'))
|
||||||
return
|
return
|
||||||
elseif extra_data[1] == 2 then
|
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
|
return
|
||||||
end
|
end
|
||||||
local images_to_remove = ''
|
local images_to_remove = ''
|
||||||
@@ -494,6 +498,7 @@ local function main(storage, image_table, extra_data)
|
|||||||
remove_temp_files(images_to_remove)
|
remove_temp_files(images_to_remove)
|
||||||
dt.print_error(ENF.name..' failed')
|
dt.print_error(ENF.name..' failed')
|
||||||
dt.print(string.format(_("%s failed"), ENF.name))
|
dt.print(string.format(_("%s failed"), ENF.name))
|
||||||
|
job.valid = false
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -956,7 +961,7 @@ if temp == '' then temp = nil end
|
|||||||
GUI.Target.add_tags = dt.new_widget('entry'){
|
GUI.Target.add_tags = dt.new_widget('entry'){
|
||||||
tooltip = _('additional tags to be added on import, seperate with commas, all spaces will be removed'),
|
tooltip = _('additional tags to be added on import, seperate with commas, all spaces will be removed'),
|
||||||
text = temp,
|
text = temp,
|
||||||
placeholder = _('enter tags, seperated by commas'),
|
placeholder = _('enter tags, separated by commas'),
|
||||||
editable = true
|
editable = true
|
||||||
}
|
}
|
||||||
temp = dt.preferences.read(mod, 'active_current_preset_ind', 'integer')
|
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
|
GUI.Presets.variants_type.sensitive = GUI.Presets.variants.value
|
||||||
temp = df.get_executable_path_preference(AIS.name)
|
temp = df.get_executable_path_preference(AIS.name)
|
||||||
GUI.exes.align_image_stack = dt.new_widget('file_chooser_button'){
|
GUI.exes.align_image_stack = dt.new_widget('file_chooser_button'){
|
||||||
title = 'AIS binary path',
|
title = 'align_image_stack ' .. _('binary path'),
|
||||||
value = temp,
|
value = temp,
|
||||||
tooltip = temp,
|
tooltip = temp,
|
||||||
is_directory = false,
|
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)
|
temp = df.get_executable_path_preference(ENF.name)
|
||||||
GUI.exes.enfuse = dt.new_widget('file_chooser_button'){
|
GUI.exes.enfuse = dt.new_widget('file_chooser_button'){
|
||||||
title = 'enfuse binary path',
|
title = 'enfuse ' .. _('binary path'),
|
||||||
value = temp,
|
value = temp,
|
||||||
tooltip = temp,
|
tooltip = temp,
|
||||||
is_directory = false,
|
is_directory = false,
|
||||||
@@ -1039,7 +1044,7 @@ GUI.exes.enfuse = dt.new_widget('file_chooser_button'){
|
|||||||
}
|
}
|
||||||
temp = df.get_executable_path_preference(EXF.name)
|
temp = df.get_executable_path_preference(EXF.name)
|
||||||
GUI.exes.exiftool = dt.new_widget('file_chooser_button'){
|
GUI.exes.exiftool = dt.new_widget('file_chooser_button'){
|
||||||
title = 'Exiftool binary path',
|
title = 'exiftool ' .. _('binary path'),
|
||||||
value = temp,
|
value = temp,
|
||||||
tooltip = temp,
|
tooltip = temp,
|
||||||
is_directory = false,
|
is_directory = false,
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ du.check_min_api_version("7.0.0", "exportLUT")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("exportLUT", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -46,7 +44,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "exportLUT",
|
name = _("export LUT"),
|
||||||
purpose = _("export a style as a LUT"),
|
purpose = _("export a style as a LUT"),
|
||||||
author = "Noah Clarke",
|
author = "Noah Clarke",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportLUT"
|
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
|
if dt.configuration.running_os == 'windows' then mkdir_command = 'mkdir ' end
|
||||||
|
|
||||||
local file_chooser_button = dt.new_widget("file_chooser_button"){
|
local file_chooser_button = dt.new_widget("file_chooser_button"){
|
||||||
title = _("identity_file_chooser"),
|
title = _("choose the identity file"),
|
||||||
value = "",
|
value = "",
|
||||||
is_directory = false
|
is_directory = false
|
||||||
}
|
}
|
||||||
|
|
||||||
local export_chooser_button = dt.new_widget("file_chooser_button"){
|
local export_chooser_button = dt.new_widget("file_chooser_button"){
|
||||||
title = _("export_location_chooser"),
|
title = _("choose the export location"),
|
||||||
value = "",
|
value = "",
|
||||||
is_directory = true
|
is_directory = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ du.check_min_api_version("7.0.0", MODULE_NAME)
|
|||||||
-- translation
|
-- translation
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("ext_editor", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -89,7 +87,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "ext_editor",
|
name = _("external editors"),
|
||||||
purpose = _("edit images with external editors"),
|
purpose = _("edit images with external editors"),
|
||||||
author = "Marco Carrarini, marco.carrarini@gmail.com",
|
author = "Marco Carrarini, marco.carrarini@gmail.com",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor"
|
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)
|
du.check_min_api_version("7.0.0", MODULE)
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("face_recognition", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -65,7 +63,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "face_recognition",
|
name = _("face recognition"),
|
||||||
purpose = _("use facial recognition to tag images"),
|
purpose = _("use facial recognition to tag images"),
|
||||||
author = "Sebastian Witt",
|
author = "Sebastian Witt",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition"
|
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")
|
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)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -76,7 +74,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "fujifilm_dynamic_range",
|
name = _("fujifilm dynamic range"),
|
||||||
purpose = _("compensate for Fujifilm raw files made using \"dynamic range\""),
|
purpose = _("compensate for Fujifilm raw files made using \"dynamic range\""),
|
||||||
author = "Dan Torop <dant@pnym.net>",
|
author = "Dan Torop <dant@pnym.net>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range"
|
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")
|
du.check_min_api_version("7.0.0", "fujifilm_ratings")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -42,7 +40,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "fujifilm_ratings",
|
name = _("fujifilm ratings"),
|
||||||
purpose = _("import Fujifilm in-camera ratings"),
|
purpose = _("import Fujifilm in-camera ratings"),
|
||||||
author = "Ben Mendis <ben.mendis@gmail.com>",
|
author = "Ben Mendis <ben.mendis@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings"
|
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
|
return
|
||||||
end
|
end
|
||||||
if not df.check_if_bin_exists("exiftool") then
|
if not df.check_if_bin_exists("exiftool") then
|
||||||
dt.print_error(_("exiftool not found"))
|
dt.print_error("exiftool not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local RAF_filename = df.sanitize_filename(tostring(image))
|
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")
|
du.check_min_api_version("7.0.0", "geoJSON_export")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("geoJSON_export", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -51,7 +49,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "geoJSON_export",
|
name = _("geoJSON export"),
|
||||||
purpose = _("export a geoJSON file from geo data"),
|
purpose = _("export a geoJSON file from geo data"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoJSON_export"
|
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)
|
local function create_geoJSON_file(storage, image_table, extra_data)
|
||||||
if not df.check_if_bin_exists("mkdir") then
|
if not df.check_if_bin_exists("mkdir") then
|
||||||
dt.print_error(_("mkdir not found"))
|
dt.print_error("mkdir not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if not df.check_if_bin_exists("convert") then
|
if not df.check_if_bin_exists("convert") then
|
||||||
dt.print_error(_("convert not found"))
|
dt.print_error("convert not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if not df.check_if_bin_exists("xdg-open") then
|
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
|
return
|
||||||
end
|
end
|
||||||
if not df.check_if_bin_exists("xdg-user-dir") 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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -295,7 +293,7 @@ local function create_geoJSON_file(storage, image_table, extra_data)
|
|||||||
file:write(geoJSON_file)
|
file:write(geoJSON_file)
|
||||||
file:close()
|
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
|
-- Open the file with the standard programm
|
||||||
if ( dt.preferences.read("geoJSON_export","OpengeoJSONFile","bool") == true ) then
|
if ( dt.preferences.read("geoJSON_export","OpengeoJSONFile","bool") == true ) then
|
||||||
@@ -315,19 +313,19 @@ end
|
|||||||
dt.preferences.register("geoJSON_export",
|
dt.preferences.register("geoJSON_export",
|
||||||
"CreateMapBoxHTMLFile",
|
"CreateMapBoxHTMLFile",
|
||||||
"bool",
|
"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"),
|
_("creates an HTML file that loads the geoJSON file. (needs a MapBox key"),
|
||||||
false )
|
false )
|
||||||
dt.preferences.register("geoJSON_export",
|
dt.preferences.register("geoJSON_export",
|
||||||
"mapBoxKey",
|
"mapBoxKey",
|
||||||
"string",
|
"string",
|
||||||
_("geoJSON export: MapBox key"),
|
"geoJSON export: MapBox " .. _("key"),
|
||||||
"https://www.mapbox.com/studio/account/tokens",
|
"https://www.mapbox.com/studio/account/tokens",
|
||||||
'' )
|
'' )
|
||||||
dt.preferences.register("geoJSON_export",
|
dt.preferences.register("geoJSON_export",
|
||||||
"OpengeoJSONFile",
|
"OpengeoJSONFile",
|
||||||
"bool",
|
"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"),
|
_("opens the geoJSON file after the export with the standard program for geoJSON files"),
|
||||||
false )
|
false )
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
--[[
|
--[[
|
||||||
This file is part of darktable,
|
This file is part of darktable,
|
||||||
copyright (c) 2016 Tobias Jakobs
|
copyright (c) 2016 Tobias Jakobs
|
||||||
|
copyright (c) 2025 Balázs Dura-Kovács
|
||||||
|
|
||||||
darktable is free software: you can redistribute it and/or modify
|
darktable is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
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")
|
du.check_min_api_version("7.0.0", "geoToolbox")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("geoToolbox", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -44,7 +43,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "geoToolbox",
|
name = _("geo toolbox"),
|
||||||
purpose = _("geodata tools"),
|
purpose = _("geodata tools"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoToolbox"
|
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")
|
local labelDistance = dt.new_widget("label")
|
||||||
labelDistance.label = _("distance:")
|
labelDistance.label = _("distance:")
|
||||||
|
|
||||||
local label_copy_gps_lat = dt.new_widget("check_button")
|
local checkbox_copy_gps_lat = dt.new_widget("check_button")
|
||||||
{
|
{
|
||||||
label = _("latitude:"),
|
|
||||||
value = true
|
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
|
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
|
value = true
|
||||||
}
|
}
|
||||||
|
local entry_gps_ele = dt.new_widget("entry")
|
||||||
|
{
|
||||||
|
tooltip = _("elevation (editable)"),
|
||||||
|
placeholder = _("elevation")
|
||||||
|
}
|
||||||
-- </GUI>
|
-- </GUI>
|
||||||
|
|
||||||
local function select_with_gps()
|
local function select_with_gps()
|
||||||
@@ -169,7 +180,7 @@ local function get_first_coordinate()
|
|||||||
first_elevation = ''
|
first_elevation = ''
|
||||||
first_image_date = 0
|
first_image_date = 0
|
||||||
|
|
||||||
for _,image in ipairs(sel_images) do
|
for jj,image in ipairs(sel_images) do
|
||||||
if not image then
|
if not image then
|
||||||
first_have_data = false
|
first_have_data = false
|
||||||
else
|
else
|
||||||
@@ -208,7 +219,7 @@ local function get_second_coordinate()
|
|||||||
second_elevation = ''
|
second_elevation = ''
|
||||||
second_image_date = 0
|
second_image_date = 0
|
||||||
|
|
||||||
for _,image in ipairs(sel_images) do
|
for jj,image in ipairs(sel_images) do
|
||||||
if not image then
|
if not image then
|
||||||
second_have_data = false
|
second_have_data = false
|
||||||
else
|
else
|
||||||
@@ -244,7 +255,7 @@ local calc_in_between_slider = dt.new_widget("slider")
|
|||||||
--ToDo: this needs more love
|
--ToDo: this needs more love
|
||||||
local function calc_in_between()
|
local function calc_in_between()
|
||||||
local sel_images = dt.gui.action_images
|
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
|
if image then
|
||||||
image_date = make_time_stamp(image.exif_datetime_taken)
|
image_date = make_time_stamp(image.exif_datetime_taken)
|
||||||
if (first_have_data and second_have_data) then
|
if (first_have_data and second_have_data) then
|
||||||
@@ -291,25 +302,25 @@ local function copy_gps()
|
|||||||
copy_gps_longitude = ''
|
copy_gps_longitude = ''
|
||||||
copy_gps_elevation = ''
|
copy_gps_elevation = ''
|
||||||
|
|
||||||
for _,image in ipairs(sel_images) do
|
for jj,image in ipairs(sel_images) do
|
||||||
if not image then
|
if not image then
|
||||||
copy_gps_have_data = false
|
copy_gps_have_data = false
|
||||||
else
|
else
|
||||||
copy_gps_have_data = true
|
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
|
copy_gps_latitude = image.latitude
|
||||||
end
|
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
|
copy_gps_longitude = image.longitude
|
||||||
end
|
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
|
copy_gps_elevation = image.elevation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
label_copy_gps_lat.label = string.format(_("latitude: "), copy_gps_latitude)
|
entry_gps_lat.text = copy_gps_latitude
|
||||||
label_copy_gps_lon.label = string.format(_("longitude: "),copy_gps_longitude)
|
entry_gps_lon.text = copy_gps_longitude
|
||||||
label_copy_gps_ele.label = string.format(_("elevation: "), copy_gps_elevation)
|
entry_gps_ele.text = copy_gps_elevation
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -318,15 +329,15 @@ end
|
|||||||
local function paste_gps(image)
|
local function paste_gps(image)
|
||||||
local sel_images = dt.gui.action_images
|
local sel_images = dt.gui.action_images
|
||||||
|
|
||||||
for _,image in ipairs(sel_images) do
|
for jj,image in ipairs(sel_images) do
|
||||||
if (label_copy_gps_lat.value) then
|
if (checkbox_copy_gps_lat.value) then
|
||||||
image.latitude = copy_gps_latitude
|
image.latitude = entry_gps_lat.text
|
||||||
end
|
end
|
||||||
if (label_copy_gps_lon.value) then
|
if (checkbox_copy_gps_lon.value) then
|
||||||
image.longitude = copy_gps_longitude
|
image.longitude = entry_gps_lon.text
|
||||||
end
|
end
|
||||||
if (label_copy_gps_ele.value) then
|
if (checkbox_copy_gps_ele.value) then
|
||||||
image.elevation = copy_gps_elevation
|
image.elevation = entry_gps_ele.text
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -345,7 +356,7 @@ local function open_location_in_gnome_maps()
|
|||||||
local i = 0;
|
local i = 0;
|
||||||
|
|
||||||
-- Use the first image with geo information
|
-- 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
|
if ((image.longitude and image.latitude) and
|
||||||
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
||||||
) then
|
) then
|
||||||
@@ -372,12 +383,12 @@ end
|
|||||||
local function reverse_geocode()
|
local function reverse_geocode()
|
||||||
|
|
||||||
if not df.check_if_bin_exists("curl") then
|
if not df.check_if_bin_exists("curl") then
|
||||||
dt.print_error(_("curl not found"))
|
dt.print_error("curl not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not df.check_if_bin_exists("jq") then
|
if not df.check_if_bin_exists("jq") then
|
||||||
dt.print_error(_("jq not found"))
|
dt.print_error("jq not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -388,7 +399,7 @@ local function reverse_geocode()
|
|||||||
local i = 0;
|
local i = 0;
|
||||||
|
|
||||||
-- Use the first image with geo information
|
-- 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
|
if ((image.longitude and image.latitude) and
|
||||||
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
||||||
) then
|
) then
|
||||||
@@ -463,7 +474,7 @@ local function calc_distance()
|
|||||||
|
|
||||||
local sel_images = dt.gui.selection()
|
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
|
if ((image.longitude and image.latitude) and
|
||||||
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
||||||
) then
|
) then
|
||||||
@@ -547,7 +558,7 @@ local function altitude_profile()
|
|||||||
local elevationAdd = 0;
|
local elevationAdd = 0;
|
||||||
|
|
||||||
local sel_images = dt.gui.action_images
|
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
|
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
|
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
|
||||||
) then
|
) then
|
||||||
@@ -597,7 +608,7 @@ local function install_module()
|
|||||||
if not gT.module_installed then
|
if not gT.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"geoToolbox", -- Module name
|
"geoToolbox", -- Module name
|
||||||
"geo toolbox", -- name
|
_("geo toolbox"), -- name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
false, -- resetable
|
false, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
{[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"),
|
tooltip = _("copy GPS data"),
|
||||||
clicked_callback = copy_gps
|
clicked_callback = copy_gps
|
||||||
},
|
},
|
||||||
label_copy_gps_lat,
|
dt.new_widget("box"){
|
||||||
label_copy_gps_lon,
|
orientation = "horizontal",
|
||||||
label_copy_gps_ele,
|
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")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("paste GPS data"),
|
label = _("apply GPS data to image"),
|
||||||
tooltip = _("paste GPS data"),
|
tooltip = _("apply GPS data to image"),
|
||||||
clicked_callback = paste_gps
|
clicked_callback = paste_gps
|
||||||
},
|
},
|
||||||
separator2,--------------------------------------------------------
|
separator2,--------------------------------------------------------
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ local gimp_widget = nil
|
|||||||
|
|
||||||
du.check_min_api_version("7.0.0", "gimp")
|
du.check_min_api_version("7.0.0", "gimp")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("gimp", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -85,7 +83,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "gimp",
|
name = _("edit with GIMP"),
|
||||||
purpose = _("export and edit with GIMP"),
|
purpose = _("export and edit with GIMP"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gimp"
|
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 dt = require "darktable"
|
||||||
local df = require "lib/dtutils.file"
|
local df = require "lib/dtutils.file"
|
||||||
local dl = require "lib/dtutils"
|
local dl = require "lib/dtutils"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dl.check_min_api_version("7.0.0", "gpx_export")
|
dl.check_min_api_version("7.0.0", "gpx_export")
|
||||||
|
|
||||||
gettext.bindtextdomain("gpx_export", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("gpx_export", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return data structure for script_manager
|
-- return data structure for script_manager
|
||||||
@@ -40,7 +38,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "gpx_export",
|
name = _("gpx export"),
|
||||||
purpose = _("export gpx information to a file"),
|
purpose = _("export gpx information to a file"),
|
||||||
author = "Jannis_V",
|
author = "Jannis_V",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export"
|
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
|
if not gpx.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"gpx_exporter",
|
"gpx_exporter",
|
||||||
"gpx export",
|
_("gpx export"),
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
true, -- resetable
|
true, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
{[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")
|
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)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -45,7 +43,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "harmonic_armature_guide",
|
name = _("harmonic armature guide"),
|
||||||
purpose = _("harmonic artmature guide"),
|
purpose = _("harmonic artmature guide"),
|
||||||
author = "Hubert Kowalski",
|
author = "Hubert Kowalski",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/harmonic_armature_guide"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("hif_group_leader", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -77,7 +75,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "hif_group_leader",
|
name = _("HIF group leader"),
|
||||||
purpose = _("make hif image group leader"),
|
purpose = _("make hif image group leader"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hif_group_leader"
|
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
|
-- works with darktable API version from 5.0.0 on
|
||||||
du.check_min_api_version("7.0.0", "hugin")
|
du.check_min_api_version("7.0.0", "hugin")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("hugin", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -67,7 +65,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "hugin",
|
name = _("hugin"),
|
||||||
purpose = _("stitch images into a panorama"),
|
purpose = _("stitch images into a panorama"),
|
||||||
author = "Wolfgang Goetz",
|
author = "Wolfgang Goetz",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin"
|
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,
|
local function show_status(storage, image, format, filename,
|
||||||
number, total, high_quality, extra_data)
|
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
|
end
|
||||||
|
|
||||||
local function create_panorama(storage, image_table, extra_data) --finalize
|
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
|
end
|
||||||
|
|
||||||
if first_file == nil then
|
if first_file == nil then
|
||||||
dt.print("no file selected")
|
dt.print(_("no file selected"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
|||||||
-- works with LUA API version 5.0.0
|
-- works with LUA API version 5.0.0
|
||||||
du.check_min_api_version("7.0.0", "image_stack")
|
du.check_min_api_version("7.0.0", "image_stack")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("image_stack", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -84,7 +82,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "image_stack",
|
name = _("image stack"),
|
||||||
purpose = _("process a stack of images"),
|
purpose = _("process a stack of images"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack"
|
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"){
|
local chkbtn_radial_distortion = dt.new_widget("check_button"){
|
||||||
label = _('optimize radial distortion for all images'),
|
label = _('optimize radial distortion for all images'),
|
||||||
value = dt.preferences.read("align_image_stack", "def_radial_distortion", "bool"),
|
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"){
|
local chkbtn_optimize_field = dt.new_widget("check_button"){
|
||||||
label = _('optimize field of view for all images'),
|
label = _('optimize field of view for all images'),
|
||||||
value = dt.preferences.read("align_image_stack", "def_optimize_field", "bool"),
|
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"){
|
local chkbtn_optimize_image_center = dt.new_widget("check_button"){
|
||||||
label = _('optimize image center shift for all images'),
|
label = _('optimize image center shift for all images'),
|
||||||
value = dt.preferences.read("align_image_stack", "def_optimize_image_center", "bool"),
|
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"){
|
local chkbtn_auto_crop = dt.new_widget("check_button"){
|
||||||
@@ -506,7 +504,7 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
return
|
return
|
||||||
end
|
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
|
job.percent = job.percent + percent_step
|
||||||
|
|
||||||
-- align images if requested
|
-- align images if requested
|
||||||
|
|||||||
@@ -116,8 +116,6 @@ img_time.event_registered = false
|
|||||||
|
|
||||||
du.check_min_api_version("7.0.0", "image_time")
|
du.check_min_api_version("7.0.0", "image_time")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("image_time", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -127,7 +125,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
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"),
|
purpose = _("synchronize image time for images shot with different cameras or adjust or set image time"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time"
|
||||||
@@ -140,8 +138,9 @@ script_data.show = nil -- only required for libs since the destroy_method only h
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
local PS = dt.configuration.runnin_os == "windows" and "\\" or "/"
|
local PS <const> = dt.configuration.runnin_os == "windows" and "\\" or "/"
|
||||||
local ERROR = -1
|
local ERROR <const> = -1
|
||||||
|
local HAS_INTERNAL_METADATA = dt.metadata.exists("Exif.Image.DataTime")
|
||||||
|
|
||||||
-- function to convert from exif time to system time
|
-- function to convert from exif time to system time
|
||||||
local function exiftime2systime(exiftime)
|
local function exiftime2systime(exiftime)
|
||||||
@@ -156,12 +155,12 @@ local function systime2exiftime(systime)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function vars2exiftime(year, month, day, hour, min, sec)
|
local function vars2exiftime(year, month, day, hour, min, sec)
|
||||||
local y = tonumber(year) and string.format("%4d", year) or " "
|
local y = tonumber(year) and string.format("%4d", year) or "0000"
|
||||||
local mo = tonumber(month) and string.format("%02d", month) or " "
|
local mo = tonumber(month) and string.format("%02d", month) or "00"
|
||||||
local d = tonumber(day) and string.format("%02d", day) or " "
|
local d = tonumber(day) and string.format("%02d", day) or "00"
|
||||||
local h = tonumber(hour) and string.format("%02d", hour) or " "
|
local h = tonumber(hour) and string.format("%02d", hour) or "00"
|
||||||
local m = tonumber(min) and string.format("%02d", min) or " "
|
local m = tonumber(min) and string.format("%02d", min) or "00"
|
||||||
local s = tonumber(sec) and string.format("%02d", sec) or " "
|
local s = tonumber(sec) and string.format("%02d", sec) or "00"
|
||||||
return(y .. ":" .. mo .. ":" .. d .. " " .. h .. ":" .. m .. ":" .. s)
|
return(y .. ":" .. mo .. ":" .. d .. " " .. h .. ":" .. m .. ":" .. s)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -195,7 +194,7 @@ end
|
|||||||
|
|
||||||
local function synchronize_time(images)
|
local function synchronize_time(images)
|
||||||
local sign = 1
|
local sign = 1
|
||||||
if img_time.sdir.value == "subtract" then
|
if img_time.sdir.value == _("subtract") then
|
||||||
sign = -1
|
sign = -1
|
||||||
end
|
end
|
||||||
synchronize_times(images, tonumber(img_time.diff_entry.text) * sign)
|
synchronize_times(images, tonumber(img_time.diff_entry.text) * sign)
|
||||||
@@ -213,7 +212,6 @@ local function year_month2months(year, month)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function months2year_month(months)
|
local function months2year_month(months)
|
||||||
dt.print_log("months is " .. months)
|
|
||||||
local year = math.floor(months / 12)
|
local year = math.floor(months / 12)
|
||||||
local month = months - (year * 12)
|
local month = months - (year * 12)
|
||||||
|
|
||||||
@@ -226,7 +224,7 @@ local function get_image_taken_time(image)
|
|||||||
|
|
||||||
local exiv2 = df.check_if_bin_exists("exiv2")
|
local exiv2 = df.check_if_bin_exists("exiv2")
|
||||||
if exiv2 then
|
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
|
if p then
|
||||||
for line in p:lines() do
|
for line in p:lines() do
|
||||||
if string.match(line, "Exif.Image.DateTime") then
|
if string.match(line, "Exif.Image.DateTime") then
|
||||||
@@ -244,28 +242,37 @@ end
|
|||||||
|
|
||||||
local function _get_windows_image_file_creation_time(image)
|
local function _get_windows_image_file_creation_time(image)
|
||||||
local datetime = nil
|
local datetime = nil
|
||||||
local p = io.popen("dir " .. image.path .. PS .. image.filename)
|
local datestring = nil
|
||||||
if p then
|
|
||||||
for line in p:lines() do
|
if HAS_INTERNAL_METADATA then
|
||||||
if string.match(line, ds.sanitize_lua(image.filename)) then
|
datestring = image.DateString
|
||||||
local mo, day, yr, hr, min, apm = string.match(line, "(%d+)/(%d+)/(%d+) (%d-):(%d+) (%S+)")
|
|
||||||
if apm == "PM" then
|
|
||||||
hr = hr + 12
|
|
||||||
end
|
|
||||||
datetime = vars2exiftime(yr, mo, day, hr, min, 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
p:close()
|
|
||||||
else
|
else
|
||||||
dt.print(string.format(_("unable to get information for %s"), image.filename))
|
local p = io.popen("dir " .. image.path .. PS .. image.filename)
|
||||||
datetime = ERROR
|
if p then
|
||||||
|
for line in p:lines() do
|
||||||
|
if string.match(line, ds.sanitize_lua(image.filename)) then
|
||||||
|
datestring = line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
p:close()
|
||||||
|
else
|
||||||
|
dt.print(string.format(_("unable to get information for %s"), image.filename))
|
||||||
|
datetime = ERROR
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if datestring then
|
||||||
|
local mo, day, yr, hr, min, apm = string.match(datestring, "(%d+)/(%d+)/(%d+) (%d-):(%d+) (%S+)")
|
||||||
|
if apm == "PM" then
|
||||||
|
hr = hr + 12
|
||||||
|
end
|
||||||
|
datetime = vars2exiftime(yr, mo, day, hr, min, 0)
|
||||||
end
|
end
|
||||||
return datetime
|
return datetime
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _get_nix_image_file_creation_time(image)
|
local function _get_nix_image_file_creation_time(image)
|
||||||
local datetime = nil
|
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
|
if p then
|
||||||
for line in p:lines() do
|
for line in p:lines() do
|
||||||
if string.match(line, ds.sanitize_lua(image.filename)) then
|
if string.match(line, ds.sanitize_lua(image.filename)) then
|
||||||
@@ -511,7 +518,7 @@ img_time.stack = dt.new_widget("stack"){
|
|||||||
dt.new_widget("box"){
|
dt.new_widget("box"){
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
dt.new_widget("label"){label = _("set time")},
|
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.sdy,
|
||||||
img_time.smo,
|
img_time.smo,
|
||||||
img_time.syr,
|
img_time.syr,
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ du.check_min_api_version("7.0.0", MODULE)
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("jpg_group_leader", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -77,7 +75,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "jpg_group_leader",
|
name = _("JPG group leader"),
|
||||||
purpose = _("make jpg image group leader"),
|
purpose = _("make jpg image group leader"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/jpg_group_leader"
|
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")
|
du.check_min_api_version("7.0.0", "kml_export")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("kml_export", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -56,7 +54,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "kml_export",
|
name = _("kml export"),
|
||||||
purpose = _("export KML/KMZ data to a file"),
|
purpose = _("export KML/KMZ data to a file"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export"
|
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
|
end
|
||||||
|
|
||||||
if not df.check_if_bin_exists(magickPath) then
|
if not df.check_if_bin_exists(magickPath) then
|
||||||
dt.print_error(_("magick not found"))
|
dt.print_error("magick not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if dt.configuration.running_os == "linux" then
|
if dt.configuration.running_os == "linux" then
|
||||||
if not df.check_if_bin_exists("xdg-user-dir") 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
|
return
|
||||||
end
|
end
|
||||||
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
|
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true
|
||||||
and dt.configuration.running_os == "linux") then
|
and dt.configuration.running_os == "linux") then
|
||||||
if not df.check_if_bin_exists("zip") then
|
if not df.check_if_bin_exists("zip") then
|
||||||
dt.print_error(_("zip not found"))
|
dt.print_error("zip not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
exportDirectory = dt.configuration.tmp_dir
|
exportDirectory = dt.configuration.tmp_dir
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ local gettext = dt.gettext.gettext
|
|||||||
|
|
||||||
du.check_min_api_version("2.0.0", "passport_guide")
|
du.check_min_api_version("2.0.0", "passport_guide")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("passport_guide", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -50,7 +48,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "passport_guide",
|
name = _("passport guide"),
|
||||||
purpose = _("guides for cropping passport photos"),
|
purpose = _("guides for cropping passport photos"),
|
||||||
author = "Kåre Hampf",
|
author = "Kåre Hampf",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide"
|
||||||
|
|||||||
@@ -40,21 +40,19 @@ USAGE
|
|||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
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")
|
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
|
-- 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)
|
local function _(msgid)
|
||||||
return gettext.dgettext("passport_guide_germany", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
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"),
|
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",
|
author = "menschmachine",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide_germany"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("pdf_slideshow", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -62,7 +60,7 @@ du.check_min_api_version("7.0.0", "pdf_slideshow")
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "pdf_slideshow",
|
name = _("PDF slideshow"),
|
||||||
purpose = _("generates a PDF slideshow (via Latex) containing all selected images one per slide"),
|
purpose = _("generates a PDF slideshow (via Latex) containing all selected images one per slide"),
|
||||||
author = "Pascal Obry",
|
author = "Pascal Obry",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow"
|
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)
|
du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
dt.gettext.bindtextdomain("photils", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
@@ -56,7 +55,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "photils",
|
name = _("photils"),
|
||||||
purpose = _("suggest tags based on image classification"),
|
purpose = _("suggest tags based on image classification"),
|
||||||
author = "Tobias Scheck",
|
author = "Tobias Scheck",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils"
|
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 out_arg = df.sanitize_filename(tmp_file)
|
||||||
local executable = photils_installed
|
local executable = photils_installed
|
||||||
|
|
||||||
if dt.configuration.running_os == "macos" then
|
|
||||||
executable = executable .. "/Contents/MacOS/photils-cli"
|
|
||||||
end
|
|
||||||
|
|
||||||
if with_export then
|
if with_export then
|
||||||
dt.print_log("use export to for prediction")
|
dt.print_log("use export to for prediction")
|
||||||
local export_file = df.create_tmp_file()
|
local export_file = df.create_tmp_file()
|
||||||
@@ -339,7 +334,7 @@ function PHOTILS.on_tags_clicked()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if #PHOTILS.tags == 0 then
|
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.warning_label.label = msg
|
||||||
GUI.stack.active = GUI.error_view
|
GUI.stack.active = GUI.error_view
|
||||||
return
|
return
|
||||||
@@ -394,7 +389,7 @@ end
|
|||||||
local function install_module()
|
local function install_module()
|
||||||
if not PHOTILS.module_installed then
|
if not PHOTILS.module_installed then
|
||||||
dt.register_lib(MODULE_NAME,
|
dt.register_lib(MODULE_NAME,
|
||||||
"photils autotagger",
|
_("photils auto-tagger"),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
PHOTILS.plugin_display_views,
|
PHOTILS.plugin_display_views,
|
||||||
|
|||||||
@@ -51,8 +51,6 @@ du.check_min_api_version("7.0.0", "quicktag")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("quicktag", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -62,7 +60,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "quicktag",
|
name = _("quick tag"),
|
||||||
purpose = _("use buttons to quickly apply tags assigned to them"),
|
purpose = _("use buttons to quickly apply tags assigned to them"),
|
||||||
author = "Christian Kanzian",
|
author = "Christian Kanzian",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag"
|
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
|
if not qt.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"quicktag", -- Module name
|
"quicktag", -- Module name
|
||||||
"quicktag", -- name
|
_("quick tag"), -- name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
false, -- resetable
|
false, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
|
||||||
@@ -306,7 +304,7 @@ end
|
|||||||
for i=1,qnr do
|
for i=1,qnr do
|
||||||
dt.register_event("quicktag " .. tostring(i), "shortcut",
|
dt.register_event("quicktag " .. tostring(i), "shortcut",
|
||||||
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
|
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
|
||||||
string.format(_("quicktag %i"),i))
|
string.format(_("quick tag %i"),i))
|
||||||
end
|
end
|
||||||
|
|
||||||
script_data.destroy = destroy
|
script_data.destroy = destroy
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ du.check_min_api_version("7.0.0", "rate_group")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("rate_group", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -57,7 +55,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "rate_group",
|
name = _("rate group"),
|
||||||
purpose = _("rate all images in a group"),
|
purpose = _("rate all images in a group"),
|
||||||
author = "Dom H (dom@hxy.io)",
|
author = "Dom H (dom@hxy.io)",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group"
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ du.deprecated("contrib/rename-tags.lua","darktable release 4.0")
|
|||||||
|
|
||||||
local gettext = darktable.gettext.gettext
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
darktable.gettext.bindtextdomain("rename-tags", darktable.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -49,7 +47,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "rename-tags",
|
name = _("rename tags"),
|
||||||
purpose = _("rename an existing tag"),
|
purpose = _("rename an existing tag"),
|
||||||
author = "Sebastian Witt (se.witt@gmx.net)",
|
author = "Sebastian Witt (se.witt@gmx.net)",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags"
|
||||||
|
|||||||
@@ -43,13 +43,12 @@
|
|||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
local df = require "lib/dtutils.file"
|
local df = require "lib/dtutils.file"
|
||||||
|
local ds = require "lib/dtutils.string"
|
||||||
|
|
||||||
du.check_min_api_version("7.0.0", "rename_images")
|
du.check_min_api_version("7.0.0", "rename_images")
|
||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("rename_images", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -57,12 +56,6 @@ end
|
|||||||
-- namespace variable
|
-- namespace variable
|
||||||
local rename = {
|
local rename = {
|
||||||
presets = {},
|
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 = {},
|
widgets = {},
|
||||||
}
|
}
|
||||||
rename.module_installed = false
|
rename.module_installed = false
|
||||||
@@ -72,7 +65,7 @@ rename.event_registered = false
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "rename_images",
|
name = _("rename images"),
|
||||||
purpose = _("rename an image file or files"),
|
purpose = _("rename an image file or files"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images"
|
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
|
-- 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)
|
local function stop_job(job)
|
||||||
job.valid = false
|
job.valid = false
|
||||||
end
|
end
|
||||||
@@ -225,14 +141,14 @@ local function do_rename(images)
|
|||||||
for i, image in ipairs(images) do
|
for i, image in ipairs(images) do
|
||||||
if job.valid then
|
if job.valid then
|
||||||
job.percent = i / #images
|
job.percent = i / #images
|
||||||
build_substitution_list(image, i, datetime, USER, PICTURES, HOME, DESKTOP)
|
ds.build_substitute_list(image, i, pattern, USER, PICTURES, HOME, DESKTOP)
|
||||||
local new_name = substitute_list(pattern)
|
local new_name = ds.substitute_list(pattern)
|
||||||
if new_name == -1 then
|
if new_name == -1 then
|
||||||
dt.print(_("unable to do variable substitution, exiting..."))
|
dt.print(_("unable to do variable substitution, exiting..."))
|
||||||
stop_job(job)
|
stop_job(job)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
clear_substitute_list()
|
ds.clear_substitute_list()
|
||||||
local args = {}
|
local args = {}
|
||||||
local path = string.sub(df.get_path(new_name), 1, -2)
|
local path = string.sub(df.get_path(new_name), 1, -2)
|
||||||
if string.len(path) == 0 then
|
if string.len(path) == 0 then
|
||||||
@@ -269,60 +185,19 @@ local function do_rename(images)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function reset_callback()
|
|
||||||
rename.widgets.pattern.text = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- W I D G E T S
|
-- W I D G E T S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
rename.widgets.pattern = dt.new_widget("entry"){
|
rename.widgets.pattern = dt.new_widget("entry"){
|
||||||
tooltip = _("$(ROLL_NAME) - film roll name\n") ..
|
tooltip = ds.get_substitution_tooltip(),
|
||||||
_("$(FILE_FOLDER) - image file folder\n") ..
|
placeholder = _("enter pattern") .. "$(FILE_FOLDER)/$(FILE_NAME)",
|
||||||
_("$(FILE_NAME) - image file name\n") ..
|
text = "",
|
||||||
_("$(FILE_EXTENSION) - image file extension\n") ..
|
reset_callback = function(self)
|
||||||
_("$(ID) - image id\n") ..
|
self.text = ""
|
||||||
_("$(VERSION) - version number\n") ..
|
dt.preferences.write(MODULE_NAME, "pattern", "string", self.text)
|
||||||
_("$(SEQUENCE) - sequence number of selection\n") ..
|
end
|
||||||
_("$(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 = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local pattern_pref = dt.preferences.read(MODULE_NAME, "pattern", "string")
|
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 dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
local df = require "lib/dtutils.file"
|
local df = require "lib/dtutils.file"
|
||||||
|
local ds = require "lib/dtutils.string"
|
||||||
|
local log = require "lib/dtutils.log"
|
||||||
|
|
||||||
-- module name
|
-- module name
|
||||||
local MODULE = "select_non_existing"
|
local MODULE <const> = "select_non_existing"
|
||||||
|
|
||||||
du.check_min_api_version("9.1.0", MODULE)
|
du.check_min_api_version("9.1.0", MODULE)
|
||||||
|
|
||||||
-- figure out the path separator
|
-- 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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("select_non_existing", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -41,26 +44,61 @@ local function stop_job(job)
|
|||||||
job.valid = false
|
job.valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function select_nonexisting_images(event, images)
|
local function get_file_list(dir)
|
||||||
local selection = {}
|
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)
|
if dt.configuration.running_os == "windows" then
|
||||||
for key,image in ipairs(images) do
|
cmd = "forfiles /P " .. ds.sanitize(dir) .. " /M * /C \"cmd /c echo @file\""
|
||||||
if(job.valid) then
|
else
|
||||||
job.percent = (key - 1)/#images
|
cmd = "cd " .. ds.sanitize(dir) ..";ls -1"
|
||||||
local filepath = image.path..PS..image.filename
|
end
|
||||||
local file_exists = df.test_file(filepath, "e")
|
log.msg(log.info, "cmd is " .. cmd)
|
||||||
dt.print_log(filepath.." exists? => "..tostring(file_exists))
|
|
||||||
if (not file_exists) then
|
local p = io.popen(cmd)
|
||||||
table.insert(selection, image)
|
if p then
|
||||||
end
|
for line in p:lines() do
|
||||||
else
|
if line:len() > 4 then
|
||||||
break
|
if not string.match(line, "xmp$") and not string.match(line, "XMP$") then
|
||||||
|
line = line:gsub("\"", "")
|
||||||
|
file_list[line] = true
|
||||||
end
|
end
|
||||||
|
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
|
end
|
||||||
|
|
||||||
local function destroy()
|
local function destroy()
|
||||||
@@ -76,7 +114,7 @@ dt.gui.libs.select.register_selection(
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "select_non_existing",
|
name = _("select non existing"),
|
||||||
purpose = _("enable selection of non-existing images"),
|
purpose = _("enable selection of non-existing images"),
|
||||||
author = "Dirk Dittmar",
|
author = "Dirk Dittmar",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing"
|
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")
|
du.check_min_api_version("7.0.0", "select_untagged")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("select_untagged", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -35,7 +33,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "select_untagged",
|
name = _("select untagged"),
|
||||||
purpose = _("enable selection of untagged images"),
|
purpose = _("enable selection of untagged images"),
|
||||||
author = "Jannis_V",
|
author = "Jannis_V",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged"
|
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")
|
du.check_min_api_version("7.0.0", "slideshowMusic")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("slideshowMusic", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -42,7 +40,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "slideshowMusic",
|
name = _("slideshow music"),
|
||||||
purpose = _("play music during a slideshow"),
|
purpose = _("play music during a slideshow"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic"
|
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 = require("lib/dtutils")
|
||||||
local dtutils_file = require("lib/dtutils.file")
|
local dtutils_file = require("lib/dtutils.file")
|
||||||
local dtutils_system = require("lib/dtutils.system")
|
local dtutils_system = require("lib/dtutils.system")
|
||||||
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
local LIB_ID = "transfer_hierarchy"
|
local LIB_ID = "transfer_hierarchy"
|
||||||
dtutils.check_min_api_version("7.0.0", LIB_ID)
|
dtutils.check_min_api_version("7.0.0", LIB_ID)
|
||||||
|
|
||||||
darktable.gettext.bindtextdomain("transfer_hierarchy", darktable.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return darktable.gettext.gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- return data structure for script_manager
|
-- return data structure for script_manager
|
||||||
@@ -91,7 +90,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
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"),
|
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)",
|
author = "August Schwerdfeger (august@schwerdfeger.name)",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy"
|
||||||
@@ -145,7 +144,7 @@ end
|
|||||||
local function install_module()
|
local function install_module()
|
||||||
if not th.module_installed then
|
if not th.module_installed then
|
||||||
darktable.register_lib(LIB_ID,
|
darktable.register_lib(LIB_ID,
|
||||||
"transfer hierarchy", true, true, {
|
_("transfer hierarchy"), true, true, {
|
||||||
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
|
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
|
||||||
}, th.transfer_widget, nil, nil)
|
}, th.transfer_widget, nil, nil)
|
||||||
th.module_installed = true
|
th.module_installed = true
|
||||||
@@ -271,7 +270,7 @@ local function doTransfer(transferFunc)
|
|||||||
local sourceBase = sourceTextBox.text
|
local sourceBase = sourceTextBox.text
|
||||||
local destBase = destinationTextBox.text
|
local destBase = destinationTextBox.text
|
||||||
local destFilms = {}
|
local destFilms = {}
|
||||||
for film, _ in pairs(films) do
|
for film, i in pairs(films) do
|
||||||
films[film] = destBase .. string.sub(film.path, #sourceBase+1)
|
films[film] = destBase .. string.sub(film.path, #sourceBase+1)
|
||||||
if not pathExists(films[film]) then
|
if not pathExists(films[film]) then
|
||||||
if createDirectory(films[film]) == nil 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")
|
du.check_min_api_version("7.0.0", "video_ffmpeg")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("video_ffmpeg", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -52,7 +50,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "video_ffmpeg",
|
name = _("video ffmpeg"),
|
||||||
purpose = _("timelapse video plugin based on ffmpeg"),
|
purpose = _("timelapse video plugin based on ffmpeg"),
|
||||||
author = "Dominik Markiewicz",
|
author = "Dominik Markiewicz",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contib/video_ffmpeg"
|
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
|
end
|
||||||
|
|
||||||
local framerates_selector = dt.new_widget("combobox"){
|
local framerates_selector = dt.new_widget("combobox"){
|
||||||
label = _("framerate"),
|
label = _("frame rate"),
|
||||||
tooltip = _("select framerate of output video"),
|
tooltip = _("select frame rate of output video"),
|
||||||
value = combobox_pref_read("framerate", framerates),
|
value = combobox_pref_read("framerate", framerates),
|
||||||
changed_callback = combobox_pref_write("framerate"),
|
changed_callback = combobox_pref_write("framerate"),
|
||||||
table.unpack(framerates)
|
table.unpack(framerates)
|
||||||
@@ -418,7 +416,7 @@ local function export(extra_data)
|
|||||||
local ffmpeg_path = df.check_if_bin_exists("ffmpeg")
|
local ffmpeg_path = df.check_if_bin_exists("ffmpeg")
|
||||||
if not ffmpeg_path then
|
if not ffmpeg_path then
|
||||||
dt.print_error("ffmpeg not found")
|
dt.print_error("ffmpeg not found")
|
||||||
dt.print("ERROR - ffmpeg not found")
|
dt.print(_("ERROR - ffmpeg not found"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local dir = extra_data["tmp_dir"]
|
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)
|
dt.print_error(filename, file.filename)
|
||||||
df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"])
|
df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"])
|
||||||
end
|
end
|
||||||
dt.print("Start video building...")
|
dt.print(_("start video building..."))
|
||||||
local result, path = export(extra_data)
|
local result, path = export(extra_data)
|
||||||
if result ~= 0 then
|
if result ~= 0 then
|
||||||
dt.print(_("ERROR: cannot build image, see console for more info"))
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
323
examples/ai_denoise.lua
Normal file
323
examples/ai_denoise.lua
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
--[[
|
||||||
|
This file is part of darktable,
|
||||||
|
copyright (c) 2026 darktable developers.
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
ai_denoise - example script demonstrating the darktable.ai Lua
|
||||||
|
API by denoising the selected images with tile-based inference.
|
||||||
|
|
||||||
|
Adds an "AI denoise" panel to the lighttable right column with a
|
||||||
|
"denoise selected" button. Each selected image is loaded through
|
||||||
|
the develop pipeline (so denoise sees the output of all enabled
|
||||||
|
IOPs, not the original raw), run tile-by-tile through the active
|
||||||
|
denoise model, and saved as a 16-bit TIFF grouped with the source.
|
||||||
|
|
||||||
|
Demonstrates:
|
||||||
|
* model lookup via dt.ai.model_for_task
|
||||||
|
* tensor crop/paste for tile-based inference
|
||||||
|
* linear <-> sRGB conversion (NR models train on gamma input)
|
||||||
|
* edge-replicated padding to suppress border artifacts
|
||||||
|
* background job with progress bar and cancellation
|
||||||
|
|
||||||
|
LIMITATIONS
|
||||||
|
* Image must be at least TILE_SIZE x TILE_SIZE (768 x 768 for
|
||||||
|
denoise-nind / denoise-nafnet); smaller inputs return an error.
|
||||||
|
* If a save fails mid-batch the Lua error propagates and aborts
|
||||||
|
the whole job. Wrap denoise_one in pcall if you need
|
||||||
|
batch-robust behaviour.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
* a denoise model enabled in preferences -> AI (denoise-nind,
|
||||||
|
denoise-nafnet, ...)
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* require this script from your luarc
|
||||||
|
* select one or more images in lighttable
|
||||||
|
* click the "denoise selected" button in the AI denoise panel
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
* file an issue on https://github.com/darktable-org/darktable
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE_NAME = "ai_denoise"
|
||||||
|
|
||||||
|
-- darktable's NR models use static-shape ONNX: each model has a fixed
|
||||||
|
-- input dimension baked in at training time. The Lua API doesn't
|
||||||
|
-- expose this, so keep TILE_SIZE in sync with the model's
|
||||||
|
-- `attributes.input_sizes` in its config.json.
|
||||||
|
local TILE_SIZE = 768 -- denoise-nind, denoise-nafnet
|
||||||
|
|
||||||
|
-- overlap between adjacent tiles. larger values produce smoother
|
||||||
|
-- seams at the cost of more compute per pixel
|
||||||
|
local OVERLAP = 64
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
-- Surround the input with `pad` pixels on every side, replicating the
|
||||||
|
-- outermost rows/columns ("edge clamp" padding). Boundary tiles then
|
||||||
|
-- see plausible context instead of a literal image edge, so the
|
||||||
|
-- network's internal zero-padding no longer leaks into the visible
|
||||||
|
-- output as colour artifacts along the image rim.
|
||||||
|
local function pad_replicate(input, pad)
|
||||||
|
local C = input:shape()[2]
|
||||||
|
local H = input:shape()[3]
|
||||||
|
local W = input:shape()[4]
|
||||||
|
local padded = dt.ai.create_tensor({1, C, H + 2 * pad, W + 2 * pad})
|
||||||
|
|
||||||
|
-- center: original image
|
||||||
|
padded:paste(input, pad, pad)
|
||||||
|
|
||||||
|
-- left and right strips: replicate first / last column of input
|
||||||
|
local left_col = input:crop(0, 0, H, 1)
|
||||||
|
local right_col = input:crop(0, W - 1, H, 1)
|
||||||
|
for i = 0, pad - 1 do
|
||||||
|
padded:paste(left_col, pad, i)
|
||||||
|
padded:paste(right_col, pad, pad + W + i)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- top and bottom strips: replicate first / last row of the already
|
||||||
|
-- horizontally-padded buffer so the four corners get filled correctly
|
||||||
|
local top_row = padded:crop(pad, 0, 1, W + 2 * pad)
|
||||||
|
local bottom_row = padded:crop(pad + H - 1, 0, 1, W + 2 * pad)
|
||||||
|
for i = 0, pad - 1 do
|
||||||
|
padded:paste(top_row, i, 0)
|
||||||
|
padded:paste(bottom_row, pad + H + i, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
return padded
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Tile a [1,C,H,W] tensor through the model with overlap-blended seams.
|
||||||
|
-- Each tile is tile_size x tile_size, successive tiles step by
|
||||||
|
-- (tile_size - 2*overlap), and only the central (non-overlap) region
|
||||||
|
-- of each tile is pasted into the output. Image-boundary edges keep
|
||||||
|
-- their full extent; only image-interior seams drop the overlap margin.
|
||||||
|
--
|
||||||
|
-- The trailing tile in each axis is pinned to the image boundary so
|
||||||
|
-- the residual right/bottom strip is never smaller than 2*overlap --
|
||||||
|
-- otherwise it would have to be skipped (the model has a fixed input
|
||||||
|
-- size) and those pixels would never be written.
|
||||||
|
local function run_tiled(ctx, input, tile_size, overlap,
|
||||||
|
job, base_pct, span_pct)
|
||||||
|
local C = input:shape()[2]
|
||||||
|
local H = input:shape()[3]
|
||||||
|
local W = input:shape()[4]
|
||||||
|
if H < tile_size or W < tile_size then
|
||||||
|
return nil, string.format("image too small (%dx%d) for tile %d",
|
||||||
|
W, H, tile_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
local step = tile_size - 2 * overlap
|
||||||
|
|
||||||
|
-- enumerate tile origins along one axis, ending exactly at the boundary
|
||||||
|
local function origins(extent)
|
||||||
|
local out, p = {}, 0
|
||||||
|
while p + tile_size < extent do
|
||||||
|
out[#out + 1] = p
|
||||||
|
p = p + step
|
||||||
|
end
|
||||||
|
out[#out + 1] = math.max(0, extent - tile_size)
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
local ys, xs = origins(H), origins(W)
|
||||||
|
local total = #xs * #ys
|
||||||
|
|
||||||
|
local output = dt.ai.create_tensor({1, C, H, W})
|
||||||
|
local done = 0
|
||||||
|
|
||||||
|
for _, ty in ipairs(ys) do
|
||||||
|
for _, tx in ipairs(xs) do
|
||||||
|
if job and not job.valid then return nil, "cancelled" end
|
||||||
|
|
||||||
|
local tile_in = input:crop(ty, tx, tile_size, tile_size)
|
||||||
|
local tile_out = ctx:run(tile_in)
|
||||||
|
|
||||||
|
-- central region to keep: drop overlap on every inner edge;
|
||||||
|
-- full extent at image-boundary edges
|
||||||
|
local at_top = (ty == 0)
|
||||||
|
local at_left = (tx == 0)
|
||||||
|
local at_bottom = (ty + tile_size >= H)
|
||||||
|
local at_right = (tx + tile_size >= W)
|
||||||
|
|
||||||
|
local cy = at_top and 0 or overlap
|
||||||
|
local cx = at_left and 0 or overlap
|
||||||
|
local ch = tile_size - cy - (at_bottom and 0 or overlap)
|
||||||
|
local cw = tile_size - cx - (at_right and 0 or overlap)
|
||||||
|
|
||||||
|
output:paste(tile_out:crop(cy, cx, ch, cw), ty + cy, tx + cx)
|
||||||
|
|
||||||
|
done = done + 1
|
||||||
|
if job then
|
||||||
|
job.percent = base_pct + span_pct * (done / total)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Denoise one image: load through the develop pipeline, run tiled
|
||||||
|
-- inference, save as 16-bit TIFF, group with the source.
|
||||||
|
local function denoise_one(ctx, img, tile_size, job, base_pct, span_pct)
|
||||||
|
-- darktable returns the develop-pipeline output as scene-linear RGB;
|
||||||
|
-- the NR models were trained on sRGB-gamma input, so we re-encode
|
||||||
|
-- before inference and decode back to linear before saving
|
||||||
|
local input = dt.ai.load_image(img)
|
||||||
|
if not input then return false, "load_image failed" end
|
||||||
|
input:linear_to_srgb()
|
||||||
|
|
||||||
|
-- pad so the image's outer rim is no longer at a tile boundary;
|
||||||
|
-- the padded strip absorbs the network's edge artifacts and gets
|
||||||
|
-- cropped away after inference
|
||||||
|
local H = input:shape()[3]
|
||||||
|
local W = input:shape()[4]
|
||||||
|
local padded_in = pad_replicate(input, OVERLAP)
|
||||||
|
|
||||||
|
local padded_out, err = run_tiled(ctx, padded_in, tile_size, OVERLAP,
|
||||||
|
job, base_pct, span_pct)
|
||||||
|
if not padded_out then return false, err end
|
||||||
|
|
||||||
|
local output = padded_out:crop(OVERLAP, OVERLAP, H, W)
|
||||||
|
output:srgb_to_linear()
|
||||||
|
|
||||||
|
local base = img.filename:match("(.+)%..+$") or img.filename
|
||||||
|
local out_path = img.path .. "/" .. base .. "_denoised.tif"
|
||||||
|
output:save_tiff(out_path, 16, img)
|
||||||
|
|
||||||
|
-- import the result and group with the source so they live together
|
||||||
|
-- in the lighttable
|
||||||
|
local imported = dt.database.import(out_path)
|
||||||
|
if imported then imported:group_with(img) end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Button handler: looks up the active denoise model, fans out the
|
||||||
|
-- selected images, and reports progress via a cancellable job.
|
||||||
|
local function process_denoise()
|
||||||
|
local model_id = dt.ai.model_for_task("denoise")
|
||||||
|
if not model_id then
|
||||||
|
dt.print("no denoise model enabled -- pick one in preferences -> AI")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
if #images == 0 then
|
||||||
|
dt.print("select at least one image")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- progress + cancel job. the cancel callback flips job.valid; the
|
||||||
|
-- main loop polls it between tiles to stop cleanly
|
||||||
|
local job = dt.gui.create_job(
|
||||||
|
string.format("AI denoise (%d image%s)",
|
||||||
|
#images, #images == 1 and "" or "s"),
|
||||||
|
true,
|
||||||
|
function(j) j.valid = false end)
|
||||||
|
|
||||||
|
dt.print_log(string.format(
|
||||||
|
"[ai_denoise] starting: %d image%s with model %s",
|
||||||
|
#images, #images == 1 and "" or "s", model_id))
|
||||||
|
local t_start = os.time()
|
||||||
|
|
||||||
|
local ctx = dt.ai.load_model(model_id)
|
||||||
|
if not ctx then
|
||||||
|
dt.print("failed to load model: " .. model_id)
|
||||||
|
job.valid = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local span = 1.0 / #images
|
||||||
|
local done = 0
|
||||||
|
for i, img in ipairs(images) do
|
||||||
|
if not job.valid then break end
|
||||||
|
dt.print_log(string.format("[ai_denoise] [%d/%d] %s",
|
||||||
|
i, #images, img.filename))
|
||||||
|
local ok, err = denoise_one(ctx, img, TILE_SIZE, job,
|
||||||
|
(i - 1) * span, span)
|
||||||
|
if not ok then
|
||||||
|
dt.print(string.format("[%d/%d] %s: %s",
|
||||||
|
i, #images, img.filename, tostring(err)))
|
||||||
|
dt.print_log(string.format("[ai_denoise] [%d/%d] %s FAILED: %s",
|
||||||
|
i, #images, img.filename, tostring(err)))
|
||||||
|
else
|
||||||
|
done = done + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx:close()
|
||||||
|
job.valid = false
|
||||||
|
local elapsed = os.time() - t_start
|
||||||
|
dt.print(string.format("denoise complete: %d image%s",
|
||||||
|
#images, #images == 1 and "" or "s"))
|
||||||
|
dt.print_log(string.format(
|
||||||
|
"[ai_denoise] finished: %d/%d in %ds",
|
||||||
|
done, #images, elapsed))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- script_manager integration: called when the user disables the script.
|
||||||
|
-- darktable can't remove a registered lib from the UI at runtime, so we
|
||||||
|
-- only stop any in-flight work; the panel itself remains until restart.
|
||||||
|
local function destroy()
|
||||||
|
-- nothing persistent to clean up: each click runs its own job and
|
||||||
|
-- closes its own model context
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE_NAME, -- plugin id
|
||||||
|
"AI denoise", -- displayed name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- no reset button
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
|
||||||
|
dt.new_widget("box") {
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("button") {
|
||||||
|
label = "denoise selected",
|
||||||
|
clicked_callback = process_denoise,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nil, nil
|
||||||
|
)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = "AI denoise",
|
||||||
|
purpose = "tile-based AI denoise using the darktable.ai Lua API",
|
||||||
|
author = "Andrii Ryzhkov",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/ai_denoise"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
481
examples/ai_raw_denoise.lua
Normal file
481
examples/ai_raw_denoise.lua
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
--[[
|
||||||
|
This file is part of darktable,
|
||||||
|
copyright (c) 2026 darktable developers.
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
ai_raw_denoise - example script demonstrating the darktable.ai Lua
|
||||||
|
API by denoising the selected raw images with tile-based inference
|
||||||
|
on the raw CFA.
|
||||||
|
|
||||||
|
Adds an "AI raw denoise" panel to the lighttable right column with
|
||||||
|
a "denoise selected" button. Each selected image's raw CFA is
|
||||||
|
loaded, Bayer-packed to 4 channels, run tile-by-tile through the
|
||||||
|
currently enabled rawdenoise model (which denoises AND demosaicks
|
||||||
|
in one pass), and saved as a LinearRaw DNG grouped with the
|
||||||
|
source image.
|
||||||
|
|
||||||
|
Demonstrates:
|
||||||
|
* model lookup via dt.ai.model_for_task
|
||||||
|
* loading model variant files via dt.ai.load_model file arg
|
||||||
|
* raw CFA loading via dt.ai.load_raw
|
||||||
|
* tensor bayer_pack for 4-channel CFA tiling
|
||||||
|
* tile loop with non-trivial input->output scale (here 1 -> 2)
|
||||||
|
* edge-replicated padding to suppress border artifacts
|
||||||
|
* LinearRaw DNG output via dt.ai.save_dng_linear
|
||||||
|
* background job with progress bar and cancellation
|
||||||
|
|
||||||
|
LIMITATIONS
|
||||||
|
* Bayer sensors only. The script loads model_bayer.onnx and uses
|
||||||
|
bayer_pack, which assumes a 2x2 Bayer layout. X-Trans and
|
||||||
|
Foveon sensors need model_linear.onnx with a different
|
||||||
|
preprocessing pipeline (no bayer_pack, demosaicked input).
|
||||||
|
* Image must be at least TILE_SIZE x TILE_SIZE in packed pixels
|
||||||
|
(1024 x 1024 of CFA for the default 512 packed tile size).
|
||||||
|
* If a save fails mid-batch the Lua error propagates and aborts
|
||||||
|
the whole job. Wrap denoise_one in pcall if you need
|
||||||
|
batch-robust behaviour.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
* a rawdenoise model enabled in preferences -> AI
|
||||||
|
(e.g. rawdenoise-nind)
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* require this script from your luarc
|
||||||
|
* select one or more raw images in lighttable
|
||||||
|
* click the "denoise selected" button in the AI raw denoise panel
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
* file an issue on https://github.com/darktable-org/darktable
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE_NAME = "ai_raw_denoise"
|
||||||
|
|
||||||
|
-- The rawdenoise-nind bayer model is a denoise+demosaic combo:
|
||||||
|
-- input : [1, 4, 512, 512] packed CFA (4 phase planes)
|
||||||
|
-- output : [1, 3, 1024, 1024] linear RGB at 2x the input resolution
|
||||||
|
-- So each tile-size pixel of the input covers a 2x2 CFA block, and the
|
||||||
|
-- model emits two output pixels per input pixel in each axis.
|
||||||
|
-- TILE_SIZE is in packed pixels (model input); SCALE turns those into
|
||||||
|
-- output pixels. Keep in sync with `attributes.input_sizes` in the
|
||||||
|
-- model's config.json.
|
||||||
|
local TILE_SIZE = 512 -- rawdenoise-nind
|
||||||
|
local SCALE = 2 -- model output dim / input dim
|
||||||
|
local OUT_CH = 3 -- model emits linear RGB
|
||||||
|
|
||||||
|
-- overlap between adjacent tiles, in packed pixels (input space).
|
||||||
|
-- larger values produce smoother seams at the cost of more compute
|
||||||
|
local OVERLAP = 32
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
-- Bayer-only CFA channel lookup. Returns 0/1/2 (R/G/B) for the cell
|
||||||
|
-- at sensor position (r, c) given the 32-bit `filters` mask from
|
||||||
|
-- load_raw's meta table. Used to find the R-cell origin so bayer_pack's
|
||||||
|
-- plane 0 always holds R. X-Trans (filters == 9) uses meta.xtrans
|
||||||
|
-- instead and is not handled here.
|
||||||
|
local function fc(r, c, filters)
|
||||||
|
local shift = ((r % 2) * 4) + ((c % 2) * 2)
|
||||||
|
return math.floor(filters / (2 ^ shift)) % 4
|
||||||
|
end
|
||||||
|
|
||||||
|
-- daylight WB multipliers (R, G=1, B) derived from the camera's
|
||||||
|
-- XYZ->camRGB color matrix and the D65 white point. mirrors the C
|
||||||
|
-- path's _bayer_wb_daylight in restore_raw_bayer.c — the rawdenoise
|
||||||
|
-- bayer model is trained on daylight-WB'd input, so feeding it
|
||||||
|
-- un-WB'd camRGB produces wrong colors. Falls back to {1, 1, 1} if
|
||||||
|
-- the matrix is missing or degenerate.
|
||||||
|
local function daylight_wb(color_matrix)
|
||||||
|
local d65 = {0.95047, 1.0, 1.08883}
|
||||||
|
if not color_matrix then return 1.0, 1.0, 1.0 end
|
||||||
|
local resp = {0, 0, 0}
|
||||||
|
for c = 1, 3 do
|
||||||
|
resp[c] = color_matrix[c][1] * d65[1]
|
||||||
|
+ color_matrix[c][2] * d65[2]
|
||||||
|
+ color_matrix[c][3] * d65[3]
|
||||||
|
end
|
||||||
|
if resp[1] <= 0 or resp[2] <= 0 or resp[3] <= 0 then
|
||||||
|
return 1.0, 1.0, 1.0
|
||||||
|
end
|
||||||
|
return resp[2] / resp[1], 1.0, resp[2] / resp[3]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Surround the input with `pad` pixels on every side, replicating the
|
||||||
|
-- outermost rows/columns ("edge clamp" padding). Boundary tiles then
|
||||||
|
-- see plausible context instead of a literal image edge, so the
|
||||||
|
-- network's internal zero-padding no longer leaks into the visible
|
||||||
|
-- output as colour artifacts along the image rim.
|
||||||
|
local function pad_replicate(input, pad)
|
||||||
|
local C = input:shape()[2]
|
||||||
|
local H = input:shape()[3]
|
||||||
|
local W = input:shape()[4]
|
||||||
|
local padded = dt.ai.create_tensor({1, C, H + 2 * pad, W + 2 * pad})
|
||||||
|
|
||||||
|
-- center: original image
|
||||||
|
padded:paste(input, pad, pad)
|
||||||
|
|
||||||
|
-- left and right strips: replicate first / last column of input
|
||||||
|
local left_col = input:crop(0, 0, H, 1)
|
||||||
|
local right_col = input:crop(0, W - 1, H, 1)
|
||||||
|
for i = 0, pad - 1 do
|
||||||
|
padded:paste(left_col, pad, i)
|
||||||
|
padded:paste(right_col, pad, pad + W + i)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- top and bottom strips: replicate first / last row of the already
|
||||||
|
-- horizontally-padded buffer so the four corners get filled correctly
|
||||||
|
local top_row = padded:crop(pad, 0, 1, W + 2 * pad)
|
||||||
|
local bottom_row = padded:crop(pad + H - 1, 0, 1, W + 2 * pad)
|
||||||
|
for i = 0, pad - 1 do
|
||||||
|
padded:paste(top_row, i, 0)
|
||||||
|
padded:paste(bottom_row, pad + H + i, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
return padded
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Tile a [1,C_in,H,W] tensor through a model that emits [1,C_out,H*scale,
|
||||||
|
-- W*scale]. Each tile is tile_size x tile_size in input space; successive
|
||||||
|
-- tiles step by (tile_size - 2*overlap); only the central (non-overlap)
|
||||||
|
-- region of each tile's output is pasted into the result, with all
|
||||||
|
-- offsets multiplied by `scale`. Image-boundary edges keep their full
|
||||||
|
-- extent; only image-interior seams drop the overlap margin.
|
||||||
|
--
|
||||||
|
-- The trailing tile in each axis is pinned to the image boundary so
|
||||||
|
-- the residual right/bottom strip is never smaller than 2*overlap --
|
||||||
|
-- otherwise it would have to be skipped (the model has a fixed input
|
||||||
|
-- size) and those pixels would never be written.
|
||||||
|
local function run_tiled(ctx, input, tile_size, overlap, scale, c_out,
|
||||||
|
job, base_pct, span_pct)
|
||||||
|
local H = input:shape()[3]
|
||||||
|
local W = input:shape()[4]
|
||||||
|
if H < tile_size or W < tile_size then
|
||||||
|
return nil, string.format("image too small (%dx%d) for tile %d",
|
||||||
|
W, H, tile_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
local step = tile_size - 2 * overlap
|
||||||
|
|
||||||
|
-- enumerate tile origins along one axis, ending exactly at the boundary
|
||||||
|
local function origins(extent)
|
||||||
|
local out, p = {}, 0
|
||||||
|
while p + tile_size < extent do
|
||||||
|
out[#out + 1] = p
|
||||||
|
p = p + step
|
||||||
|
end
|
||||||
|
out[#out + 1] = math.max(0, extent - tile_size)
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
local ys, xs = origins(H), origins(W)
|
||||||
|
local total = #xs * #ys
|
||||||
|
|
||||||
|
-- output is scale-times larger spatially and may have a different
|
||||||
|
-- channel count than the input
|
||||||
|
local output = dt.ai.create_tensor({1, c_out, H * scale, W * scale})
|
||||||
|
local done = 0
|
||||||
|
|
||||||
|
for _, ty in ipairs(ys) do
|
||||||
|
for _, tx in ipairs(xs) do
|
||||||
|
if job and not job.valid then return nil, "cancelled" end
|
||||||
|
|
||||||
|
local tile_in = input:crop(ty, tx, tile_size, tile_size)
|
||||||
|
local tile_out = ctx:run(tile_in)
|
||||||
|
|
||||||
|
-- central region to keep: drop overlap on every inner edge;
|
||||||
|
-- full extent at image-boundary edges. all dimensions in OUTPUT
|
||||||
|
-- (post-scale) space, since that's where we paste
|
||||||
|
local at_top = (ty == 0)
|
||||||
|
local at_left = (tx == 0)
|
||||||
|
local at_bottom = (ty + tile_size >= H)
|
||||||
|
local at_right = (tx + tile_size >= W)
|
||||||
|
|
||||||
|
local ov = overlap * scale
|
||||||
|
local tos = tile_size * scale
|
||||||
|
local cy = at_top and 0 or ov
|
||||||
|
local cx = at_left and 0 or ov
|
||||||
|
local ch = tos - cy - (at_bottom and 0 or ov)
|
||||||
|
local cw = tos - cx - (at_right and 0 or ov)
|
||||||
|
|
||||||
|
output:paste(tile_out:crop(cy, cx, ch, cw),
|
||||||
|
ty * scale + cy, tx * scale + cx)
|
||||||
|
|
||||||
|
done = done + 1
|
||||||
|
if job then
|
||||||
|
job.percent = base_pct + span_pct * (done / total)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Denoise one raw image. The bayer rawdenoise model denoises AND
|
||||||
|
-- demosaicks in one pass, so its output is linear RGB at the
|
||||||
|
-- original CFA resolution (not a CFA mosaic). Saved as a LinearRaw
|
||||||
|
-- DNG, which darktable can re-import as an already-debayered raw.
|
||||||
|
--
|
||||||
|
-- Pipeline:
|
||||||
|
-- load CFA [1,1,H,W] + image metadata
|
||||||
|
-- -> bayer_pack [1,4,H/2,W/2]
|
||||||
|
-- -> normalize to [0, 1] using black/white levels (the model is
|
||||||
|
-- trained on (val - black) / (white - black), not raw ADC)
|
||||||
|
-- -> pad to [1,4,H/2+2P,W/2+2P]
|
||||||
|
-- -> tile through model (scale=2, c_out=3) -> [1,3,(H+4P),(W+4P)]
|
||||||
|
-- -> crop padding back off -> [1,3,H,W]
|
||||||
|
-- -> save linear DNG -> import + group with source
|
||||||
|
local function denoise_one(ctx, img, tile_size, job, base_pct, span_pct)
|
||||||
|
-- load_raw returns (tensor, meta). tensor is [1,1,H,W] in raw ADC
|
||||||
|
-- units; meta carries black/white/wb/CFA info we need to normalise
|
||||||
|
-- the input into the [0, 1] range the model was trained on
|
||||||
|
local cfa, meta = dt.ai.load_raw(img)
|
||||||
|
if not cfa then return false, "load_raw failed" end
|
||||||
|
|
||||||
|
-- this script is Bayer-only: filters == 9 is X-Trans (handled by a
|
||||||
|
-- different preprocessing pipeline + model_linear.onnx); filters == 0
|
||||||
|
-- means monochrome / non-CFA, which bayer_pack can't handle either
|
||||||
|
if not meta.filters or meta.filters == 0 or meta.filters == 9 then
|
||||||
|
return false, "unsupported CFA pattern (Bayer-only script)"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Crop to the visible region and shift the origin onto the R cell
|
||||||
|
-- of the CFA pattern. bayer_pack splits by position only, so to make
|
||||||
|
-- plane 0 always hold R (which the model expects) the cropped CFA's
|
||||||
|
-- (0, 0) must sit at an R site. Mirrors the C path's _bayer_origin
|
||||||
|
-- + RGGB shift in restore_raw_bayer.c.
|
||||||
|
local cy = 0
|
||||||
|
local cx = 0
|
||||||
|
local cw = cfa:shape()[4]
|
||||||
|
local ch = cfa:shape()[3]
|
||||||
|
if meta.crop_x and meta.crop_y then
|
||||||
|
cy = meta.crop_y - (meta.crop_y % 2)
|
||||||
|
cx = meta.crop_x - (meta.crop_x % 2)
|
||||||
|
cw = (meta.visible_width or cw) + (meta.crop_x - cx)
|
||||||
|
ch = (meta.visible_height or ch) + (meta.crop_y - cy)
|
||||||
|
end
|
||||||
|
-- find R cell within the 2x2 starting at (cy, cx)
|
||||||
|
local sy, sx = 0, 0
|
||||||
|
for ty = 0, 1 do
|
||||||
|
for tx = 0, 1 do
|
||||||
|
if fc(cy + ty, cx + tx, meta.filters) == 0 then
|
||||||
|
sy, sx = ty, tx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cy = cy + sy
|
||||||
|
cx = cx + sx
|
||||||
|
ch = ch - sy
|
||||||
|
cw = cw - sx
|
||||||
|
-- ensure even dims for bayer_pack
|
||||||
|
ch = ch - (ch % 2)
|
||||||
|
cw = cw - (cw % 2)
|
||||||
|
if cy > 0 or cx > 0
|
||||||
|
or cw < cfa:shape()[4] or ch < cfa:shape()[3]
|
||||||
|
then
|
||||||
|
cfa = cfa:crop(cy, cx, ch, cw)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- uniform-black approximation: most sensors report identical black
|
||||||
|
-- levels across the 4 CFA sites, in which case a single scalar
|
||||||
|
-- (val - black) / (white - black) normalisation is exact. cameras
|
||||||
|
-- with non-uniform per-site black levels (e.g. some PDAF sensors)
|
||||||
|
-- would need per-plane scaling via scale_add_planes -- left as an
|
||||||
|
-- exercise
|
||||||
|
local black = meta.black_level
|
||||||
|
local range = meta.white_level - black
|
||||||
|
if range <= 0 then range = 65535 end
|
||||||
|
|
||||||
|
-- pack the 1-channel CFA into 4 phase planes so each channel holds
|
||||||
|
-- one Bayer site type. After the RGGB shift above:
|
||||||
|
-- plane 0 = R, planes 1,2 = G, plane 3 = B
|
||||||
|
local packed = cfa:bayer_pack()
|
||||||
|
packed:scale_add(1.0 / range, -black / range)
|
||||||
|
|
||||||
|
-- apply daylight WB so the four planes sit in the same range the
|
||||||
|
-- model was trained on (R, G, G, B → R*wb_R, G, G, B*wb_B). without
|
||||||
|
-- this, the model sees unbalanced channels and produces wrong colors
|
||||||
|
local wb_R, wb_G, wb_B = daylight_wb(meta.color_matrix)
|
||||||
|
packed:scale_add_planes({wb_R, wb_G, wb_G, wb_B})
|
||||||
|
local pH = packed:shape()[3]
|
||||||
|
local pW = packed:shape()[4]
|
||||||
|
|
||||||
|
-- save the post-normalisation input mean for gain-matching the
|
||||||
|
-- model output: the bayer rawdenoise model emits values on its own
|
||||||
|
-- internal scale, not [0, 1], so we rescale so output mean matches
|
||||||
|
-- input mean (the per-tile equivalent of dt's C reference path)
|
||||||
|
local in_mean = packed:mean()
|
||||||
|
|
||||||
|
-- pad so the image rim is no longer at a tile boundary; the padded
|
||||||
|
-- strip absorbs the network's edge artifacts and is cropped away
|
||||||
|
-- after inference (in output space, where the pad covers 2*OVERLAP
|
||||||
|
-- pixels per side)
|
||||||
|
local padded_in = pad_replicate(packed, OVERLAP)
|
||||||
|
|
||||||
|
local padded_out, err = run_tiled(ctx, padded_in, tile_size, OVERLAP,
|
||||||
|
SCALE, OUT_CH,
|
||||||
|
job, base_pct, span_pct)
|
||||||
|
if not padded_out then return false, err end
|
||||||
|
|
||||||
|
-- crop the padded region (in output coords: pad * scale per side)
|
||||||
|
-- back to the original-CFA resolution
|
||||||
|
local out_rgb = padded_out:crop(OVERLAP * SCALE, OVERLAP * SCALE,
|
||||||
|
pH * SCALE, pW * SCALE)
|
||||||
|
|
||||||
|
-- gain-match: rescale the output so its mean matches the input
|
||||||
|
-- mean, bringing the model's internal-scale output back into the
|
||||||
|
-- [0, 1] range save_dng_linear expects
|
||||||
|
local out_mean = out_rgb:mean()
|
||||||
|
if out_mean > 1e-8 then
|
||||||
|
out_rgb:scale_add(in_mean / out_mean)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- invert the daylight WB so the saved tensor is un-WB'd camRGB
|
||||||
|
-- (save_dng_linear's contract — the consumer applies AsShotNeutral
|
||||||
|
-- on import)
|
||||||
|
out_rgb:scale_add_planes({1.0 / wb_R, 1.0 / wb_G, 1.0 / wb_B})
|
||||||
|
|
||||||
|
local base = img.filename:match("(.+)%..+$") or img.filename
|
||||||
|
local out_path = img.path .. "/" .. base .. "_rawdenoised.dng"
|
||||||
|
-- LinearRaw DNG: tensor values must be normalised to [0, 1] camRGB;
|
||||||
|
-- the model handles black-subtract + scale internally, so we just
|
||||||
|
-- pass the output through
|
||||||
|
dt.ai.save_dng_linear(out_rgb, img, out_path)
|
||||||
|
|
||||||
|
-- import the result and group with the source so they live together
|
||||||
|
-- in the lighttable
|
||||||
|
local imported = dt.database.import(out_path)
|
||||||
|
if imported then imported:group_with(img) end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Button handler: looks up the active rawdenoise model, fans out the
|
||||||
|
-- selected images, and reports progress via a cancellable job.
|
||||||
|
local function process_raw_denoise()
|
||||||
|
local model_id = dt.ai.model_for_task("rawdenoise")
|
||||||
|
if not model_id then
|
||||||
|
dt.print("no rawdenoise model enabled -- pick one in preferences -> AI")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
if #images == 0 then
|
||||||
|
dt.print("select at least one image")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- progress + cancel job. the cancel callback flips job.valid; the
|
||||||
|
-- main loop polls it between tiles to stop cleanly
|
||||||
|
local job = dt.gui.create_job(
|
||||||
|
string.format("AI raw denoise (%d image%s)",
|
||||||
|
#images, #images == 1 and "" or "s"),
|
||||||
|
true,
|
||||||
|
function(j) j.valid = false end)
|
||||||
|
|
||||||
|
dt.print_log(string.format(
|
||||||
|
"[ai_raw_denoise] starting: %d image%s with model %s",
|
||||||
|
#images, #images == 1 and "" or "s", model_id))
|
||||||
|
local t_start = os.time()
|
||||||
|
|
||||||
|
-- rawdenoise packages ship multiple files (one per CFA type); pick
|
||||||
|
-- the Bayer variant. X-Trans / Foveon would use model_linear.onnx
|
||||||
|
-- with a different preprocessing pipeline (no bayer_pack)
|
||||||
|
local ctx = dt.ai.load_model(model_id, nil, "model_bayer.onnx")
|
||||||
|
if not ctx then
|
||||||
|
dt.print("failed to load model: " .. model_id)
|
||||||
|
job.valid = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local span = 1.0 / #images
|
||||||
|
local done = 0
|
||||||
|
for i, img in ipairs(images) do
|
||||||
|
if not job.valid then break end
|
||||||
|
dt.print_log(string.format("[ai_raw_denoise] [%d/%d] %s",
|
||||||
|
i, #images, img.filename))
|
||||||
|
local ok, err = denoise_one(ctx, img, TILE_SIZE, job,
|
||||||
|
(i - 1) * span, span)
|
||||||
|
if not ok then
|
||||||
|
dt.print(string.format("[%d/%d] %s: %s",
|
||||||
|
i, #images, img.filename, tostring(err)))
|
||||||
|
dt.print_log(string.format("[ai_raw_denoise] [%d/%d] %s FAILED: %s",
|
||||||
|
i, #images, img.filename, tostring(err)))
|
||||||
|
else
|
||||||
|
done = done + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx:close()
|
||||||
|
job.valid = false
|
||||||
|
local elapsed = os.time() - t_start
|
||||||
|
dt.print(string.format("raw denoise complete: %d image%s",
|
||||||
|
#images, #images == 1 and "" or "s"))
|
||||||
|
dt.print_log(string.format(
|
||||||
|
"[ai_raw_denoise] finished: %d/%d in %ds",
|
||||||
|
done, #images, elapsed))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- script_manager integration: called when the user disables the script.
|
||||||
|
-- darktable can't remove a registered lib from the UI at runtime, so we
|
||||||
|
-- only stop any in-flight work; the panel itself remains until restart.
|
||||||
|
local function destroy()
|
||||||
|
-- nothing persistent to clean up: each click runs its own job and
|
||||||
|
-- closes its own model context
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE_NAME, -- plugin id
|
||||||
|
"AI raw denoise", -- displayed name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- no reset button
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
|
||||||
|
dt.new_widget("box") {
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("button") {
|
||||||
|
label = "denoise selected",
|
||||||
|
clicked_callback = process_raw_denoise,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nil, nil
|
||||||
|
)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = "AI raw denoise",
|
||||||
|
purpose = "tile-based AI raw denoise using the darktable.ai Lua API",
|
||||||
|
author = "Andrii Ryzhkov",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/ai_raw_denoise"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -27,8 +27,6 @@ local dt = require "darktable"
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("api_version", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -41,7 +39,7 @@ end
|
|||||||
|
|
||||||
local result = dt.configuration.api_version_string
|
local result = dt.configuration.api_version_string
|
||||||
dt.print_log("API Version: " .. result)
|
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
|
-- 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
|
-- 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 = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "api_version",
|
name = _("APIversion"),
|
||||||
purpose = _("display api_version example"),
|
purpose = _("display api_version example"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("darkroom_demo", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -93,7 +91,7 @@ dt.gui.current_view(dt.gui.views.darkroom)
|
|||||||
|
|
||||||
local max_images = 10
|
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)
|
sleep(1500)
|
||||||
|
|
||||||
-- display first 10 images of collection pausing for a second between each
|
-- 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 = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "darkroom_demo",
|
name = _("darkroom demo"),
|
||||||
purpose = _("example demonstrating how to control image display in darkroom mode"),
|
purpose = _("example demonstrating how to control image display in darkroom mode"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("gettextExample", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
-- Translate a string using the darktable textdomain
|
-- Translate a string using the darktable textdomain
|
||||||
dt.print_error(gettext("image"))
|
dt.print_error(gettext("image"))
|
||||||
|
|
||||||
@@ -88,7 +86,7 @@ dt.print_error(_("hello world!"))
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "gettextExample",
|
name = _("gettext example"),
|
||||||
purpose = _("example of how translations works"),
|
purpose = _("example of how translations works"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextExample"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextExample"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ local NaN = 0/0
|
|||||||
local wg = {}
|
local wg = {}
|
||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
dt.gettext.bindtextdomain("gui_action", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
@@ -16,7 +15,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "gui_action",
|
name = _("gui action"),
|
||||||
purpose = _("example of how to use darktable.gui.action() calls"),
|
purpose = _("example of how to use darktable.gui.action() calls"),
|
||||||
author = "Diederik ter Rahe",
|
author = "Diederik ter Rahe",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gui_action"
|
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"){
|
wg.action = dt.new_widget("entry"){
|
||||||
text = "lib/filter/view",
|
text = "lib/filter/view",
|
||||||
placeholder = "action path",
|
placeholder = _("action path"),
|
||||||
tooltip = "enter the full path of an action, for example 'lib/filter/view'"
|
tooltip = _("enter the full path of an action, for example 'lib/filter/view'")
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.instance = dt.new_widget("combobox"){
|
wg.instance = dt.new_widget("combobox"){
|
||||||
label = "instance",
|
label = _("instance"),
|
||||||
tooltip = "the instance of an image processing module to execute action on",
|
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"
|
"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"){
|
wg.element = dt.new_widget("entry"){
|
||||||
text = "",
|
text = "",
|
||||||
placeholder = "action element",
|
placeholder = _("action element"),
|
||||||
tooltip = "enter the element of an action, for example 'selection', or leave empty for default"
|
tooltip = _("enter the element of an action, for example 'selection', or leave empty for default")
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.effect = dt.new_widget("entry"){
|
wg.effect = dt.new_widget("entry"){
|
||||||
text = "next",
|
text = "next",
|
||||||
placeholder = "action effect",
|
placeholder = _("action effect"),
|
||||||
tooltip = "enter the effect of an action, for example 'next', or leave empty for default"
|
tooltip = _("enter the effect of an action, for example 'next', or leave empty for default")
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.speed = dt.new_widget("entry"){
|
wg.speed = dt.new_widget("entry"){
|
||||||
text = "1",
|
text = "1",
|
||||||
placeholder = "action speed",
|
placeholder = _("action speed"),
|
||||||
tooltip = "enter the speed to use in action execution, or leave empty to only read state"
|
tooltip = _("enter the speed to use in action execution, or leave empty to only read state")
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.check = dt.new_widget("check_button"){
|
wg.check = dt.new_widget("check_button"){
|
||||||
label = 'perform action',
|
label = _('perform action'),
|
||||||
tooltip = 'perform action or only read return',
|
tooltip = _('perform action or only read return'),
|
||||||
clicked_callback = function()
|
clicked_callback = function()
|
||||||
wg.speed.sensitive = wg.check.value
|
wg.speed.sensitive = wg.check.value
|
||||||
end,
|
end,
|
||||||
@@ -73,7 +72,7 @@ wg.return_value = dt.new_widget("entry"){
|
|||||||
|
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"execute_action", -- Module name
|
"execute_action", -- Module name
|
||||||
"execute gui actions", -- name
|
_("execute gui actions"), -- name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
false, -- resetable
|
false, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100},
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100},
|
||||||
@@ -85,37 +84,37 @@ dt.register_lib(
|
|||||||
dt.new_widget("box")
|
dt.new_widget("box")
|
||||||
{
|
{
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("label"){label = "action path", halign = "start"},
|
dt.new_widget("label"){label = _("action path"), halign = "start"},
|
||||||
wg.action
|
wg.action
|
||||||
},
|
},
|
||||||
wg.instance,
|
wg.instance,
|
||||||
dt.new_widget("box")
|
dt.new_widget("box")
|
||||||
{
|
{
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("label"){label = "element", halign = "start"},
|
dt.new_widget("label"){label = _("element"), halign = "start"},
|
||||||
wg.element
|
wg.element
|
||||||
},
|
},
|
||||||
dt.new_widget("box")
|
dt.new_widget("box")
|
||||||
{
|
{
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("label"){label = "effect", halign = "start"},
|
dt.new_widget("label"){label = _("effect"), halign = "start"},
|
||||||
wg.effect
|
wg.effect
|
||||||
},
|
},
|
||||||
wg.check,
|
wg.check,
|
||||||
dt.new_widget("box")
|
dt.new_widget("box")
|
||||||
{
|
{
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("label"){label = "speed", halign = "start"},
|
dt.new_widget("label"){label = _("speed"), halign = "start"},
|
||||||
wg.speed
|
wg.speed
|
||||||
},
|
},
|
||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = "execute action",
|
label = _("execute action"),
|
||||||
tooltip = "execute the action specified in the fields above",
|
tooltip = _("execute the action specified in the fields above"),
|
||||||
clicked_callback = function(_)
|
clicked_callback = function(_)
|
||||||
local sp = NaN
|
local sp = NaN
|
||||||
if wg.check.value then sp = wg.speed.text end
|
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
|
end
|
||||||
},
|
},
|
||||||
dt.new_widget("box")
|
dt.new_widget("box")
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ du.check_min_api_version("2.0.0", "hello_world")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("hello_world", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -55,7 +53,7 @@ dt.print(_("hello, world"))
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "hello_world",
|
name = _("hello world"),
|
||||||
purpose = _("example of how to print a message to the screen"),
|
purpose = _("example of how to print a message to the screen"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world"
|
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 = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "lighttable_demo",
|
name = _("lighttable demo"),
|
||||||
purpose = _("example demonstrating how to control lighttable display modes"),
|
purpose = _("example demonstrating how to control lighttable display modes"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo"
|
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
|
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("moduleExample", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -49,7 +47,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "moduleExample",
|
name = ("module example"),
|
||||||
purpose = _("example of how to create a lighttable module"),
|
purpose = _("example of how to create a lighttable module"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleExample"
|
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
|
-- https://www.darktable.org/lua-api/index.html#darktable_register_lib
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"exampleModule", -- Module name
|
"exampleModule", -- Module name
|
||||||
"exampleModule", -- name
|
_("example module"), -- name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
false, -- resetable
|
false, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||||
@@ -87,7 +85,7 @@ local function install_module()
|
|||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("my button"),
|
label = _("my ") .. "button",
|
||||||
clicked_callback = function (_)
|
clicked_callback = function (_)
|
||||||
dt.print(_("button clicked"))
|
dt.print(_("button clicked"))
|
||||||
end
|
end
|
||||||
@@ -112,10 +110,10 @@ local function restart()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/types_lua_check_button.html
|
-- 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
|
-- 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
|
-- https://www.darktable.org/lua-api/types_lua_entry.html
|
||||||
local entry = dt.new_widget("entry")
|
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
|
-- https://www.darktable.org/lua-api/types_lua_file_chooser_button.html
|
||||||
local file_chooser_button = dt.new_widget("file_chooser_button")
|
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
|
value = "", -- The currently selected file
|
||||||
is_directory = false -- True if the file chooser button only allows directories to be selecte
|
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
|
-- https://www.darktable.org/lua-api/types_lua_label.html
|
||||||
local label = dt.new_widget("label")
|
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
|
-- https://www.darktable.org/lua-api/types_lua_separator.html
|
||||||
local separator = dt.new_widget("separator"){}
|
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
|
-- https://www.darktable.org/lua-api/types_lua_slider.html
|
||||||
local slider = dt.new_widget("slider")
|
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_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
|
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
|
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.
|
by hovering over the image and pressing the key combination.
|
||||||
|
|
||||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
* ufraw-batch - https://ufraw.sourceforge.net
|
* exiftool - https://exiftool.org
|
||||||
MacOS - install with homebrew
|
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
* require this script from your main lua file
|
* require this script from your main lua file
|
||||||
@@ -44,6 +43,7 @@
|
|||||||
* Send to Bill Ferguson, wpferguson@gmail.com
|
* Send to Bill Ferguson, wpferguson@gmail.com
|
||||||
|
|
||||||
CHANGES
|
CHANGES
|
||||||
|
20251205 - replaced ufraw-batch with exiftool
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -72,8 +72,6 @@ local dtsys = require "lib/dtutils.system" -- system utilities
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("multi_os", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -141,6 +139,36 @@ local function copy_image_attributes(from, to, ...)
|
|||||||
end
|
end
|
||||||
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
|
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
|
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.
|
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")
|
local exiftool_executable = df.check_if_bin_exists("exiftool")
|
||||||
if ufraw_executable then
|
if exiftool_executable then
|
||||||
for _, image in ipairs(images) do
|
for _, image in ipairs(images) do
|
||||||
if image.is_raw then
|
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.
|
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.
|
operating system aware and uses the corresponding quotes.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
if dtsys.external_command(ufraw_executable .. " --silent --embedded-image " .. df.sanitize_filename(img_file)) then
|
if dtsys.external_command(exiftool_executable .. " -b -JpgFromRaw " .. df.sanitize_filename(img_file) .. " > " .. df.sanitize_filename(jpg_img_file)) then
|
||||||
local jpg_img_file = df.chop_filetype(img_file) .. ".embedded.jpg"
|
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
|
dt.print_log("jpg_img_file set to ", jpg_img_file) -- print a debugging message
|
||||||
local myimage = dt.database.import(jpg_img_file)
|
local myimage = dt.database.import(jpg_img_file)
|
||||||
myimage:group_with(image.group_leader)
|
myimage:group_with(image.group_leader)
|
||||||
@@ -190,8 +223,8 @@ local function extract_embedded_jpeg(images)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
dt.print_error("ufraw-batch not found. Exiting...") -- print debugging error message
|
dt.print_error("exiftool not found. Exiting...") -- print debugging error message
|
||||||
dt.print("ufraw-batch not found, exiting...") -- print the error to the screen
|
dt.print("exiftool not found, exiting...") -- print the error to the screen
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -215,7 +248,7 @@ end
|
|||||||
if dt.configuration.running_os ~= "linux" then
|
if dt.configuration.running_os ~= "linux" then
|
||||||
local executable = "ufraw-batch"
|
local executable = "ufraw-batch"
|
||||||
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
|
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),
|
value = df.get_executable_path_preference(executable),
|
||||||
is_directory = false,
|
is_directory = false,
|
||||||
changed_callback = function(self)
|
changed_callback = function(self)
|
||||||
@@ -226,7 +259,7 @@ if dt.configuration.running_os ~= "linux" then
|
|||||||
}
|
}
|
||||||
dt.preferences.register("executable_paths", "ufraw-batch", -- name
|
dt.preferences.register("executable_paths", "ufraw-batch", -- name
|
||||||
"file", -- type
|
"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
|
_('installed location of ufraw-batch, requires restart to take effect.'), -- tooltip
|
||||||
"ufraw-batch", -- default
|
"ufraw-batch", -- default
|
||||||
ufraw_batch_path_widget
|
ufraw_batch_path_widget
|
||||||
@@ -262,7 +295,7 @@ dt.register_event(
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "multi_os",
|
name = _("multi OS"),
|
||||||
purpose = _("example module thet runs on different operating systems"),
|
purpose = _("example module thet runs on different operating systems"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/multi_os"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("panels_demo", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -95,11 +93,11 @@ dt.gui.panel_show_all()
|
|||||||
|
|
||||||
-- hide center_top, center_bottom, left, top, right, bottom in order
|
-- 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)
|
sleep(1500)
|
||||||
|
|
||||||
for i = 1, #panels do
|
for i = 1, #panels do
|
||||||
dt.print(_("hiding " .. panels[i]))
|
dt.print(string.format(_("hiding %s"), panels[i]))
|
||||||
dt.gui.panel_hide(panels[i])
|
dt.gui.panel_hide(panels[i])
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
end
|
end
|
||||||
@@ -110,7 +108,7 @@ end
|
|||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
for i = #panels, 1, -1 do
|
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])
|
dt.gui.panel_show(panels[i])
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
end
|
end
|
||||||
@@ -148,7 +146,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "panels_demo",
|
name = _("panels demo"),
|
||||||
purpose = _("example demonstrating how to contol panel visibility"),
|
purpose = _("example demonstrating how to contol panel visibility"),
|
||||||
author = "Bill Ferguson <wpferguson@gmail.com>",
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("preferenceExamples", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -37,7 +35,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "preferenceExamples",
|
name = _("preference examples"),
|
||||||
purpose = _("example to show the different preference types that are possible"),
|
purpose = _("example to show the different preference types that are possible"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceExamples"
|
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.
|
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
|
"preferenceExamplesString", -- name
|
||||||
"string", -- type
|
"string", -- type
|
||||||
_("example string"), -- label
|
_("example") .. " string", -- label
|
||||||
_("example string tooltip"), -- tooltip
|
_("example") .. " string " .. _("tooltip"), -- tooltip
|
||||||
"String") -- default
|
"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.
|
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
|
"preferenceExamplesBool", -- name
|
||||||
"bool", -- type
|
"bool", -- type
|
||||||
_("example boolean"), -- label
|
_("example") .. " boolean", -- label
|
||||||
_("example boolean tooltip"), -- tooltip
|
_("example") .. " boolean " .. _("tooltip"), -- tooltip
|
||||||
true) -- default
|
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.
|
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
|
"preferenceExamplesInteger", -- name
|
||||||
"integer", -- type
|
"integer", -- type
|
||||||
_("example integer"), -- label
|
_("example") .. " integer", -- label
|
||||||
_("example integer tooltip"), -- tooltip
|
_("example") .. " integer " .. _("tooltip"), -- tooltip
|
||||||
2, -- default
|
2, -- default
|
||||||
1, -- min
|
1, -- min
|
||||||
99) -- max
|
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.
|
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
|
"preferenceExamplesFloat", -- name
|
||||||
"float", -- type
|
"float", -- type
|
||||||
_("example float"), -- label
|
_("example") .. " float", -- label
|
||||||
_("example float tooltip"), -- tooltip
|
_("example") .. " float " .. _("tooltip"), -- tooltip
|
||||||
1.3, -- default
|
1.3, -- default
|
||||||
1, -- min
|
1, -- min
|
||||||
99, -- max
|
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.
|
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
|
"preferenceExamplesFile", -- name
|
||||||
"file", -- type
|
"file", -- type
|
||||||
_("example file"), -- label
|
_("example") .. " file", -- label
|
||||||
_("example file tooltip"), -- tooltip
|
_("example") .. " file " .. _("tooltip"), -- tooltip
|
||||||
"") -- default
|
"") -- 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.
|
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
|
"preferenceExamplesDirectory", -- name
|
||||||
"directory", -- type
|
"directory", -- type
|
||||||
_("example directory"), -- label
|
_("example") .. " directory", -- label
|
||||||
_("example directory tooltip"), -- tooltip
|
_("example") .. " directory " .. _("tooltip"), -- tooltip
|
||||||
"") -- default
|
"") -- 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.
|
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
|
"preferenceExamplesEnum", -- name
|
||||||
"enum", -- type
|
"enum", -- type
|
||||||
_("example enum"), -- label
|
_("example") .. " enum", -- label
|
||||||
_("example enum tooltip"), -- tooltip
|
_("example") .. " enum " .. _("tooltip"), -- tooltip
|
||||||
"Enum 1", -- default
|
"Enum 1", -- default
|
||||||
"Enum 1", "Enum 2") -- values
|
"Enum 1", "Enum 2") -- values
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ du.check_min_api_version("5.0.0", "printExamples")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("printExamples", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -60,7 +58,7 @@ dt.print_log("print log")
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "printExamples",
|
name = _("print examples"),
|
||||||
purpose = _("example showing the different types of printing messages"),
|
purpose = _("example showing the different types of printing messages"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printExamples"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("running_os", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -54,7 +52,7 @@ dt.print(string.format(_("you are running: %s"), dt.configuration.running_os))
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "running_os",
|
name = _("running OS"),
|
||||||
purpose = _("example of how to determine the operating system being used"),
|
purpose = _("example of how to determine the operating system being used"),
|
||||||
author = "Tobias Jakobs",
|
author = "Tobias Jakobs",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os"
|
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:A#-1=iop/toneequal;focus
|
||||||
midi:B-1=iop/colorbalancergb;focus
|
midi:B-1=iop/colorbalancergb;focus
|
||||||
midi:C0=iop/channelmixerrgb;focus
|
midi:C0=iop/channelmixerrgb;focus
|
||||||
|
midi:C#0=iop/colorequal;focus
|
||||||
|
midi:D0=iop/colorequal/page;previous
|
||||||
|
midi:D#0=iop/colorequal/page;next
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
To add a new module, simply add an entry to MODULE_PROFILES below.
|
||||||
|
Get action paths from darktable's shortcut interface.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
@@ -56,139 +63,283 @@ local du = require "lib/dtutils"
|
|||||||
du.check_min_api_version("9.2.0", "x-touch")
|
du.check_min_api_version("9.2.0", "x-touch")
|
||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
dt.gettext.bindtextdomain("gui_action", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
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
|
local DEFAULT_MAPPINGS = {
|
||||||
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
{"iop/exposure/exposure"},
|
||||||
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
{"iop/filmicrgb/white relative exposure"},
|
||||||
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
{"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
|
-- Priority order for checking module focus (most commonly used first)
|
||||||
for k = 1,8 do
|
local MODULE_CHECK_ORDER = {
|
||||||
dt.gui.mimic("slider", "knob ".. k,
|
"toneequal",
|
||||||
function(action, element, effect, size)
|
"colorequal",
|
||||||
-- take the number from the mimic name
|
"colorbalancergb",
|
||||||
local k = tonumber(action:sub(-1))
|
"agx",
|
||||||
|
"sigmoid",
|
||||||
|
"primaries",
|
||||||
|
"channelmixerrgb",
|
||||||
|
"colorzones"
|
||||||
|
}
|
||||||
|
|
||||||
-- only operate in darkroom; return NAN otherwise
|
-- ============================================================================
|
||||||
if dt.gui.current_view() ~= dt.gui.views.darkroom then
|
-- CORE LOGIC - No need to edit below unless changing architecture
|
||||||
return 0/0
|
-- ============================================================================
|
||||||
end
|
|
||||||
|
|
||||||
local maskval = 0/0
|
-- Helper functions
|
||||||
if k < 8 then
|
local function is_in_darkroom()
|
||||||
-- first try if the mask slider at that position is active
|
return dt.gui.current_view() == darkroom_view
|
||||||
local s = { "opacity",
|
end
|
||||||
"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
|
|
||||||
|
|
||||||
-- try if colorzones module is focused; if so select element of graph
|
local function is_valid(value)
|
||||||
elseif dt.gui.action("iop/colorzones", "focus") ~= 0 then
|
return value == value -- NaN check
|
||||||
which = "iop/colorzones/graph"
|
end
|
||||||
local e = { "red",
|
|
||||||
"orange",
|
|
||||||
"yellow",
|
|
||||||
"green",
|
|
||||||
"aqua",
|
|
||||||
"blue",
|
|
||||||
"purple",
|
|
||||||
"magenta" }
|
|
||||||
element = e[k]
|
|
||||||
|
|
||||||
|
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,
|
-- Execute an action configuration
|
||||||
-- check sliders
|
local function execute_action(action_config, element, effect, size)
|
||||||
elseif dt.gui.action("iop/sigmoid", "focus") ~= 0 and k <8 then
|
if not action_config then
|
||||||
local e = { "red attenuation", "red rotation", "green attenuation", "green rotation", "blue attenuation", "blue rotation", "recover purity" }
|
return nil
|
||||||
which = "iop/sigmoid/primaries/"..e[k]
|
end
|
||||||
|
|
||||||
-- if the rgb primaries is focused,
|
local path = action_config[1]
|
||||||
-- check sliders
|
local elem = action_config[2] or element
|
||||||
elseif dt.gui.action("iop/primaries", "focus") ~= 0 and k >=1 then
|
local action_type = action_config[3]
|
||||||
local e = { "red hue", "red purity", "green hue", "green purity", "blue hue", "blue purity", "tint hue", "tint purity" }
|
|
||||||
which = "iop/primaries/" ..e[k]
|
|
||||||
|
|
||||||
-- if the tone equalizer is focused,
|
-- Handle special action types
|
||||||
-- select one of the sliders in the "simple" tab
|
if action_type == "tab" then
|
||||||
elseif dt.gui.action("iop/toneequal", "focus") ~= 0 then
|
effect = translate_tab_effect(effect)
|
||||||
which ="iop/toneequal/simple/"..(k-9).." EV"
|
end
|
||||||
|
|
||||||
-- if color calibration is focused,
|
return dt.gui.action(path, elem, effect, size)
|
||||||
-- the last 4 knobs are sent there
|
end
|
||||||
elseif dt.gui.action("iop/channelmixerrgb", "focus") ~= 0
|
|
||||||
and k >= 5 then
|
-- Try mask properties first
|
||||||
-- knob 5 selects the active tab; pressing it resets to CAT
|
local function try_mask_properties(knob_idx, element, effect, size)
|
||||||
if k == 5 then
|
if knob_idx >= NUM_KNOBS then
|
||||||
which = "iop/channelmixerrgb/page"
|
return NAN
|
||||||
element = "CAT"
|
end
|
||||||
-- since the tab action is not a slider,
|
|
||||||
-- the effects need to be translated
|
local mask_action = MASK_PROPERTIES[knob_idx]
|
||||||
if effect == "up" then effect = "next"
|
if not mask_action then
|
||||||
elseif effect == "down" then effect = "previous"
|
return NAN
|
||||||
else effect = "activate"
|
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
|
end
|
||||||
else
|
else
|
||||||
-- knobs 6, 7 and 8 are for the three color sliders on each tab
|
-- Profile exists but this knob is nil -> use default
|
||||||
which = "iop/focus/sliders"
|
-- This avoids checking other modules when we know the focused one
|
||||||
local e = { "1st",
|
local default_action = DEFAULT_MAPPINGS[knob_idx]
|
||||||
"2nd",
|
return execute_action(default_action, element, effect, size) or NAN
|
||||||
"3rd" }
|
|
||||||
element = e[k - 5]
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- now pass the element/effect/size to the selected slider
|
-- If no profile exists for this module, continue checking others
|
||||||
return dt.gui.action(which, element, effect, size)
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
local function destroy()
|
-- Setup mimic sliders
|
||||||
-- nothing to destroy
|
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
|
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
|
return script_data
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ dtutils.libdoc.functions["check_min_api_version"] = {
|
|||||||
|
|
||||||
function dtutils.check_min_api_version(min_api, script_name)
|
function dtutils.check_min_api_version(min_api, script_name)
|
||||||
local current_api = dt.configuration.api_version_string
|
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("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("The current lua api version is " .. current_api)
|
||||||
dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. min_api .. " or later required.")
|
dt.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)
|
function dtutils.check_max_api_version(max_api, script_name)
|
||||||
local current_api = dt.configuration.api_version_string
|
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("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("The current lua api version is " .. current_api)
|
||||||
dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. max_api .. " or earlier required.")
|
dt.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)
|
du.deprecated(script_name, removal_string)
|
||||||
script_name - name of the script being deprecated
|
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]],
|
Description = [[deprecated prints an error message saying the script is deprecated and when it will be removed]],
|
||||||
Return_Value = [[]],
|
Return_Value = [[]],
|
||||||
Limitations = [[]],
|
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)
|
dt.print_error("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
|
||||||
end
|
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
|
return dtutils
|
||||||
|
|||||||
@@ -24,15 +24,12 @@ dtutils_file.libdoc = {
|
|||||||
functions = {}
|
functions = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("5.0.0", "dtutils.file")
|
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)
|
local function _(msgid)
|
||||||
return gettext.dgettext("dtutils.file", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -522,9 +519,9 @@ function dtutils_file.file_copy(fromFile, toFile)
|
|||||||
local result = nil
|
local result = nil
|
||||||
-- if cp exists, use it
|
-- if cp exists, use it
|
||||||
if dt.configuration.running_os == "windows" then
|
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
|
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
|
end
|
||||||
|
|
||||||
-- if cp was not present, or if cp failed, then a pure lua solution
|
-- 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
|
if not success then
|
||||||
-- an error occurred, so let's try using the operating system function
|
-- an error occurred, so let's try using the operating system function
|
||||||
if dtutils_file.check_if_bin_exists("mv") then
|
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
|
end
|
||||||
-- if the mv didn't exist or succeed, then...
|
-- if the mv didn't exist or succeed, then...
|
||||||
if not success then
|
if not success then
|
||||||
@@ -815,7 +812,7 @@ dtutils_file.libdoc.functions["mkdir"] = {
|
|||||||
function dtutils_file.mkdir(path)
|
function dtutils_file.mkdir(path)
|
||||||
if not dtutils_file.check_if_file_exists(path) then
|
if not dtutils_file.check_if_file_exists(path) then
|
||||||
local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p"
|
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
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@@ -840,7 +837,7 @@ dtutils_file.libdoc.functions["rmdir"] = {
|
|||||||
|
|
||||||
function dtutils_file.rmdir(path)
|
function dtutils_file.rmdir(path)
|
||||||
local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r"
|
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
|
end
|
||||||
|
|
||||||
dtutils_file.libdoc.functions["create_tmp_file"] = {
|
dtutils_file.libdoc.functions["create_tmp_file"] = {
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ local dtutils_string = {}
|
|||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
local log = require "lib/dtutils.log"
|
local log = require "lib/dtutils.log"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local DEFAULT_LOG_LEVEL = log.error
|
local DEFAULT_LOG_LEVEL = log.error
|
||||||
|
|
||||||
|
local function _(msg)
|
||||||
|
return gettext(msg)
|
||||||
|
end
|
||||||
|
|
||||||
dtutils_string.log_level = DEFAULT_LOG_LEVEL
|
dtutils_string.log_level = DEFAULT_LOG_LEVEL
|
||||||
|
|
||||||
dtutils_string.libdoc = {
|
dtutils_string.libdoc = {
|
||||||
@@ -301,8 +306,16 @@ end
|
|||||||
local function _should_be_sanitized(str)
|
local function _should_be_sanitized(str)
|
||||||
local old_log_level = log.log_level()
|
local old_log_level = log.log_level()
|
||||||
local result = false
|
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)
|
log.log_level(dtutils_string.log_level)
|
||||||
if string.match(str, "[^%g]") then
|
if string.match(str, pattern) then
|
||||||
result = true
|
result = true
|
||||||
end
|
end
|
||||||
log.log_level(old_log_level)
|
log.log_level(old_log_level)
|
||||||
@@ -530,10 +543,13 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
|||||||
"FILE.NAME",
|
"FILE.NAME",
|
||||||
"FILE.EXTENSION",
|
"FILE.EXTENSION",
|
||||||
"ID",
|
"ID",
|
||||||
|
"IMAGE.ID",
|
||||||
|
"IMAGE.ID.NEXT",
|
||||||
"VERSION",
|
"VERSION",
|
||||||
"VERSION.IF.MULTI",
|
"VERSION.IF.MULTI",
|
||||||
"VERSION.NAME",
|
"VERSION.NAME",
|
||||||
"DARKTABLE.VERSION",
|
"DARKTABLE.VERSION",
|
||||||
|
"Xmp.darktable.version",
|
||||||
"DARKTABLE.NAME", -- Not Implemented
|
"DARKTABLE.NAME", -- Not Implemented
|
||||||
"SEQUENCE",
|
"SEQUENCE",
|
||||||
"WIDTH.SENSOR",
|
"WIDTH.SENSOR",
|
||||||
@@ -553,6 +569,7 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
|||||||
"MONTH.SHORT",
|
"MONTH.SHORT",
|
||||||
"DAY",
|
"DAY",
|
||||||
"HOUR",
|
"HOUR",
|
||||||
|
"HOUR.AMPM", -- Not Implemented
|
||||||
"MINUTE",
|
"MINUTE",
|
||||||
"SECOND",
|
"SECOND",
|
||||||
"MSEC",
|
"MSEC",
|
||||||
@@ -563,6 +580,7 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
|||||||
"EXIF.MONTH.SHORT",
|
"EXIF.MONTH.SHORT",
|
||||||
"EXIF.DAY",
|
"EXIF.DAY",
|
||||||
"EXIF.HOUR",
|
"EXIF.HOUR",
|
||||||
|
"EXIF.HOUR.AMPM", -- Not Implemented
|
||||||
"EXIF.MINUTE",
|
"EXIF.MINUTE",
|
||||||
"EXIF.SECOND",
|
"EXIF.SECOND",
|
||||||
"EXIF.MSEC",
|
"EXIF.MSEC",
|
||||||
@@ -571,12 +589,23 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
|||||||
"EXIF.ISO",
|
"EXIF.ISO",
|
||||||
"EXIF.EXPOSURE",
|
"EXIF.EXPOSURE",
|
||||||
"EXIF.EXPOSURE.BIAS",
|
"EXIF.EXPOSURE.BIAS",
|
||||||
|
"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", -- Not Implemented
|
"EXIF.FOCAL.LENGTH.EQUIV", -- Not Implemented
|
||||||
"EXIF.FOCUS.DISTANCE",
|
"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",
|
"LONGITUDE",
|
||||||
"LATITUDE",
|
"LATITUDE",
|
||||||
"ELEVATION",
|
"ELEVATION",
|
||||||
@@ -585,14 +614,16 @@ local PLACEHOLDERS = {"ROLL.NAME",
|
|||||||
"RATING.ICONS", -- Not Implemented
|
"RATING.ICONS", -- Not Implemented
|
||||||
"LABELS",
|
"LABELS",
|
||||||
"LABELS.ICONS", -- Not Implemented
|
"LABELS.ICONS", -- Not Implemented
|
||||||
"MAKER",
|
|
||||||
"MODEL",
|
|
||||||
"LENS",
|
|
||||||
"TITLE",
|
"TITLE",
|
||||||
|
"Xmp.dc.title",
|
||||||
"DESCRIPTION",
|
"DESCRIPTION",
|
||||||
|
"Xmp.dc.description",
|
||||||
"CREATOR",
|
"CREATOR",
|
||||||
|
"Xmp.dc.creator",
|
||||||
"PUBLISHER",
|
"PUBLISHER",
|
||||||
|
"Xmp.dc.publisher",
|
||||||
"RIGHTS",
|
"RIGHTS",
|
||||||
|
"Xmp.dc.rights",
|
||||||
"TAGS", -- Not Implemented
|
"TAGS", -- Not Implemented
|
||||||
"SIDECAR.TXT", -- Not Implemented
|
"SIDECAR.TXT", -- Not Implemented
|
||||||
"FOLDER.PICTURES",
|
"FOLDER.PICTURES",
|
||||||
@@ -629,6 +660,37 @@ local function get_colorlabels(image)
|
|||||||
return labels
|
return labels
|
||||||
end
|
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
|
-- find the $CATEGORYn and $CATEGORY[n,m] requests and add them to the substitute list
|
||||||
|
|
||||||
local function build_category_substitution_list(image, variable_string)
|
local function build_category_substitution_list(image, variable_string)
|
||||||
@@ -688,7 +750,7 @@ end
|
|||||||
|
|
||||||
-- build the argument substitution list from each image
|
-- 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()
|
local old_log_level = log.log_level()
|
||||||
log.log_level(dtutils_string.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
|
is_api_9_1 = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local is_api_9_4 = dt.configuration.api_version_string >= "9.4.0" and true or false
|
||||||
|
|
||||||
local datetime = os.date("*t")
|
local datetime = os.date("*t")
|
||||||
local long_month = os.date("%B")
|
local long_month = os.date("%B")
|
||||||
local short_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 labels = get_colorlabels(image)
|
||||||
|
|
||||||
|
local emon_long, emon_short
|
||||||
local eyear, emon, eday, ehour, emin, esec, emsec
|
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
|
if dt.preferences.read("darktable", "lighttable/ui/milliseconds", "bool") and is_api_9_1 then
|
||||||
eyear, emon, eday, ehour, emin, esec, emsec =
|
use_millisecs = true
|
||||||
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
|
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.path, -- FILE.FOLDER
|
||||||
image.filename, -- FILE.NAME
|
dtutils_string.get_basename(image.filename),-- FILE.NAME
|
||||||
dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION
|
dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION
|
||||||
image.id, -- ID
|
image.id, -- ID
|
||||||
|
image.id, -- IMAGE.ID
|
||||||
|
dt.database[#dt.database].id + 1, -- IMAGE.ID.NEXT
|
||||||
image.duplicate_index, -- VERSION
|
image.duplicate_index, -- VERSION
|
||||||
version_multi, -- VERSION.IF_MULTI
|
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, -- DARKTABLE.VERSION
|
||||||
|
dt.configuration.version, -- Xmp.darktable.version
|
||||||
"", -- DARKTABLE.NAME
|
"", -- DARKTABLE.NAME
|
||||||
string.format("%04d", sequence), -- SEQUENCE
|
string.format("%04d", sequence), -- SEQUENCE
|
||||||
image.width, -- WIDTH.SENSOR
|
image.width, -- WIDTH.SENSOR
|
||||||
@@ -749,16 +829,18 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
|||||||
short_month, -- MONTH.SHORT
|
short_month, -- MONTH.SHORT
|
||||||
string.format("%02d", datetime.day), -- DAY
|
string.format("%02d", datetime.day), -- DAY
|
||||||
string.format("%02d", datetime.hour), -- HOUR
|
string.format("%02d", datetime.hour), -- HOUR
|
||||||
|
"", -- HOUR.AMPM
|
||||||
string.format("%02d", datetime.min), -- MINUTE
|
string.format("%02d", datetime.min), -- MINUTE
|
||||||
string.format("%02d", datetime.sec), -- SECOND
|
string.format("%02d", datetime.sec), -- SECOND
|
||||||
0, -- MSEC
|
0, -- MSEC
|
||||||
eyear, -- EXIF.YEAR
|
eyear, -- EXIF.YEAR
|
||||||
string.sub(eyear, 3), -- EXIF.YEAR.SHORT
|
(eyear ~= nil) and string.sub(eyear, 3) or "", -- EXIF.YEAR.SHORT
|
||||||
emon, -- EXIF.MONTH
|
emon, -- EXIF.MONTH
|
||||||
os.date("%B", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.LONG
|
emon_long, -- EXIF.MONTH.LONG
|
||||||
os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT
|
emon_short, -- EXIF.MONTH.SHORT
|
||||||
eday, -- EXIF.DAY
|
eday, -- EXIF.DAY
|
||||||
ehour, -- EXIF.HOUR
|
ehour, -- EXIF.HOUR
|
||||||
|
"", -- EXIF.HOUR.AMPM
|
||||||
emin, -- EXIF.MINUTE
|
emin, -- EXIF.MINUTE
|
||||||
esec, -- EXIF.SECOND
|
esec, -- EXIF.SECOND
|
||||||
emsec, -- EXIF.MSEC
|
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("%d", image.exif_iso), -- EXIF.ISO
|
||||||
string.format("%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE
|
string.format("%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE
|
||||||
image.exif_exposure_bias, -- EXIF.EXPOSURE.BIAS
|
image.exif_exposure_bias, -- EXIF.EXPOSURE.BIAS
|
||||||
|
"", -- EXIF.EXPOSURE.PROGRAM
|
||||||
string.format("%.01f", image.exif_aperture), -- EXIF.APERTURE
|
string.format("%.01f", image.exif_aperture), -- EXIF.APERTURE
|
||||||
string.format("%.01f", image.exif_crop),-- EXIF.CROP_FACTOR
|
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), -- EXIF.FOCAL.LENGTH
|
||||||
string.format("%.0f", image.exif_focal_length * image.exif_crop), -- EXIF.FOCAL.LENGTH.EQUIV
|
string.format("%.0f", image.exif_focal_length * image.exif_crop), -- EXIF.FOCAL.LENGTH.EQUIV
|
||||||
image.exif_focus_distance, -- EXIF.FOCUS.DISTANCE
|
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.longitude or "", -- LONGITUDE
|
||||||
image.latitude or "", -- LATITUDE
|
image.latitude or "", -- LATITUDE
|
||||||
image.elevation or "", -- ELEVATION
|
image.elevation or "", -- ELEVATION
|
||||||
@@ -781,21 +874,23 @@ function dtutils_string.build_substition_list(image, sequence, variable_string,
|
|||||||
"", -- RATING.ICONS - wont be implemented
|
"", -- RATING.ICONS - wont be implemented
|
||||||
labels, -- LABELS
|
labels, -- LABELS
|
||||||
"", -- LABELS.ICONS - wont be implemented
|
"", -- LABELS.ICONS - wont be implemented
|
||||||
image.exif_maker, -- MAKER
|
image.title and image.title or "", -- TITLE
|
||||||
image.exif_model, -- MODEL
|
image.title and image.title or "", -- Xmp.dc.title
|
||||||
image.exif_lens, -- LENS
|
image.description and image.description or "", -- DESCRIPTION
|
||||||
image.title, -- TITLE
|
image.description and image.description or "", -- Xmp.dc.description
|
||||||
image.description, -- DESCRIPTION
|
image.creator and image.creator or "", -- CREATOR
|
||||||
image.creator, -- CREATOR
|
image.creator and image.creator or "", -- Xmp.dc.creator
|
||||||
image.publisher, -- PUBLISHER
|
image.publisher and image.publisher or "", -- PUBLISHER
|
||||||
image.rights, -- RIGHTS
|
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
|
"", -- TAGS - wont be implemented
|
||||||
"", -- SIDECAR.TXT - wont be implemented
|
"", -- SIDECAR.TXT - wont be implemented
|
||||||
pictures_folder, -- FOLDER.PICTURES
|
pictures_folder, -- FOLDER.PICTURES
|
||||||
home_folder, -- FOLDER.HOME
|
home_folder, -- FOLDER.HOME
|
||||||
desktop_folder, -- FOLDER.DESKTOP
|
desktop_folder, -- FOLDER.DESKTOP
|
||||||
"", -- OPENCL.ACTIVATED - wont be implemented
|
"", -- OPENCL.ACTIVATED - wont be implemented
|
||||||
user_name, -- USERNAME
|
(user_name ~= nil) and user_name or "",-- USERNAME
|
||||||
"", -- NL - wont be implemented
|
"", -- NL - wont be implemented
|
||||||
"" -- JOBCODE - 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
|
-- do category substitutions separately
|
||||||
|
|
||||||
build_category_substitution_list(image, variable_string)
|
build_category_substitution_list(image, variable_string)
|
||||||
|
build_rollname_substitution_list(image, variable_string)
|
||||||
|
|
||||||
log.log_level(old_log_level)
|
log.log_level(old_log_level)
|
||||||
end
|
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
|
-- handle different versions of names
|
||||||
|
|
||||||
local function check_legacy_vars(var_name)
|
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
|
if string.match(var_string, "CATEGORY%d") or string.match(var_string, "CATEGORY%[") then
|
||||||
log.msg(log.info, "substituting for " .. var_string)
|
log.msg(log.info, "substituting for " .. var_string)
|
||||||
ret_val = substitutes[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)
|
log.msg(log.info, "ret_val is " .. ret_val)
|
||||||
|
|
||||||
elseif string.match(var_string, "SEQUENCE%[") then
|
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 seq_val = tonumber(substitutes[var])
|
||||||
local pat = "%0" .. width .. "d"
|
local pat = "%0" .. width .. "d"
|
||||||
substitutes[var_string] = string.format(pat, start + (seq_val - 1))
|
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
|
return str
|
||||||
end
|
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
|
return dtutils_string
|
||||||
|
|||||||
@@ -86,8 +86,15 @@ function dtutils_system.windows_command(command)
|
|||||||
local file = io.open(fname, "w")
|
local file = io.open(fname, "w")
|
||||||
if file then
|
if file then
|
||||||
dt.print_log("opened file")
|
dt.print_log("opened file")
|
||||||
command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell
|
file:write("@echo off\n")
|
||||||
file:write(command)
|
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()
|
file:close()
|
||||||
|
|
||||||
result = dt.control.execute(fname)
|
result = dt.control.execute(fname)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ msgstr ""
|
|||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"Language: \n"
|
"Language: \n"
|
||||||
"MIME-Version: 1.0\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"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 1.5.4\n"
|
"X-Generator: Poedit 1.5.4\n"
|
||||||
"Language: de_DE\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
|
||||||
288
official/auto_straighten.lua
Normal file
288
official/auto_straighten.lua
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
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
|
||||||
|
local sign = 1.0
|
||||||
|
dt.print_log("roll angle is " .. val)
|
||||||
|
log.msg(log.debug, "roll angle is " .. val)
|
||||||
|
-- check for Nidon and change the sign
|
||||||
|
if string.match(string.upper(image.exif_maker), "NIKON") then
|
||||||
|
sign = -1.0
|
||||||
|
end
|
||||||
|
roll = determine_correction(tonumber(val) * sign)
|
||||||
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("check_for_updates", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -47,7 +45,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "check_for_updates",
|
name = _("check for updates"),
|
||||||
purpose = _("check for newer darktable releases"),
|
purpose = _("check for newer darktable releases"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates"
|
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")
|
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)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -42,7 +40,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "copy_paste_metadata",
|
name = _("copy paste metadata"),
|
||||||
purpose = _("adds keyboard shortcuts and buttons to copy/paste metadata between images"),
|
purpose = _("adds keyboard shortcuts and buttons to copy/paste metadata between images"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("delete_long_tags", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -46,7 +44,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "delete_long_tags",
|
name = _("delete long tags"),
|
||||||
purpose = _("delete all tags longer than a set length"),
|
purpose = _("delete all tags longer than a set length"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("delete_unused_tags", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -50,7 +48,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "delete_unused_tags",
|
name = _("delete unused tags"),
|
||||||
purpose = _("delete unused tags"),
|
purpose = _("delete unused tags"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags"
|
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")
|
du.check_min_api_version("7.0.0", "enfuse")
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("enfuse", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -54,7 +52,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "enfuse",
|
name = _("enfuse"),
|
||||||
purpose = _("exposure blend images"),
|
purpose = _("exposure blend images"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/enfuse"
|
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
|
if not enf.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"enfuse", -- plugin name
|
"enfuse", -- plugin name
|
||||||
"enfuse", -- name
|
_("enfuse"), -- name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
false, -- resetable
|
false, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
{[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
|
if version < "4.2" then
|
||||||
exposure_mu = dt.new_widget("slider")
|
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"),
|
tooltip = _("center also known as mean of gaussian weighting function (0 <= mean <= 1); default: 0.5"),
|
||||||
hard_min = 0,
|
hard_min = 0,
|
||||||
hard_max = 1,
|
hard_max = 1,
|
||||||
@@ -146,7 +144,7 @@ if enfuse_installed then
|
|||||||
else
|
else
|
||||||
exposure_mu = dt.new_widget("slider")
|
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"),
|
tooltip = _("optimum exposure value, usually the maximum of the weighting function (0 <= optimum <=1); default 0.5"),
|
||||||
hard_min = 0,
|
hard_min = 0,
|
||||||
hard_max = 1,
|
hard_max = 1,
|
||||||
@@ -156,7 +154,7 @@ if enfuse_installed then
|
|||||||
|
|
||||||
local depth = dt.new_widget("combobox")
|
local depth = dt.new_widget("combobox")
|
||||||
{
|
{
|
||||||
label = "depth",
|
label = _("depth"),
|
||||||
tooltip = _("the number of bits per channel of the output image"),
|
tooltip = _("the number of bits per channel of the output image"),
|
||||||
value = dt.preferences.read("enfuse", "depth", "integer"),
|
value = dt.preferences.read("enfuse", "depth", "integer"),
|
||||||
changed_callback = function(w) dt.preferences.write("enfuse", "depth", "integer", w.selected) end,
|
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")
|
local blend_colorspace = dt.new_widget("combobox")
|
||||||
{
|
{
|
||||||
label = "blend colorspace",
|
label = _("blend colorspace"),
|
||||||
tooltip = _("force blending in selected colorspace"),
|
tooltip = _("force blending in selected colorspace"),
|
||||||
changed_callback = function(w) dt.preferences.write("enfuse", "blend_colorspace", "string", w.selected) end,
|
changed_callback = function(w) dt.preferences.write("enfuse", "blend_colorspace", "string", w.selected) end,
|
||||||
"", "identity", "ciecam"
|
"", "identity", "ciecam"
|
||||||
|
|||||||
568
official/extract_burst_roll_images.lua
Normal file
568
official/extract_burst_roll_images.lua
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
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 EXIV2TAG <const> = "Exif.Canon.RawBurstModeRoll"
|
||||||
|
|
||||||
|
local HAS_INTERNAL_METADATA <const> = dt.metadata.exists(EXIV2TAG)
|
||||||
|
|
||||||
|
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.select_button = 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
|
||||||
|
false) -- default
|
||||||
|
|
||||||
|
-- display selection button
|
||||||
|
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.
|
||||||
|
"select_button", -- name
|
||||||
|
"bool", -- type
|
||||||
|
_("display select burst roll images button"), -- label
|
||||||
|
_("add a button to the select module to select burst roll images"), -- 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
|
||||||
|
|
||||||
|
if HAS_INTERNAL_METADATA then
|
||||||
|
if image.RawBurstModeRoll then
|
||||||
|
local val = get_last_value(image.RawBurstModeRoll)
|
||||||
|
val = tonumber(val)
|
||||||
|
if type(val) == "number" then
|
||||||
|
if val > 0 then
|
||||||
|
result = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
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 line > 0 then
|
||||||
|
log.msg(log.info, image.filename .. " is a burst roll container image")
|
||||||
|
result = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
p:close()
|
||||||
|
end
|
||||||
|
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:len() > 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 extract_burst_images(images)
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
if is_burst_roll_image(image) then
|
||||||
|
extract_burst(image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
local function select_burst_roll_images(event, images)
|
||||||
|
local old_log_level = set_log_level()
|
||||||
|
local selected = {}
|
||||||
|
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
if is_burst_roll_image(image) then
|
||||||
|
table.insert(selected, image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
restore_log_level(old_log_level)
|
||||||
|
return selected
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_select_sensitive()
|
||||||
|
if #dt.collection > 0 then
|
||||||
|
dt.gui.libs.select.set_sensitive(MODULE, true)
|
||||||
|
else
|
||||||
|
dt.gui.libs.select.set_sensitive(MODULE, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_action_sensitive()
|
||||||
|
local sensitive = false
|
||||||
|
if #dt.gui.action_images > 0 then
|
||||||
|
if is_burst_roll_image(dt.gui.action_images[1]) then
|
||||||
|
sensitive = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dt.gui.libs.image.set_sensitive(MODULE, sensitive)
|
||||||
|
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 HAS_INTERNAL_METADATA then
|
||||||
|
log.msg(log.info, "Exif.Canon.RawBurstModeRoll detected, using internal metadata")
|
||||||
|
else
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
ebri.on_import = pref_read("on_import", "bool")
|
||||||
|
ebri.select_button = pref_read("select_button", "bool")
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- U S E R I N T E R F A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
if ebri.select_button then
|
||||||
|
dt.gui.libs.select.register_selection(MODULE, _("select burst roll images"), select_burst_roll_images,
|
||||||
|
_("select burst roll image containers"))
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.gui.libs.image.register_action(
|
||||||
|
MODULE,
|
||||||
|
_("extract burst roll images"),
|
||||||
|
function(event, images)
|
||||||
|
extract_burst_images(images)
|
||||||
|
end,
|
||||||
|
_("extract and group burst roll images with container")
|
||||||
|
)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- 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")
|
||||||
|
if ebri.select_button then
|
||||||
|
dt.gui.libs.select.destroy_selection(MODULE)
|
||||||
|
end
|
||||||
|
dt.gui.libs.image.action.destroy_action(MODULE)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
dt.register_event(MODULE, "selection-changed",
|
||||||
|
function(event)
|
||||||
|
set_action_sensitive()
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
if dt.preferences.read("darktable", "plugins/lighttable/act_on", "bool") then
|
||||||
|
dt.print_log("adding mouse-over-image-changed event")
|
||||||
|
dt.register_event(MODULE, "mouse-over-image-changed",
|
||||||
|
function(event, image)
|
||||||
|
set_action_sensitive()
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event(MODULE, "collection-changed",
|
||||||
|
function(event)
|
||||||
|
set_select_sensitive()
|
||||||
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("generate_image_txt", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -53,7 +51,7 @@ du.check_min_api_version("7.0.0", "generate_image_txt")
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "generate_image_txt",
|
name = _("generate image text"),
|
||||||
purpose = _("overlay metadata on the selected image(s)"),
|
purpose = _("overlay metadata on the selected image(s)"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt"
|
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 = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/group_persistence" -- 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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("image_path_in_ui", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext(msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
@@ -46,7 +44,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "image_path_in_ui",
|
name = _("image path in UI"),
|
||||||
purpose = _("print the image path in the UI"),
|
purpose = _("print the image path in the UI"),
|
||||||
author = "Jérémy Rosen",
|
author = "Jérémy Rosen",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui"
|
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()
|
local function install_module()
|
||||||
if not ipiu.module_installed then
|
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}
|
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER",300}
|
||||||
}, main_label
|
}, main_label
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ local dt = require "darktable"
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("import_filter_manager", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -43,7 +41,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "import_filter_manager",
|
name = _("import filter manager"),
|
||||||
purpose = _("manage import filters"),
|
purpose = _("manage import filters"),
|
||||||
author = "Tobias Ellinghaus",
|
author = "Tobias Ellinghaus",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager"
|
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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("import_filters", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -41,7 +39,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "import_filters",
|
name = _("import filters"),
|
||||||
purpose = _("import filtering"),
|
purpose = _("import filtering"),
|
||||||
author = "Tobias Ellinghaus & Christian Mandel",
|
author = "Tobias Ellinghaus & Christian Mandel",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filters"
|
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/stable/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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("save_selection", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
@@ -51,7 +49,7 @@ end
|
|||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "save_selection",
|
name = _("save selection"),
|
||||||
purpose = _("shortcuts providing multiple selection buffers"),
|
purpose = _("shortcuts providing multiple selection buffers"),
|
||||||
author = "Jérémy Rosen",
|
author = "Jérémy Rosen",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/save_selection"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/save_selection"
|
||||||
|
|||||||
225
official/scheduler.lua
Normal file
225
official/scheduler.lua
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
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 = _("selection to collection"), -- 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,19 +40,21 @@ du.check_min_api_version("7.0.0", "selection_to_pdf")
|
|||||||
|
|
||||||
local gettext = dt.gettext.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dt.gettext.bindtextdomain("selection_to_pdf", dt.configuration.config_dir .."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msg)
|
local function _(msg)
|
||||||
return gettext(msg)
|
return gettext(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local NIX_OPEN <const> = "xdg-open "
|
||||||
|
local MAC_OPEN <const> = "open "
|
||||||
|
local WIN_OPEN <const> = "start "
|
||||||
|
|
||||||
-- return data structure for script_manager
|
-- return data structure for script_manager
|
||||||
|
|
||||||
local script_data = {}
|
local script_data = {}
|
||||||
|
|
||||||
script_data.metadata = {
|
script_data.metadata = {
|
||||||
name = "selection_to_pdf",
|
name = _("selection to PDF"),
|
||||||
purpose = _("generate a pdf file of selected images"),
|
purpose = _("generate a PDF file of selected images"),
|
||||||
author = "Jérémy Rosen & Pascal Obry",
|
author = "Jérémy Rosen & Pascal Obry",
|
||||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf"
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf"
|
||||||
}
|
}
|
||||||
@@ -64,9 +66,9 @@ script_data.show = nil -- only required for libs since the destroy_method only h
|
|||||||
|
|
||||||
dt.preferences.register
|
dt.preferences.register
|
||||||
("selection_to_pdf","Open with","string",
|
("selection_to_pdf","Open with","string",
|
||||||
_("a pdf viewer"),
|
_("a PDF viewer"),
|
||||||
_("can be an absolute pathname or the tool may be in the PATH"),
|
_("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"),
|
||||||
"xdg-open")
|
"")
|
||||||
|
|
||||||
local title_widget = dt.new_widget("entry") {
|
local title_widget = dt.new_widget("entry") {
|
||||||
placeholder = _("title")
|
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.
|
-- 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
|
-- so \includegraphics{file.01.jpg} wont work. We need to output the filename
|
||||||
-- and extention separated, e.g: \includegraphics{{file.01}.jpg}
|
-- 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,"\\begin{minipage}[b]{"..width.."\\textwidth}\n")
|
||||||
my_write(latexfile,"\\includegraphics[width=\\textwidth]{{"..filenoext.."}"..ext.."}\\newline\n")
|
my_write(latexfile,"\\includegraphics[width=\\textwidth]{{"..filenoext.."}"..ext.."}\\newline\n")
|
||||||
my_write(latexfile,"\\centering{"..i..": \\verb|"..title.."|}\n")
|
my_write(latexfile,"\\centering{"..i..": \\verb|"..title.."|}\n")
|
||||||
@@ -131,7 +134,7 @@ local function destroy()
|
|||||||
dt.print_log("done destroying")
|
dt.print_log("done destroying")
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
dt.register_storage("export_pdf", _("export thumbnails to PDF"),
|
||||||
nil,
|
nil,
|
||||||
function(storage,image_table)
|
function(storage,image_table)
|
||||||
local my_title = title_widget.text
|
local my_title = title_widget.text
|
||||||
@@ -140,7 +143,7 @@ dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
|||||||
end
|
end
|
||||||
local thumbs_per_line = no_of_thumbs_widget.value
|
local thumbs_per_line = no_of_thumbs_widget.value
|
||||||
thumbs_per_line = tonumber(thumbs_per_line)
|
thumbs_per_line = tonumber(thumbs_per_line)
|
||||||
width = (1/thumbs_per_line) - 0.01
|
width = (1 / thumbs_per_line) - 0.01
|
||||||
|
|
||||||
local preamble = [[
|
local preamble = [[
|
||||||
\documentclass[a4paper,10pt]{article}
|
\documentclass[a4paper,10pt]{article}
|
||||||
@@ -150,31 +153,31 @@ dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
|||||||
\usepackage{geometry}
|
\usepackage{geometry}
|
||||||
\geometry{a4paper,left=5mm,right=5mm, top=5mm, bottom=5mm}
|
\geometry{a4paper,left=5mm,right=5mm, top=5mm, bottom=5mm}
|
||||||
\begin{document}
|
\begin{document}
|
||||||
{\Large\bfseries ]]..my_title..[[} \\
|
{\Large\bfseries ]] .. my_title .. [[} \\
|
||||||
\bigskip\bigskip
|
\bigskip\bigskip
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local errmsg
|
local errmsg
|
||||||
local latexfile
|
local latexfile
|
||||||
latexfile,errmsg=io.open(filename,"w")
|
latexfile,errmsg = io.open(filename, "w")
|
||||||
if not latexfile then
|
if not latexfile then
|
||||||
error(errmsg)
|
error(errmsg)
|
||||||
end
|
end
|
||||||
my_write(latexfile,preamble)
|
my_write(latexfile, preamble)
|
||||||
local i = 1
|
local i = 1
|
||||||
for img,file in pairs(image_table) do
|
for img, file in pairs(image_table) do
|
||||||
thumbnail(latexfile,i,img,file)
|
thumbnail(latexfile, i, img, file)
|
||||||
if i % thumbs_per_line == 0 then
|
if i % thumbs_per_line == 0 then
|
||||||
my_write(latexfile,"\n\\bigskip\n")
|
my_write(latexfile, "\n\\bigskip\n")
|
||||||
end
|
end
|
||||||
i = i+1
|
i = i + 1
|
||||||
end
|
end
|
||||||
my_write(latexfile,ending)
|
my_write(latexfile,ending)
|
||||||
latexfile:close()
|
latexfile:close()
|
||||||
|
|
||||||
-- convert to PDF
|
-- convert to PDF
|
||||||
local dir=string.gsub(filename, "(.*/)(.*)", "%1")
|
local dir = string.gsub(filename, "(.*/)(.*)", "%1")
|
||||||
local locfile=string.gsub(filename, "(.*/)(.*)", "%2")
|
local locfile = string.gsub(filename, "(.*/)(.*)", "%2")
|
||||||
local command = "pdflatex -halt-on-error -output-directory "..dir.." "..locfile
|
local command = "pdflatex -halt-on-error -output-directory "..dir.." "..locfile
|
||||||
local result = dt.control.execute(command)
|
local result = dt.control.execute(command)
|
||||||
if result ~= 0 then
|
if result ~= 0 then
|
||||||
@@ -183,12 +186,21 @@ dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"),
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- open the PDF
|
-- 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 = 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)
|
local result = dt.control.execute(command)
|
||||||
if result ~= 0 then
|
if result ~= 0 then
|
||||||
dt.print(_("problem running pdf viewer")) -- this one is probably usefull to the user
|
dt.print(_("problem running PDF viewer")) -- this one is probably usefull to the user
|
||||||
error("Problem running "..command)
|
error("Problem running "..command)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user