Compare commits
584 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 | ||
|
|
3d1f12fe44 | ||
|
|
a079860cd8 | ||
|
|
b07c680332 | ||
|
|
200fc059c0 | ||
|
|
aeb1f2226c | ||
|
|
46e729b40e | ||
|
|
66b613dc29 | ||
|
|
535ba78a06 | ||
|
|
cc11c1c3b8 | ||
|
|
fba639e98a | ||
|
|
3e37c06b84 | ||
|
|
1b272be5f5 | ||
|
|
9f423eeef3 | ||
|
|
dc81c0b1c5 | ||
|
|
e8a99c8396 | ||
|
|
7c93868bf0 | ||
|
|
36023d8d4f | ||
|
|
97aa4ce363 | ||
|
|
316a89d6a8 | ||
|
|
d4a479b025 | ||
|
|
dd22c947c6 | ||
|
|
09f47b7b09 | ||
|
|
ba94914c0e | ||
|
|
3bb4e9c59a | ||
|
|
f8c076f308 | ||
|
|
94f41db6fc | ||
|
|
5727b2d251 | ||
|
|
c95f8a23b2 | ||
|
|
88e4ac1a64 | ||
|
|
a3fe02bf95 | ||
|
|
ff20098175 | ||
|
|
cdf4b743dd | ||
|
|
3efc1805ca | ||
|
|
0b46cf8abf | ||
|
|
28622e620e | ||
|
|
f21a117d29 | ||
|
|
35cbff03dd | ||
|
|
5571e2f2dc | ||
|
|
3c77f2e863 | ||
|
|
4ff1111341 | ||
|
|
abfa8e9878 | ||
|
|
bbe9109fca | ||
|
|
34d5df65b2 | ||
|
|
539f25517b | ||
|
|
5b55a3116d | ||
|
|
3e032616a0 | ||
|
|
9f2dbef4f0 | ||
|
|
2abb83b485 | ||
|
|
32e966f8d9 | ||
|
|
67538a4de2 | ||
|
|
96471ae7e7 | ||
|
|
038418b89f | ||
|
|
fe48d7498c | ||
|
|
2f330ea9cc | ||
|
|
81b0f1fb48 | ||
|
|
9088bc9144 | ||
|
|
c3ce8d532d | ||
|
|
f1d8ad7fce | ||
|
|
a3b295112f | ||
|
|
c36618f5fb | ||
|
|
fecbb39347 | ||
|
|
cbe2c5329a | ||
|
|
dcbe575e9c | ||
|
|
417c165d16 | ||
|
|
232baab86b | ||
|
|
3ed4219e6c | ||
|
|
97e33672ea | ||
|
|
7b2018412a | ||
|
|
3e2afffed8 | ||
|
|
1399e8a4b4 | ||
|
|
5c3b2777f0 | ||
|
|
bf138abeb2 | ||
|
|
284c0f9714 | ||
|
|
e41029d3f4 | ||
|
|
55e68f8cd0 | ||
|
|
ac945b9843 | ||
|
|
f6916c3a15 | ||
|
|
cd3788f012 | ||
|
|
ebd2cd7bf7 | ||
|
|
52b4508e08 | ||
|
|
b1b3392b92 | ||
|
|
19fdbc14c5 | ||
|
|
63d5aad465 | ||
|
|
9d6ff85893 | ||
|
|
125af63bda | ||
|
|
ddf0c89402 | ||
|
|
3b0ff3475a | ||
|
|
401509cfe4 | ||
|
|
9c4e906033 | ||
|
|
65c22e475a | ||
|
|
a11726889d | ||
|
|
e78e718769 | ||
|
|
86ee8adbce | ||
|
|
40949b3855 | ||
|
|
06ab263e74 | ||
|
|
bccc86c70a | ||
|
|
231e9b352b | ||
|
|
e47ab34745 | ||
|
|
1fd2ca20ab | ||
|
|
05a73d8810 | ||
|
|
12640c7d29 | ||
|
|
febc3b1659 | ||
|
|
9fdd69556c | ||
|
|
4735be470a | ||
|
|
66b33d6f48 | ||
|
|
37dba718a2 | ||
|
|
c11845cbf9 | ||
|
|
f9fe33f930 | ||
|
|
3b8e802e42 | ||
|
|
f54d449316 | ||
|
|
1b0fe4d7b9 | ||
|
|
cf1dca9e1a | ||
|
|
45d54ef4e8 | ||
|
|
8bfab0325b | ||
|
|
4b99d00ab5 | ||
|
|
a5961eb066 | ||
|
|
95e19ef163 | ||
|
|
d4eb6577bf | ||
|
|
294b693905 | ||
|
|
23835edc1f | ||
|
|
5f29211f79 | ||
|
|
025f4299ea | ||
|
|
dc50d40886 | ||
|
|
867c49b1b9 | ||
|
|
13f02c95ea | ||
|
|
d55dae73f0 | ||
|
|
34b9831472 | ||
|
|
014a0fc1c9 | ||
|
|
bf17dccd69 | ||
|
|
890168d0d0 | ||
|
|
191aecacdf | ||
|
|
44ec01b136 | ||
|
|
a177da2040 | ||
|
|
c899040688 | ||
|
|
e8af665b3c | ||
|
|
875734d455 | ||
|
|
14d5a79db2 | ||
|
|
e1b2715ee9 | ||
|
|
7ecd5ca55d | ||
|
|
931e7a4d37 | ||
|
|
1792410efe | ||
|
|
8cbd9224f1 | ||
|
|
24b552d619 | ||
|
|
68ae9326bf | ||
|
|
b4626f2787 | ||
|
|
2fc8ed78e4 | ||
|
|
236b25a235 | ||
|
|
a4f6af73ec | ||
|
|
fad74d6438 | ||
|
|
3e2d4c956d | ||
|
|
cd1d623390 | ||
|
|
9e0374d597 | ||
|
|
e47672b495 | ||
|
|
1905be8577 | ||
|
|
4726821e7d | ||
|
|
7ac37ddc1a | ||
|
|
ad10542b30 | ||
|
|
5331a38f10 | ||
|
|
c9943a2065 | ||
|
|
3889742e06 | ||
|
|
6bf3706455 | ||
|
|
612ecaff6c | ||
|
|
a85364ecff | ||
|
|
c0af03c503 | ||
|
|
9b61e4793b | ||
|
|
75a19e74a0 | ||
|
|
21d7e72b0c | ||
|
|
05d555eae5 | ||
|
|
8e412c3eb3 | ||
|
|
80147a9ebc | ||
|
|
3f6a3e25f1 | ||
|
|
800cbeef9d | ||
|
|
574f6a976f | ||
|
|
030b360e57 | ||
|
|
b883714192 | ||
|
|
d556f48168 | ||
|
|
cd7cac0eec | ||
|
|
9b31f18470 | ||
|
|
42a194b3d7 | ||
|
|
6748a7125e | ||
|
|
c2b3f29f3b | ||
|
|
572a634d90 | ||
|
|
652606ffb0 | ||
|
|
803537c63e | ||
|
|
4648c2d9af | ||
|
|
edceb3e7e9 | ||
|
|
da6bddcd77 | ||
|
|
b11f43e903 | ||
|
|
ef218a4d2e | ||
|
|
b061f27e7a | ||
|
|
8e64d54ab1 | ||
|
|
56b40a80ac | ||
|
|
d359d8ecb4 | ||
|
|
2bc55d00bb | ||
|
|
d3cf564b71 | ||
|
|
4fb8b97084 | ||
|
|
32c5cceb1d | ||
|
|
f38c14d6b5 | ||
|
|
3070ca0bc4 | ||
|
|
0122b3f717 | ||
|
|
79678fd82f | ||
|
|
034ddfbafb | ||
|
|
aebd26d03a | ||
|
|
d609823491 | ||
|
|
8f414f783a | ||
|
|
2e521553bf | ||
|
|
e93f5fc812 | ||
|
|
3dbbb7dfd5 | ||
|
|
fbb92e1023 | ||
|
|
ea4c6373f9 | ||
|
|
40b2569a21 | ||
|
|
ef6e4d8f0b | ||
|
|
28ae2ea501 | ||
|
|
657407c7a0 | ||
|
|
b254577bc8 | ||
|
|
63763bedc8 | ||
|
|
3d50d0fdb4 | ||
|
|
1ca6b18f5e | ||
|
|
b82ffc2af3 | ||
|
|
d142151055 | ||
|
|
91b78529fe | ||
|
|
7aec6884f4 | ||
|
|
7f140828bb | ||
|
|
4f13a56814 | ||
|
|
27d3ea253d | ||
|
|
5209b9899d | ||
|
|
6a40c836c0 | ||
|
|
c4df81bdef | ||
|
|
e99827d237 | ||
|
|
a0cbe8efd5 | ||
|
|
b4ee97a768 | ||
|
|
4bdaaaf1ce | ||
|
|
3cf0fea092 | ||
|
|
97e1451dc6 | ||
|
|
4cbb7724f2 | ||
|
|
8f6c2c82d3 | ||
|
|
a6d5001e84 | ||
|
|
6af180a313 | ||
|
|
86cc22d943 | ||
|
|
c1fbfd80d4 | ||
|
|
040a83fb17 | ||
|
|
2bc23a1446 | ||
|
|
97f4c911ed | ||
|
|
53d9a42a1a | ||
|
|
c96c5234c4 | ||
|
|
9a3eb7bc3e | ||
|
|
53f15ba8b8 | ||
|
|
ecdc74b5a6 | ||
|
|
422fdbe2cf | ||
|
|
887bc03f32 | ||
|
|
e32c0c70a8 | ||
|
|
440dfcadfd | ||
|
|
93cc0adb39 | ||
|
|
f8ec85ddae | ||
|
|
688333f756 | ||
|
|
8f314059e4 | ||
|
|
9386a08af0 | ||
|
|
19cd118f8f | ||
|
|
c196f98bc6 | ||
|
|
56ec258cc7 | ||
|
|
5da64a7da0 |
122
ChangeLog.md
122
ChangeLog.md
@@ -1,21 +1,133 @@
|
|||||||
## Changes from most recent to oldest
|
## Changes from most recent to oldest
|
||||||
|
|
||||||
** 19 Jun 2021 - wpferguson - fix issue 312, image_path_in_ui**
|
**06 Jun 2024 - wpferguson**
|
||||||
|
* fix fujifilm_ratings running on all images
|
||||||
|
* added string library functions to sanitize windows io.popen and os.execute functions
|
||||||
|
* added database maintenance script
|
||||||
|
|
||||||
** 02 Jun 2021 - wpferguson - fix contrib/quicktag**
|
**05 Jun 2024 - wpferguson**
|
||||||
|
* added fix for executable_manager not being visible on windows
|
||||||
|
|
||||||
|
**30 May 2024 - kkotowicz**
|
||||||
|
* open in explorer now uses applescript on macos to open multiple files
|
||||||
|
|
||||||
|
**20 May 2024 - wpferguson**
|
||||||
|
* added string variable substitution to the string library
|
||||||
|
|
||||||
|
**16 May 2024 - wpferguson**
|
||||||
|
* fix crash in script_manager
|
||||||
|
|
||||||
|
**15 May 2024 - wpferguson**
|
||||||
|
* added metadata to scripts (name, author, purpose, help url)
|
||||||
|
|
||||||
|
**06 May 2024 - christian.sueltrop**
|
||||||
|
* added passport_guide_germany script
|
||||||
|
|
||||||
|
**08 Apr 2024 - wpferguson**
|
||||||
|
* made script_manager aware of library modules in other directories besides lib
|
||||||
|
|
||||||
|
**29 Mar 2024 - wpferguson**
|
||||||
|
* updated examples/gui_action to use NaN instead of nan
|
||||||
|
|
||||||
|
**29 Mar 2024 - dterrahe**
|
||||||
|
* add lua action script example
|
||||||
|
|
||||||
|
**28 Jan 2024 - wpferguson**
|
||||||
|
* fix script_manager crash when script existed in top level lua directory
|
||||||
|
|
||||||
|
**24 Jan 2024 - wpferguson**
|
||||||
|
* don't set lib visibility unless we are in lighttable mode
|
||||||
|
|
||||||
|
**15 Jan 2024 - MStraeten**
|
||||||
|
* update x-touch.lua with support for primaries slider
|
||||||
|
|
||||||
|
**14 Jan 2024 - wpferguson**
|
||||||
|
* added cycle_group_leader script
|
||||||
|
* added hif_group_leader script
|
||||||
|
* added jpg_group_leader script
|
||||||
|
|
||||||
|
**28 Oct 2023 - ddittmar**
|
||||||
|
* Added select non existing image script
|
||||||
|
|
||||||
|
**17 Oct 2023 - wpferguson**
|
||||||
|
* script_manager wrap username in quotes to handle spaces in username
|
||||||
|
|
||||||
|
**20 Sep 2023 - wpferguson**
|
||||||
|
* script_manager explicitly set stopped scripts to false
|
||||||
|
|
||||||
|
**18 Aug 2023 - wpferguson**
|
||||||
|
* swap the position of executable_manager and script_manager due to windows gtk bug
|
||||||
|
|
||||||
|
**17 Jul 2023 - wpferguson**
|
||||||
|
* update check for update instructions
|
||||||
|
* update flatpak readme
|
||||||
|
|
||||||
|
**16 Jul 2023 - wpferguson**
|
||||||
|
* added script_data.show function to contrib/gpx_export
|
||||||
|
|
||||||
|
**15 Jul 2023 - wpferguson**
|
||||||
|
* script_manager updates
|
||||||
|
* added check_max_api_version for the case where the API no longer supports the required
|
||||||
|
functions
|
||||||
|
|
||||||
|
**14 Jul 2023 - spaceChRis**
|
||||||
|
* add tooltip to filter manager
|
||||||
|
|
||||||
|
**25 Mar 2023 - wpferguson**
|
||||||
|
* Added script_manager darktable shortcut integration
|
||||||
|
* Moved filename/path/extension string functions to string library
|
||||||
|
|
||||||
|
**06 Mar 2023 - wpferguson - Added option to disable script_manager update check**
|
||||||
|
|
||||||
|
**12 Jan 2022 - wpferguson - Documented contrib/rename_images.lua**
|
||||||
|
|
||||||
|
**28 Dec 2021 - wpferguson - Replaced deprecated _which_ command with _command -v_ in lib/dtutils/file.lua**
|
||||||
|
|
||||||
|
**12 Dec 2021 - wpferguson**
|
||||||
|
* Added deprecated function to library
|
||||||
|
* Added deprecation warning to contrib/rename-tags.lua
|
||||||
|
|
||||||
|
**22 Oct 2021 - wpferguson for Volker Bödker - make sure sequence is 4 digits in rename_images.lua**
|
||||||
|
|
||||||
|
**31 Aug 2021 - wpferguson - remove styles hiding from AutoGrouper.lua**
|
||||||
|
|
||||||
|
**02 Jul 2021 - wpferguson - merged API-7.0.0-dev branch to master**
|
||||||
|
* API-7.0.0 is darktable 3.6
|
||||||
|
* breaking changes
|
||||||
|
* register_event argments changed
|
||||||
|
* register_action arguments changed
|
||||||
|
* register_selection arguments changed
|
||||||
|
* register_event arguments changed
|
||||||
|
* scripts updated to API-7.0.0 compatibility
|
||||||
|
* script_manger updated to be API aware and check out the proper branch
|
||||||
|
based on darktable API version
|
||||||
|
|
||||||
|
**01 Jul 2021 - wpferguson - created branch for API-6.1.0 - darktable 3.4**
|
||||||
|
|
||||||
|
**20 Jun 2021 - wpferguson - created branches for older API versions**
|
||||||
|
* API-6.0.0 - darktable 3.2.1
|
||||||
|
* API-5.0.2 - darktable 3.0
|
||||||
|
* API-5.0.1 - darktable 2.6.1
|
||||||
|
* API-5.0.0 - darktable 2.4
|
||||||
|
* API-4.0.0 - darktable 2.2
|
||||||
|
* API-3.0.0 - darktable 2.0
|
||||||
|
|
||||||
|
**19 Jun 2021 - wpferguson - fix issue 312, image_path_in_ui**
|
||||||
|
|
||||||
|
**02 Jun 2021 - wpferguson - fix contrib/quicktag**
|
||||||
* set new entry field is_password to false so entry
|
* set new entry field is_password to false so entry
|
||||||
is visible to user while typing.
|
is visible to user while typing.
|
||||||
|
|
||||||
** 19 Mar 2021 - wpferguson - fixed crash in contrib/HDRmerge.lua**
|
**19 Mar 2021 - wpferguson - fixed crash in contrib/HDRmerge.lua**
|
||||||
* Made generated filename routine gracefully handle names that
|
* Made generated filename routine gracefully handle names that
|
||||||
are not in the expected format.
|
are not in the expected format.
|
||||||
|
|
||||||
** 15 Mar 2021 - scheckmedia - updated contrib/photils.lua**
|
**15 Mar 2021 - scheckmedia - updated contrib/photils.lua**
|
||||||
* refactor print method
|
* refactor print method
|
||||||
* add option to apply selected tags from a single image to multiple images
|
* add option to apply selected tags from a single image to multiple images
|
||||||
* add setting parameter to enable/disable the export of an image before tag suggestion
|
* add setting parameter to enable/disable the export of an image before tag suggestion
|
||||||
|
|
||||||
** 25 Feb 2021 - wpferguson - added detached mode to contrib/gimp.lua**
|
**25 Feb 2021 - wpferguson - added detached mode to contrib/gimp.lua**
|
||||||
|
|
||||||
* Added run_detached checkbox to the exporter GUI. Selecting run_detached
|
* Added run_detached checkbox to the exporter GUI. Selecting run_detached
|
||||||
let's GIMP keep running and accepting additional images. It does not return
|
let's GIMP keep running and accepting additional images. It does not return
|
||||||
|
|||||||
153
README.md
153
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,40 +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
|
||||||
[clear_GPS](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/clear_GPS)|Yes|LMW|Reset GPS information for selected images
|
[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
|
||||||
[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
|
change_group_leader|Yes|LMW|Change which image leads group
|
||||||
[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
|
[clear_GPS](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images
|
||||||
[cr2hdr](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO
|
[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
|
||||||
[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
|
color_profile_manager|Yes|LMW|Manage darktable input and output color profiles
|
||||||
[exportLUT](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/exportLUT)|Yes|LMW|Create a LUT from a style and export it
|
[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
|
||||||
[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
|
[cr2hdr](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO
|
||||||
[face_recognition](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition
|
cycle_group_leader|Yes|LMW|cycle through images of a group making each the group leader in turn
|
||||||
[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
|
dbmaint|Yes|LMW|find and remove database entries for missing film rolls and images
|
||||||
[fujifilm_ratings](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
|
[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
|
||||||
[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 ...
|
[exportLUT](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it
|
||||||
[geoToolbox](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geoToolbox)|No|LMW|A toolbox of geo functions
|
[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
|
||||||
[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
|
[face_recognition](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition
|
||||||
[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
|
[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
|
||||||
[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
|
[fujifilm_ratings](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
|
||||||
[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
|
[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 ...
|
||||||
[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
|
[geoToolbox](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions
|
||||||
[image_time](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time
|
[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
|
||||||
[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
|
[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
|
||||||
[LabelsToTags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/LabelsToTags)|Yes|LMW|Apply tags based on color labels and ratings
|
harmonic_armature|Yes|LMW|provide a harmonic armature guide
|
||||||
[OpenInExplorer](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/OpenInExplorer)|No|LMW|Open the selected images in the system file manager
|
hif_group_leader|Yes|LMW|change the group leader in a raw+heif image pair to the heif image
|
||||||
[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
|
[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
|
||||||
[pdf_slideshow](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow
|
[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
|
||||||
[photils](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images
|
[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
|
||||||
[quicktag](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags
|
[image_time](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time
|
||||||
[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
|
jpg_group_leader|Yes|LMW|change the group leader for a raw+jpg pair to the jpg image
|
||||||
[rename-tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name
|
[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
|
||||||
[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)
|
[LabelsToTags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings
|
||||||
[select_untagged](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images
|
[OpenInExplorer](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager
|
||||||
[slideshowMusic](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow
|
[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
|
||||||
[transfer_hierarchy](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy
|
passport_guide_germany|Yes|LMW|Add passport cropping guide for German passports to darkroom crop tool
|
||||||
[video_ffmpeg](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable
|
[pdf_slideshow](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow
|
||||||
|
[photils](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images
|
||||||
|
[quicktag](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags
|
||||||
|
[rate_group](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images
|
||||||
|
[rename_images](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images
|
||||||
|
[rename-tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name
|
||||||
|
[RL_out_sharp](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
|
||||||
|
[select_non_existing](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images
|
||||||
|
[select_untagged](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images
|
||||||
|
[slideshowMusic](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow
|
||||||
|
[transfer_hierarchy](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy
|
||||||
|
[video_ffmpeg](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable
|
||||||
|
website_gallery_export|No|LMW|Export a website gallery for selected images
|
||||||
|
|
||||||
### Example Scripts
|
### Example Scripts
|
||||||
|
|
||||||
@@ -75,17 +87,19 @@ 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
|
||||||
[hello_world](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts
|
gui_actions|Yes|LMW|demonstrate controlling the GUI using darktable.gui.action calls
|
||||||
[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
|
[hello_world](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts
|
||||||
[moduleExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/moduleExample)|Yes|LMW|How to create a lighttable module
|
[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
|
||||||
[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
|
[moduleExample](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module
|
||||||
[panels_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels
|
[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
|
||||||
[preferenceExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/preferenceExamples)|Yes|LMW|How to use preferences in a script
|
[panels_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels
|
||||||
[printExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/printExamples)|Yes|LMW|How to use various print functions from a script
|
[preferenceExamples](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in 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
|
[printExamples](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script
|
||||||
|
[running_os](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system
|
||||||
|
x-touch|Yes|LMW|demonstrate how to use an x-touch mini MIDI controller to control the darktable GUI
|
||||||
|
|
||||||
### Tools
|
### Tools
|
||||||
|
|
||||||
@@ -93,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
|
||||||
|
|
||||||
@@ -114,6 +128,9 @@ The following third-party projects are listed for information only. Think of thi
|
|||||||
* [arru/darktable-scripts](https://github.com/arru/darktable-scripts)
|
* [arru/darktable-scripts](https://github.com/arru/darktable-scripts)
|
||||||
* [nbremond77/darktable](https://github.com/nbremond77/darktable/tree/master/scripts)
|
* [nbremond77/darktable](https://github.com/nbremond77/darktable/tree/master/scripts)
|
||||||
* [s5k6/dtscripts](https://github.com/s5k6/dtscripts)
|
* [s5k6/dtscripts](https://github.com/s5k6/dtscripts)
|
||||||
|
* [ChristianBirzer/darktable_extra_scripts](https://github.com/ChristianBirzer/darktable_extra_scripts)
|
||||||
|
* [fjb2020/darktable-scripts](https://github.com//fjb2020/darktable-scripts)
|
||||||
|
* [bastibe/Darktable-Film-Simulation-Panel](https://github.com/bastibe/Darktable-Film-Simulation-Panel)
|
||||||
|
|
||||||
## Download and Install
|
## Download and Install
|
||||||
|
|
||||||
@@ -163,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
|
||||||
|
|
||||||
@@ -171,7 +188,7 @@ The recommended way to enable and disable specific scripts is using the script m
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
echo "require 'tools/script_manager'" > %LOCALAPPDATA%\darktable\luarc
|
echo require "tools/script_manager" > %LOCALAPPDATA%\darktable\luarc
|
||||||
|
|
||||||
### Snap
|
### Snap
|
||||||
|
|
||||||
@@ -179,7 +196,7 @@ The recommended way to enable and disable specific scripts is using the script m
|
|||||||
|
|
||||||
### Flatpak
|
### Flatpak
|
||||||
|
|
||||||
echo require "tools/script_manager"' > ~/.var/app/org.darktable.Darktable/config/darktable/luarc
|
echo 'require "tools/script_manager"' > ~/.var/app/org.darktable.Darktable/config/darktable/luarc
|
||||||
|
|
||||||
You can also create or add lines to the luarc file from the command line:
|
You can also create or add lines to the luarc file from the command line:
|
||||||
|
|
||||||
@@ -222,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
|
||||||
|
|||||||
@@ -37,138 +37,168 @@ selected images, the other button performs grouping on the entire active collect
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "AutoGrouper")
|
||||||
|
|
||||||
local MOD = 'autogrouper'
|
local MOD = 'autogrouper'
|
||||||
local gettext = dt.gettext
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
local gettext = dt.gettext.gettext
|
||||||
local CURR_API_STRING = dt.configuration_api_version_string
|
|
||||||
gettext.bindtextdomain("AutoGrouper",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("AutoGrouper", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("auto group"),
|
||||||
|
purpose = _("automatically group images by time interval"),
|
||||||
|
author = "Kevin Ertel",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/AutoGrouper/"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local Ag = {}
|
local Ag = {}
|
||||||
Ag.module_installed = false
|
Ag.module_installed = false
|
||||||
Ag.event_registered = false
|
Ag.event_registered = false
|
||||||
|
|
||||||
local GUI = {
|
local GUI = {
|
||||||
gap = {},
|
gap = {},
|
||||||
selected = {},
|
selected = {},
|
||||||
collection = {}
|
collection = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
|
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
|
||||||
if test >= low and test <= high then
|
if test >= low and test <= high then
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second
|
local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second
|
||||||
first_time = first.exif_datetime_taken
|
first_time = first.exif_datetime_taken
|
||||||
if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end
|
if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end
|
||||||
first_time = tonumber(string.gsub(first_time, '[^0-9]*',''))
|
first_time = tonumber(string.gsub(first_time, '[^0-9]*',''))
|
||||||
second_time = second.exif_datetime_taken
|
second_time = second.exif_datetime_taken
|
||||||
if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end
|
if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end
|
||||||
second_time = tonumber(string.gsub(second_time, '[^0-9]*',''))
|
second_time = tonumber(string.gsub(second_time, '[^0-9]*',''))
|
||||||
return first_time < second_time
|
return first_time < second_time
|
||||||
end
|
end
|
||||||
|
|
||||||
local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations
|
local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations
|
||||||
local cleaned = string.gsub(str, '[^%d]',':')
|
local cleaned = string.gsub(str, '[^%d]',':')
|
||||||
cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss
|
cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss
|
||||||
local year = string.sub(cleaned,1,4)
|
local year = string.sub(cleaned,1,4)
|
||||||
local month = string.sub(cleaned,6,7)
|
local month = string.sub(cleaned,6,7)
|
||||||
local day = string.sub(cleaned,9,10)
|
local day = string.sub(cleaned,9,10)
|
||||||
local hour = string.sub(cleaned,12,13)
|
local hour = string.sub(cleaned,12,13)
|
||||||
local min = string.sub(cleaned,15,16)
|
local min = string.sub(cleaned,15,16)
|
||||||
local sec = string.sub(cleaned,18,19)
|
local sec = string.sub(cleaned,18,19)
|
||||||
return {year = year, month = month, day = day, hour = hour, min = min, sec = sec}
|
return {year = year, month = month, day = day, hour = hour, min = min, sec = sec}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image
|
local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image
|
||||||
local curr_time = SeperateTime(curr_image.exif_datetime_taken)
|
local curr_time = SeperateTime(curr_image.exif_datetime_taken)
|
||||||
local prev_time = SeperateTime(prev_image.exif_datetime_taken)
|
local prev_time = SeperateTime(prev_image.exif_datetime_taken)
|
||||||
return os.time(curr_time)-os.time(prev_time)
|
return os.time(curr_time)-os.time(prev_time)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function main(on_collection)
|
local function main(on_collection)
|
||||||
local images = {}
|
local images = {}
|
||||||
if on_collection then
|
if on_collection then
|
||||||
local col_images = dt.collection
|
local col_images = dt.collection
|
||||||
for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection
|
for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection
|
||||||
table.insert(images,i,image)
|
table.insert(images,i,image)
|
||||||
end
|
|
||||||
else
|
|
||||||
images = dt.gui.selection()
|
|
||||||
end
|
end
|
||||||
dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value)
|
else
|
||||||
if #images < 2 then
|
images = dt.gui.selection()
|
||||||
dt.print('please select at least 2 images')
|
end
|
||||||
return
|
dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value)
|
||||||
end
|
if #images < 2 then
|
||||||
table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp
|
dt.print('please select at least 2 images')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp
|
||||||
|
|
||||||
for i, image in ipairs(images) do
|
for i, image in ipairs(images) do
|
||||||
if i == 1 then
|
if i == 1 then
|
||||||
prev_image = image
|
prev_image = image
|
||||||
elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group
|
image:make_group_leader()
|
||||||
local curr_image = image
|
elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group
|
||||||
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
|
local curr_image = image
|
||||||
images[i]:group_with(images[i-1])
|
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
|
||||||
end
|
images[i]:group_with(images[i-1].group_leader)
|
||||||
prev_image = curr_image
|
end
|
||||||
end
|
prev_image = curr_image
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function install_module()
|
local function install_module()
|
||||||
if not Ag.module_installed then
|
if not Ag.module_installed then
|
||||||
|
dt.print_log("installing module")
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
'AutoGroup_Lib', -- Module name
|
MOD, -- Module name
|
||||||
_('auto group'), -- name
|
_('auto group'), -- name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
true, -- resetable
|
true, -- resetable
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers
|
||||||
dt.new_widget("box"){
|
dt.new_widget("box"){
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
GUI.gap,
|
GUI.gap,
|
||||||
GUI.selected,
|
GUI.selected,
|
||||||
GUI.collection
|
GUI.collection
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Ag.module_installed = true
|
Ag.module_installed = true
|
||||||
|
dt.print_log("module installed")
|
||||||
|
dt.print_log("styles module visibility is " .. tostring(dt.gui.libs["styles"].visible))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MOD].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MOD].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- GUI --
|
-- GUI --
|
||||||
temp = dt.preferences.read(MOD, 'active_gap', 'integer')
|
temp = dt.preferences.read(MOD, 'active_gap', 'integer')
|
||||||
if not InRange(temp, 1, 86400) then temp = 3 end
|
if not InRange(temp, 1, 86400) then temp = 3 end
|
||||||
GUI.gap = dt.new_widget('slider'){
|
GUI.gap = dt.new_widget('slider'){
|
||||||
label = _('group gap [sec.]'),
|
label = _('group gap [sec.]'),
|
||||||
tooltip = _('minimum gap, in seconds, between groups'),
|
tooltip = _('minimum gap, in seconds, between groups'),
|
||||||
soft_min = 1,
|
soft_min = 1,
|
||||||
soft_max = 60,
|
soft_max = 60,
|
||||||
hard_min = 1,
|
hard_min = 1,
|
||||||
hard_max = 86400,
|
hard_max = 86400,
|
||||||
step = 1,
|
step = 1,
|
||||||
digits = 0,
|
digits = 0,
|
||||||
value = temp,
|
value = temp,
|
||||||
reset_callback = function(self)
|
reset_callback = function(self)
|
||||||
self.value = 3
|
self.value = 3
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
GUI.selected = dt.new_widget("button"){
|
GUI.selected = dt.new_widget("button"){
|
||||||
label = _('auto group: selected'),
|
label = _('auto group: selected'),
|
||||||
tooltip =_('auto group selected images'),
|
tooltip =_('auto group selected images'),
|
||||||
clicked_callback = function() main(false) end
|
clicked_callback = function() main(false) end
|
||||||
}
|
}
|
||||||
GUI.collection = dt.new_widget("button"){
|
GUI.collection = dt.new_widget("button"){
|
||||||
label = _('auto group: collection'),
|
label = _('auto group: collection'),
|
||||||
tooltip =_('auto group the entire collection'),
|
tooltip =_('auto group the entire collection'),
|
||||||
clicked_callback = function() main(true) end
|
clicked_callback = function() main(true) end
|
||||||
}
|
}
|
||||||
|
|
||||||
if dt.gui.current_view().id == "lighttable" then
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
@@ -176,14 +206,20 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not Ag.event_registered then
|
if not Ag.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"AutoGrouper", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
Ag.event_registered = true
|
Ag.event_registered = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -46,27 +46,42 @@ In the "Selected Images" module click on "Collect on this Image"
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local gettext = dt.gettext
|
local du = require "lib/dtutils"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
local previous = nil
|
local previous = nil
|
||||||
local all_active = false
|
local all_active = false
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
du.check_min_api_version("7.0.0", "CollectHelper")
|
||||||
gettext.bindtextdomain("CollectHelper",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("CollectHelper", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("collection helper"),
|
||||||
|
purpose = _("add collection helper buttons"),
|
||||||
|
author = "Kevin Ertel",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/CollectHelper/"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- FUNCTION --
|
-- FUNCTION --
|
||||||
local function CheckSingleImage(selection)
|
local function CheckSingleImage(selection)
|
||||||
if #selection ~= 1 then
|
if #selection ~= 1 then
|
||||||
dt.print(_("Please select a single image"))
|
dt.print(_("please select a single image"))
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function CheckHasColorLabel(selection)
|
local function CheckHasColorLabel(selection)
|
||||||
local ret = false
|
local ret = false
|
||||||
for _,image in pairs(selection) do
|
for _,image in pairs(selection) do
|
||||||
@@ -83,6 +98,7 @@ local function PreviousCollection()
|
|||||||
previous = dt.gui.libs.collect.filter(previous)
|
previous = dt.gui.libs.collect.filter(previous)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function CollectOnFolder(all_rules, all_active)
|
local function CollectOnFolder(all_rules, all_active)
|
||||||
local images = dt.gui.selection()
|
local images = dt.gui.selection()
|
||||||
if CheckSingleImage(images) then
|
if CheckSingleImage(images) then
|
||||||
@@ -103,6 +119,7 @@ local function CollectOnFolder(all_rules, all_active)
|
|||||||
previous = dt.gui.libs.collect.filter(rules)
|
previous = dt.gui.libs.collect.filter(rules)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function CollectOnColors(all_rules, all_active)
|
local function CollectOnColors(all_rules, all_active)
|
||||||
local images = dt.gui.selection()
|
local images = dt.gui.selection()
|
||||||
if CheckSingleImage(images) then
|
if CheckSingleImage(images) then
|
||||||
@@ -159,6 +176,7 @@ local function CollectOnColors(all_rules, all_active)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function CollectOnAll_AND()
|
local function CollectOnAll_AND()
|
||||||
local images = dt.gui.selection()
|
local images = dt.gui.selection()
|
||||||
if CheckSingleImage(images) then
|
if CheckSingleImage(images) then
|
||||||
@@ -174,50 +192,67 @@ local function CollectOnAll_AND()
|
|||||||
previous = dt.gui.libs.collect.filter(rules)
|
previous = dt.gui.libs.collect.filter(rules)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs.image.destroy_action("CollectHelper_prev")
|
||||||
|
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
||||||
|
dt.gui.libs.image.destroy_action("CollectHelper_folder")
|
||||||
|
end
|
||||||
|
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
||||||
|
dt.gui.libs.image.destroy_action("CollectHelper_labels")
|
||||||
|
end
|
||||||
|
if dt.preferences.read('module_CollectHelper','all_and','bool') then
|
||||||
|
dt.gui.libs.image.destroy_action("CollectHelper_and")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- GUI --
|
-- GUI --
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("collect: previous"),
|
"CollectHelper_prev", _("collect: previous"),
|
||||||
function() PreviousCollection() end,
|
function() PreviousCollection() end,
|
||||||
_("Sets the Collect parameters to be the previously active parameters")
|
_("sets the collect parameters to be the previously active parameters")
|
||||||
)
|
)
|
||||||
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("collect: folder"),
|
"CollectHelper_folder", _("collect: folder"),
|
||||||
function() CollectOnFolder(_ , false) end,
|
function() CollectOnFolder(_ , false) end,
|
||||||
_("Sets the Collect parameters to be the selected images's folder")
|
_("sets the collect parameters to be the selected images's folder")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("collect: color label(s)"),
|
"CollectHelper_labels", _("collect: color label(s)"),
|
||||||
function() CollectOnColors(_ , false) end,
|
function() CollectOnColors(_ , false) end,
|
||||||
_("Sets the Collect parameters to be the selected images's color label(s)")
|
_("sets the collect parameters to be the selected images's color label(s)")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
if dt.preferences.read('module_CollectHelper','all_and','bool') then
|
if dt.preferences.read('module_CollectHelper','all_and','bool') then
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("collect: all (AND)"),
|
"CollectHelper_and", _("collect: all (AND)"),
|
||||||
function() CollectOnAll_AND() end,
|
function() CollectOnAll_AND() end,
|
||||||
_("Sets the Collect parameters based on all activated CollectHelper options")
|
_("sets the collect parameters based on all activated CollectHelper options")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PREFERENCES --
|
-- PREFERENCES --
|
||||||
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 utelizes 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
|
||||||
"bool", -- type
|
"bool", -- type
|
||||||
_('CollectHelper: Color Label(s)'), -- label
|
_('CollectHelper: color label(s)'), -- label
|
||||||
_('Enable the button that allows you to swap to a collection based on selected image\'s COLOR LABEL(S)'), -- tooltip
|
_('enable the button that allows you to swap to a collection based on selected image\'s color label(s)'), -- tooltip
|
||||||
true -- default
|
true -- default
|
||||||
)
|
)
|
||||||
dt.preferences.register("module_CollectHelper", "folder", -- name
|
dt.preferences.register("module_CollectHelper", "folder", -- name
|
||||||
"bool", -- type
|
"bool", -- type
|
||||||
_('CollectHelper: Folder'), -- label
|
_('CollectHelper: folder'), -- label
|
||||||
_('Enable the button that allows you to swap to a collection based on selected image\'s FOLDER location'), -- tooltip
|
_('enable the button that allows you to swap to a collection based on selected image\'s folder location'), -- tooltip
|
||||||
true -- default
|
true -- default
|
||||||
)
|
)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -45,21 +45,41 @@ Select a style, whether you want tags to be copied from the original, and any ad
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require 'darktable'
|
local dt = require 'darktable'
|
||||||
|
local du = require "lib/dtutils"
|
||||||
local df = require 'lib/dtutils.file'
|
local df = require 'lib/dtutils.file'
|
||||||
|
|
||||||
local dsys = require 'lib/dtutils.system'
|
local dsys = require 'lib/dtutils.system'
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "HDRmerge")
|
||||||
|
|
||||||
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("HDR merge"),
|
||||||
|
purpose = _("merge bracketed images into an HDR DNG image"),
|
||||||
|
author = "Kevin Ertel",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/HDRmerge"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
local mod = 'module_HDRMerge'
|
local mod = 'module_HDRMerge'
|
||||||
local os_path_seperator = '/'
|
local os_path_seperator = '/'
|
||||||
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
local CURR_API_STRING = dt.configuration.api_version_string
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
local gettext = dt.gettext
|
|
||||||
gettext.bindtextdomain('HDRMerge', dt.configuration.config_dir..'/lua/locale/')
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext('HDRMerge', msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
local temp
|
local temp
|
||||||
local HDRM = { --HDRMerge Program Table
|
local HDRM = { --HDRMerge Program Table
|
||||||
name = 'HDRMerge',
|
name = 'HDRMerge',
|
||||||
@@ -195,8 +215,8 @@ end
|
|||||||
local function main()
|
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
|
||||||
@@ -264,7 +284,7 @@ local function main()
|
|||||||
end
|
end
|
||||||
dt.print(_('HDRMerge completed successfully'))
|
dt.print(_('HDRMerge completed successfully'))
|
||||||
else
|
else
|
||||||
dt.print_error(_('HDRMerge failed'))
|
dt.print_error('HDRMerge failed')
|
||||||
dt.print(_('HDRMerge failed'))
|
dt.print(_('HDRMerge failed'))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -287,6 +307,14 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["HDRMerge_Lib"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["HDRMerge_Lib"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- GUI Elements --
|
-- GUI Elements --
|
||||||
local lbl_hdr = dt.new_widget('section_label'){
|
local lbl_hdr = dt.new_widget('section_label'){
|
||||||
label = _('HDRMerge options')
|
label = _('HDRMerge options')
|
||||||
@@ -357,7 +385,7 @@ local lbl_import = dt.new_widget('section_label'){
|
|||||||
}
|
}
|
||||||
GUI.Target.style = dt.new_widget('combobox'){
|
GUI.Target.style = dt.new_widget('combobox'){
|
||||||
label = _('apply style on import'),
|
label = _('apply style on import'),
|
||||||
tooltip = _('Apply selected style on auto-import to newly created image'),
|
tooltip = _('apply selected style on auto-import to newly created image'),
|
||||||
selected = 1,
|
selected = 1,
|
||||||
_('none'),
|
_('none'),
|
||||||
changed_callback = function(self)
|
changed_callback = function(self)
|
||||||
@@ -386,18 +414,18 @@ 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'){
|
||||||
title = _('Select HDRmerge executable'),
|
title = _('select HDRmerge executable'),
|
||||||
value = df.get_executable_path_preference(HDRM.name),
|
value = df.get_executable_path_preference(HDRM.name),
|
||||||
is_directory = false
|
is_directory = false
|
||||||
}
|
}
|
||||||
@@ -439,7 +467,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not HDRM.event_registered then
|
if not HDRM.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"HDRmerge", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -449,3 +477,10 @@ else
|
|||||||
HDRM.event_registered = true
|
HDRM.event_registered = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -50,7 +50,29 @@
|
|||||||
local darktable = require("darktable")
|
local darktable = require("darktable")
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "LabelsToTags")
|
du.check_min_api_version("7.0.0", "LabelsToTags")
|
||||||
|
|
||||||
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("labels to tags"),
|
||||||
|
purpose = _("allows the mass-application of tags using color labels and ratings as a guide"),
|
||||||
|
author = "August Schwerdfeger (august@schwerdfeger.name)",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/LabelsToTags"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- Lua 5.3 no longer has "unpack" but "table.unpack"
|
-- Lua 5.3 no longer has "unpack" but "table.unpack"
|
||||||
unpack = unpack or table.unpack
|
unpack = unpack or table.unpack
|
||||||
@@ -59,8 +81,7 @@ local ltt = {}
|
|||||||
ltt.module_installed = false
|
ltt.module_installed = false
|
||||||
ltt.event_registered = false
|
ltt.event_registered = false
|
||||||
|
|
||||||
local LIB_ID = "LabelsToTags"
|
local LIB_ID = _("LabelsToTags")
|
||||||
local CURR_API_STRING = darktable.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Helper functions: BEGIN
|
-- Helper functions: BEGIN
|
||||||
|
|
||||||
@@ -101,23 +122,23 @@ end
|
|||||||
|
|
||||||
|
|
||||||
local initialAvailableMappings = {
|
local initialAvailableMappings = {
|
||||||
["Colors"] = { ["+*****"] = { "Red" },
|
[_("colors")] = { ["+*****"] = { _("red") },
|
||||||
["*+****"] = { "Yellow" },
|
["*+****"] = { _("yellow") },
|
||||||
["**+***"] = { "Green" },
|
["**+***"] = { _("green") },
|
||||||
["***+**"] = { "Blue" },
|
["***+**"] = { _("blue") },
|
||||||
["****+*"] = { "Purple" } },
|
["****+*"] = { _("purple") } },
|
||||||
["Single colors"] = { ["+----*"] = { "Red", "Only red" },
|
[_("single colors")] = { ["+----*"] = { _("red"), _("only red") },
|
||||||
["-+---*"] = { "Yellow", "Only yellow" },
|
["-+---*"] = { _("yellow"), _("only yellow") },
|
||||||
["--+--*"] = { "Green", "Only green" },
|
["--+--*"] = { _("green"), _("only green") },
|
||||||
["---+-*"] = { "Blue", "Only blue" },
|
["---+-*"] = { _("blue"), _("only blue") },
|
||||||
["----+*"] = { "Purple", "Only purple" } },
|
["----+*"] = { _("purple"), _("only purple") } },
|
||||||
["Ratings"] = { ["*****0"] = { "No stars", "Not rejected" },
|
[_("ratings")] = { ["*****0"] = { _("no stars"), _("not rejected") },
|
||||||
["*****1"] = { "One star", "Not rejected" },
|
["*****1"] = { _("one star"), _("not rejected") },
|
||||||
["*****2"] = { "Two stars", "Not rejected" },
|
["*****2"] = { _("two stars"), _("not rejected") },
|
||||||
["*****3"] = { "Three stars", "Not rejected" },
|
["*****3"] = { _("three stars"), _("not rejected") },
|
||||||
["*****4"] = { "Four stars", "Not rejected" },
|
["*****4"] = { _("four stars"), _("not rejected") },
|
||||||
["*****5"] = { "Five stars", "Not rejected" },
|
["*****5"] = { _("five stars"), _("not rejected") },
|
||||||
["*****R"] = { "Rejected" } }
|
["*****R"] = { _("rejected") } }
|
||||||
}
|
}
|
||||||
|
|
||||||
local availableMappings = {}
|
local availableMappings = {}
|
||||||
@@ -132,28 +153,28 @@ end
|
|||||||
|
|
||||||
local function getComboboxTooltip()
|
local function getComboboxTooltip()
|
||||||
if availableMappings == nil or next(availableMappings) == nil then
|
if availableMappings == nil or next(availableMappings) == nil then
|
||||||
return("No registered mappings -- using defaults")
|
return(_("no registered mappings -- using defaults"))
|
||||||
else
|
else
|
||||||
return("Select a label-to-tag mapping")
|
return(_("select a label-to-tag mapping"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local mappingComboBox = darktable.new_widget("combobox"){
|
local mappingComboBox = darktable.new_widget("combobox"){
|
||||||
label = "mapping",
|
label = _("mapping"),
|
||||||
value = 1,
|
value = 1,
|
||||||
tooltip = getComboboxTooltip(),
|
tooltip = getComboboxTooltip(),
|
||||||
reset_callback = function(selfC)
|
reset_callback = function(selfC)
|
||||||
if selfC == nil then
|
if selfC == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
i = 1
|
i = 1
|
||||||
for _,m in pairs(keySet(getAvailableMappings())) do
|
for _,m in pairs(keySet(getAvailableMappings())) do
|
||||||
selfC[i] = m
|
selfC[i] = m
|
||||||
i = i+1
|
i = i+1
|
||||||
end
|
end
|
||||||
n = #selfC
|
n = #selfC
|
||||||
for j = i,n do
|
for j = i,n do
|
||||||
selfC[i] = nil
|
selfC[i] = nil
|
||||||
end
|
end
|
||||||
selfC.value = 1
|
selfC.value = 1
|
||||||
selfC.tooltip = getComboboxTooltip()
|
selfC.tooltip = getComboboxTooltip()
|
||||||
@@ -162,7 +183,7 @@ local mappingComboBox = darktable.new_widget("combobox"){
|
|||||||
}
|
}
|
||||||
|
|
||||||
local function doTagging(selfC)
|
local function doTagging(selfC)
|
||||||
local job = darktable.gui.create_job(string.format("labels to tags (%d image" .. (#(darktable.gui.action_images) == 1 and "" or "s") .. ")",#(darktable.gui.action_images)),true)
|
local job = darktable.gui.create_job(string.format(_("labels to tags (%d image%s)"), #(darktable.gui.action_images), (#(darktable.gui.action_images) == 1 and "" or "s")), true)
|
||||||
job.percent = 0.0
|
job.percent = 0.0
|
||||||
local pctIncrement = 1.0 / #(darktable.gui.action_images)
|
local pctIncrement = 1.0 / #(darktable.gui.action_images)
|
||||||
|
|
||||||
@@ -172,17 +193,17 @@ local function doTagging(selfC)
|
|||||||
local tagsToApply = {}
|
local tagsToApply = {}
|
||||||
local hash = generateLabelHash(img)
|
local hash = generateLabelHash(img)
|
||||||
for k,v in pairs(availableMappings[mappingComboBox.value]) do
|
for k,v in pairs(availableMappings[mappingComboBox.value]) do
|
||||||
if hashMatch(hash,k) then
|
if hashMatch(hash,k) then
|
||||||
for _,tag in ipairs(v) do
|
for _,tag in ipairs(v) do
|
||||||
tagsToApply[tag] = true
|
tagsToApply[tag] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for k,_ in pairs(tagsToApply) do
|
for k,_ in pairs(tagsToApply) do
|
||||||
if memoizedTags[k] == nil then
|
if memoizedTags[k] == nil then
|
||||||
memoizedTags[k] = darktable.tags.create(k)
|
memoizedTags[k] = darktable.tags.create(k)
|
||||||
end
|
end
|
||||||
darktable.tags.attach(memoizedTags[k],img)
|
darktable.tags.attach(memoizedTags[k],img)
|
||||||
end
|
end
|
||||||
job.percent = job.percent + pctIncrement
|
job.percent = job.percent + pctIncrement
|
||||||
end
|
end
|
||||||
@@ -193,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,14 +229,14 @@ darktable.register_tag_mapping = function(name, mapping)
|
|||||||
end
|
end
|
||||||
for pattern,tags in pairs(mapping) do
|
for pattern,tags in pairs(mapping) do
|
||||||
if string.match(pattern,PATTERN_PATTERN) == nil then
|
if string.match(pattern,PATTERN_PATTERN) == nil then
|
||||||
darktable.print_error("In tag mapping '" .. name .. "': Pattern '" .. pattern .. "' invalid")
|
darktable.print_error("In tag mapping '" .. name .. "': Pattern '" .. pattern .. "' invalid")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
for _,tag in ipairs(tags) do
|
for _,tag in ipairs(tags) do
|
||||||
if type(tag) ~= "string" then
|
if type(tag) ~= "string" then
|
||||||
darktable.print_error("In tag mapping '" .. name .. "': All tag mappings must be lists of strings")
|
darktable.print_error("In tag mapping '" .. name .. "': All tag mappings must be lists of strings")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
availableMappings[name] = mapping
|
availableMappings[name] = mapping
|
||||||
@@ -224,13 +245,21 @@ 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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
darktable.gui.libs[LIB_ID].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
darktable.gui.libs[LIB_ID].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
darktable.register_tag_mapping("Example",
|
darktable.register_tag_mapping("Example",
|
||||||
{ ["+----*"] = { "Red", "Only red" },
|
{ ["+----*"] = { "Red", "Only red" },
|
||||||
@@ -248,7 +277,7 @@ if darktable.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not ltt.event_registered then
|
if not ltt.event_registered then
|
||||||
darktable.register_event(
|
darktable.register_event(
|
||||||
"view-changed",
|
LIB_ID, "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -259,3 +288,9 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -51,25 +51,37 @@ 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 dsys = require "lib/dtutils.system"
|
local dsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
--Check API version
|
--Check API version
|
||||||
du.check_min_api_version("5.0.0", "OpenInExplorer")
|
du.check_min_api_version("7.0.0", "OpenInExplorer")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("OpenInExplorer",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("OpenInExplorer", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("open in explorer"),
|
||||||
|
purpose = _("open a selected file in the system file manager"),
|
||||||
|
author = "Kevin Ertel",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/OpenInExplorer"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local act_os = dt.configuration.running_os
|
local act_os = dt.configuration.running_os
|
||||||
local PS = act_os == "windows" and "\\" or "/"
|
local PS = act_os == "windows" and "\\" or "/"
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
--Detect OS and quit if it is not supported.
|
--Detect OS and quit if it is not supported.
|
||||||
if act_os ~= "macos" and act_os ~= "windows" and act_os ~= "linux" then
|
if act_os ~= "macos" and act_os ~= "windows" and act_os ~= "linux" then
|
||||||
dt.print(_("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"))
|
dt.print(_("OpenInExplorer plug-in only supports linux, macos, and windows at this time"))
|
||||||
dt.print_error("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time")
|
dt.print_error("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -85,11 +97,11 @@ local function check_if_links_dir_exists()
|
|||||||
local dir_exists = true
|
local dir_exists = true
|
||||||
if not links_dir then
|
if not links_dir then
|
||||||
--Just for paranoic reasons. I tried, but I couldn't devise a setting for a nil value.
|
--Just for paranoic reasons. I tried, but I couldn't devise a setting for a nil value.
|
||||||
dt.print(_("No links directory selected.\nPlease check the dt preferences (lua options)"))
|
dt.print(_("no links directory selected\nplease check the dt preferences (lua options)"))
|
||||||
dt.print_error("OpenInExplorer: No links directory selected")
|
dt.print_error("OpenInExplorer: No links directory selected")
|
||||||
dir_exists = false
|
dir_exists = false
|
||||||
elseif not df.check_if_file_exists(links_dir) then
|
elseif not df.check_if_file_exists(links_dir) then
|
||||||
dt.print(string.format(_("Links directory '%s' not found.\nPlease check the dt preferences (lua options)"), links_dir))
|
dt.print(string.format(_("links directory '%s' not found\nplease check the dt preferences (lua options)"), links_dir))
|
||||||
dt.print_error(string.format("OpenInExplorer: Links directory '%s' not found", links_dir))
|
dt.print_error(string.format("OpenInExplorer: Links directory '%s' not found", links_dir))
|
||||||
dir_exists = false
|
dir_exists = false
|
||||||
end
|
end
|
||||||
@@ -104,9 +116,26 @@ open_dir.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesk
|
|||||||
|
|
||||||
local open_files = {}
|
local open_files = {}
|
||||||
open_files.windows = "explorer.exe /select, %s"
|
open_files.windows = "explorer.exe /select, %s"
|
||||||
open_files.macos = "open -Rn %s"
|
open_files.macos = "osascript -e 'tell application \"Finder\" to (reveal {%s}) activate'"
|
||||||
open_files.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s ""]]
|
open_files.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s ""]]
|
||||||
|
|
||||||
|
reveal_file_osx_cmd = "\"%s\" as POSIX file"
|
||||||
|
|
||||||
|
--Call the osx Finder with each selected image selected.
|
||||||
|
--For images in multiple folders, Finder will open a separate window for each
|
||||||
|
local function call_list_of_files_osx(selected_images)
|
||||||
|
local cmds = {}
|
||||||
|
for _, image in pairs(selected_images) do
|
||||||
|
current_image = image.path..PS..image.filename
|
||||||
|
-- AppleScript needs double quoted strings, and the whole command is wrapped in single quotes.
|
||||||
|
table.insert(cmds, string.format(reveal_file_osx_cmd, string.gsub(string.gsub(current_image, "\"", "\\\""), "'", "'\"'\"'")))
|
||||||
|
end
|
||||||
|
reveal_cmd = table.concat(cmds, ",")
|
||||||
|
run_cmd = string.format(open_files.macos, reveal_cmd)
|
||||||
|
dt.print_log("OSX run_cmd = "..run_cmd)
|
||||||
|
dsys.external_command(run_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
--Call the file mangager for each selected image on Linux.
|
--Call the file mangager for each selected image on Linux.
|
||||||
--There is one call to busctl containing a list of all the image file names.
|
--There is one call to busctl containing a list of all the image file names.
|
||||||
local function call_list_of_files(selected_images)
|
local function call_list_of_files(selected_images)
|
||||||
@@ -146,7 +175,7 @@ local function set_links(selected_images)
|
|||||||
end
|
end
|
||||||
]]
|
]]
|
||||||
if dsys.external_command(run_cmd) ~= 0 then
|
if dsys.external_command(run_cmd) ~= 0 then
|
||||||
dt.print(_("Failed to create links. Missing rights?"))
|
dt.print(_("failed to create links, missing rights?"))
|
||||||
dt.print_error("OpenInExplorer: Failed to create links")
|
dt.print_error("OpenInExplorer: Failed to create links")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -162,18 +191,20 @@ end
|
|||||||
local function open_in_fmanager()
|
local function open_in_fmanager()
|
||||||
local images = dt.gui.selection()
|
local images = dt.gui.selection()
|
||||||
if #images == 0 then
|
if #images == 0 then
|
||||||
dt.print(_("Please select an image"))
|
dt.print(_("please select an image"))
|
||||||
else
|
else
|
||||||
if use_links and not check_if_links_dir_exists() then
|
if use_links and not check_if_links_dir_exists() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if #images > 15 and not use_links then
|
if #images > 15 and not use_links then
|
||||||
dt.print(_("Please select fewer images (max. 15)"))
|
dt.print(_("please select fewer images (max. 15)"))
|
||||||
elseif use_links then
|
elseif use_links then
|
||||||
set_links(images)
|
set_links(images)
|
||||||
else
|
else
|
||||||
if act_os == "linux" then
|
if act_os == "linux" then
|
||||||
call_list_of_files(images)
|
call_list_of_files(images)
|
||||||
|
elseif act_os == "macos" then
|
||||||
|
call_list_of_files_osx(images)
|
||||||
else
|
else
|
||||||
call_file_by_file(images)
|
call_file_by_file(images)
|
||||||
end
|
end
|
||||||
@@ -181,37 +212,50 @@ local function open_in_fmanager()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs.image.destroy_action("OpenInExplorer")
|
||||||
|
dt.destroy_event("OpenInExplorer", "shortcut")
|
||||||
|
if act_os ~= "windows" then
|
||||||
|
dt.preferences.destroy("OpenInExplorer", "linked_image_files_dir")
|
||||||
|
end
|
||||||
|
dt.preferences.destroy("OpenInExplorer", "use_links")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- GUI --
|
-- GUI --
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("show in file explorer"),
|
"OpenInExplorer", _("show in file explorer"),
|
||||||
function() open_in_fmanager() end,
|
function() open_in_fmanager() end,
|
||||||
_("Open the file manager at the selected image's location")
|
_("open the file manager at the selected image's location")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if act_os ~= "windows" then
|
if act_os ~= "windows" then
|
||||||
dt.preferences.register("OpenInExplorer", "linked_image_files_dir", -- name
|
dt.preferences.register("OpenInExplorer", "linked_image_files_dir", -- name
|
||||||
"directory", -- type
|
"directory", -- type
|
||||||
_("OpenInExplorer: linked files directory"), -- label
|
_("OpenInExplorer: linked files directory"), -- label
|
||||||
_("Directory to store the links to the file names. Requires restart to take effect"), -- tooltip
|
_("directory to store the links to the file names, requires restart to take effect"), -- tooltip
|
||||||
"Links to image files", -- default
|
"Links to image files", -- default
|
||||||
dt.new_widget("file_chooser_button"){
|
dt.new_widget("file_chooser_button"){
|
||||||
title = _("Select directory"),
|
title = _("select directory"),
|
||||||
is_directory = true,
|
is_directory = true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
dt.preferences.register("OpenInExplorer", "use_links", -- name
|
dt.preferences.register("OpenInExplorer", "use_links", -- name
|
||||||
"bool", -- type
|
"bool", -- type
|
||||||
_("OpenInExplorer: use links"), -- label
|
_("OpenInExplorer: use links"), -- label
|
||||||
_("Use links instead of multiple windows. Requires restart to take effect"), -- tooltip
|
_("use links instead of multiple windows, requires restart to take effect"), -- tooltip
|
||||||
false, -- default
|
false, -- default
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"shortcut",
|
"OpenInExplorer", "shortcut",
|
||||||
function(event, shortcut) open_in_fmanager() end,
|
function(event, shortcut) open_in_fmanager() end,
|
||||||
"OpenInExplorer"
|
"OpenInExplorer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -63,25 +63,52 @@ local dtsys = require "lib/dtutils.system"
|
|||||||
local MODULE_NAME = "RL_out_sharp"
|
local MODULE_NAME = "RL_out_sharp"
|
||||||
|
|
||||||
-- check API version
|
-- check API version
|
||||||
du.check_min_api_version("5.0.0", MODULE_NAME)
|
du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||||
|
|
||||||
|
-- translation
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("RL output sharpening"),
|
||||||
|
purpose = _("Richardson-Lucy output sharpening using GMic"),
|
||||||
|
author = "Marco Carrarini <marco.carrarini@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/RL_out_sharp"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- OS compatibility
|
-- OS compatibility
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
-- translation
|
|
||||||
local gettext = dt.gettext
|
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- initialize module preferences
|
-- initialize module preferences
|
||||||
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
|
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
|
||||||
dt.preferences.write(MODULE_NAME, "sigma", "string", "0.7")
|
dt.preferences.write(MODULE_NAME, "sigma", "string", "0.7")
|
||||||
dt.preferences.write(MODULE_NAME, "iterations", "string", "10")
|
dt.preferences.write(MODULE_NAME, "iterations", "string", "10")
|
||||||
dt.preferences.write(MODULE_NAME, "jpg_quality", "string", "95")
|
dt.preferences.write(MODULE_NAME, "jpg_quality", "string", "95")
|
||||||
dt.preferences.write(MODULE_NAME, "initialized", "bool", true)
|
dt.preferences.write(MODULE_NAME, "initialized", "bool", true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- preserve original image metadata in the output image -----------------------
|
||||||
|
local function preserve_metadata(original, sharpened)
|
||||||
|
local exiftool = df.check_if_bin_exists("exiftool")
|
||||||
|
|
||||||
|
if exiftool then
|
||||||
|
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
|
||||||
|
else
|
||||||
|
dt.print_log(MODULE_NAME .. " exiftool not found, metadata not preserved")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- setup export ---------------------------------------------------------------
|
-- setup export ---------------------------------------------------------------
|
||||||
@@ -128,7 +155,7 @@ local function export2RL(storage, image_table, extra_data)
|
|||||||
for image, temp_name in pairs(image_table) do
|
for image, temp_name in pairs(image_table) do
|
||||||
|
|
||||||
i = i + 1
|
i = i + 1
|
||||||
dt.print(_("sharpening image ")..i.." ...")
|
dt.print(string.format(_("sharpening image %d ..."), i))
|
||||||
-- create unique filename
|
-- create unique filename
|
||||||
new_name = output_folder..PS..df.get_basename(temp_name)..".jpg"
|
new_name = output_folder..PS..df.get_basename(temp_name)..".jpg"
|
||||||
new_name = df.create_unique_filename(new_name)
|
new_name = df.create_unique_filename(new_name)
|
||||||
@@ -145,7 +172,10 @@ local function export2RL(storage, image_table, extra_data)
|
|||||||
if result ~= 0 then
|
if result ~= 0 then
|
||||||
dt.print(_("sharpening error"))
|
dt.print(_("sharpening error"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- copy metadata from input_file to output_file
|
||||||
|
preserve_metadata(input_file, output_file)
|
||||||
|
|
||||||
-- delete temp image
|
-- delete temp image
|
||||||
os.remove(temp_name)
|
os.remove(temp_name)
|
||||||
@@ -155,6 +185,12 @@ local function export2RL(storage, image_table, extra_data)
|
|||||||
dt.print(_("finished exporting"))
|
dt.print(_("finished exporting"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- script_manager integration
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("exp2RL")
|
||||||
|
end
|
||||||
|
|
||||||
-- new widgets ----------------------------------------------------------------
|
-- new widgets ----------------------------------------------------------------
|
||||||
|
|
||||||
output_folder_selector = dt.new_widget("file_chooser_button"){
|
output_folder_selector = dt.new_widget("file_chooser_button"){
|
||||||
@@ -224,6 +260,12 @@ sigma_slider.value = dt.preferences.read(MODULE_NAME, "sigma", "float")
|
|||||||
iterations_slider.value = dt.preferences.read(MODULE_NAME, "iterations", "float")
|
iterations_slider.value = dt.preferences.read(MODULE_NAME, "iterations", "float")
|
||||||
jpg_quality_slider.value = dt.preferences.read(MODULE_NAME, "jpg_quality", "float")
|
jpg_quality_slider.value = dt.preferences.read(MODULE_NAME, "jpg_quality", "float")
|
||||||
|
|
||||||
|
-- script_manager integration
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
-- end of script --------------------------------------------------------------
|
-- end of script --------------------------------------------------------------
|
||||||
|
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
132
contrib/auto_snapshot.lua
Normal file
132
contrib/auto_snapshot.lua
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
auto_snapshot.lua - automatically take a snapshot when an image is loaded in darkroom
|
||||||
|
|
||||||
|
Copyright (C) 2024 Bill Ferguson <wpferguson@gamil.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
auto_snapshot -
|
||||||
|
|
||||||
|
automatically take a snapshot when an image is loaded in darkroom
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
None
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* start the script from script_manager
|
||||||
|
* open an image in darkroom
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
Bill Ferguson <wpferguson@gamil.com>
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local log = require "lib/dtutils.log"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE <const> = "auto_snapshot"
|
||||||
|
local DEFAULT_LOG_LEVEL <const> = log.error
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- A P I C H E C K
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- I 1 8 N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("auto snapshot"), -- name of script
|
||||||
|
purpose = _("automatically take a snapshot when an image is loaded in darkroom"), -- purpose of script
|
||||||
|
author = "Bill Ferguson <wpferguson@gamil.com>", -- your name and optionally e-mail address
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_snapshot/" -- URL to help/documentation
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- L O G L E V E L
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
log.log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- N A M E S P A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local auto_snapshot = {}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- P R E F E R E N C E S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.preferences.register(MODULE, "always_create_snapshot", "bool", "auto_snapshot - " .. _("always automatically create_snapshot"),
|
||||||
|
_("create a snapshot even if the image is altered"), false)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- D A R K T A B L E I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_event(MODULE, "darkroom-image-loaded")
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- E V E N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.register_event(MODULE, "darkroom-image-loaded",
|
||||||
|
function(event, clean, image)
|
||||||
|
local always = dt.preferences.read(MODULE, "always_create_snapshot", "bool")
|
||||||
|
if clean and always then
|
||||||
|
dt.gui.libs.snapshots.take_snapshot()
|
||||||
|
elseif clean and not image.is_altered then
|
||||||
|
dt.gui.libs.snapshots.take_snapshot()
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return script_data
|
||||||
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
|
||||||
@@ -39,102 +39,36 @@ GPLv2
|
|||||||
local darktable = require "darktable"
|
local darktable = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
local filelib = require "lib/dtutils.file"
|
local filelib = require "lib/dtutils.file"
|
||||||
|
local syslib = require "lib/dtutils.system"
|
||||||
|
|
||||||
-- Forward declare the functions
|
du.check_min_api_version("7.0.0", "autostyle")
|
||||||
local autostyle_apply_one_image,autostyle_apply_one_image_event,autostyle_apply,exiftool_attribute,capture
|
|
||||||
local CURR_API_STRING = darktable.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Tested it with darktable 1.6.1 and darktable git from 2014-01-25
|
local gettext = darktable.gettext.gettext
|
||||||
du.check_min_api_version("2.0.2", "autostyle")
|
|
||||||
|
|
||||||
-- Receive the event triggered
|
local function _(msgid)
|
||||||
function autostyle_apply_one_image_event(event,image)
|
return gettext(msgid)
|
||||||
autostyle_apply_one_image(image)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Apply the style to an image, if it matches the tag condition
|
-- return data structure for script_manager
|
||||||
function autostyle_apply_one_image (image)
|
|
||||||
-- We need the tag, the value and the style_name provided from the configuration string
|
|
||||||
local tag,value,style_name=string.match(darktable.preferences.read("autostyle","exif_tag","string"),"(%g+)%s*=%s*(%g+)%s*=>%s*(%g+)")
|
|
||||||
|
|
||||||
-- check they all exist (correct syntax)
|
local script_data = {}
|
||||||
if (not tag) then
|
|
||||||
darktable.print("EXIF TAG not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if (not value) then
|
|
||||||
darktable.print("value to match not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if (not style_name) then
|
|
||||||
darktable.print("style name not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if not filelib.check_if_bin_exists("exiftool") then
|
|
||||||
darktable.print("Can't find exiftool")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
|
local have_not_printed_config_message = true
|
||||||
|
|
||||||
-- First find the style (we have its name)
|
script_data.metadata = {
|
||||||
local styles= darktable.styles
|
name = _("auto style"),
|
||||||
local style
|
purpose = _("automatically apply a style based on image EXIF tag"),
|
||||||
for _,s in ipairs(styles) do
|
author = "Marc Cousin <cousinmarc@gmail.com>",
|
||||||
if s.name == style_name then
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle/"
|
||||||
style=s
|
}
|
||||||
end
|
|
||||||
end
|
|
||||||
if (not style) then
|
|
||||||
darktable.print("style not found for autostyle: " .. style_name)
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply the style to image, if it is tagged
|
script_data.destroy = nil -- function to destory the script
|
||||||
local ok,auto_dr_attr= pcall(exiftool_attribute,image.path .. '/' .. image.filename,tag)
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
--darktable.print_error("dr_attr:" .. auto_dr_attr)
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
-- If the lookup fails, stop here
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
if (not ok) then
|
|
||||||
darktable.print("Couldn't get attribute " .. auto_dr_attr .. " from exiftool's output")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if auto_dr_attr==value then
|
|
||||||
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. darktable.preferences.read("autostyle","exif_tag","string") )
|
|
||||||
darktable.styles.apply(style,image)
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. darktable.preferences.read("autostyle","exif_tag","string") .. " not matched: " .. auto_dr_attr)
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function autostyle_apply( shortcut)
|
|
||||||
local images = darktable.gui.action_images
|
|
||||||
local images_processed =0
|
|
||||||
local images_submitted =0
|
|
||||||
for _,image in pairs(images) do
|
|
||||||
images_submitted = images_submitted +1
|
|
||||||
images_processed = images_processed + autostyle_apply_one_image(image)
|
|
||||||
end
|
|
||||||
darktable.print("Applied auto style to " .. images_processed .. " out of " .. images_submitted .. " image(s)")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Retrieve the attribute through exiftool
|
|
||||||
function exiftool_attribute(path,attr)
|
|
||||||
local cmd="exiftool -" .. attr .. " '" .. path .. "'";
|
|
||||||
local exifresult=get_stdout(cmd)
|
|
||||||
local attribute=string.match(exifresult,": (.*)")
|
|
||||||
if (attribute == nil) then
|
|
||||||
darktable.print_error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">")
|
|
||||||
-- Raise an error to the caller
|
|
||||||
error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">");
|
|
||||||
end
|
|
||||||
-- darktable.print_error("Returning attribute: " .. attribute)
|
|
||||||
return attribute
|
|
||||||
end
|
|
||||||
|
|
||||||
-- run command and retrieve stdout
|
-- run command and retrieve stdout
|
||||||
function get_stdout(cmd)
|
local function get_stdout(cmd)
|
||||||
-- Open the command, for reading
|
-- Open the command, for reading
|
||||||
local fd = assert(io.popen(cmd, 'r'))
|
local fd = assert(io.popen(cmd, 'r'))
|
||||||
darktable.control.read(fd)
|
darktable.control.read(fd)
|
||||||
@@ -151,13 +85,116 @@ function get_stdout(cmd)
|
|||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Retrieve the attribute through exiftool
|
||||||
|
local function exiftool_attribute(path, attr)
|
||||||
|
local cmd = "exiftool -" .. attr .. " '" .. path .. "'";
|
||||||
|
local exifresult = get_stdout(cmd)
|
||||||
|
local attribute = string.match(exifresult, ": (.*)")
|
||||||
|
if (attribute == nil) then
|
||||||
|
darktable.print_error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">")
|
||||||
|
-- Raise an error to the caller
|
||||||
|
error( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">");
|
||||||
|
end
|
||||||
|
-- darktable.print_error("Returning attribute: " .. attribute)
|
||||||
|
return attribute
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply the style to an image, if it matches the tag condition
|
||||||
|
local function autostyle_apply_one_image (image)
|
||||||
|
|
||||||
|
local pref = darktable.preferences.read("autostyle", "exif_tag", "string")
|
||||||
|
|
||||||
|
if pref and string.len(pref) >= 6 then
|
||||||
|
-- We need the tag, the value and the style_name provided from the configuration string
|
||||||
|
local tag, value, style_name = string.match(pref, "(%g+)%s*=%s*([%g ]-)%s*=>%s*(%g+)")
|
||||||
|
|
||||||
|
-- check they all exist (correct syntax)
|
||||||
|
if (not tag) then
|
||||||
|
darktable.print(string.format(_("EXIF tag not found in %s"), pref))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if (not value) then
|
||||||
|
darktable.print(string.format(_("value to match not found in %s"), pref))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if (not style_name) then
|
||||||
|
darktable.print(string.format(_("style name not found in %s"), pref))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if not filelib.check_if_bin_exists("exiftool") then
|
||||||
|
darktable.print(_("can't find exiftool"))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- First find the style (we have its name)
|
||||||
|
local styles = darktable.styles
|
||||||
|
local style
|
||||||
|
for _, s in ipairs(styles) do
|
||||||
|
if s.name == style_name then
|
||||||
|
style = s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if (not style) then
|
||||||
|
darktable.print(string.format(_("style not found for autostyle: %s"), style_name))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply the style to image, if it is tagged
|
||||||
|
local ok, auto_dr_attr = pcall(exiftool_attribute, image.path .. '/' .. image.filename,tag)
|
||||||
|
--darktable.print_error("dr_attr:" .. auto_dr_attr)
|
||||||
|
-- If the lookup fails, stop here
|
||||||
|
if (not ok) then
|
||||||
|
darktable.print(string.format(_("couldn't get attribute %s from exiftool's output"), auto_dr_attr))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if auto_dr_attr == value then
|
||||||
|
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. pref)
|
||||||
|
darktable.styles.apply(style,image)
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. pref .. " not matched: " .. auto_dr_attr)
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
elseif have_not_printed_config_message then
|
||||||
|
have_not_printed_config_message = false
|
||||||
|
darktable.print(string.format(_("%s is not configured, please configure the preference in Lua options"), script_data.metadata.name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Receive the event triggered
|
||||||
|
local function autostyle_apply_one_image_event(event,image)
|
||||||
|
autostyle_apply_one_image(image)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function autostyle_apply(shortcut)
|
||||||
|
local images = darktable.gui.action_images
|
||||||
|
local images_processed = 0
|
||||||
|
local images_submitted = 0
|
||||||
|
for _,image in pairs(images) do
|
||||||
|
images_submitted = images_submitted + 1
|
||||||
|
images_processed = images_processed + autostyle_apply_one_image(image)
|
||||||
|
end
|
||||||
|
darktable.print(string.format(_("applied auto style to %d out of %d image(s)"), images_processed, images_submitted))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
darktable.destroy_event("autostyle", "shortcut")
|
||||||
|
darktable.destroy_event("autostyle", "post-import-image")
|
||||||
|
end
|
||||||
|
|
||||||
-- Registering events
|
-- Registering events
|
||||||
darktable.register_event("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("post-import-image",
|
darktable.register_event("autostyle", "post-import-image",
|
||||||
autostyle_apply_one_image_event)
|
autostyle_apply_one_image_event)
|
||||||
|
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
return script_data
|
||||||
|
|||||||
205
contrib/change_group_leader.lua
Normal file
205
contrib/change_group_leader.lua
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
--[[
|
||||||
|
change group leader
|
||||||
|
|
||||||
|
copyright (c) 2020, 2022 Bill Ferguson <wpferguson@gmail.com>
|
||||||
|
copyright (c) 2021 Angel Angelov
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
CHANGE GROUP LEADER
|
||||||
|
automatically change the leader of raw+jpg paired image groups
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
|
||||||
|
* add the following line in the file $CONFIGDIR/luarc
|
||||||
|
require "change_group_leader"
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* in lighttable mode, select the image groups you wish to process,
|
||||||
|
select whether you want to set the leader to "jpg" or "raw",
|
||||||
|
and click "Execute"
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local MODULE = "change_group_leader"
|
||||||
|
|
||||||
|
du.check_min_api_version("3.0.0", MODULE)
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("change group leader"),
|
||||||
|
purpose = _("automatically change the leader of raw+jpg paired image groups"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/change_group_leader"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
-- create a namespace to contain persistent data and widgets
|
||||||
|
local chg_grp_ldr = {}
|
||||||
|
|
||||||
|
local cgl = chg_grp_ldr
|
||||||
|
|
||||||
|
cgl.widgets = {}
|
||||||
|
|
||||||
|
cgl.event_registered = false
|
||||||
|
cgl.module_installed = false
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not cgl.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE, -- Module name
|
||||||
|
_("change group leader"), -- Visible name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700}}, -- containers
|
||||||
|
cgl.widgets.box,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
cgl.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_group_leader(images, mode)
|
||||||
|
for _, img in ipairs(images) do
|
||||||
|
dt.print_log("checking image " .. img.id .. " named " .. img.filename)
|
||||||
|
local found = false
|
||||||
|
if mode == "jpg" then
|
||||||
|
if string.match(string.lower(img.filename), "jpg$") then
|
||||||
|
dt.print_log("jpg matched image " .. img.filename)
|
||||||
|
found = true
|
||||||
|
end
|
||||||
|
elseif mode == "raw" then
|
||||||
|
if img.is_raw and img.duplicate_index == 0 then
|
||||||
|
dt.print_log("found raw " .. img.filename)
|
||||||
|
found = true
|
||||||
|
end
|
||||||
|
elseif mode == "non-raw" then
|
||||||
|
if img.is_ldr then
|
||||||
|
dt.print_log("found ldr " .. img.filename)
|
||||||
|
found = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
dt.print_error(MODULE .. ": unrecognized mode " .. mode)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if found then
|
||||||
|
dt.print_log("making " .. img.filename .. " group leader")
|
||||||
|
img:make_group_leader()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function process_image_groups(images)
|
||||||
|
if #images < 1 then
|
||||||
|
dt.print(_("no images selected"))
|
||||||
|
dt.print_log(MODULE .. "no images seletected, returning...")
|
||||||
|
else
|
||||||
|
local mode = cgl.widgets.mode.value
|
||||||
|
for _,img in ipairs(images) do
|
||||||
|
dt.print_log("checking image " .. img.id)
|
||||||
|
local group_images = img:get_group_members()
|
||||||
|
if group_images == 1 then
|
||||||
|
dt.print_log("only one image in group for image " .. image.id)
|
||||||
|
else
|
||||||
|
find_group_leader(group_images, mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- W I D G E T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
cgl.widgets.mode = dt.new_widget("combobox"){
|
||||||
|
label = _("select new group leader"),
|
||||||
|
tooltip = _("select type of image to be group leader"),
|
||||||
|
selected = 1,
|
||||||
|
"jpg", "raw", "non-raw",
|
||||||
|
}
|
||||||
|
|
||||||
|
cgl.widgets.execute = dt.new_widget("button"){
|
||||||
|
label = _("execute"),
|
||||||
|
clicked_callback = function()
|
||||||
|
process_image_groups(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
cgl.widgets.box = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
cgl.widgets.mode,
|
||||||
|
cgl.widgets.execute,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- D A R K T A B L E I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not cgl.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
cgl.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -39,21 +39,32 @@
|
|||||||
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
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("clear GPS info"),
|
||||||
|
purpose = _("remove GPS data from selected image(s)"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps/"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- not a number
|
-- not a number
|
||||||
local NaN = 0/0
|
local NaN = 0/0
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "clear_GPS")
|
du.check_min_api_version("7.0.0", "clear_GPS")
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("clear_GPS",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext("clear_GPS", msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function clear_GPS(images)
|
local function clear_GPS(images)
|
||||||
for _, image in ipairs(images) do
|
for _, image in ipairs(images) do
|
||||||
@@ -64,14 +75,23 @@ local function clear_GPS(images)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_event("clear_GPS", "shortcut")
|
||||||
|
dt.gui.libs.image.destroy_action("clear_GPS")
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("clear GPS data"),
|
"clear_GPS", _("clear GPS data"),
|
||||||
function(event, images) clear_GPS(images) end,
|
function(event, images) clear_GPS(images) end,
|
||||||
_("Clear GPS data from selected images")
|
_("clear GPS data from selected images")
|
||||||
)
|
)
|
||||||
|
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"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
|
||||||
|
|||||||
377
contrib/color_profile_manager.lua
Normal file
377
contrib/color_profile_manager.lua
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
color_profile_manager.lua - manage external darktable color profiles
|
||||||
|
|
||||||
|
Copyright (C) 2021 Bill Ferguson <wpferguson@gmail.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
color_profile_manager - manage external darktable color profiles
|
||||||
|
|
||||||
|
This script provides a tool to manage input and output external color
|
||||||
|
profiles used by darktable. Color profiles can be added or removed
|
||||||
|
to/from the correct directories so that darktable can find and use
|
||||||
|
them.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
* None
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* require this script from your main lua file
|
||||||
|
* select the input or output set of color profiles
|
||||||
|
* a list of profiles is displayed. Click the check box beside the
|
||||||
|
profile name to select it for removal. Click the "remove profile"
|
||||||
|
button to remove the profile.
|
||||||
|
* use the file selector to select a color profile to add to the currently
|
||||||
|
selected set (input or output)
|
||||||
|
* click the "add profile" button to add the profile to the selected set
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
* Send to Bill Ferguson, wpferguson@gmail.com or raise an issue on
|
||||||
|
https://github.com/dakrtable-org/lua-scripts
|
||||||
|
]]
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "color_profile_manager")
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- L O C A L I Z A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE_NAME = "color_profile_manager"
|
||||||
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
local CS = dt.configuration.running_os == "windows" and "&" or ";"
|
||||||
|
local DIR_CMD = dt.configuration.running_os == "windows" and "dir /b" or "ls"
|
||||||
|
local COLOR_IN_DIR = dt.configuration.config_dir .. PS .. "color" .. PS .. "in"
|
||||||
|
local COLOR_OUT_DIR = dt.configuration.config_dir .. PS .. "color" .. PS .. "out"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- N A M E S P A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local cpm = {}
|
||||||
|
cpm.widgets = {}
|
||||||
|
cpm.widgets.profile_box = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
}
|
||||||
|
cpm.module_installed = false
|
||||||
|
cpm.event_registered = false
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function check_for_directories()
|
||||||
|
if not df.test_file(COLOR_IN_DIR, "d") then
|
||||||
|
dt.print_log("didn't find " .. COLOR_IN_DIR)
|
||||||
|
return false
|
||||||
|
elseif not df.test_file(COLOR_OUT_DIR, "d") then
|
||||||
|
dt.print_log("didn't find " .. COLOR_OUT_DIR)
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
dt.print_log("both directories exist")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_directories()
|
||||||
|
df.mkdir(COLOR_IN_DIR)
|
||||||
|
df.mkdir(COLOR_OUT_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function list_profiles(dir)
|
||||||
|
local files = {}
|
||||||
|
local p = io.popen(DIR_CMD .. " " .. dir)
|
||||||
|
if p then
|
||||||
|
for line in p:lines() do
|
||||||
|
table.insert(files, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
p:close()
|
||||||
|
return files
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_profile(file, dir)
|
||||||
|
df.file_copy(file, dir)
|
||||||
|
dt.print(string.format(_("added color profile %s to %s"), file, dir))
|
||||||
|
dt.print_log("color profile " .. file .. " added to " .. dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove_profile(file, dir)
|
||||||
|
os.remove(dir .. PS .. file)
|
||||||
|
dt.print(string.format(_("removed color profile %s from %s"), file, dir))
|
||||||
|
dt.print_log("color profile " .. file .. " removed from " .. dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clear_profile_box()
|
||||||
|
for i = #cpm.widgets.profile_box, 1, -1 do
|
||||||
|
cpm.widgets.profile_box[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_profile_callback()
|
||||||
|
local profile_dir = COLOR_IN_DIR
|
||||||
|
if cpm.widgets.profile_set.selected == 2 then
|
||||||
|
profile_dir = COLOR_OUT_DIR
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_profile = cpm.widgets.profile_selector.value
|
||||||
|
if string.lower(df.get_filetype(new_profile)) ~= "icm" and string.lower(df.get_filetype(new_profile)) ~= "icc" then
|
||||||
|
dt.print(_("ERROR: color profile must be an icc or icm file"))
|
||||||
|
dt.print_error(new_profile .. " selected and isn't an icc or icm file")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set selector value to directory that new profile came from
|
||||||
|
-- in case there are more
|
||||||
|
cpm.widgets.profile_selector.value = df.get_path(cpm.widgets.profile_selector.value)
|
||||||
|
add_profile(new_profile, profile_dir)
|
||||||
|
local files = list_profiles(profile_dir)
|
||||||
|
local widgets = {}
|
||||||
|
|
||||||
|
local profile_ptr = 1
|
||||||
|
for i, file in ipairs(files) do
|
||||||
|
if #cpm.widgets.profile_box == 0 or cpm.widgets.profile_box[profile_ptr].label ~= file then
|
||||||
|
table.insert(widgets, dt.new_widget("check_button"){value = false, label = file})
|
||||||
|
else
|
||||||
|
table.insert(widgets, cpm.widgets.profile_box[profile_ptr])
|
||||||
|
profile_ptr = profile_ptr + 1
|
||||||
|
if profile_ptr > #cpm.widgets.profile_box then
|
||||||
|
profile_ptr = #cpm.widgets.profile_box
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
clear_profile_box()
|
||||||
|
for _, widget in ipairs(widgets) do
|
||||||
|
table.insert(cpm.widgets.profile_box, widget)
|
||||||
|
end
|
||||||
|
if not cpm.widgets.remove_box.visible then
|
||||||
|
cpm.widgets.remove_box.visible = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove_profile_callback()
|
||||||
|
local widgets_to_keep = {}
|
||||||
|
local profile_dir = COLOR_IN_DIR
|
||||||
|
if cpm.widgets.profile_set.selected == 2 then
|
||||||
|
profile_dir = COLOR_OUT_DIR
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, widget in ipairs(cpm.widgets.profile_box) do
|
||||||
|
if widget.value == true then
|
||||||
|
remove_profile(widget.label, profile_dir)
|
||||||
|
else
|
||||||
|
table.insert(widgets_to_keep, widget)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
clear_profile_box()
|
||||||
|
for _, widget in ipairs(widgets_to_keep) do
|
||||||
|
table.insert(cpm.widgets.profile_box, widget)
|
||||||
|
end
|
||||||
|
if #cpm.widgets.profile_box == 0 then
|
||||||
|
cpm.widgets.remove_box.visible = false
|
||||||
|
else
|
||||||
|
cpm.widgets.remove_box.visible = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function list_profile_callback(choice)
|
||||||
|
local list_dir = COLOR_IN_DIR
|
||||||
|
if choice == 2 then
|
||||||
|
list_dir = COLOR_OUT_DIR
|
||||||
|
end
|
||||||
|
|
||||||
|
local files = list_profiles(list_dir)
|
||||||
|
|
||||||
|
if #files == 0 then
|
||||||
|
cpm.widgets.remove_box.visible = false
|
||||||
|
else
|
||||||
|
cpm.widgets.remove_box.visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
clear_profile_box()
|
||||||
|
|
||||||
|
for i, file in ipairs(files) do
|
||||||
|
cpm.widgets.profile_box[i] = dt.new_widget("check_button"){value = false, label = file}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not cpm.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE_NAME, -- Module name
|
||||||
|
_("color profile manager"), -- Visible name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers
|
||||||
|
cpm.widgets.main_widget,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
cpm.module_installed = true
|
||||||
|
dt.control.sleep(500)
|
||||||
|
if not cpm.initialized then
|
||||||
|
cpm.widgets.profile_set.visible = false
|
||||||
|
cpm.widgets.add_box.visible = false
|
||||||
|
cpm.widgets.remove_box.visible = false
|
||||||
|
else
|
||||||
|
cpm.widgets.profile_set.selected = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- W I D G E T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
cpm.initialized = check_for_directories()
|
||||||
|
dt.print_log("cpm.initialized is " .. tostring(cpm.initialized))
|
||||||
|
|
||||||
|
if not cpm.initialized then
|
||||||
|
dt.print_log("creating init_box")
|
||||||
|
cpm.widgets.init_box = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("button"){
|
||||||
|
label = _("initialize color profiles"),
|
||||||
|
tooltip = _("create the directory structure to contain the color profiles"),
|
||||||
|
clicked_callback = function(this)
|
||||||
|
add_directories()
|
||||||
|
cpm.initialized = true
|
||||||
|
cpm.widgets.profile_set.visible = true
|
||||||
|
cpm.widgets.add_box.visible = true
|
||||||
|
cpm.widgets.remove_box.visible = false
|
||||||
|
cpm.widgets.init_box.visible = false
|
||||||
|
cpm.widgets.profile_set.selected = 1
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
cpm.widgets.profile_set = dt.new_widget("combobox"){
|
||||||
|
label = _("select profile set"),
|
||||||
|
tooltip = _("select input or output profiles"),
|
||||||
|
visible = cpm.initialized and false or true,
|
||||||
|
changed_callback = function(this)
|
||||||
|
if cpm.initialized then
|
||||||
|
list_profile_callback(this.selected)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
_("input"), _("output"),
|
||||||
|
}
|
||||||
|
|
||||||
|
cpm.widgets.profile_selector = dt.new_widget("file_chooser_button"){
|
||||||
|
title = _("select color profile to add"),
|
||||||
|
tooltip = _("select the .icc or .icm file to add"),
|
||||||
|
is_directory = false
|
||||||
|
}
|
||||||
|
|
||||||
|
cpm.widgets.add_box = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
visible = cpm.initialized and true or false,
|
||||||
|
dt.new_widget("section_label"){label = _("add profile")},
|
||||||
|
cpm.widgets.profile_selector,
|
||||||
|
dt.new_widget("button"){
|
||||||
|
label = _("add selected color profile"),
|
||||||
|
tooltip = _("add selected file to profiles"),
|
||||||
|
clicked_callback = function(this)
|
||||||
|
add_profile_callback()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cpm.widgets.remove_box = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
visible = cpm.initialized and true or false,
|
||||||
|
dt.new_widget("section_label"){label = _("remove profile")},
|
||||||
|
cpm.widgets.profile_box,
|
||||||
|
dt.new_widget("button"){
|
||||||
|
label = _("remove selected profile(s)"),
|
||||||
|
tooltip = _("remove the checked profile(s)"),
|
||||||
|
clicked_callback = function(this)
|
||||||
|
remove_profile_callback()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local main_widgets = {}
|
||||||
|
|
||||||
|
if not cpm.initialized then
|
||||||
|
table.insert(main_widgets, cpm.widgets.init_box)
|
||||||
|
end
|
||||||
|
table.insert(main_widgets, cpm.widgets.profile_set)
|
||||||
|
table.insert(main_widgets, cpm.widgets.remove_box)
|
||||||
|
table.insert(main_widgets, cpm.widgets.add_box)
|
||||||
|
|
||||||
|
cpm.widgets.main_widget = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
table.unpack(main_widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not cpm.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
MODULE_NAME, "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
cpm.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("color profile manager"),
|
||||||
|
purpose = _("manage external darktable color profiles"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/color_profile_manager"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -40,18 +40,30 @@ local dt = require "darktable"
|
|||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
local debug = require "darktable.debug"
|
local debug = require "darktable.debug"
|
||||||
|
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "copy_attach_detach_tags")
|
du.check_min_api_version("7.0.0", "copy_attach_detach_tags")
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("copy_attach_detach_tags", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("copy attach detach tags"),
|
||||||
|
purpose = _("shortcuts to copy, paste, replace, or remove tags from images"),
|
||||||
|
author = "Christian Kanzian",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local cadt = {}
|
local cadt = {}
|
||||||
cadt.module_installed = false
|
cadt.module_installed = false
|
||||||
cadt.event_registered = false
|
cadt.event_registered = false
|
||||||
@@ -91,7 +103,7 @@ local function mcopy_tags()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.print(_('Image tags copied ...'))
|
dt.print(_('image tags copied ...'))
|
||||||
|
|
||||||
--create UI tag list
|
--create UI tag list
|
||||||
local taglist = ""
|
local taglist = ""
|
||||||
@@ -113,7 +125,7 @@ local function mcopy_tags()
|
|||||||
local function attach_tags()
|
local function attach_tags()
|
||||||
|
|
||||||
if next(image_tags) == nil then
|
if next(image_tags) == nil then
|
||||||
dt.print(_('No tag to attach, please copy tags first.'))
|
dt.print(_('no tag to attach, please copy tags first.'))
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -137,7 +149,7 @@ local function attach_tags()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dt.print(_('Tags attached ...'))
|
dt.print(_('tags attached ...'))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function detach_tags()
|
local function detach_tags()
|
||||||
@@ -153,13 +165,13 @@ local function detach_tags()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dt.print(_('Tags removed from image(s).'))
|
dt.print(_('tags removed from image(s).'))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function replace_tags()
|
local function replace_tags()
|
||||||
detach_tags()
|
detach_tags()
|
||||||
attach_tags()
|
attach_tags()
|
||||||
dt.print(_('Tags replaced'))
|
dt.print(_('tags replaced'))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function install_module()
|
local function install_module()
|
||||||
@@ -182,6 +194,41 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_event("cadt_ct", "shortcut")
|
||||||
|
dt.destroy_event("cadt_at", "shortcut")
|
||||||
|
dt.destroy_event("cadt_dt", "shortcut")
|
||||||
|
dt.destroy_event("cadt_rt", "shortcut")
|
||||||
|
dt.gui.libs["tagging_addon"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
-- shortcut for copy
|
||||||
|
dt.register_event("cadt_ct", "shortcut",
|
||||||
|
mcopy_tags,
|
||||||
|
_('copy tags from selected image(s)'))
|
||||||
|
|
||||||
|
-- shortcut for attach
|
||||||
|
dt.register_event("cadt_at", "shortcut",
|
||||||
|
attach_tags,
|
||||||
|
_('paste tags to selected image(s)'))
|
||||||
|
|
||||||
|
-- shortcut for detaching tags
|
||||||
|
dt.register_event("cadt_dt", "shortcut",
|
||||||
|
detach_tags,
|
||||||
|
_('remove tags from selected image(s)'))
|
||||||
|
|
||||||
|
-- shortcut for replace tags
|
||||||
|
dt.register_event("cadt_rt", "shortcut",
|
||||||
|
replace_tags,
|
||||||
|
_('replace tags from selected image(s)'))
|
||||||
|
dt.gui.libs["tagging_addon"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs["tagging_addon"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- create modul Tagging addons
|
-- create modul Tagging addons
|
||||||
taglist_label.reset_callback = mcopy_tags
|
taglist_label.reset_callback = mcopy_tags
|
||||||
|
|
||||||
@@ -234,7 +281,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not cadt.event_registered then
|
if not cadt.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"cadt", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -247,24 +294,30 @@ end
|
|||||||
|
|
||||||
|
|
||||||
-- shortcut for copy
|
-- shortcut for copy
|
||||||
dt.register_event("shortcut",
|
dt.register_event("cadt_ct", "shortcut",
|
||||||
mcopy_tags,
|
mcopy_tags,
|
||||||
_('copy tags from selected image(s)'))
|
_('copy tags from selected image(s)'))
|
||||||
|
|
||||||
-- shortcut for attach
|
-- shortcut for attach
|
||||||
dt.register_event("shortcut",
|
dt.register_event("cadt_at", "shortcut",
|
||||||
attach_tags,
|
attach_tags,
|
||||||
_('paste tags to selected image(s)'))
|
_('paste tags to selected image(s)'))
|
||||||
|
|
||||||
-- shortcut for detaching tags
|
-- shortcut for detaching tags
|
||||||
dt.register_event("shortcut",
|
dt.register_event("cadt_dt", "shortcut",
|
||||||
detach_tags,
|
detach_tags,
|
||||||
_('remove tags from selected image(s)'))
|
_('remove tags from selected image(s)'))
|
||||||
|
|
||||||
-- shortcut for replace tags
|
-- shortcut for replace tags
|
||||||
dt.register_event("shortcut",
|
dt.register_event("cadt_rt", "shortcut",
|
||||||
replace_tags,
|
replace_tags,
|
||||||
_('replace tags from selected image(s)'))
|
_('replace tags from selected image(s)'))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;
|
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;
|
||||||
|
|||||||
@@ -35,9 +35,29 @@ USAGE
|
|||||||
local darktable = require "darktable"
|
local darktable = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
-- Tested with darktable 2.0.1
|
du.check_min_api_version("7.0.0", "cr2hdr")
|
||||||
du.check_min_api_version("2.0.0", "cr2hdr")
|
|
||||||
local CURR_API_STRING = darktable.configuration.api_version_string
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("cr2hdr"),
|
||||||
|
purpose = _("process Magic Lantern dual ISO images"),
|
||||||
|
author = "Till Theato <theato@ttill.de>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local queue = {}
|
local queue = {}
|
||||||
local processed_files = {}
|
local processed_files = {}
|
||||||
@@ -80,7 +100,7 @@ end
|
|||||||
local function convert_images()
|
local function convert_images()
|
||||||
if next(queue) == nil then return end
|
if next(queue) == nil then return end
|
||||||
|
|
||||||
job = darktable.gui.create_job("Dual ISO conversion", true, stop_conversion)
|
job = darktable.gui.create_job(_("dual ISO conversion"), true, stop_conversion)
|
||||||
for key,image in pairs(queue) do
|
for key,image in pairs(queue) do
|
||||||
if job.valid then
|
if job.valid then
|
||||||
job.percent = (key-1)/#queue
|
job.percent = (key-1)/#queue
|
||||||
@@ -91,7 +111,7 @@ local function convert_images()
|
|||||||
end
|
end
|
||||||
local success_count = 0
|
local success_count = 0
|
||||||
for _ in pairs(processed_files) do success_count = success_count + 1 end
|
for _ in pairs(processed_files) do success_count = success_count + 1 end
|
||||||
darktable.print("Dual ISO conversion successful on " .. success_count .. "/" .. #queue .. " images.")
|
darktable.print(string.format(_("dual ISO conversion successful on %d/%d images."), success_count, #queue))
|
||||||
job.valid = false
|
job.valid = false
|
||||||
processed_files = {}
|
processed_files = {}
|
||||||
queue = {}
|
queue = {}
|
||||||
@@ -108,11 +128,21 @@ local function convert_action_images(shortcut)
|
|||||||
convert_images()
|
convert_images()
|
||||||
end
|
end
|
||||||
|
|
||||||
darktable.register_event("shortcut",
|
local function destroy()
|
||||||
convert_action_images, "Run cr2hdr (Magic Lantern DualISO converter) on selected images")
|
darktable.destroy_event("cr2hdr", "shortcut")
|
||||||
darktable.register_event("post-import-image",
|
darktable.destroy_event("cr2hdr", "post-import-image")
|
||||||
|
darktable.destroy_event("cr2hdr", "post-import-film")
|
||||||
|
end
|
||||||
|
|
||||||
|
darktable.register_event("cr2hdr", "shortcut",
|
||||||
|
convert_action_images, _("run cr2hdr (Magic Lantern DualISO converter) on selected images"))
|
||||||
|
darktable.register_event("cr2hdr", "post-import-image",
|
||||||
file_imported)
|
file_imported)
|
||||||
darktable.register_event("post-import-film",
|
darktable.register_event("cr2hdr", "post-import-film",
|
||||||
film_imported)
|
film_imported)
|
||||||
|
|
||||||
darktable.preferences.register("cr2hdr", "onimport", "bool", "Invoke on import", "If true then cr2hdr will try to proccess every file during importing. Warning: cr2hdr is quite slow even in figuring out on whether the file is Dual ISO or not.", false)
|
darktable.preferences.register("cr2hdr", "onimport", "bool", _("invoke on import"), _("if true then cr2hdr will try to proccess every file during importing\nwarning: cr2hdr is quite slow even in figuring out on whether the file is dual ISO or not."), false)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
162
contrib/cycle_group_leader.lua
Normal file
162
contrib/cycle_group_leader.lua
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
cycle_group_leader.lua - change image group leader
|
||||||
|
|
||||||
|
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
cycle_group_leader - change image grouip leader
|
||||||
|
|
||||||
|
cycle_group_leader changes the group leader to the next
|
||||||
|
image in the group. If the end of the group is reached
|
||||||
|
then the next image is wrapped around to the first image.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
None
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* enable with script_manager
|
||||||
|
* assign a key to the shortcut
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
Bill Ferguson <wpferguson@gmail.com>
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE = "cycle_group_leader"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- A P I C H E C K
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", MODULE)
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- I 1 8 N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("cycle group leader"),
|
||||||
|
purpose = _("change image group leader"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cycle_group_leader"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function toggle_global_toolbox_grouping()
|
||||||
|
dt.gui.libs.global_toolbox.grouping = false
|
||||||
|
dt.gui.libs.global_toolbox.grouping = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hinter_msg(msg)
|
||||||
|
dt.print_hinter(msg)
|
||||||
|
dt.control.sleep(1500)
|
||||||
|
dt.print_hinter(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N P R O G R A M
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function cycle_group_leader(image)
|
||||||
|
local group_images = image:get_group_members()
|
||||||
|
if #group_images < 2 then
|
||||||
|
hinter_msg(_("no images to cycle through in group"))
|
||||||
|
return
|
||||||
|
else
|
||||||
|
local position = nil
|
||||||
|
for i, img in ipairs(group_images) do
|
||||||
|
if image == img then
|
||||||
|
position = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if position == #group_images then
|
||||||
|
position = 1
|
||||||
|
else
|
||||||
|
position = position + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
new_leader = group_images[position]
|
||||||
|
new_leader:make_group_leader()
|
||||||
|
dt.gui.selection({new_leader})
|
||||||
|
|
||||||
|
if dt.gui.libs.global_toolbox.grouping then
|
||||||
|
-- toggle the grouping to make the new leader show
|
||||||
|
toggle_global_toolbox_grouping()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- D A R K T A B L E I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
-- put things to destroy (events, storages, etc) here
|
||||||
|
dt.destroy_event(MODULE, "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- E V E N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.register_event(MODULE, "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
|
-- ignore the film roll, it contains all the images, not just the imported
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
if #images < 1 then
|
||||||
|
dt.print(_("no image selected, please select an image and try again"))
|
||||||
|
else
|
||||||
|
cycle_group_leader(images[1])
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
_("cycle group leader")
|
||||||
|
)
|
||||||
|
|
||||||
|
return script_data
|
||||||
334
contrib/dbmaint.lua
Normal file
334
contrib/dbmaint.lua
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
dbmaint.lua - perform database maintenance
|
||||||
|
|
||||||
|
Copyright (C) 2024 Bill Ferguson <wpferguson@gamil.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
dbmaint - perform database maintenance
|
||||||
|
|
||||||
|
Perform database maintenance to clean up missing images and filmstrips.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
None
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* start dbmaint from script_manager
|
||||||
|
* scan for missing film rolls or missing images
|
||||||
|
* look at the results and choose to delete or not
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
Bill Ferguson <wpferguson@gamil.com>
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local log = require "lib/dtutils.log"
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE <const> = "dbmaint"
|
||||||
|
local DEFAULT_LOG_LEVEL <const> = log.error
|
||||||
|
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- A P I C H E C K
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- I 1 8 N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("db maintenance"),
|
||||||
|
purpose = _("perform database maintenance"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gamil.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- L O G L E V E L
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
log.log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- N A M E S P A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local dbmaint = {}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- G L O B A L V A R I A B L E S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dbmaint.main_widget = nil
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- A L I A S E S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local namespace = dbmaint
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
-- helper functions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
local function set_log_level(level)
|
||||||
|
local old_log_level = log.log_level()
|
||||||
|
log.log_level(level)
|
||||||
|
return old_log_level
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restore_log_level(level)
|
||||||
|
log.log_level(level)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scan_film_rolls()
|
||||||
|
local missing_films = {}
|
||||||
|
|
||||||
|
for _, filmroll in ipairs(dt.films) do
|
||||||
|
if not df.check_if_file_exists(filmroll.path) then
|
||||||
|
table.insert(missing_films, filmroll)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return missing_films
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scan_images(film)
|
||||||
|
local old_log_level = set_log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
local missing_images = {}
|
||||||
|
|
||||||
|
if film then
|
||||||
|
for i = 1, #film do
|
||||||
|
local image = film[i]
|
||||||
|
log.msg(log.debug, "checking " .. image.filename)
|
||||||
|
if not df.check_if_file_exists(image.path .. PS .. image.filename) then
|
||||||
|
log.msg(log.info, image.filename .. " not found")
|
||||||
|
table.insert(missing_images, image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
restore_log_level(old_log_level)
|
||||||
|
return missing_images
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove_missing_film_rolls(list)
|
||||||
|
for _, filmroll in ipairs(list) do
|
||||||
|
filmroll:delete(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- force the lighttable to reload
|
||||||
|
|
||||||
|
local function refresh_lighttable(film)
|
||||||
|
local rules = dt.gui.libs.collect.filter()
|
||||||
|
dt.gui.libs.collect.filter(rules)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove_missing_images(list)
|
||||||
|
local film = list[1].film
|
||||||
|
for _, image in ipairs(list) do
|
||||||
|
image:delete()
|
||||||
|
end
|
||||||
|
refresh_lighttable(film)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not namespace.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE, -- Module name
|
||||||
|
_("DB maintenance"), -- Visible name
|
||||||
|
true, -- expandable
|
||||||
|
true, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers
|
||||||
|
namespace.main_widget,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
namespace.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- U S E R I N T E R F A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dbmaint.list_widget = dt.new_widget("text_view"){
|
||||||
|
editable = false,
|
||||||
|
reset_callback = function(this)
|
||||||
|
this.text = ""
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
dbmaint.chooser = dt.new_widget("combobox"){
|
||||||
|
label = _("scan for"),
|
||||||
|
selected = 1,
|
||||||
|
_("film rolls"), _("images"),
|
||||||
|
reset_callback = function(this)
|
||||||
|
this.selected = 1
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
dbmaint.scan_button = dt.new_widget("button"){
|
||||||
|
label = _("scan"),
|
||||||
|
tooltip = _("click to scan for missing film rolls/files"),
|
||||||
|
clicked_callback = function(this)
|
||||||
|
local found = nil
|
||||||
|
local found_text = ""
|
||||||
|
local old_log_level = set_log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
log.msg(log.debug, "Button clicked")
|
||||||
|
if dbmaint.chooser.selected == 1 then -- film rolls
|
||||||
|
found = scan_film_rolls()
|
||||||
|
if #found > 0 then
|
||||||
|
for _, film in ipairs(found) do
|
||||||
|
local dir_name = du.split(film.path, PS)
|
||||||
|
found_text = found_text .. dir_name[#dir_name] .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.msg(log.debug, "checking path " .. dt.collection[1].path .. " for missing files")
|
||||||
|
found = scan_images(dt.collection[1].film)
|
||||||
|
if #found > 0 then
|
||||||
|
for _, image in ipairs(found) do
|
||||||
|
found_text = found_text .. image.filename .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #found > 0 then
|
||||||
|
log.msg(log.debug, "found " .. #found .. " missing items")
|
||||||
|
dbmaint.list_widget.text = found_text
|
||||||
|
dbmaint.found = found
|
||||||
|
dbmaint.remove_button.sensitive = true
|
||||||
|
else
|
||||||
|
log.msg(log.debug, "no missing items found")
|
||||||
|
end
|
||||||
|
restore_log_level(old_log_level)
|
||||||
|
end,
|
||||||
|
reset_callback = function(this)
|
||||||
|
dbmaint.found = nil
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
dbmaint.remove_button = dt.new_widget("button"){
|
||||||
|
label = _("remove"),
|
||||||
|
tooltip = _("remove missing film rolls/images"),
|
||||||
|
sensitive = false,
|
||||||
|
clicked_callback = function(this)
|
||||||
|
if dbmaint.chooser.selected == 1 then -- film rolls
|
||||||
|
remove_missing_film_rolls(dbmaint.found)
|
||||||
|
else
|
||||||
|
remove_missing_images(dbmaint.found)
|
||||||
|
end
|
||||||
|
dbmaint.found = nil
|
||||||
|
dbmaint.list_widget.text = ""
|
||||||
|
this.sensitive = false
|
||||||
|
end,
|
||||||
|
reset_callback = function(this)
|
||||||
|
this.sensitive = false
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
dbmaint.main_widget = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("section_label"){label = _("missing items")},
|
||||||
|
dbmaint.list_widget,
|
||||||
|
dt.new_widget("label"){label = ""},
|
||||||
|
dbmaint.chooser,
|
||||||
|
dt.new_widget("label"){label = ""},
|
||||||
|
dbmaint.scan_button,
|
||||||
|
dbmaint.remove_button
|
||||||
|
}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- D A R K T A B L E I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE].visible = false
|
||||||
|
|
||||||
|
if namespace.event_registered then
|
||||||
|
dt.destroy_event(MODULE, "view-changed")
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE].visible = true
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- E V E N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not namespace.event_registered then
|
||||||
|
dt.register_event(MODULE, "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
namespace.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -65,15 +65,31 @@ local mod = 'module_enfuseAdvanced'
|
|||||||
local os_path_seperator = '/'
|
local os_path_seperator = '/'
|
||||||
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
||||||
|
|
||||||
du.check_min_api_version('5.0.0', 'enfuseAdvanced')
|
du.check_min_api_version("7.0.0", "enfuseAdvanced")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
gettext.bindtextdomain('enfuseAdvanced',dt.configuration.config_dir..'/lua/locale/')
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext('enfuseAdvanced', msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("enfuse advanced"),
|
||||||
|
purpose = _("focus stack or exposure blend images"),
|
||||||
|
author = "Kevin Ertel",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseAdvanced"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- INITS --
|
-- INITS --
|
||||||
local AIS = {
|
local AIS = {
|
||||||
name = 'align_image_stack',
|
name = 'align_image_stack',
|
||||||
@@ -251,7 +267,7 @@ local function ExeUpdate(prog_tbl) --updates executable paths and verifies them
|
|||||||
if not prog.bin then
|
if not prog.bin then
|
||||||
prog.install_error = true
|
prog.install_error = true
|
||||||
dt.preferences.write(mod, 'bin_exists', 'bool', false)
|
dt.preferences.write(mod, 'bin_exists', 'bool', false)
|
||||||
dt.print(_('issue with ')..prog.name.._(' executable'))
|
dt.print(string.format(_("issue with %s executable"), prog.name))
|
||||||
else
|
else
|
||||||
prog.bin = CleanSpaces(prog.bin)
|
prog.bin = CleanSpaces(prog.bin)
|
||||||
end
|
end
|
||||||
@@ -272,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)
|
||||||
@@ -329,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
|
||||||
@@ -368,7 +390,7 @@ local function SaveToPreference(preset) --save the present values of enfuse GUI
|
|||||||
dt.preferences.write(mod, preset..argument, arg_data.style, temp)
|
dt.preferences.write(mod, preset..argument, arg_data.style, temp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dt.print(_('saved to ')..preset)
|
dt.print(string.format(_("saved to %s"), preset))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function LoadFromPreference(preset) --load values from the specified 'preset' into the GUI elements
|
local function LoadFromPreference(preset) --load values from the specified 'preset' into the GUI elements
|
||||||
@@ -384,7 +406,7 @@ local function LoadFromPreference(preset) --load values from the specified 'pres
|
|||||||
dt.preferences.write(mod, 'active_'..argument, arg_data.style, temp)
|
dt.preferences.write(mod, 'active_'..argument, arg_data.style, temp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dt.print(_('loaded from ')..preset)
|
dt.print(string.format(_("loaded from %s"), preset))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function remove_temp_files(images_to_remove) --deletes all files specified by the input string
|
local function remove_temp_files(images_to_remove) --deletes all files specified by the input string
|
||||||
@@ -419,7 +441,7 @@ local function support_format(storage, format) --tells dt we only support TIFF e
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) --outputs message to user showing script export status
|
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) --outputs message to user showing script export status
|
||||||
dt.print(_('export for image fusion ')..tostring(math.floor(number))..' / '..tostring(math.floor(total)))
|
dt.print(string.format(_("export for image fusion %d / %d"), math.floor(number), math.floor(total)))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function main(storage, image_table, extra_data)
|
local function main(storage, image_table, extra_data)
|
||||||
@@ -427,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 = ''
|
||||||
@@ -447,8 +469,8 @@ local function main(storage, image_table, extra_data)
|
|||||||
job.valid = false
|
job.valid = false
|
||||||
if resp ~= 0 then
|
if resp ~= 0 then
|
||||||
remove_temp_files(images_to_remove)
|
remove_temp_files(images_to_remove)
|
||||||
dt.print_error(AIS.name.._(' failed'))
|
dt.print(string.format(_("%s failed"), AIS.name))
|
||||||
dt.print(AIS.name.._(' failed'))
|
dt.print_error(AIS.name .. ' failed')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -474,8 +496,9 @@ local function main(storage, image_table, extra_data)
|
|||||||
local resp = dsys.external_command(run_cmd)
|
local resp = dsys.external_command(run_cmd)
|
||||||
if resp ~= 0 then
|
if resp ~= 0 then
|
||||||
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(ENF.name.._(' failed'))
|
dt.print(string.format(_("%s failed"), ENF.name))
|
||||||
|
job.valid = false
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -516,6 +539,10 @@ local function main(storage, image_table, extra_data)
|
|||||||
dt.print('image fusion process complete')
|
dt.print('image fusion process complete')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage('module_enfuseAdvanced')
|
||||||
|
end
|
||||||
|
|
||||||
--GUI--
|
--GUI--
|
||||||
stack_compression = dt.new_widget('stack'){}
|
stack_compression = dt.new_widget('stack'){}
|
||||||
local label_AIS_options= dt.new_widget('section_label'){
|
local label_AIS_options= dt.new_widget('section_label'){
|
||||||
@@ -716,7 +743,7 @@ GUI.ENF.hard_masks = dt.new_widget('check_button'){
|
|||||||
GUI.ENF.save_masks = dt.new_widget('check_button'){
|
GUI.ENF.save_masks = dt.new_widget('check_button'){
|
||||||
label = _('save masks'),
|
label = _('save masks'),
|
||||||
value = dt.preferences.read(mod, 'active_save_masks', 'bool'),
|
value = dt.preferences.read(mod, 'active_save_masks', 'bool'),
|
||||||
tooltip = _('Save the generated weight masks to your home directory,\nenblend saves masks as 8 bit grayscale, \ni.e. single channel images. \nfor accuracy we recommend to choose a lossless format.'),
|
tooltip = _('save the generated weight masks to your home directory,\nenblend saves masks as 8 bit grayscale, \ni.e. single channel images. \nfor accuracy we recommend to choose a lossless format.'),
|
||||||
clicked_callback = function(self) dt.preferences.write(mod, 'active_save_masks', 'bool', self.value) end,
|
clicked_callback = function(self) dt.preferences.write(mod, 'active_save_masks', 'bool', self.value) end,
|
||||||
reset_callback = function(self) self.value = false end
|
reset_callback = function(self) self.value = false end
|
||||||
}
|
}
|
||||||
@@ -869,7 +896,7 @@ GUI.Target.output_directory = dt.new_widget('file_chooser_button'){
|
|||||||
GUI.Target.source_location = dt.new_widget('check_button'){
|
GUI.Target.source_location = dt.new_widget('check_button'){
|
||||||
label = _('save to source image location'),
|
label = _('save to source image location'),
|
||||||
value = dt.preferences.read(mod, 'active_source_location', 'bool'),
|
value = dt.preferences.read(mod, 'active_source_location', 'bool'),
|
||||||
tooltip = _('If checked ignores the location above and saves output image(s) to the same location as the source images.'),
|
tooltip = _('if checked ignores the location above and saves output image(s) to the same location as the source images.'),
|
||||||
clicked_callback = function(self) dt.preferences.write(mod, 'active_source_location', 'bool', self.value) end,
|
clicked_callback = function(self) dt.preferences.write(mod, 'active_source_location', 'bool', self.value) end,
|
||||||
reset_callback = function(self) self.value = true end
|
reset_callback = function(self) self.value = true end
|
||||||
}
|
}
|
||||||
@@ -903,8 +930,8 @@ GUI.Target.auto_import = dt.new_widget('check_button'){
|
|||||||
}
|
}
|
||||||
temp = dt.preferences.read(mod, 'active_apply_style_ind', 'integer')
|
temp = dt.preferences.read(mod, 'active_apply_style_ind', 'integer')
|
||||||
GUI.Target.apply_style = dt.new_widget('combobox'){
|
GUI.Target.apply_style = dt.new_widget('combobox'){
|
||||||
label = _('Apply Style on Import'),
|
label = _('apply style on Import'),
|
||||||
tooltip = _('Apply selected style on auto-import to newly created blended image'),
|
tooltip = _('apply selected style on auto-import to newly created blended image'),
|
||||||
selected = 1,
|
selected = 1,
|
||||||
'none',
|
'none',
|
||||||
changed_callback = function(self)
|
changed_callback = function(self)
|
||||||
@@ -925,16 +952,16 @@ GUI.Target.apply_style.selected = temp
|
|||||||
GUI.Target.copy_tags = dt.new_widget('check_button'){
|
GUI.Target.copy_tags = dt.new_widget('check_button'){
|
||||||
label = _('copy tags'),
|
label = _('copy tags'),
|
||||||
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
|
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
|
||||||
tooltip = _('Copy tags from first image.'),
|
tooltip = _('copy tags from first image.'),
|
||||||
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
|
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
|
||||||
reset_callback = function(self) self.value = true end
|
reset_callback = function(self) self.value = true end
|
||||||
}
|
}
|
||||||
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, 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')
|
||||||
@@ -1001,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,
|
||||||
@@ -1009,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,
|
||||||
@@ -1017,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,
|
||||||
@@ -1142,3 +1169,7 @@ else
|
|||||||
GUI.options_contain.active = 4
|
GUI.options_contain.active = 4
|
||||||
GUI.show_options.sensitive = false
|
GUI.show_options.sensitive = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -31,15 +31,30 @@ local du = require "lib/dtutils"
|
|||||||
local df = require("lib/dtutils.file")
|
local df = require("lib/dtutils.file")
|
||||||
local ds = require("lib/dtutils.system")
|
local ds = require("lib/dtutils.system")
|
||||||
|
|
||||||
local gettext = dt.gettext
|
du.check_min_api_version("7.0.0", "exportLUT")
|
||||||
|
|
||||||
gettext.bindtextdomain("exportLUT",dt.configuration.config_dir.."/lua/locale/")
|
local gettext = dt.gettext.gettext
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("exportLUT", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("export LUT"),
|
||||||
|
purpose = _("export a style as a LUT"),
|
||||||
|
author = "Noah Clarke",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportLUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
du.check_min_api_version("5.0.0", "exportLUT")
|
du.check_min_api_version("5.0.0", "exportLUT")
|
||||||
|
|
||||||
local eL = {}
|
local eL = {}
|
||||||
@@ -54,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
|
||||||
}
|
}
|
||||||
@@ -96,9 +111,9 @@ end
|
|||||||
local function export_luts()
|
local function export_luts()
|
||||||
local identity = dt.database.import(file_chooser_button.value)
|
local identity = dt.database.import(file_chooser_button.value)
|
||||||
if(type(identity) ~= "userdata") then
|
if(type(identity) ~= "userdata") then
|
||||||
dt.print(_("Invalid identity lut file"))
|
dt.print(_("invalid identity lut file"))
|
||||||
else
|
else
|
||||||
local job = dt.gui.create_job(_('Exporting styles as haldCLUTs'), true, end_job)
|
local job = dt.gui.create_job(_('exporting styles as haldCLUTs'), true, end_job)
|
||||||
|
|
||||||
local size = 1
|
local size = 1
|
||||||
|
|
||||||
@@ -117,9 +132,9 @@ local function export_luts()
|
|||||||
io_lut:write_image(identity, output_path(style.name, job))
|
io_lut:write_image(identity, output_path(style.name, job))
|
||||||
count = count + 1
|
count = count + 1
|
||||||
job.percent = count / size
|
job.percent = count / size
|
||||||
dt.print(_("Exported: ") .. output_path(style.name, job))
|
dt.print(string.format(_("exported: %s"), output_path(style.name, job)))
|
||||||
end
|
end
|
||||||
dt.print(_("Done exporting haldCLUTs"))
|
dt.print(_("done exporting haldCLUTs"))
|
||||||
job.valid = false
|
job.valid = false
|
||||||
identity:reset()
|
identity:reset()
|
||||||
end
|
end
|
||||||
@@ -128,7 +143,7 @@ end
|
|||||||
local function install_module()
|
local function install_module()
|
||||||
if not eL.module_installed then
|
if not eL.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
_("export haldclut"),
|
"export haldclut",
|
||||||
_("export haldclut"),
|
_("export haldclut"),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
@@ -145,6 +160,14 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["export haldclut"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["export haldclut"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
local export_button = dt.new_widget("button"){
|
local export_button = dt.new_widget("button"){
|
||||||
label = _("export"),
|
label = _("export"),
|
||||||
clicked_callback = export_luts
|
clicked_callback = export_luts
|
||||||
@@ -162,7 +185,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not eL.event_registered then
|
if not eL.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"exportLUT", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -173,3 +196,9 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -73,12 +73,30 @@ local dtsys = require "lib/dtutils.system"
|
|||||||
|
|
||||||
-- module name
|
-- module name
|
||||||
local MODULE_NAME = "ext_editor"
|
local MODULE_NAME = "ext_editor"
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||||
|
|
||||||
|
-- translation
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
-- check API version
|
local function _(msgid)
|
||||||
du.check_min_api_version("5.0.2", MODULE_NAME) -- darktable 3.x
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("external editors"),
|
||||||
|
purpose = _("edit images with external editors"),
|
||||||
|
author = "Marco Carrarini, marco.carrarini@gmail.com",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- OS compatibility
|
-- OS compatibility
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
@@ -90,13 +108,6 @@ ee.event_registered = false
|
|||||||
ee.widgets = {}
|
ee.widgets = {}
|
||||||
|
|
||||||
|
|
||||||
-- translation
|
|
||||||
local gettext = dt.gettext
|
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- maximum number of external programs, can be increased to necessity
|
-- maximum number of external programs, can be increased to necessity
|
||||||
local MAX_EDITORS = 9
|
local MAX_EDITORS = 9
|
||||||
|
|
||||||
@@ -112,7 +123,7 @@ local allowed_file_types = {"JPG", "jpg", "JPEG", "jpeg", "TIF", "tif", "TIFF",
|
|||||||
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
|
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
|
||||||
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", 0)
|
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", 0)
|
||||||
dt.preferences.write(MODULE_NAME, "initialized", "bool", true)
|
dt.preferences.write(MODULE_NAME, "initialized", "bool", true)
|
||||||
end
|
end
|
||||||
local lastchoice = 0
|
local lastchoice = 0
|
||||||
|
|
||||||
|
|
||||||
@@ -137,24 +148,24 @@ local function UpdateProgramList(combobox, button_edit, button_edit_copy, update
|
|||||||
program_names[i] = name
|
program_names[i] = name
|
||||||
program_paths[i] = df.sanitize_filename(dt.preferences.read(MODULE_NAME, "program_path_"..i, "string"))
|
program_paths[i] = df.sanitize_filename(dt.preferences.read(MODULE_NAME, "program_path_"..i, "string"))
|
||||||
n_entries = i
|
n_entries = i
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer")
|
|
||||||
if lastchoice == 0 and n_entries > 0 then lastchoice = 1 end
|
|
||||||
if lastchoice > n_entries then lastchoice = n_entries end
|
|
||||||
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice)
|
|
||||||
|
|
||||||
-- widgets enabled if there is at least one program configured
|
|
||||||
combobox.selected = lastchoice
|
|
||||||
local active = n_entries > 0
|
|
||||||
combobox.sensitive = active
|
|
||||||
button_edit.sensitive = active
|
|
||||||
button_edit_copy.sensitive = active
|
|
||||||
|
|
||||||
if update_button_pressed then dt.print(n_entries.._(" editors configured")) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer")
|
||||||
|
if lastchoice == 0 and n_entries > 0 then lastchoice = 1 end
|
||||||
|
if lastchoice > n_entries then lastchoice = n_entries end
|
||||||
|
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice)
|
||||||
|
|
||||||
|
-- widgets enabled if there is at least one program configured
|
||||||
|
combobox.selected = lastchoice
|
||||||
|
local active = n_entries > 0
|
||||||
|
combobox.sensitive = active
|
||||||
|
button_edit.sensitive = active
|
||||||
|
button_edit_copy.sensitive = active
|
||||||
|
|
||||||
|
if update_button_pressed then dt.print(string.format(_("%d editors configured"), n_entries)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- callback for buttons "edit" and "edit a copy" ------------------------------
|
-- callback for buttons "edit" and "edit a copy" ------------------------------
|
||||||
local function OpenWith(images, choice, copy)
|
local function OpenWith(images, choice, copy)
|
||||||
@@ -163,13 +174,13 @@ local function OpenWith(images, choice, copy)
|
|||||||
if choice > n_entries then
|
if choice > n_entries then
|
||||||
dt.print(_("not a valid choice"))
|
dt.print(_("not a valid choice"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check if one image is selected, return if not
|
-- check if one image is selected, return if not
|
||||||
if #images ~= 1 then
|
if #images ~= 1 then
|
||||||
dt.print(_("please select one image"))
|
dt.print(_("please select one image"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local bin = program_paths[choice]
|
local bin = program_paths[choice]
|
||||||
local friendly_name = program_names[choice]
|
local friendly_name = program_names[choice]
|
||||||
@@ -188,18 +199,18 @@ local function OpenWith(images, choice, copy)
|
|||||||
if v == file_ext then
|
if v == file_ext then
|
||||||
allowed = true
|
allowed = true
|
||||||
break
|
break
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
if not allowed then
|
if not allowed then
|
||||||
dt.print(_("file type not allowed"))
|
dt.print(_("file type not allowed"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- save image tags, rating and color
|
-- save image tags, rating and color
|
||||||
local tags = {}
|
local tags = {}
|
||||||
for i, tag in ipairs(dt.tags.get_tags(image)) do
|
for i, tag in ipairs(dt.tags.get_tags(image)) do
|
||||||
if not (string.sub(tag.name, 1, 9) == "darktable") then table.insert(tags, tag) end
|
if not (string.sub(tag.name, 1, 9) == "darktable") then table.insert(tags, tag) end
|
||||||
end
|
end
|
||||||
local rating = image.rating
|
local rating = image.rating
|
||||||
local red = image.red
|
local red = image.red
|
||||||
local blue = image.blue
|
local blue = image.blue
|
||||||
@@ -219,19 +230,19 @@ local function OpenWith(images, choice, copy)
|
|||||||
-- physical copy, check result, return if error
|
-- physical copy, check result, return if error
|
||||||
local copy_success = df.file_copy(name, new_name)
|
local copy_success = df.file_copy(name, new_name)
|
||||||
if not copy_success then
|
if not copy_success then
|
||||||
dt.print(_("error copying file ")..name)
|
dt.print(string.format(_("error copying file %s"), name))
|
||||||
return
|
return
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- launch the external editor, check result, return if error
|
-- launch the external editor, check result, return if error
|
||||||
local run_cmd = bin.." "..df.sanitize_filename(new_name)
|
local run_cmd = bin.." "..df.sanitize_filename(new_name)
|
||||||
dt.print(_("launching ")..friendly_name.."...")
|
dt.print(string.format(_("launching %s..."), friendly_name))
|
||||||
local result = dtsys.external_command(run_cmd)
|
local result = dtsys.external_command(run_cmd)
|
||||||
if result ~= 0 then
|
if result ~= 0 then
|
||||||
dt.print(_("error launching ")..friendly_name)
|
dt.print(string.format(_("error launching %s"), friendly_name))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if copy then
|
if copy then
|
||||||
-- import in database and group
|
-- import in database and group
|
||||||
@@ -256,7 +267,7 @@ local function OpenWith(images, choice, copy)
|
|||||||
while not found do
|
while not found do
|
||||||
index, new_leader = next(group_members, index)
|
index, new_leader = next(group_members, index)
|
||||||
if new_leader ~= image_leader then found = true end
|
if new_leader ~= image_leader then found = true end
|
||||||
end
|
end
|
||||||
new_leader:make_group_leader()
|
new_leader:make_group_leader()
|
||||||
image:delete()
|
image:delete()
|
||||||
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
||||||
@@ -269,15 +280,15 @@ local function OpenWith(images, choice, copy)
|
|||||||
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
||||||
new_image = dt.database.import(name)
|
new_image = dt.database.import(name)
|
||||||
new_image:group_with()
|
new_image:group_with()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- case 3: image is in a group but is not leader
|
-- case 3: image is in a group but is not leader
|
||||||
image:delete()
|
image:delete()
|
||||||
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
||||||
new_image = dt.database.import(name)
|
new_image = dt.database.import(name)
|
||||||
new_image:group_with(image_leader)
|
new_image:group_with(image_leader)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- restore image tags, rating and color
|
-- restore image tags, rating and color
|
||||||
for i, tag in ipairs(tags) do dt.tags.attach(tag, new_image) end
|
for i, tag in ipairs(tags) do dt.tags.attach(tag, new_image) end
|
||||||
@@ -296,14 +307,14 @@ local function OpenWith(images, choice, copy)
|
|||||||
-- refresh darkroom view
|
-- refresh darkroom view
|
||||||
if dt.gui.current_view().id == "darkroom" then
|
if dt.gui.current_view().id == "darkroom" then
|
||||||
dt.gui.views.darkroom.display_image(new_image)
|
dt.gui.views.darkroom.display_image(new_image)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- callback function for shortcuts --------------------------------------------
|
-- callback function for shortcuts --------------------------------------------
|
||||||
local function program_shortcut(event, shortcut)
|
local function program_shortcut(event, shortcut)
|
||||||
OpenWith(dt.gui.action_images, tonumber(string.sub(shortcut, -2)), false)
|
OpenWith(dt.gui.action_images, tonumber(string.sub(shortcut, -2)), false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- export images and reimport in collection -----------------------------------
|
-- export images and reimport in collection -----------------------------------
|
||||||
@@ -322,25 +333,25 @@ local function export2collection(storage, image_table, extra_data)
|
|||||||
-- move image to collection folder, check result, return if error
|
-- move image to collection folder, check result, return if error
|
||||||
move_success = df.file_move(temp_name, new_name)
|
move_success = df.file_move(temp_name, new_name)
|
||||||
if not move_success then
|
if not move_success then
|
||||||
dt.print(_("error moving file ")..temp_name)
|
dt.print(string.format(_("error moving file %s"), temp_name))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- import in database and group
|
-- import in database and group
|
||||||
new_image = dt.database.import(new_name)
|
new_image = dt.database.import(new_name)
|
||||||
new_image:group_with(image.group_leader)
|
new_image:group_with(image.group_leader)
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.print(_("finished exporting"))
|
dt.print(_("finished exporting"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- install the module in the UI -----------------------------------------------
|
-- install the module in the UI -----------------------------------------------
|
||||||
local function install_module(dr)
|
local function install_module(dr)
|
||||||
|
|
||||||
local views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}
|
local views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 90}}
|
||||||
if dr then
|
if dr then
|
||||||
views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100},
|
views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 90},
|
||||||
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}}
|
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -363,6 +374,27 @@ local function install_module(dr)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
for i = 1, MAX_EDITORS do
|
||||||
|
dt.destroy_event(MODULE_NAME .. i, "shortcut")
|
||||||
|
end
|
||||||
|
dt.destroy_storage("exp2coll")
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
for i = 1, MAX_EDITORS do
|
||||||
|
dt.register_event(MODULE_NAME .. i, "shortcut",
|
||||||
|
program_shortcut, string.format(_("edit with program %02d"), i))
|
||||||
|
end
|
||||||
|
dt.register_storage("exp2coll", _("collection"), nil, export2collection)
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- combobox, with variable number of entries ----------------------------------
|
-- combobox, with variable number of entries ----------------------------------
|
||||||
local combobox = dt.new_widget("combobox") {
|
local combobox = dt.new_widget("combobox") {
|
||||||
@@ -372,7 +404,7 @@ local combobox = dt.new_widget("combobox") {
|
|||||||
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", self.selected)
|
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", self.selected)
|
||||||
end,
|
end,
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- button edit ----------------------------------------------------------------
|
-- button edit ----------------------------------------------------------------
|
||||||
@@ -382,8 +414,8 @@ local button_edit = dt.new_widget("button") {
|
|||||||
--sensitive = false,
|
--sensitive = false,
|
||||||
clicked_callback = function()
|
clicked_callback = function()
|
||||||
OpenWith(dt.gui.action_images, combobox.selected, false)
|
OpenWith(dt.gui.action_images, combobox.selected, false)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- button edit a copy ---------------------------------------------------------
|
-- button edit a copy ---------------------------------------------------------
|
||||||
@@ -392,8 +424,8 @@ local button_edit_copy = dt.new_widget("button") {
|
|||||||
tooltip = _("create a copy of the selected image and open it in external editor"),
|
tooltip = _("create a copy of the selected image and open it in external editor"),
|
||||||
clicked_callback = function()
|
clicked_callback = function()
|
||||||
OpenWith(dt.gui.action_images, combobox.selected, true)
|
OpenWith(dt.gui.action_images, combobox.selected, true)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- button update list ---------------------------------------------------------
|
-- button update list ---------------------------------------------------------
|
||||||
@@ -402,18 +434,18 @@ local button_update_list = dt.new_widget("button") {
|
|||||||
tooltip = _("update list of programs if lua preferences are changed"),
|
tooltip = _("update list of programs if lua preferences are changed"),
|
||||||
clicked_callback = function()
|
clicked_callback = function()
|
||||||
UpdateProgramList(combobox, button_edit, button_edit_copy, true)
|
UpdateProgramList(combobox, button_edit, button_edit_copy, true)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- box for the buttons --------------------------------------------------------
|
-- box for the buttons --------------------------------------------------------
|
||||||
-- it doesn't seem there is a way to make the buttons equal in size
|
-- it doesn't seem there is a way to make the buttons equal in size
|
||||||
local box1 = dt.new_widget("box") {
|
local box1 = dt.new_widget("box") {
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
button_edit,
|
button_edit,
|
||||||
button_edit_copy,
|
button_edit_copy,
|
||||||
button_update_list
|
button_update_list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- table with all the widgets --------------------------------------------------
|
-- table with all the widgets --------------------------------------------------
|
||||||
@@ -428,7 +460,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not ee.event_registered then
|
if not ee.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
MODULE_NAME, "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module(show_dr)
|
install_module(show_dr)
|
||||||
@@ -451,13 +483,13 @@ dt.register_storage("exp2coll", _("collection"), nil, export2collection)
|
|||||||
-- register the new preferences -----------------------------------------------
|
-- register the new preferences -----------------------------------------------
|
||||||
for i = MAX_EDITORS, 1, -1 do
|
for i = MAX_EDITORS, 1, -1 do
|
||||||
dt.preferences.register(MODULE_NAME, "program_path_"..i, "file",
|
dt.preferences.register(MODULE_NAME, "program_path_"..i, "file",
|
||||||
_("executable for external editor ")..i,
|
string.format(_("executable for external editor %d"), i),
|
||||||
_("select executable for external editor") , _("(None)"))
|
_("select executable for external editor") , _("(none)"))
|
||||||
|
|
||||||
dt.preferences.register(MODULE_NAME, "program_name_"..i, "string",
|
dt.preferences.register(MODULE_NAME, "program_name_"..i, "string",
|
||||||
_("name of external editor ")..i,
|
string.format(_("name of external editor %d"), i),
|
||||||
_("friendly name of external editor"), "")
|
_("friendly name of external editor"), "")
|
||||||
end
|
end
|
||||||
dt.preferences.register(MODULE_NAME, "show_in_darkrooom", "bool",
|
dt.preferences.register(MODULE_NAME, "show_in_darkrooom", "bool",
|
||||||
_("show external editors in darkroom"),
|
_("show external editors in darkroom"),
|
||||||
_("check to show external editors module also in darkroom (requires restart)"), false)
|
_("check to show external editors module also in darkroom (requires restart)"), false)
|
||||||
@@ -465,11 +497,18 @@ dt.preferences.register(MODULE_NAME, "show_in_darkrooom", "bool",
|
|||||||
|
|
||||||
-- register the new shortcuts -------------------------------------------------
|
-- register the new shortcuts -------------------------------------------------
|
||||||
for i = 1, MAX_EDITORS do
|
for i = 1, MAX_EDITORS do
|
||||||
dt.register_event("shortcut",
|
dt.register_event(MODULE_NAME .. i, "shortcut",
|
||||||
program_shortcut, _("edit with program ")..string.format("%02d", i))
|
program_shortcut, string.format(_("edit with program %02d"), i))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
-- end of script --------------------------------------------------------------
|
-- end of script --------------------------------------------------------------
|
||||||
|
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -44,15 +44,35 @@ 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 dtsys = require "lib/dtutils.system"
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
local gettext = dt.gettext
|
|
||||||
|
|
||||||
-- constants
|
-- constants
|
||||||
|
|
||||||
local MODULE = "face_recognition"
|
local MODULE = "face_recognition"
|
||||||
local PS = dt.configuration.running_os == "windows" and '\\' or '/'
|
local PS = dt.configuration.running_os == "windows" and '\\' or '/'
|
||||||
local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt"
|
local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt"
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
du.check_min_api_version("7.0.0", MODULE)
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("face recognition"),
|
||||||
|
purpose = _("use facial recognition to tag images"),
|
||||||
|
author = "Sebastian Witt",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- namespace
|
-- namespace
|
||||||
|
|
||||||
@@ -60,16 +80,6 @@ local fc = {}
|
|||||||
fc.module_installed = false
|
fc.module_installed = false
|
||||||
fc.event_registered = false
|
fc.event_registered = false
|
||||||
|
|
||||||
-- ensure we meet the minimum api
|
|
||||||
du.check_min_api_version("5.0.0", "face_recognition")
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("face_recognition", dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext("face_recognition", msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function build_image_table(images)
|
local function build_image_table(images)
|
||||||
local image_table = {}
|
local image_table = {}
|
||||||
local file_extension = ""
|
local file_extension = ""
|
||||||
@@ -129,7 +139,7 @@ local function do_export(img_tbl, images)
|
|||||||
job.percent = 0.0
|
job.percent = 0.0
|
||||||
for export,img in pairs(img_tbl) do
|
for export,img in pairs(img_tbl) do
|
||||||
exp_cnt = exp_cnt + 1
|
exp_cnt = exp_cnt + 1
|
||||||
dt.print(string.format(_("Exporting image %i of %i images"), exp_cnt, images))
|
dt.print(string.format(_("exporting image %i of %i images"), exp_cnt, images))
|
||||||
exporter:write_image(img, export, upsize)
|
exporter:write_image(img, export, upsize)
|
||||||
job.percent = job.percent + percent_step
|
job.percent = job.percent + percent_step
|
||||||
end
|
end
|
||||||
@@ -199,7 +209,7 @@ local function face_recognition ()
|
|||||||
local bin_path = df.check_if_bin_exists("face_recognition")
|
local bin_path = df.check_if_bin_exists("face_recognition")
|
||||||
|
|
||||||
if not bin_path then
|
if not bin_path then
|
||||||
dt.print(_("Face recognition not found"))
|
dt.print(_("face recognition not found"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -251,7 +261,7 @@ local function face_recognition ()
|
|||||||
local command = bin_path .. " --cpus " .. nrCores .. " --tolerance " .. tolerance .. " " .. knownPath .. " " .. path .. " > " .. OUTPUT
|
local command = bin_path .. " --cpus " .. nrCores .. " --tolerance " .. tolerance .. " " .. knownPath .. " " .. path .. " > " .. OUTPUT
|
||||||
os.setlocale()
|
os.setlocale()
|
||||||
dt.print_log("Face recognition: Running command: " .. command)
|
dt.print_log("Face recognition: Running command: " .. command)
|
||||||
dt.print(_("Starting face recognition..."))
|
dt.print(_("starting face recognition..."))
|
||||||
|
|
||||||
dtsys.external_command(command)
|
dtsys.external_command(command)
|
||||||
|
|
||||||
@@ -259,9 +269,9 @@ local function face_recognition ()
|
|||||||
local f = io.open(OUTPUT, "rb")
|
local f = io.open(OUTPUT, "rb")
|
||||||
|
|
||||||
if not f then
|
if not f then
|
||||||
dt.print(_("Face recognition failed"))
|
dt.print(_("face recognition failed"))
|
||||||
else
|
else
|
||||||
dt.print(_("Face recognition finished"))
|
dt.print(_("face recognition finished"))
|
||||||
f:close ()
|
f:close ()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -344,7 +354,7 @@ end
|
|||||||
local function install_module()
|
local function install_module()
|
||||||
if not fc.module_installed then
|
if not fc.module_installed then
|
||||||
dt.register_lib(
|
dt.register_lib(
|
||||||
"face_recognition", -- Module name
|
MODULE, -- Module name
|
||||||
_("face recognition"), -- Visible name
|
_("face recognition"), -- Visible name
|
||||||
true, -- expandable
|
true, -- expandable
|
||||||
true, -- resetable
|
true, -- resetable
|
||||||
@@ -357,6 +367,14 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- build the interface
|
-- build the interface
|
||||||
|
|
||||||
fc.unknown_tag = dt.new_widget("entry"){
|
fc.unknown_tag = dt.new_widget("entry"){
|
||||||
@@ -385,7 +403,7 @@ fc.category_tags = dt.new_widget("entry"){
|
|||||||
|
|
||||||
fc.tolerance = dt.new_widget("slider"){
|
fc.tolerance = dt.new_widget("slider"){
|
||||||
label = _("tolerance"),
|
label = _("tolerance"),
|
||||||
tooltip = ("detection tolerance - 0.6 default - lower if too many faces detected"),
|
tooltip = _("detection tolerance - 0.6 default - lower if too many faces detected"),
|
||||||
soft_min = 0.0,
|
soft_min = 0.0,
|
||||||
hard_min = 0.0,
|
hard_min = 0.0,
|
||||||
soft_max = 1.0,
|
soft_max = 1.0,
|
||||||
@@ -468,12 +486,12 @@ table.insert(widgets, fc.num_cores)
|
|||||||
table.insert(widgets, fc.export_format)
|
table.insert(widgets, fc.export_format)
|
||||||
table.insert(widgets, dt.new_widget("box"){
|
table.insert(widgets, dt.new_widget("box"){
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("label"){ label = _("width ")},
|
dt.new_widget("label"){ label = _("width")},
|
||||||
fc.width,
|
fc.width,
|
||||||
})
|
})
|
||||||
table.insert(widgets, dt.new_widget("box"){
|
table.insert(widgets, dt.new_widget("box"){
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("label"){ label = _("height ")},
|
dt.new_widget("label"){ label = _("height")},
|
||||||
fc.height,
|
fc.height,
|
||||||
})
|
})
|
||||||
table.insert(widgets, fc.execute)
|
table.insert(widgets, fc.execute)
|
||||||
@@ -491,7 +509,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not fc.event_registered then
|
if not fc.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
MODULE, "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -512,5 +530,11 @@ if not dt.preferences.read(MODULE, "initialized", "bool") then
|
|||||||
dt.preferences.write(MODULE, "initialized", "bool", true)
|
dt.preferences.write(MODULE, "initialized", "bool", true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
--
|
--
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Compensate for Fujifilm raw files made using "dynamic range".
|
Compensate for Fujifilm raw files made using "dynamic range".
|
||||||
|
|
||||||
Copyright (C) 2021 Dan Torop <dant@pnym.net>
|
Copyright (C) 2021, 2022 Dan Torop <dant@pnym.net>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -60,9 +60,30 @@ cameras may behave in other ways.
|
|||||||
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 dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("4.0.0", "fujifilm_dynamic_range")
|
du.check_min_api_version("7.0.0", "fujifilm_dynamic_range")
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("fujifilm dynamic range"),
|
||||||
|
purpose = _("compensate for Fujifilm raw files made using \"dynamic range\""),
|
||||||
|
author = "Dan Torop <dant@pnym.net>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function detect_dynamic_range(event, image)
|
local function detect_dynamic_range(event, image)
|
||||||
if image.exif_maker ~= "FUJIFILM" then
|
if image.exif_maker ~= "FUJIFILM" then
|
||||||
@@ -103,9 +124,33 @@ local function detect_dynamic_range(event, image)
|
|||||||
-- note that scene-referred workflow exposure preset also pushes exposure up by 0.5 EV
|
-- note that scene-referred workflow exposure preset also pushes exposure up by 0.5 EV
|
||||||
image.exif_exposure_bias = image.exif_exposure_bias + tonumber(raf_result)
|
image.exif_exposure_bias = image.exif_exposure_bias + tonumber(raf_result)
|
||||||
dt.print_log("[fujifilm_dynamic_range] raw exposure bias " .. tostring(raf_result))
|
dt.print_log("[fujifilm_dynamic_range] raw exposure bias " .. tostring(raf_result))
|
||||||
|
-- handle any duplicates
|
||||||
|
if #image:get_group_members() > 1 then
|
||||||
|
local basename = df.get_basename(image.filename)
|
||||||
|
local grouped_images = image:get_group_members()
|
||||||
|
for _, img in ipairs(grouped_images) do
|
||||||
|
if string.match(img.filename, basename) and img.duplicate_index > 0 then
|
||||||
|
-- its a duplicate
|
||||||
|
img.exif_exposure_bias = img.exif_exposure_bias + tonumber(raf_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.register_event("post-import-image",
|
local function destroy()
|
||||||
|
dt.destroy_event("fujifilm_dr", "post-import-image")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not df.check_if_bin_exists("exiftool") then
|
||||||
|
dt.print_log("Please install exiftool to use fujifilm_dynamic_range")
|
||||||
|
error "[fujifilm_dynamic_range] exiftool not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event("fujifilm_dr", "post-import-image",
|
||||||
detect_dynamic_range)
|
detect_dynamic_range)
|
||||||
|
|
||||||
dt.print_log("[fujifilm_dynamic_range] loaded")
|
dt.print_log("[fujifilm_dynamic_range] loaded")
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -26,49 +26,70 @@ Dependencies:
|
|||||||
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 dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local gettext = dt.gettext
|
du.check_min_api_version("7.0.0", "fujifilm_ratings")
|
||||||
|
|
||||||
du.check_min_api_version("4.0.0", "fujifilm_ratings")
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("fujifilm_ratings", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("fujifilm ratings"),
|
||||||
|
purpose = _("import Fujifilm in-camera ratings"),
|
||||||
|
author = "Ben Mendis <ben.mendis@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function detect_rating(event, image)
|
local function detect_rating(event, image)
|
||||||
|
if not string.match(image.filename, "%.RAF$") and not string.match(image.filename, "%.raf$") then
|
||||||
|
return
|
||||||
|
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))
|
||||||
local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG")
|
local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG")
|
||||||
local command = "exiftool -Rating " .. JPEG_filename
|
local command = "exiftool -Rating " .. JPEG_filename
|
||||||
dt.print_error(command)
|
dt.print_log(command)
|
||||||
local output = io.popen(command)
|
local output = io.popen(command)
|
||||||
local jpeg_result = output:read("*all")
|
local jpeg_result = output:read("*all")
|
||||||
output:close()
|
output:close()
|
||||||
if string.len(jpeg_result) > 0 then
|
if string.len(jpeg_result) > 0 then
|
||||||
jpeg_result = string.gsub(jpeg_result, "^Rating.*(%d)", "%1")
|
jpeg_result = string.gsub(jpeg_result, "^Rating.*(%d)", "%1")
|
||||||
image.rating = tonumber(jpeg_result)
|
image.rating = tonumber(jpeg_result)
|
||||||
dt.print_error(_("Using JPEG Rating: ") .. tostring(jpeg_result))
|
dt.print_log("using JPEG rating: " .. jpeg_result)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
command = "exiftool -Rating " .. RAF_filename
|
command = "exiftool -Rating " .. RAF_filename
|
||||||
dt.print_error(command)
|
dt.print_log(command)
|
||||||
output = io.popen(command)
|
output = io.popen(command)
|
||||||
local raf_result = output:read("*all")
|
local raf_result = output:read("*all")
|
||||||
output:close()
|
output:close()
|
||||||
if string.len(raf_result) > 0 then
|
if string.len(raf_result) > 0 then
|
||||||
raf_result = string.gsub(raf_result, "^Rating.*(%d)", "%1")
|
raf_result = string.gsub(raf_result, "^Rating.*(%d)", "%1")
|
||||||
image.rating = tonumber(raf_result)
|
image.rating = tonumber(raf_result)
|
||||||
dt.print_error(_("Using RAF Rating: ") .. tostring(raf_result))
|
dt.print_log("using RAF rating: " .. raf_result)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.register_event("post-import-image",
|
local function destroy()
|
||||||
|
dt.destroy_event("fujifilm_rat", "post-import-image")
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event("fujifilm_rat", "post-import-image",
|
||||||
detect_rating)
|
detect_rating)
|
||||||
|
|
||||||
print(_("fujifilm_ratings loaded."))
|
script_data.destroy = destroy
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -35,17 +35,31 @@ USAGE
|
|||||||
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 gettext = dt.gettext
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "geoJSON_export")
|
du.check_min_api_version("7.0.0", "geoJSON_export")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("geoJSON_export", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("geoJSON export"),
|
||||||
|
purpose = _("export a geoJSON file from geo data"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoJSON_export"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- Sort a table
|
-- Sort a table
|
||||||
local function spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
|
local function spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
|
||||||
-- collect the keys
|
-- collect the keys
|
||||||
@@ -71,24 +85,24 @@ local function spairs(_table, order) -- Code copied from http://stackoverflow.co
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
||||||
dt.print(string.format(_("Export Image %i/%i"), number, total))
|
dt.print(string.format(_("export image %i/%i"), number, total))
|
||||||
end
|
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
|
||||||
|
|
||||||
@@ -279,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
|
||||||
@@ -291,24 +305,28 @@ local function create_geoJSON_file(storage, image_table, extra_data)
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("geoJSON_export")
|
||||||
|
end
|
||||||
|
|
||||||
-- Preferences
|
-- Preferences
|
||||||
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 a HTML file, that loads the geoJASON 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 )
|
||||||
|
|
||||||
local handle = io.popen("xdg-user-dir DESKTOP")
|
local handle = io.popen("xdg-user-dir DESKTOP")
|
||||||
@@ -320,9 +338,13 @@ end
|
|||||||
dt.preferences.register("geoJSON_export",
|
dt.preferences.register("geoJSON_export",
|
||||||
"ExportDirectory",
|
"ExportDirectory",
|
||||||
"directory",
|
"directory",
|
||||||
_("geoJSON export: Export directory"),
|
_("geoJSON export: export directory"),
|
||||||
_("A directory that will be used to export the geoJSON files"),
|
_("a directory that will be used to export the geoJSON files"),
|
||||||
result )
|
result )
|
||||||
|
|
||||||
-- Register
|
-- Register
|
||||||
dt.register_storage("geoJSON_export", "geoJSON Export", nil, create_geoJSON_file)
|
dt.register_storage("geoJSON_export", "geoJSON Export", nil, create_geoJSON_file)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -28,19 +29,31 @@ require "geoToolbox"
|
|||||||
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 dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local gettext = dt.gettext
|
du.check_min_api_version("7.0.0", "geoToolbox")
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "geoToolbox")
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("geoToolbox", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("geo toolbox"),
|
||||||
|
purpose = _("geodata tools"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoToolbox"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
local gT = {}
|
local gT = {}
|
||||||
gT.module_installed = false
|
gT.module_installed = false
|
||||||
@@ -49,23 +62,35 @@ gT.event_registered = false
|
|||||||
|
|
||||||
-- <GUI>
|
-- <GUI>
|
||||||
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()
|
||||||
@@ -155,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
|
||||||
@@ -194,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
|
||||||
@@ -230,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
|
||||||
@@ -277,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 = _("latitude: ") .. copy_gps_latitude
|
entry_gps_lat.text = copy_gps_latitude
|
||||||
label_copy_gps_lon.label = _("longitude: ") ..copy_gps_longitude
|
entry_gps_lon.text = copy_gps_longitude
|
||||||
label_copy_gps_ele.label = _("elevation: ") .. copy_gps_elevation
|
entry_gps_ele.text = copy_gps_elevation
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -304,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
|
||||||
@@ -331,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
|
||||||
@@ -358,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
|
||||||
|
|
||||||
@@ -374,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
|
||||||
@@ -424,7 +449,7 @@ local function get_distance(lat1, lon1, ele1, lat2, lon2, ele2)
|
|||||||
math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) *
|
math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) *
|
||||||
math.sin(dLon/2) * math.sin(dLon/2)
|
math.sin(dLon/2) * math.sin(dLon/2)
|
||||||
;
|
;
|
||||||
local angle = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
|
local angle = 2 * math.atan(math.sqrt(a), math.sqrt(1-a));
|
||||||
local distance = earthRadius * angle; -- Distance in km
|
local distance = earthRadius * angle; -- Distance in km
|
||||||
|
|
||||||
-- Add the elevation to the calculation
|
-- Add the elevation to the calculation
|
||||||
@@ -449,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
|
||||||
@@ -481,12 +506,12 @@ local function calc_distance()
|
|||||||
|
|
||||||
if (distance < 1) then
|
if (distance < 1) then
|
||||||
distance = distance * 1000
|
distance = distance * 1000
|
||||||
distanceUnit = _("m")
|
distanceUnit = "m"
|
||||||
else
|
else
|
||||||
distanceUnit = _("km")
|
distanceUnit = "km"
|
||||||
end
|
end
|
||||||
|
|
||||||
return string.format(_("Distance: %.2f %s"), distance, distanceUnit)
|
return string.format(_("distance: %.2f %s"), distance, distanceUnit)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function print_calc_distance()
|
local function print_calc_distance()
|
||||||
@@ -509,12 +534,12 @@ local altitude_filename = dt.new_widget("entry")
|
|||||||
text = "altitude.csv",
|
text = "altitude.csv",
|
||||||
placeholder = "altitude.csv",
|
placeholder = "altitude.csv",
|
||||||
editable = true,
|
editable = true,
|
||||||
tooltip = _("Name of the exported file"),
|
tooltip = _("name of the exported file"),
|
||||||
reset_callback = function(self) self.text = "text" end
|
reset_callback = function(self) self.text = "text" end
|
||||||
}
|
}
|
||||||
|
|
||||||
local function altitude_profile()
|
local function altitude_profile()
|
||||||
dt.print(_("Start export"))
|
dt.print(_("start export"))
|
||||||
local sel_images = dt.gui.action_images
|
local sel_images = dt.gui.action_images
|
||||||
|
|
||||||
local lat1 = 0;
|
local lat1 = 0;
|
||||||
@@ -533,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
|
||||||
@@ -575,7 +600,7 @@ local function altitude_profile()
|
|||||||
file = io.open(exportDirectory.."/"..exportFilename, "w")
|
file = io.open(exportDirectory.."/"..exportFilename, "w")
|
||||||
file:write(csv_file)
|
file:write(csv_file)
|
||||||
file:close()
|
file:close()
|
||||||
dt.print(_("File created in ")..exportDirectory)
|
dt.print(string.format(_("file created in %s"), exportDirectory))
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -583,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
|
||||||
@@ -595,6 +620,33 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["geoToolbox"].visible = false
|
||||||
|
dt.destroy_event("geoToolbox_cd", "shortcut")
|
||||||
|
dt.destroy_event("geoToolbox", "mouse-over-image-changed")
|
||||||
|
dt.destroy_event("geoToolbox_wg", "shortcut")
|
||||||
|
dt.destroy_event("geoToolbox_ng", "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.register_event("geoToolbox_cd", "shortcut",
|
||||||
|
print_calc_distance, _("calculate the distance from latitude and longitude in km"))
|
||||||
|
dt.register_event("geoToolbox", "mouse-over-image-changed",
|
||||||
|
toolbox_calc_distance)
|
||||||
|
|
||||||
|
dt.register_event("geoToolbox_wg", "shortcut",
|
||||||
|
select_with_gps, _("select all images with GPS information"))
|
||||||
|
dt.register_event("geoToolbox_ng", "shortcut",
|
||||||
|
select_without_gps, _("select all images without GPS information"))
|
||||||
|
|
||||||
|
dt.gui.libs["geoToolbox"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs["geoToolbox"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local separator = dt.new_widget("separator"){}
|
local separator = dt.new_widget("separator"){}
|
||||||
local separator2 = dt.new_widget("separator"){}
|
local separator2 = dt.new_widget("separator"){}
|
||||||
@@ -608,29 +660,41 @@ gT.widget = dt.new_widget("box")
|
|||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("select geo images"),
|
label = _("select geo images"),
|
||||||
tooltip = _("Select all images with GPS information"),
|
tooltip = _("select all images with GPS information"),
|
||||||
clicked_callback = select_with_gps
|
clicked_callback = select_with_gps
|
||||||
},
|
},
|
||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("select non-geo images"),
|
label = _("select non-geo images"),
|
||||||
tooltip = _("Select all images without GPS information"),
|
tooltip = _("select all images without GPS information"),
|
||||||
clicked_callback = select_without_gps
|
clicked_callback = select_without_gps
|
||||||
},
|
},
|
||||||
separator,--------------------------------------------------------
|
separator,--------------------------------------------------------
|
||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("copy GPS data"),
|
label = _("copy GPS data"),
|
||||||
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,--------------------------------------------------------
|
||||||
@@ -660,14 +724,14 @@ gT.widget = dt.new_widget("box")
|
|||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("open in Gnome Maps"),
|
label = _("open in Gnome Maps"),
|
||||||
tooltip = _("Open location in Gnome Maps"),
|
tooltip = _("open location in Gnome Maps"),
|
||||||
clicked_callback = open_location_in_gnome_maps
|
clicked_callback = open_location_in_gnome_maps
|
||||||
},
|
},
|
||||||
separator4,--------------------------------------------------------
|
separator4,--------------------------------------------------------
|
||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("reverse geocode"),
|
label = _("reverse geocode"),
|
||||||
tooltip = _("This just shows the name of the location, but doesn't add it as tag"),
|
tooltip = _("this just shows the name of the location, but doesn't add it as tag"),
|
||||||
clicked_callback = reverse_geocode
|
clicked_callback = reverse_geocode
|
||||||
},
|
},
|
||||||
separator5,--------------------------------------------------------
|
separator5,--------------------------------------------------------
|
||||||
@@ -677,7 +741,7 @@ gT.widget = dt.new_widget("box")
|
|||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("export altitude CSV file"),
|
label = _("export altitude CSV file"),
|
||||||
tooltip = _("Create an altitude profile using the GPS data in the metadata"),
|
tooltip = _("create an altitude profile using the GPS data in the metadata"),
|
||||||
clicked_callback = altitude_profile
|
clicked_callback = altitude_profile
|
||||||
},
|
},
|
||||||
labelDistance
|
labelDistance
|
||||||
@@ -689,7 +753,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not gT.event_registered then
|
if not gT.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"geoToolbox", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -705,19 +769,25 @@ dt.preferences.register("geoToolbox",
|
|||||||
"mapBoxKey",
|
"mapBoxKey",
|
||||||
"string",
|
"string",
|
||||||
_("geoToolbox export: MapBox Key"),
|
_("geoToolbox export: MapBox Key"),
|
||||||
_("https://www.mapbox.com/studio/account/tokens"),
|
"https://www.mapbox.com/studio/account/tokens",
|
||||||
'' )
|
'' )
|
||||||
|
|
||||||
-- Register
|
-- Register
|
||||||
dt.register_event("shortcut",
|
dt.register_event("geoToolbox_cd", "shortcut",
|
||||||
print_calc_distance, _("Calculate the distance from latitude and longitude in km"))
|
print_calc_distance, _("calculate the distance from latitude and longitude in km"))
|
||||||
dt.register_event("mouse-over-image-changed",
|
dt.register_event("geoToolbox", "mouse-over-image-changed",
|
||||||
toolbox_calc_distance)
|
toolbox_calc_distance)
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("geoToolbox_wg", "shortcut",
|
||||||
select_with_gps, _("Select all images with GPS information"))
|
select_with_gps, _("select all images with GPS information"))
|
||||||
dt.register_event("shortcut",
|
dt.register_event("geoToolbox_ng", "shortcut",
|
||||||
select_without_gps, _("Select all images without GPS information"))
|
select_without_gps, _("select all images without GPS information"))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
-- kate: hl Lua;
|
-- kate: hl Lua;
|
||||||
|
|||||||
@@ -69,18 +69,31 @@ 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 dtsys = require "lib/dtutils.system"
|
local dtsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
local gimp_widget = nil
|
local gimp_widget = nil
|
||||||
|
|
||||||
du.check_min_api_version("5.0.0", "gimp")
|
du.check_min_api_version("7.0.0", "gimp")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("gimp", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("edit with GIMP"),
|
||||||
|
purpose = _("export and edit with GIMP"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gimp"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function group_if_not_member(img, new_img)
|
local function group_if_not_member(img, new_img)
|
||||||
local image_table = img:get_group_members()
|
local image_table = img:get_group_members()
|
||||||
local is_member = false
|
local is_member = false
|
||||||
@@ -100,7 +113,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(string.format(_("Export Image %i/%i"), number, total))
|
dt.print(string.format(_("export image %i/%i"), number, total))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function gimp_edit(storage, image_table, extra_data) --finalize
|
local function gimp_edit(storage, image_table, extra_data) --finalize
|
||||||
@@ -110,7 +123,7 @@ local function gimp_edit(storage, image_table, extra_data) --finalize
|
|||||||
local gimp_executable = df.check_if_bin_exists("gimp")
|
local gimp_executable = df.check_if_bin_exists("gimp")
|
||||||
|
|
||||||
if not gimp_executable then
|
if not gimp_executable then
|
||||||
dt.print_error(_("GIMP not found"))
|
dt.print_error("GIMP not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,7 +146,7 @@ local function gimp_edit(storage, image_table, extra_data) --finalize
|
|||||||
img_list = img_list ..exp_img.. " "
|
img_list = img_list ..exp_img.. " "
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.print(_("Launching GIMP..."))
|
dt.print(_("launching GIMP..."))
|
||||||
|
|
||||||
local gimpStartCommand
|
local gimpStartCommand
|
||||||
gimpStartCommand = gimp_executable .. " " .. img_list
|
gimpStartCommand = gimp_executable .. " " .. img_list
|
||||||
@@ -189,6 +202,10 @@ local function gimp_edit(storage, image_table, extra_data) --finalize
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("module_gimp")
|
||||||
|
end
|
||||||
|
|
||||||
-- Register
|
-- Register
|
||||||
|
|
||||||
gimp_widget = dt.new_widget("check_button"){
|
gimp_widget = dt.new_widget("check_button"){
|
||||||
@@ -200,6 +217,9 @@ gimp_widget = dt.new_widget("check_button"){
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
|
dt.register_storage("module_gimp", _("edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
|
||||||
|
|
||||||
--
|
--
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -25,19 +25,30 @@ 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.gettext
|
||||||
|
|
||||||
local gettext = dt.gettext
|
dl.check_min_api_version("7.0.0", "gpx_export")
|
||||||
|
|
||||||
dl.check_min_api_version("3.0.0", "gpx-export")
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
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
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("gpx export"),
|
||||||
|
purpose = _("export gpx information to a file"),
|
||||||
|
author = "Jannis_V",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local gpx = {}
|
local gpx = {}
|
||||||
gpx.module_installed = false
|
gpx.module_installed = false
|
||||||
gpx.event_registered = false
|
gpx.event_registered = false
|
||||||
@@ -125,11 +136,11 @@ local function create_gpx_file()
|
|||||||
|
|
||||||
local file = io.open(path, "w")
|
local file = io.open(path, "w")
|
||||||
if (file == nil) then
|
if (file == nil) then
|
||||||
dt.print(_("invalid path: ")..path)
|
dt.print(string.format(_("invalid path: %s"), path))
|
||||||
else
|
else
|
||||||
file:write(gpx_file)
|
file:write(gpx_file)
|
||||||
file:close()
|
file:close()
|
||||||
dt.print(_("gpx file created: ")..path)
|
dt.print(string.format(_("gpx file created: "), path))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -137,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
|
||||||
@@ -149,6 +160,14 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["gpx_exporter"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["gpx_exporter"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
gpx.widget = dt.new_widget("box")
|
gpx.widget = dt.new_widget("box")
|
||||||
{
|
{
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
@@ -175,7 +194,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not gpx.event_registered then
|
if not gpx.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"gpx_export", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -185,3 +204,10 @@ else
|
|||||||
gpx.event_registered = true
|
gpx.event_registered = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
97
contrib/harmonic_armature_guide.lua
Normal file
97
contrib/harmonic_armature_guide.lua
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
--[[
|
||||||
|
harmonic artmature guide for darktable
|
||||||
|
|
||||||
|
copyright (c) 2021 Hubert Kowalski
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
HARMONIC ARMATURE GUIDE
|
||||||
|
Harmonic Armature (also known as 14 line armature)
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
* copy this file in $CONFIGDIR/lua/contrib where CONFIGDIR is your darktable configuration directory
|
||||||
|
* add the following line in the file $CONFIGDIR/luarc
|
||||||
|
require "contrib/harmonic_armature_guide"
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* when using guides, select "harmonic armature" as guide
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
du.check_min_api_version("2.0.0", "harmonic_armature_guide")
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("harmonic armature guide"),
|
||||||
|
purpose = _("harmonic artmature guide"),
|
||||||
|
author = "Hubert Kowalski",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/harmonic_armature_guide"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
dt.guides.register_guide("harmonic armature",
|
||||||
|
-- draw
|
||||||
|
function(cairo, x, y, width, height, zoom_scale)
|
||||||
|
cairo:save()
|
||||||
|
|
||||||
|
cairo:translate(x, y)
|
||||||
|
cairo:scale(width, height)
|
||||||
|
|
||||||
|
cairo:move_to(0,0)
|
||||||
|
cairo:line_to(1, 0.5)
|
||||||
|
cairo:line_to(0.5, 1)
|
||||||
|
cairo:line_to(0,0)
|
||||||
|
cairo:line_to(1, 1)
|
||||||
|
cairo:line_to(0.5, 0)
|
||||||
|
cairo:line_to(0, 0.5)
|
||||||
|
cairo:line_to(1, 1)
|
||||||
|
|
||||||
|
cairo:move_to(1, 0)
|
||||||
|
cairo:line_to(0, 0.5)
|
||||||
|
cairo:line_to(0.5, 1)
|
||||||
|
cairo:line_to(1, 0)
|
||||||
|
cairo:line_to(0, 1)
|
||||||
|
cairo:line_to(0.5, 0)
|
||||||
|
cairo:line_to(1, 0.5)
|
||||||
|
cairo:line_to(0, 1)
|
||||||
|
|
||||||
|
cairo:restore()
|
||||||
|
end,
|
||||||
|
-- gui
|
||||||
|
function()
|
||||||
|
return dt.new_widget("label"){label = _("harmonic armature"), halign = "start"}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
200
contrib/hif_group_leader.lua
Normal file
200
contrib/hif_group_leader.lua
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
hif_group_leader.lua - Make hif image group leader
|
||||||
|
|
||||||
|
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
hif_group_leader - Make hif image group leader
|
||||||
|
|
||||||
|
After a film roll is imported, check for RAW-JPG image groups
|
||||||
|
and make the JPG image the group leader. This is on by default
|
||||||
|
but can be disabled in preferences.
|
||||||
|
|
||||||
|
Shortcuts are included to filter existing collections or
|
||||||
|
selections of images and make the hif the group leader.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
None
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
Start script from script_manager
|
||||||
|
Assign keys to the shortcuts
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
Bill Ferguson <wpferguson@gmail.com>
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE = "hif_group_leader"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- A P I C H E C K
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", MODULE)
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- I 1 8 N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("HIF group leader"),
|
||||||
|
purpose = _("make hif image group leader"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hif_group_leader"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- P R E F E R E N C E S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.preferences.register(MODULE, "on_import", "bool", _("make hif group leader on import"), _("automatically make the hif file the group leader when raw + hif are imported"), true)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- N A M E S P A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local jgloi = {}
|
||||||
|
jgloi.images = {}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function toggle_global_toolbox_grouping()
|
||||||
|
dt.gui.libs.global_toolbox.grouping = false
|
||||||
|
dt.gui.libs.global_toolbox.grouping = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N P R O G R A M
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function make_hif_group_leader(images)
|
||||||
|
-- if the image is part of a group, make it the leader
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
if #image:get_group_members() > 1 then
|
||||||
|
image:make_group_leader()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dt.gui.libs.global_toolbox.grouping then
|
||||||
|
-- toggle the grouping to make the new leader show
|
||||||
|
toggle_global_toolbox_grouping()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_existing_hif_group_leader(images)
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
if string.lower(df.get_filetype(image.filename)) == "hif" then
|
||||||
|
if #image:get_group_members() > 1 then
|
||||||
|
image:make_group_leader()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dt.gui.libs.global_toolbox.grouping then
|
||||||
|
-- toggle the grouping to make the new leader show
|
||||||
|
toggle_global_toolbox_grouping()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- D A R K T A B L E I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
if dt.preferences.read(MODULE, "on_import", "bool") then
|
||||||
|
dt.destroy_event(MODULE, "post-import-film")
|
||||||
|
dt.destroy_event(MODULE, "post-import-image")
|
||||||
|
end
|
||||||
|
dt.destroy_event(MODULE .. "_collect", "shortcut")
|
||||||
|
dt.destroy_event(MODULE .. "_select", "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- E V E N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
if dt.preferences.read(MODULE, "on_import", "bool") then
|
||||||
|
dt.register_event(MODULE, "post-import-film",
|
||||||
|
function(event, film_roll)
|
||||||
|
-- ignore the film roll, it contains all the images, not just the imported
|
||||||
|
local images = jgloi.images
|
||||||
|
if #images > 0 then
|
||||||
|
jgloi.images = {}
|
||||||
|
make_hif_group_leader(images)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
dt.register_event(MODULE, "post-import-image",
|
||||||
|
function(event, image)
|
||||||
|
if string.lower(df.get_filetype(image.filename)) == "hif" then
|
||||||
|
table.insert(jgloi.images, image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event(MODULE .. "_collect", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
|
-- ignore the film roll, it contains all the images, not just the imported
|
||||||
|
local images = dt.collection
|
||||||
|
make_existing_hif_group_leader(images)
|
||||||
|
end,
|
||||||
|
_("make hif group leader for collection")
|
||||||
|
)
|
||||||
|
|
||||||
|
dt.register_event(MODULE .. "_select", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
make_existing_hif_group_leader(images)
|
||||||
|
end,
|
||||||
|
_("make hif group leader for selection")
|
||||||
|
)
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -40,7 +40,7 @@ local du = require "lib/dtutils"
|
|||||||
local df = require "lib/dtutils.file"
|
local df = require "lib/dtutils.file"
|
||||||
local log = require "lib/dtutils.log"
|
local log = require "lib/dtutils.log"
|
||||||
local dtsys = require "lib/dtutils.system"
|
local dtsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local namespace = 'module_hugin'
|
local namespace = 'module_hugin'
|
||||||
local user_pref_str = 'prefer_gui'
|
local user_pref_str = 'prefer_gui'
|
||||||
@@ -54,15 +54,28 @@ local executable_table = {"hugin", "hugin_executor", "pto_gen"}
|
|||||||
local PQ = dt.configuration.running_os == "windows" and '"' or "'"
|
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("5.0.0", "hugin")
|
du.check_min_api_version("7.0.0", "hugin")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("hugin", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("hugin"),
|
||||||
|
purpose = _("stitch images into a panorama"),
|
||||||
|
author = "Wolfgang Goetz",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function user_preference_changed(widget)
|
local function user_preference_changed(widget)
|
||||||
user_prefer_gui = widget.value
|
user_prefer_gui = widget.value
|
||||||
dt.preferences.write(namespace, user_pref_str, "bool", user_prefer_gui)
|
dt.preferences.write(namespace, user_pref_str, "bool", user_prefer_gui)
|
||||||
@@ -70,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
|
||||||
@@ -131,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
|
||||||
|
|
||||||
@@ -192,7 +205,7 @@ local function create_panorama(storage, image_table, extra_data) --finalize
|
|||||||
if df.check_if_file_exists(src_path) then
|
if df.check_if_file_exists(src_path) then
|
||||||
log.msg(log.debug, "found ", src_path, " importing to ", dst_path)
|
log.msg(log.debug, "found ", src_path, " importing to ", dst_path)
|
||||||
df.file_move(src_path, dst_path)
|
df.file_move(src_path, dst_path)
|
||||||
dt.print(_("importing file "..dst_path))
|
dt.print(string.format(_("importing file %s"), dst_path))
|
||||||
dt.database.import(dst_path)
|
dt.database.import(dst_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -205,6 +218,10 @@ local function create_panorama(storage, image_table, extra_data) --finalize
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage(namespace)
|
||||||
|
end
|
||||||
|
|
||||||
-- Register
|
-- Register
|
||||||
if dt.configuration.running_os ~= "linux" then
|
if dt.configuration.running_os ~= "linux" then
|
||||||
exec_widget = df.executable_path_widget(executable_table)
|
exec_widget = df.executable_path_widget(executable_table)
|
||||||
@@ -214,7 +231,7 @@ hugin_widget = dt.new_widget("box") {
|
|||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
dt.new_widget("check_button")
|
dt.new_widget("check_button")
|
||||||
{
|
{
|
||||||
label = _(" launch hugin gui"),
|
label = _("launch hugin gui"),
|
||||||
value = user_prefer_gui,
|
value = user_prefer_gui,
|
||||||
tooltip = _('launch hugin in gui mode'),
|
tooltip = _('launch hugin in gui mode'),
|
||||||
clicked_callback = user_preference_changed
|
clicked_callback = user_preference_changed
|
||||||
@@ -224,6 +241,8 @@ hugin_widget = dt.new_widget("box") {
|
|||||||
|
|
||||||
dt.register_storage(namespace, _("hugin panorama"), show_status, create_panorama, nil, nil, hugin_widget)
|
dt.register_storage(namespace, _("hugin panorama"), show_status, create_panorama, nil, nil, hugin_widget)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
--
|
--
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -64,22 +64,35 @@ 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 dtsys = require "lib/dtutils.system"
|
local dtsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
local job = nil
|
local job = nil
|
||||||
|
|
||||||
-- path separator constant
|
-- path separator constant
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
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("5.0.0", "image_stack")
|
du.check_min_api_version("7.0.0", "image_stack")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("image_stack",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("image_stack", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("image stack"),
|
||||||
|
purpose = _("process a stack of images"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- GUI definitions
|
-- GUI definitions
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@@ -126,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"){
|
||||||
@@ -250,7 +263,7 @@ end
|
|||||||
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
||||||
dt.print(string.format(_("Export Image %i/%i"), number, total))
|
dt.print(string.format(_("export image %i/%i"), number, total))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- read the gui and populate the align_image_stack arguments
|
-- read the gui and populate the align_image_stack arguments
|
||||||
@@ -443,7 +456,7 @@ local function copy_image_attributes(from, to, ...)
|
|||||||
to.rights = from.rights
|
to.rights = from.rights
|
||||||
to.description = from.description
|
to.description = from.description
|
||||||
else
|
else
|
||||||
dt.print_error(_("Unrecognized option to copy_image_attributes: " .. arg))
|
dt.print_error("Unrecognized option to copy_image_attributes: " .. arg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -452,6 +465,10 @@ local function stop_job()
|
|||||||
job.valid = false
|
job.valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("module_image_stack")
|
||||||
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- main program
|
-- main program
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@@ -483,11 +500,11 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
|
|
||||||
if image_count < 2 then
|
if image_count < 2 then
|
||||||
dt.print(_("ERROR: at least 2 images required for image stacking, exiting..."))
|
dt.print(_("ERROR: at least 2 images required for image stacking, exiting..."))
|
||||||
dt.print_error(_(image_count .. " image(s) selected, at least 2 required"))
|
dt.print_error(image_count .. " image(s) selected, at least 2 required")
|
||||||
return
|
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
|
||||||
@@ -507,13 +524,13 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
job.percent = job.percent + percent_step
|
job.percent = job.percent + percent_step
|
||||||
else
|
else
|
||||||
dt.print(_("ERROR: image alignment failed"))
|
dt.print(_("ERROR: image alignment failed"))
|
||||||
dt.print_error(_("image alignment failed"))
|
dt.print_error("image alignment failed")
|
||||||
cleanup(img_list)
|
cleanup(img_list)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
dt.print(_("ERROR: align_image_stack not found"))
|
dt.print(_("ERROR: align_image_stack not found"))
|
||||||
dt.print_error(_("align_image_stack not found"))
|
dt.print_error("align_image_stack not found")
|
||||||
cleanup(img_list)
|
cleanup(img_list)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -527,7 +544,7 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
local ignore_tif_tags = " -quiet -define tiff:ignore-tags=40965,42032,42033,42034,42036,18246,18249,36867,34864,34866 "
|
local ignore_tif_tags = " -quiet -define tiff:ignore-tags=40965,42032,42033,42034,42036,18246,18249,36867,34864,34866 "
|
||||||
if convert_executable then
|
if convert_executable then
|
||||||
local convert_command = convert_executable .. ignore_tif_tags .. convert_arguments
|
local convert_command = convert_executable .. ignore_tif_tags .. convert_arguments
|
||||||
dt.print_log(_("convert command is " .. convert_command))
|
dt.print_log("convert command is " .. convert_command)
|
||||||
dt.print(_("processing image stack"))
|
dt.print(_("processing image stack"))
|
||||||
local result = dtsys.external_command(convert_command)
|
local result = dtsys.external_command(convert_command)
|
||||||
if result == 0 then
|
if result == 0 then
|
||||||
@@ -541,7 +558,7 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
local import_filename = df.create_unique_filename(film_roll_path .. PS .. df.get_filename(output_filename))
|
local import_filename = df.create_unique_filename(film_roll_path .. PS .. df.get_filename(output_filename))
|
||||||
df.file_move(output_filename, import_filename)
|
df.file_move(output_filename, import_filename)
|
||||||
imported_image = dt.database.import(import_filename)
|
imported_image = dt.database.import(import_filename)
|
||||||
local created_tag = dt.tags.create(_("Created with|image_stack"))
|
local created_tag = dt.tags.create(_("created with|image_stack"))
|
||||||
dt.tags.attach(created_tag, imported_image)
|
dt.tags.attach(created_tag, imported_image)
|
||||||
-- all the images are the same except for time, so just copy the attributes
|
-- all the images are the same except for time, so just copy the attributes
|
||||||
-- from the first
|
-- from the first
|
||||||
@@ -555,7 +572,7 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
|
|
||||||
if tag_source then
|
if tag_source then
|
||||||
dt.print(_("tagging source images"))
|
dt.print(_("tagging source images"))
|
||||||
local source_tag = dt.tags.create(_("Source file|" .. imported_image.filename))
|
local source_tag = dt.tags.create(_("source file|" .. imported_image.filename))
|
||||||
for img, _ in pairs(image_table) do
|
for img, _ in pairs(image_table) do
|
||||||
dt.tags.attach(source_tag, img)
|
dt.tags.attach(source_tag, img)
|
||||||
end
|
end
|
||||||
@@ -567,7 +584,7 @@ local function image_stack(storage, image_table, extra_data)
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
dt.print(_("ERROR: convert executable not found"))
|
dt.print(_("ERROR: convert executable not found"))
|
||||||
dt.print_error(_("convert executable not found"))
|
dt.print_error("convert executable not found")
|
||||||
cleanup(img_list)
|
cleanup(img_list)
|
||||||
end
|
end
|
||||||
job.valid = false
|
job.valid = false
|
||||||
@@ -579,9 +596,12 @@ end
|
|||||||
|
|
||||||
dt.preferences.register("align_image_stack", "align_use_gpu", -- name
|
dt.preferences.register("align_image_stack", "align_use_gpu", -- name
|
||||||
"bool", -- type
|
"bool", -- type
|
||||||
_('align image stack: use GPU for remaping'), -- label
|
_('align image stack: use GPU for remapping'), -- label
|
||||||
_('set the GPU remapping for image align'), -- tooltip
|
_('set the GPU remapping for image align'), -- tooltip
|
||||||
false)
|
false)
|
||||||
|
|
||||||
dt.register_storage("module_image_stack", _("image stack"), show_status, image_stack, nil, nil, image_stack_widget)
|
dt.register_storage("module_image_stack", _("image stack"), show_status, image_stack, nil, nil, image_stack_widget)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -107,26 +107,40 @@ 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 ds = require "lib/dtutils.string"
|
||||||
|
local dtsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local img_time = {}
|
local img_time = {}
|
||||||
img_time.module_installed = false
|
img_time.module_installed = false
|
||||||
img_time.event_registered = false
|
img_time.event_registered = false
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "image_time")
|
du.check_min_api_version("7.0.0", "image_time")
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("image_time",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("image_time", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
local PS = dt.configuration.runnin_os == "windows" and "\\" or "/"
|
-- return data structure for script_manager
|
||||||
local ERROR = -1
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("image time"),
|
||||||
|
purpose = _("synchronize image time for images shot with different cameras or adjust or set image time"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local PS <const> = dt.configuration.runnin_os == "windows" and "\\" or "/"
|
||||||
|
local ERROR <const> = -1
|
||||||
|
local HAS_INTERNAL_METADATA = dt.metadata.exists("Exif.Image.DataTime")
|
||||||
|
|
||||||
-- function to convert from exif time to system time
|
-- function to convert from exif time to system time
|
||||||
local function exiftime2systime(exiftime)
|
local function exiftime2systime(exiftime)
|
||||||
@@ -141,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
|
||||||
|
|
||||||
@@ -168,7 +182,7 @@ local function calculate_difference(images)
|
|||||||
img_time.diff_entry.text = calc_time_difference(images[1], images[2])
|
img_time.diff_entry.text = calc_time_difference(images[1], images[2])
|
||||||
img_time.btn.sensitive = true
|
img_time.btn.sensitive = true
|
||||||
else
|
else
|
||||||
dt.print(_("Error: 2 images must be selected"))
|
dt.print(_("ERROR: 2 images must be selected"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -180,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)
|
||||||
@@ -198,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)
|
||||||
|
|
||||||
@@ -211,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
|
||||||
@@ -229,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(_("unable to get information for ") .. 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
|
||||||
@@ -259,7 +281,7 @@ local function _get_nix_image_file_creation_time(image)
|
|||||||
end
|
end
|
||||||
p:close()
|
p:close()
|
||||||
else
|
else
|
||||||
dt.print(_("unable to get information for ") .. image.filename)
|
dt.print(string.format(_("unable to get information for %s"), image.filename))
|
||||||
datetime = ERROR
|
datetime = ERROR
|
||||||
end
|
end
|
||||||
return datetime
|
return datetime
|
||||||
@@ -307,7 +329,7 @@ local function reset_time(images)
|
|||||||
image.exif_datetime_taken = get_original_image_time(image)
|
image.exif_datetime_taken = get_original_image_time(image)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
dt.print_error(_("reset time: no images selected"))
|
dt.print_error("reset time: no images selected")
|
||||||
dt.print(_("please select the images that need their time reset"))
|
dt.print(_("please select the images that need their time reset"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -410,13 +432,21 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["image_time"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["image_time"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- widgets
|
-- widgets
|
||||||
|
|
||||||
img_time.widgets = {
|
img_time.widgets = {
|
||||||
-- name, type, tooltip, placeholder,
|
-- name, type, tooltip, placeholder,
|
||||||
{"ayr", "combobox", _("years"), _("years to adjust by, 0 - ?"), {seq(0,20)}, 1},
|
{"ayr", "combobox", _("years"), _("years to adjust by, 0 - ?"), {seq(0,20)}, 1},
|
||||||
{"amo", "combobox", _("months"), ("months to adjust by, 0-12"), {seq(0,12)}, 1},
|
{"amo", "combobox", _("months"), _("months to adjust by, 0-12"), {seq(0,12)}, 1},
|
||||||
{"ady", "combobox", _("days"), ("days to adjust by, 0-31"), {seq(0,31)}, 1},
|
{"ady", "combobox", _("days"), _("days to adjust by, 0-31"), {seq(0,31)}, 1},
|
||||||
{"ahr", "combobox", _("hours"), _("hours to adjust by, 0-23"), {seq(0,23)}, 1},
|
{"ahr", "combobox", _("hours"), _("hours to adjust by, 0-23"), {seq(0,23)}, 1},
|
||||||
{"amn", "combobox", _("minutes"), _("minutes to adjust by, 0-59"), {seq(0,59)}, 1},
|
{"amn", "combobox", _("minutes"), _("minutes to adjust by, 0-59"), {seq(0,59)}, 1},
|
||||||
{"asc", "combobox", _("seconds"), _("seconds to adjust by, 0-59"), {seq(0,59)}, 1},
|
{"asc", "combobox", _("seconds"), _("seconds to adjust by, 0-59"), {seq(0,59)}, 1},
|
||||||
@@ -442,13 +472,13 @@ end
|
|||||||
img_time.syr.selected = #img_time.syr
|
img_time.syr.selected = #img_time.syr
|
||||||
|
|
||||||
img_time.diff_entry = dt.new_widget("entry"){
|
img_time.diff_entry = dt.new_widget("entry"){
|
||||||
tooltip = _("Time difference between images in seconds"),
|
tooltip = _("time difference between images in seconds"),
|
||||||
placeholder = _("Select 2 images and use the calculate button"),
|
placeholder = _("select 2 images and use the calculate button"),
|
||||||
text = "",
|
text = "",
|
||||||
}
|
}
|
||||||
|
|
||||||
img_time.calc_btn = dt.new_widget("button"){
|
img_time.calc_btn = dt.new_widget("button"){
|
||||||
label = _("Calculate"),
|
label = _("calculate"),
|
||||||
tooltip = _("calculate time difference between 2 images"),
|
tooltip = _("calculate time difference between 2 images"),
|
||||||
clicked_callback = function()
|
clicked_callback = function()
|
||||||
calculate_difference(dt.gui.action_images)
|
calculate_difference(dt.gui.action_images)
|
||||||
@@ -488,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,
|
||||||
@@ -553,7 +583,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not img_time.event_registered then
|
if not img_time.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"image_time", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -564,3 +594,9 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
200
contrib/jpg_group_leader.lua
Normal file
200
contrib/jpg_group_leader.lua
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
jpg_group_leader.lua - Make jpg image group leader
|
||||||
|
|
||||||
|
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
jpg_group_leader - Make jpg image group leader
|
||||||
|
|
||||||
|
After a film roll is imported, check for RAW-JPG image groups
|
||||||
|
and make the JPG image the group leader. This is on by default
|
||||||
|
but can be disabled in preferences.
|
||||||
|
|
||||||
|
Shortcuts are included to filter existing collections or
|
||||||
|
selections of images and make the jpg the group leader.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
None
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
Start script from script_manager
|
||||||
|
Assign keys to the shortcuts
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
Bill Ferguson <wpferguson@gmail.com>
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE = "jpg_group_leader"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- A P I C H E C K
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", MODULE)
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- I 1 8 N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- S C R I P T M A N A G E R I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("JPG group leader"),
|
||||||
|
purpose = _("make jpg image group leader"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/jpg_group_leader"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- P R E F E R E N C E S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
dt.preferences.register(MODULE, "on_import", "bool", _("make jpg group leader on import"), _("automatically make the jpg file the group leader when raw + jpg are imported"), true)
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- N A M E S P A C E
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local jgloi = {}
|
||||||
|
jgloi.images = {}
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function toggle_global_toolbox_grouping()
|
||||||
|
dt.gui.libs.global_toolbox.grouping = false
|
||||||
|
dt.gui.libs.global_toolbox.grouping = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N P R O G R A M
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function make_jpg_group_leader(images)
|
||||||
|
-- if the image is part of a group, make it the leader
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
if #image:get_group_members() > 1 then
|
||||||
|
image:make_group_leader()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dt.gui.libs.global_toolbox.grouping then
|
||||||
|
-- toggle the grouping to make the new leader show
|
||||||
|
toggle_global_toolbox_grouping()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_existing_jpg_group_leader(images)
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
if string.lower(df.get_filetype(image.filename)) == "jpg" then
|
||||||
|
if #image:get_group_members() > 1 then
|
||||||
|
image:make_group_leader()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dt.gui.libs.global_toolbox.grouping then
|
||||||
|
-- toggle the grouping to make the new leader show
|
||||||
|
toggle_global_toolbox_grouping()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- D A R K T A B L E I N T E G R A T I O N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
if dt.preferences.read(MODULE, "on_import", "bool") then
|
||||||
|
dt.destroy_event(MODULE, "post-import-film")
|
||||||
|
dt.destroy_event(MODULE, "post-import-image")
|
||||||
|
end
|
||||||
|
dt.destroy_event(MODULE .. "_collect", "shortcut")
|
||||||
|
dt.destroy_event(MODULE .. "_select", "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- E V E N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
if dt.preferences.read(MODULE, "on_import", "bool") then
|
||||||
|
dt.register_event(MODULE, "post-import-film",
|
||||||
|
function(event, film_roll)
|
||||||
|
-- ignore the film roll, it contains all the images, not just the imported
|
||||||
|
local images = jgloi.images
|
||||||
|
if #images > 0 then
|
||||||
|
jgloi.images = {}
|
||||||
|
make_jpg_group_leader(images)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
dt.register_event(MODULE, "post-import-image",
|
||||||
|
function(event, image)
|
||||||
|
if string.lower(df.get_filetype(image.filename)) == "jpg" then
|
||||||
|
table.insert(jgloi.images, image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event(MODULE .. "_collect", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
|
-- ignore the film roll, it contains all the images, not just the imported
|
||||||
|
local images = dt.collection
|
||||||
|
make_existing_jpg_group_leader(images)
|
||||||
|
end,
|
||||||
|
_("make jpg group leader for collection")
|
||||||
|
)
|
||||||
|
|
||||||
|
dt.register_event(MODULE .. "_select", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
make_existing_jpg_group_leader(images)
|
||||||
|
end,
|
||||||
|
_("make jpg group leader for selection")
|
||||||
|
)
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -39,21 +39,34 @@ local df = require "lib/dtutils.file"
|
|||||||
local ds = require "lib/dtutils.string"
|
local ds = require "lib/dtutils.string"
|
||||||
local dsys = require "lib/dtutils.system"
|
local dsys = require "lib/dtutils.system"
|
||||||
|
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
du.check_min_api_version("5.0.0", kml_export)
|
du.check_min_api_version("7.0.0", "kml_export")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("kml_export",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("kml_export", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("kml export"),
|
||||||
|
purpose = _("export KML/KMZ data to a file"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
|
||||||
dt.print(string.format(_("Export Image %i/%i"), number, total))
|
dt.print(string.format(_("export image %i/%i"), number, total))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Add duplicate index to filename
|
-- Add duplicate index to filename
|
||||||
@@ -80,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
|
||||||
@@ -95,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
|
||||||
@@ -301,20 +314,24 @@ local function create_kml_file(storage, image_table, extra_data)
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("kml_export")
|
||||||
|
end
|
||||||
|
|
||||||
-- Preferences
|
-- Preferences
|
||||||
if dt.configuration.running_os == "windows" then
|
if dt.configuration.running_os == "windows" then
|
||||||
dt.preferences.register("kml_export",
|
dt.preferences.register("kml_export",
|
||||||
"OpenKmlFile",
|
"OpenKmlFile",
|
||||||
"bool",
|
"bool",
|
||||||
_("KML export: Open KML file after export"),
|
_("KML export: Open KML file after export"),
|
||||||
_("Opens the KML file after the export with the standard program for KML files"),
|
_("opens the KML file after the export with the standard program for KML files"),
|
||||||
false )
|
false )
|
||||||
else
|
else
|
||||||
dt.preferences.register("kml_export",
|
dt.preferences.register("kml_export",
|
||||||
"OpenKmlFile",
|
"OpenKmlFile",
|
||||||
"bool",
|
"bool",
|
||||||
_("KML export: Open KML/KMZ file after export"),
|
_("KML export: open KML/KMZ file after export"),
|
||||||
_("Opens the KML file after the export with the standard program for KML files"),
|
_("opens the KML file after the export with the standard program for KML files"),
|
||||||
false )
|
false )
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -322,7 +339,7 @@ local defaultDir = ''
|
|||||||
if dt.configuration.running_os == "windows" then
|
if dt.configuration.running_os == "windows" then
|
||||||
defaultDir = os.getenv("USERPROFILE")
|
defaultDir = os.getenv("USERPROFILE")
|
||||||
elseif dt.configuration.running_os == "macos" then
|
elseif dt.configuration.running_os == "macos" then
|
||||||
defaultDir = os.getenv("home")
|
defaultDir = os.getenv("HOME")
|
||||||
else
|
else
|
||||||
local handle = io.popen("xdg-user-dir DESKTOP")
|
local handle = io.popen("xdg-user-dir DESKTOP")
|
||||||
defaultDir = handle:read()
|
defaultDir = handle:read()
|
||||||
@@ -333,23 +350,23 @@ end
|
|||||||
dt.preferences.register("kml_export",
|
dt.preferences.register("kml_export",
|
||||||
"ExportDirectory",
|
"ExportDirectory",
|
||||||
"directory",
|
"directory",
|
||||||
_("KML export: Export directory"),
|
_("KML export: export directory"),
|
||||||
_("A directory that will be used to export the KML/KMZ files"),
|
_("a directory that will be used to export the KML/KMZ files"),
|
||||||
defaultDir )
|
defaultDir )
|
||||||
|
|
||||||
if dt.configuration.running_os ~= "linux" then
|
if dt.configuration.running_os ~= "linux" then
|
||||||
dt.preferences.register("kml_export",
|
dt.preferences.register("kml_export",
|
||||||
"magickPath", -- name
|
"magickPath", -- name
|
||||||
"file", -- type
|
"file", -- type
|
||||||
_("KML export: ImageMagick binary Location"), -- label
|
_("KML export: ImageMagick binary location"), -- label
|
||||||
_("Install location of magick[.exe]. Requires restart to take effect."), -- tooltip
|
_("install location of magick[.exe], requires restart to take effect"), -- tooltip
|
||||||
"magick") -- default
|
"magick") -- default
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.preferences.register("kml_export",
|
dt.preferences.register("kml_export",
|
||||||
"CreatePath",
|
"CreatePath",
|
||||||
"bool",
|
"bool",
|
||||||
_("KML export: Connect images with path"),
|
_("KML export: connect images with path"),
|
||||||
_("connect all images with a path"),
|
_("connect all images with a path"),
|
||||||
false )
|
false )
|
||||||
|
|
||||||
@@ -357,17 +374,21 @@ if dt.configuration.running_os == "linux" then
|
|||||||
dt.preferences.register("kml_export",
|
dt.preferences.register("kml_export",
|
||||||
"CreateKMZ",
|
"CreateKMZ",
|
||||||
"bool",
|
"bool",
|
||||||
_("KML export: Create KMZ file"),
|
_("KML export: create KMZ file"),
|
||||||
_("Compress all imeges to one KMZ file"),
|
_("compress all imeges to one KMZ file"),
|
||||||
true )
|
true )
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Register
|
-- Register
|
||||||
if dt.configuration.running_os == "windows" then
|
if dt.configuration.running_os == "windows" then
|
||||||
dt.register_storage("kml_export", _("KML Export"), nil, create_kml_file)
|
dt.register_storage("kml_export", _("KML export"), nil, create_kml_file)
|
||||||
else
|
else
|
||||||
dt.register_storage("kml_export", _("KML/KMZ Export"), nil, create_kml_file)
|
dt.register_storage("kml_export", _("KML/KMZ export"), nil, create_kml_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
-- kate: hl Lua;
|
-- kate: hl Lua;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
--[[
|
--[[
|
||||||
PASSPORT CROPPING GUIDE
|
PASSPORT CROPPING GUIDE
|
||||||
guides for cropping passport photos based on documents from the Finnish police
|
guides for cropping passport photos based on documents from the Finnish police
|
||||||
(https://www.poliisi.fi/instancedata/prime_product_julkaisu/intermin/embeds/poliisiwwwstructure/38462_Passikuvaohje_EN.pdf) describing passport photo dimensions of 47x36 mm and 500x653 px for digital biometric data stored in passports. They use ISO 19794-5 standard based on ICAO 9303 regulations which should also be compliant for all of Europe.
|
(https://poliisi.fi/documents/25235045/31329600/Passport-photograph-instructions-by-the-police-2020-EN-fixed.pdf/1eec2f4c-aed7-68e0-c112-0a8f25e0328d/Passport-photograph-instructions-by-the-police-2020-EN-fixed.pdf) describing passport photo dimensions of 47x36 mm and 500x653 px for digital biometric data stored in passports. They use ISO 19794-5 standard based on ICAO 9303 regulations which should also be compliant for all of Europe.
|
||||||
|
|
||||||
INSTALLATION
|
INSTALLATION
|
||||||
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
|
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
|
||||||
@@ -37,17 +37,28 @@ 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")
|
du.check_min_api_version("2.0.0", "passport_guide")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("passport_guide",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("passport_guide", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("passport guide"),
|
||||||
|
purpose = _("guides for cropping passport photos"),
|
||||||
|
author = "Kåre Hampf",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
dt.guides.register_guide("passport",
|
dt.guides.register_guide("passport",
|
||||||
-- draw
|
-- draw
|
||||||
function(cairo, x, y, width, height, zoom_scale)
|
function(cairo, x, y, width, height, zoom_scale)
|
||||||
@@ -91,5 +102,13 @@ function()
|
|||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on; hl Lua;
|
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on; hl Lua;
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
125
contrib/passport_guide_germany.lua
Normal file
125
contrib/passport_guide_germany.lua
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
--[[
|
||||||
|
German passport photo cropping guide for darktable.
|
||||||
|
Derived from the passport cropping guide by Kåre Hampf.
|
||||||
|
|
||||||
|
copyright (c) 2017 Kåre Hampf
|
||||||
|
copyright (c) 2024 Christian Sültrop
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
PASSPORT CROPPING GUIDE
|
||||||
|
Guides for cropping passport and ID card ("Personalausweis") photos based on the "Passbild-Schablone"
|
||||||
|
from the German Federal Ministry of the Interior and Community.
|
||||||
|
(https://www.bmi.bund.de/SharedDocs/downloads/DE/veroeffentlichungen/themen/moderne-verwaltung/ausweise/passbild-schablone-erwachsene.pdf?__blob=publicationFile&v=3)
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
|
||||||
|
* add the following line in the file $CONFIGDIR/luarc
|
||||||
|
require "passport_guide_germany"
|
||||||
|
* (optional) add the line:
|
||||||
|
"plugins/darkroom/clipping/extra_aspect_ratios/passport 35x45mm=45:35"
|
||||||
|
to $CONFIGDIR/darktablerc
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* when using the cropping tool, select "Passport Photo Germany" as guide and if you added the line in yout rc
|
||||||
|
select "passport 35x45mm" as aspect
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
du.check_min_api_version("2.0.0", "passport_guide_germany")
|
||||||
|
|
||||||
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("passport guide Germany"),
|
||||||
|
purpose = _("guides for cropping passport and ID card (\"Personalausweis\") photos based on the \"Passbild-Schablone\" from the German Federal Ministry of the Interior and Community"),
|
||||||
|
author = "menschmachine",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide_germany"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
dt.guides.register_guide("Passport Photo Germany",
|
||||||
|
-- draw
|
||||||
|
function(cairo, x, y, width, height, zoom_scale)
|
||||||
|
local _width, _height
|
||||||
|
|
||||||
|
-- get the max 35x45 rectangle
|
||||||
|
local aspect_ratio = 45 / 35
|
||||||
|
if width * aspect_ratio > height then
|
||||||
|
_width = height / aspect_ratio
|
||||||
|
_height = height
|
||||||
|
else
|
||||||
|
_width = width
|
||||||
|
_height = width * aspect_ratio
|
||||||
|
end
|
||||||
|
|
||||||
|
cairo:save()
|
||||||
|
|
||||||
|
cairo:translate(x + (width - _width) / 2, y + (height - _height) / 2)
|
||||||
|
cairo:scale(_width / 35, _height / 45)
|
||||||
|
|
||||||
|
-- the outer rectangle
|
||||||
|
cairo:rectangle( 0, 0, 35, 45)
|
||||||
|
|
||||||
|
-- Nose position: The nose tip must be between these lines
|
||||||
|
cairo:draw_line(15.5, 45, 15.5, 13)
|
||||||
|
cairo:draw_line(35-15.5, 45, 35-15.5, 13)
|
||||||
|
|
||||||
|
-- Face height
|
||||||
|
-- optimum face height: The upper end of the head should be between these lines
|
||||||
|
cairo:draw_line(0, 4, 35, 4)
|
||||||
|
cairo:draw_line(0, 8, 35, 8)
|
||||||
|
|
||||||
|
-- tolerated face height: The upper end of the head must not be below this line
|
||||||
|
cairo:draw_line(6, 13, 30, 13)
|
||||||
|
|
||||||
|
-- Eye area: The eyes must be between these lines
|
||||||
|
cairo:draw_line(0, 13, 35, 13)
|
||||||
|
cairo:draw_line(0, 23, 35, 23)
|
||||||
|
|
||||||
|
-- Cheek line: The cheek must lie on this line
|
||||||
|
cairo:draw_line(9, 45-5, 27, 45-5)
|
||||||
|
|
||||||
|
cairo:restore()
|
||||||
|
end,
|
||||||
|
-- gui
|
||||||
|
function()
|
||||||
|
return dt.new_widget("label"){label = _("Passport Photo Germany"), halign = "start"}
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
-- noting to destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
|
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on; hl Lua;
|
||||||
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
@@ -42,21 +42,34 @@ 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 gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("pdf_slideshow",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("pdf_slideshow", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not df.check_if_bin_exists("pdflatex") then
|
if not df.check_if_bin_exists("pdflatex") then
|
||||||
dt.print_error(_("pdflatex not found"))
|
dt.print_error("pdflatex not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
du.check_min_api_version("4.0.0", "pdf_slideshow")
|
du.check_min_api_version("7.0.0", "pdf_slideshow")
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("PDF slideshow"),
|
||||||
|
purpose = _("generates a PDF slideshow (via Latex) containing all selected images one per slide"),
|
||||||
|
author = "Pascal Obry",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
dt.preferences.register
|
dt.preferences.register
|
||||||
("pdf_slideshow","open with","string",
|
("pdf_slideshow","open with","string",
|
||||||
@@ -164,6 +177,10 @@ local function slide(latexfile,i,image,file)
|
|||||||
my_write(latexfile,"\\end{figure}\n")
|
my_write(latexfile,"\\end{figure}\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("pdf_slideshow")
|
||||||
|
end
|
||||||
|
|
||||||
dt.register_storage("pdf_slideshow",_("pdf slideshow"),
|
dt.register_storage("pdf_slideshow",_("pdf slideshow"),
|
||||||
nil,
|
nil,
|
||||||
function(storage,image_table)
|
function(storage,image_table)
|
||||||
@@ -218,7 +235,7 @@ dt.register_storage("pdf_slideshow",_("pdf slideshow"),
|
|||||||
local result = dt.control.execute(command)
|
local result = dt.control.execute(command)
|
||||||
if result ~= 0 then
|
if result ~= 0 then
|
||||||
dt.print(_("problem running pdflatex")) -- this one is probably usefull to the user
|
dt.print(_("problem running pdflatex")) -- this one is probably usefull to the user
|
||||||
error(_("problem running ")..command)
|
error("problem running "..command)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- open the PDF
|
-- open the PDF
|
||||||
@@ -228,7 +245,7 @@ dt.register_storage("pdf_slideshow",_("pdf slideshow"),
|
|||||||
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
|
||||||
|
|
||||||
-- finally do some clean-up
|
-- finally do some clean-up
|
||||||
@@ -243,5 +260,8 @@ dt.register_storage("pdf_slideshow",_("pdf slideshow"),
|
|||||||
nil,
|
nil,
|
||||||
widget)
|
widget)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
--
|
--
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -39,17 +39,35 @@
|
|||||||
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 dtsys = require "lib/dtutils.system"
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
|
||||||
local MODULE_NAME = "photils"
|
local MODULE_NAME = "photils"
|
||||||
du.check_min_api_version("5.0.0", MODULE_NAME)
|
du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("photils"),
|
||||||
|
purpose = _("suggest tags based on image classification"),
|
||||||
|
author = "Tobias Scheck",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
local gettext = dt.gettext
|
|
||||||
gettext.bindtextdomain(MODULE_NAME,
|
|
||||||
dt.configuration.config_dir .. PS .. "lua" .. PS .. "locale" .. PS)
|
|
||||||
|
|
||||||
local exporter = dt.new_format("jpeg")
|
local exporter = dt.new_format("jpeg")
|
||||||
exporter.quality = 80
|
exporter.quality = 80
|
||||||
@@ -58,10 +76,6 @@ exporter.max_width = 224
|
|||||||
|
|
||||||
-- helper functions
|
-- helper functions
|
||||||
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function num_keys(tbl)
|
local function num_keys(tbl)
|
||||||
local num = 0
|
local num = 0
|
||||||
for _ in pairs(tbl) do num = num + 1 end
|
for _ in pairs(tbl) do num = num + 1 end
|
||||||
@@ -170,7 +184,7 @@ function PHOTILS.image_changed()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PHOTILS.tagged_image_has_changed()
|
function PHOTILS.tagged_image_has_changed()
|
||||||
GUI.warning.label = _("The suggested tags were not generated\n for the currently selected image!")
|
GUI.warning.label = _("the suggested tags were not generated\n for the currently selected image!")
|
||||||
end
|
end
|
||||||
|
|
||||||
function PHOTILS.paginate()
|
function PHOTILS.paginate()
|
||||||
@@ -227,7 +241,7 @@ end
|
|||||||
|
|
||||||
function PHOTILS.attach_tags()
|
function PHOTILS.attach_tags()
|
||||||
local num_selected = #dt.gui.selection()
|
local num_selected = #dt.gui.selection()
|
||||||
local job = dt.gui.create_job(_("Apply tag to image"), true)
|
local job = dt.gui.create_job(_("apply tag to image"), true)
|
||||||
|
|
||||||
for i = 1, num_selected, 1 do
|
for i = 1, num_selected, 1 do
|
||||||
local image = dt.gui.selection()[i]
|
local image = dt.gui.selection()[i]
|
||||||
@@ -239,7 +253,7 @@ function PHOTILS.attach_tags()
|
|||||||
job.percent = i / num_selected
|
job.percent = i / num_selected
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.print(_("Tags successfully attached to image"))
|
dt.print(_("tags successfully attached to image"))
|
||||||
job.valid = false
|
job.valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -250,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()
|
||||||
@@ -305,11 +315,11 @@ function PHOTILS.on_tags_clicked()
|
|||||||
local images = dt.gui.selection()
|
local images = dt.gui.selection()
|
||||||
|
|
||||||
if #images == 0 then
|
if #images == 0 then
|
||||||
dt.print(_("No image selected."))
|
dt.print(_("no image selected."))
|
||||||
dt.control.sleep(2000)
|
dt.control.sleep(2000)
|
||||||
else
|
else
|
||||||
if #images > 1 then
|
if #images > 1 then
|
||||||
dt.print(_("This plugin can only handle a single image."))
|
dt.print(_("this plugin can only handle a single image."))
|
||||||
dt.gui.selection({images[1]})
|
dt.gui.selection({images[1]})
|
||||||
dt.control.sleep(2000)
|
dt.control.sleep(2000)
|
||||||
end
|
end
|
||||||
@@ -324,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
|
||||||
@@ -379,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,
|
||||||
@@ -391,6 +401,22 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = false
|
||||||
|
dt.destroy_event("photils", "mouse-over-image-changed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
dt.register_event("photils", "mouse-over-image-changed",
|
||||||
|
PHOTILS.image_changed)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- add a fix number of buttons
|
-- add a fix number of buttons
|
||||||
for _ = 1, PHOTILS.per_page, 1 do
|
for _ = 1, PHOTILS.per_page, 1 do
|
||||||
local btn_tag = dt.new_widget("check_button") {
|
local btn_tag = dt.new_widget("check_button") {
|
||||||
@@ -404,9 +430,9 @@ end
|
|||||||
|
|
||||||
if not photils_installed then
|
if not photils_installed then
|
||||||
GUI.warning_label.label = _("photils-cli not found")
|
GUI.warning_label.label = _("photils-cli not found")
|
||||||
dt.print_log(_("photils-cli not found"))
|
dt.print_log("photils-cli not found")
|
||||||
else
|
else
|
||||||
GUI.warning_label.label = _("Select an image, click \"get tags\" and get \nsuggestions for tags.")
|
GUI.warning_label.label = _("select an image, click \"get tags\" and get \nsuggestions for tags.")
|
||||||
end
|
end
|
||||||
|
|
||||||
GUI.pagination = dt.new_widget("box") {
|
GUI.pagination = dt.new_widget("box") {
|
||||||
@@ -449,12 +475,12 @@ dt.preferences.register(MODULE_NAME,
|
|||||||
"export_image_before_for_tags",
|
"export_image_before_for_tags",
|
||||||
"bool",
|
"bool",
|
||||||
_("photils: use exported image for tag request"),
|
_("photils: use exported image for tag request"),
|
||||||
_("If enabled, the image passed to photils for tag suggestion is based on the exported, already edited image. " ..
|
_("if enabled, the image passed to photils for tag suggestion is based on the exported, already edited image. " ..
|
||||||
"Otherwise, the embedded thumbnail of the RAW file will be used for tag suggestion." ..
|
"otherwise, the embedded thumbnail of the RAW file will be used for tag suggestion." ..
|
||||||
"The embedded thumbnail could speedup the tag suggestion but can fail if the RAW file is not supported."),
|
"the embedded thumbnail could speedup the tag suggestion but can fail if the RAW file is not supported."),
|
||||||
true)
|
true)
|
||||||
|
|
||||||
dt.register_event("mouse-over-image-changed",
|
dt.register_event("photils", "mouse-over-image-changed",
|
||||||
PHOTILS.image_changed)
|
PHOTILS.image_changed)
|
||||||
|
|
||||||
if dt.gui.current_view().id == "lighttable" then
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
@@ -462,7 +488,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not PHOTILS.event_registered then
|
if not PHOTILS.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"photils", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -473,3 +499,9 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -47,25 +47,34 @@ local dt = require "darktable"
|
|||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
local debug = require "darktable.debug"
|
local debug = require "darktable.debug"
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "quicktag")
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("quick tag"),
|
||||||
|
purpose = _("use buttons to quickly apply tags assigned to them"),
|
||||||
|
author = "Christian Kanzian",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
|
||||||
local qt = {}
|
local qt = {}
|
||||||
qt.module_installed = false
|
qt.module_installed = false
|
||||||
qt.event_registered = false
|
qt.event_registered = false
|
||||||
qt.widget_table = {}
|
qt.widget_table = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local gettext = dt.gettext
|
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "quicktag")
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext("quicktag", msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- maximum length of button labels
|
-- maximum length of button labels
|
||||||
dt.preferences.register("quickTag",
|
dt.preferences.register("quickTag",
|
||||||
"labellength",
|
"labellength",
|
||||||
@@ -163,7 +172,7 @@ abbrevate_tags(quicktag_table)
|
|||||||
local button = {}
|
local button = {}
|
||||||
|
|
||||||
-- function to create buttons with tags as labels
|
-- function to create buttons with tags as labels
|
||||||
for j=1,qnr do
|
for j = 1, qnr do
|
||||||
button[#button+1] = dt.new_widget("button") {
|
button[#button+1] = dt.new_widget("button") {
|
||||||
label = j..": "..quicktag_table[j],
|
label = j..": "..quicktag_table[j],
|
||||||
clicked_callback = function() tagattach(tostring(quicktag_table[j]),j) end}
|
clicked_callback = function() tagattach(tostring(quicktag_table[j]),j) end}
|
||||||
@@ -188,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}},
|
||||||
@@ -205,6 +214,29 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["quicktag"].visible = false
|
||||||
|
for i=1,qnr do
|
||||||
|
dt.destroy_event("quicktag " .. tostring(i), "shortcut")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["quicktag"].visible = true
|
||||||
|
for i = 1 ,qnr do
|
||||||
|
dt.register_event("quicktag", "shortcut",
|
||||||
|
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
|
||||||
|
string.format(_("quicktag %i"),i))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs["quicktag"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
update_quicktag_list()
|
update_quicktag_list()
|
||||||
|
|
||||||
local new_quicktag = dt.new_widget("entry"){
|
local new_quicktag = dt.new_widget("entry"){
|
||||||
@@ -257,7 +289,7 @@ if dt.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not qt.event_registered then
|
if not qt.event_registered then
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed",
|
"quicktag", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -270,10 +302,17 @@ end
|
|||||||
|
|
||||||
-- create shortcuts
|
-- create shortcuts
|
||||||
for i=1,qnr do
|
for i=1,qnr do
|
||||||
dt.register_event("shortcut",
|
dt.register_event("quicktag " .. tostring(i), "shortcut",
|
||||||
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
|
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.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;
|
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;
|
||||||
|
|||||||
@@ -42,8 +42,29 @@ local dt = require "darktable"
|
|||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
-- added version check
|
-- added version check
|
||||||
du.check_min_api_version("3.0.0", "rate_group")
|
du.check_min_api_version("7.0.0", "rate_group")
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("rate group"),
|
||||||
|
purpose = _("rate all images in a group"),
|
||||||
|
author = "Dom H (dom@hxy.io)",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function apply_rating(rating)
|
local function apply_rating(rating)
|
||||||
local images = dt.gui.action_images
|
local images = dt.gui.action_images
|
||||||
@@ -54,43 +75,58 @@ local function apply_rating(rating)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if rating < 0 then
|
if rating < 0 then
|
||||||
dt.print("rejecting group(s)")
|
dt.print(_("rejecting group(s)"))
|
||||||
else
|
else
|
||||||
dt.print("applying rating " ..rating.. " to group(s)")
|
dt.print(string.format(_("applying rating %d to group(s)"), rating))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
local function destroy()
|
||||||
|
dt.destroy_event("rg_reject", "shortcut")
|
||||||
|
dt.destroy_event("rg0", "shortcut")
|
||||||
|
dt.destroy_event("rg1", "shortcut")
|
||||||
|
dt.destroy_event("rg2", "shortcut")
|
||||||
|
dt.destroy_event("rg3", "shortcut")
|
||||||
|
dt.destroy_event("rg4", "shortcut")
|
||||||
|
dt.destroy_event("rg5", "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event("rg_reject", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(-1)
|
apply_rating(-1)
|
||||||
end, "Reject group")
|
end, _("reject group"))
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("rg0", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(0)
|
apply_rating(0)
|
||||||
end, "Rate group 0")
|
end, string.format(_("rate group %d"), 0)
|
||||||
|
)
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("rg1", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(1)
|
apply_rating(1)
|
||||||
end, "Rate group 1")
|
end, string.format(_("rate group %d"), 1))
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("rg2", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(2)
|
apply_rating(2)
|
||||||
end, "Rate group 2")
|
end, string.format(_("rate group %d"), 2))
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("rg3", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(3)
|
apply_rating(3)
|
||||||
end, "Rate group 3")
|
end, string.format(_("rate group %d"), 3))
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("rg4", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(4)
|
apply_rating(4)
|
||||||
end, "Rate group 4")
|
end, string.format(_("rate group %d"), 4))
|
||||||
|
|
||||||
dt.register_event("shortcut",
|
dt.register_event("rg5", "shortcut",
|
||||||
function(event, shortcut)
|
function(event, shortcut)
|
||||||
apply_rating(5)
|
apply_rating(5)
|
||||||
end, "Rate group 5")
|
end, string.format(_("rate group %d"), 5))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -33,16 +33,38 @@ local du = require "lib/dtutils"
|
|||||||
local debug = require "darktable.debug"
|
local debug = require "darktable.debug"
|
||||||
|
|
||||||
-- check API version
|
-- check API version
|
||||||
du.check_min_api_version("3.0.0", "rename-tags")
|
du.check_min_api_version("7.0.0", "rename-tags")
|
||||||
local CURR_API_STRING = darktable.configuration.api_version_string
|
du.deprecated("contrib/rename-tags.lua","darktable release 4.0")
|
||||||
|
|
||||||
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("rename tags"),
|
||||||
|
purpose = _("rename an existing tag"),
|
||||||
|
author = "Sebastian Witt (se.witt@gmx.net)",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local rt = {}
|
local rt = {}
|
||||||
rt.module_installed = false
|
rt.module_installed = false
|
||||||
rt.event_registered = false
|
rt.event_registered = false
|
||||||
|
|
||||||
-- GUI entries
|
-- GUI entries
|
||||||
local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" }
|
local old_tag = darktable.new_widget("entry") { tooltip = _("enter old tag") }
|
||||||
local new_tag = darktable.new_widget("entry") { tooltip = "Enter new tag" }
|
local new_tag = darktable.new_widget("entry") { tooltip = _("enter new tag") }
|
||||||
|
|
||||||
local function rename_reset()
|
local function rename_reset()
|
||||||
old_tag.text = ''
|
old_tag.text = ''
|
||||||
@@ -53,11 +75,11 @@ end
|
|||||||
local function rename_tags()
|
local function rename_tags()
|
||||||
-- If entries are empty, return
|
-- If entries are empty, return
|
||||||
if old_tag.text == '' then
|
if old_tag.text == '' then
|
||||||
darktable.print ("Old tag can't be empty")
|
darktable.print (_("old tag can't be empty"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if new_tag.text == '' then
|
if new_tag.text == '' then
|
||||||
darktable.print ("New tag can't be empty")
|
darktable.print (_("new tag can't be empty"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,12 +89,12 @@ local function rename_tags()
|
|||||||
local ot = darktable.tags.find (old_tag.text)
|
local ot = darktable.tags.find (old_tag.text)
|
||||||
|
|
||||||
if not ot then
|
if not ot then
|
||||||
darktable.print ("Old tag does not exist")
|
darktable.print (_("old tag does not exist"))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Show job
|
-- Show job
|
||||||
local job = darktable.gui.create_job ("Renaming tag", true)
|
local job = darktable.gui.create_job (_("renaming tag"), true)
|
||||||
|
|
||||||
old_tag.editable = false
|
old_tag.editable = false
|
||||||
new_tag.editable = false
|
new_tag.editable = false
|
||||||
@@ -95,7 +117,7 @@ local function rename_tags()
|
|||||||
darktable.tags.delete (ot)
|
darktable.tags.delete (ot)
|
||||||
|
|
||||||
job.valid = false
|
job.valid = false
|
||||||
darktable.print ("Renamed tags for " .. count .. " images")
|
darktable.print (string.format(_("renamed tags for %d images"), count))
|
||||||
old_tag.editable = true
|
old_tag.editable = true
|
||||||
new_tag.editable = true
|
new_tag.editable = true
|
||||||
|
|
||||||
@@ -106,21 +128,29 @@ end
|
|||||||
|
|
||||||
local function install_module()
|
local function install_module()
|
||||||
if not rt.module_installed then
|
if not rt.module_installed then
|
||||||
darktable.register_lib ("rename_tags", "rename tag", true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rt.rename_widget, nil, nil)
|
darktable.register_lib ("rename_tags", _("rename tag"), true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rt.rename_widget, nil, nil)
|
||||||
rt.module_installed = true
|
rt.module_installed = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
darktable.gui.libs["rename_tags"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
darktable.gui.libs["rename_tags"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- GUI
|
-- GUI
|
||||||
local old_widget = darktable.new_widget ("box") {
|
local old_widget = darktable.new_widget ("box") {
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
darktable.new_widget("label") { label = "Old tag" },
|
darktable.new_widget("label") { label = _("old tag") },
|
||||||
old_tag
|
old_tag
|
||||||
}
|
}
|
||||||
|
|
||||||
local new_widget = darktable.new_widget ("box") {
|
local new_widget = darktable.new_widget ("box") {
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
darktable.new_widget("label") { label = "New tag" },
|
darktable.new_widget("label") { label = _("new tag") },
|
||||||
new_tag
|
new_tag
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +159,7 @@ rt.rename_widget = darktable.new_widget ("box") {
|
|||||||
reset_callback = rename_reset,
|
reset_callback = rename_reset,
|
||||||
old_widget,
|
old_widget,
|
||||||
new_widget,
|
new_widget,
|
||||||
darktable.new_widget("button") { label = "Go", clicked_callback = rename_tags }
|
darktable.new_widget("button") { label = _("go"), clicked_callback = rename_tags }
|
||||||
}
|
}
|
||||||
|
|
||||||
if darktable.gui.current_view().id == "lighttable" then
|
if darktable.gui.current_view().id == "lighttable" then
|
||||||
@@ -137,7 +167,7 @@ if darktable.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not rt.event_registered then
|
if not rt.event_registered then
|
||||||
darktable.register_event(
|
darktable.register_event(
|
||||||
"view-changed",
|
"rename_tags", "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -148,4 +178,11 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
236
contrib/rename_images.lua
Normal file
236
contrib/rename_images.lua
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
rename.lua - rename image file(s)
|
||||||
|
|
||||||
|
Copyright (C) 2020, 2021 Bill Ferguson <wpferguson@gmail.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
rename - rename an image file or files
|
||||||
|
|
||||||
|
This shortcut resets the GPS information to that contained within
|
||||||
|
the image file. If no GPS info is in the image file, the GPS data
|
||||||
|
is cleared.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* require this script from your luarc file or start it from script_manager
|
||||||
|
* select an image or images
|
||||||
|
* enter a renaming pattern
|
||||||
|
* click the button to rename the files
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
* Send to Bill Ferguson, wpferguson@gmail.com
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
|
||||||
|
TODO
|
||||||
|
* Add pattern builder
|
||||||
|
* Add new name preview
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local ds = require "lib/dtutils.string"
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "rename_images")
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- namespace variable
|
||||||
|
local rename = {
|
||||||
|
presets = {},
|
||||||
|
widgets = {},
|
||||||
|
}
|
||||||
|
rename.module_installed = false
|
||||||
|
rename.event_registered = false
|
||||||
|
|
||||||
|
-- script_manager integration
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("rename images"),
|
||||||
|
purpose = _("rename an image file or files"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- C O N S T A N T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local MODULE_NAME = "rename_images"
|
||||||
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
local USER = os.getenv("USERNAME")
|
||||||
|
local HOME = os.getenv(dt.configuration.running_os == "windows" and "HOMEPATH" or "HOME")
|
||||||
|
local PICTURES = HOME .. PS .. dt.configuration.running_os == "windows" and _("My Pictures") or _("Pictures")
|
||||||
|
local DESKTOP = HOME .. PS .. "Desktop"
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- F U N C T I O N S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function stop_job(job)
|
||||||
|
job.valid = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not rename.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE_NAME,
|
||||||
|
_("rename images"),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",700}},
|
||||||
|
dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
rename.widgets.pattern,
|
||||||
|
rename.widgets.button,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
rename.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- M A I N
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
local function do_rename(images)
|
||||||
|
if #images > 0 then
|
||||||
|
local first_image = images[1]
|
||||||
|
local pattern = rename.widgets.pattern.text
|
||||||
|
dt.preferences.write(MODULE_NAME, "pattern", "string", pattern)
|
||||||
|
dt.print_log("pattern is " .. pattern)
|
||||||
|
if string.len(pattern) > 0 then
|
||||||
|
local datetime = os.date("*t")
|
||||||
|
|
||||||
|
local job = dt.gui.create_job(_("renaming images"), true, stop_job)
|
||||||
|
for i, image in ipairs(images) do
|
||||||
|
if job.valid then
|
||||||
|
job.percent = i / #images
|
||||||
|
ds.build_substitute_list(image, i, pattern, USER, PICTURES, HOME, DESKTOP)
|
||||||
|
local new_name = ds.substitute_list(pattern)
|
||||||
|
if new_name == -1 then
|
||||||
|
dt.print(_("unable to do variable substitution, exiting..."))
|
||||||
|
stop_job(job)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
ds.clear_substitute_list()
|
||||||
|
local args = {}
|
||||||
|
local path = string.sub(df.get_path(new_name), 1, -2)
|
||||||
|
if string.len(path) == 0 then
|
||||||
|
path = image.path
|
||||||
|
end
|
||||||
|
local filename = df.get_filename(new_name)
|
||||||
|
local filmname = image.path
|
||||||
|
if path ~= image.path then
|
||||||
|
if not df.check_if_file_exists(df.sanitize_filename(path)) then
|
||||||
|
df.mkdir(df.sanitize_filename(path))
|
||||||
|
end
|
||||||
|
filmname = path
|
||||||
|
end
|
||||||
|
args[1] = dt.films.new(filmname)
|
||||||
|
args[2] = image
|
||||||
|
if filename ~= image.filename then
|
||||||
|
args[3] = filename
|
||||||
|
end
|
||||||
|
dt.database.move_image(table.unpack(args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stop_job(job)
|
||||||
|
local collect_rules = dt.gui.libs.collect.filter()
|
||||||
|
dt.gui.libs.collect.filter(collect_rules)
|
||||||
|
dt.gui.views.lighttable.set_image_visible(first_image)
|
||||||
|
dt.print(string.format(_("renamed %d images"), #images))
|
||||||
|
else -- pattern length
|
||||||
|
dt.print_error("no pattern supplied, returning...")
|
||||||
|
dt.print(_("please enter the new name or pattern"))
|
||||||
|
end
|
||||||
|
else -- image count
|
||||||
|
dt.print_error("no images selected, returning...")
|
||||||
|
dt.print(_("please select some images and try again"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
-- W I D G E T S
|
||||||
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
rename.widgets.pattern = dt.new_widget("entry"){
|
||||||
|
tooltip = ds.get_substitution_tooltip(),
|
||||||
|
placeholder = _("enter pattern") .. "$(FILE_FOLDER)/$(FILE_NAME)",
|
||||||
|
text = "",
|
||||||
|
reset_callback = function(self)
|
||||||
|
self.text = ""
|
||||||
|
dt.preferences.write(MODULE_NAME, "pattern", "string", self.text)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
local pattern_pref = dt.preferences.read(MODULE_NAME, "pattern", "string")
|
||||||
|
if pattern_pref then
|
||||||
|
rename.widgets.pattern.text = pattern_pref
|
||||||
|
end
|
||||||
|
|
||||||
|
rename.widgets.button = dt.new_widget("button"){
|
||||||
|
label = _("rename"),
|
||||||
|
clicked_callback = function(this)
|
||||||
|
do_rename(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not rename.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"rename_images", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
rename.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
124
contrib/select_non_existing.lua
Normal file
124
contrib/select_non_existing.lua
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
--[[
|
||||||
|
This file is part of darktable,
|
||||||
|
copyright (c) 2023 Dirk Dittmar
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
Enable selection of non-existing images in the the currently worked on images, e.g. the ones selected by the collection module.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local ds = require "lib/dtutils.string"
|
||||||
|
local log = require "lib/dtutils.log"
|
||||||
|
|
||||||
|
-- module name
|
||||||
|
local MODULE <const> = "select_non_existing"
|
||||||
|
|
||||||
|
du.check_min_api_version("9.1.0", MODULE)
|
||||||
|
|
||||||
|
-- figure out the path separator
|
||||||
|
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
local DEFAULT_LOG_LEVEL <const> = log.info
|
||||||
|
|
||||||
|
log.log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stop_job(job)
|
||||||
|
job.valid = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_file_list(dir)
|
||||||
|
log.log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
local file_list = {}
|
||||||
|
local cmd = nil
|
||||||
|
log.msg(log.debug, "called get_file_list with " .. dir)
|
||||||
|
|
||||||
|
if dt.configuration.running_os == "windows" then
|
||||||
|
cmd = "forfiles /P " .. ds.sanitize(dir) .. " /M * /C \"cmd /c echo @file\""
|
||||||
|
else
|
||||||
|
cmd = "cd " .. ds.sanitize(dir) ..";ls -1"
|
||||||
|
end
|
||||||
|
log.msg(log.info, "cmd is " .. cmd)
|
||||||
|
|
||||||
|
local p = io.popen(cmd)
|
||||||
|
if p then
|
||||||
|
for line in p:lines() do
|
||||||
|
if line:len() > 4 then
|
||||||
|
if not string.match(line, "xmp$") and not string.match(line, "XMP$") then
|
||||||
|
line = line:gsub("\"", "")
|
||||||
|
file_list[line] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
p:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
return file_list
|
||||||
|
end
|
||||||
|
|
||||||
|
local function select_nonexisting_images(event, images)
|
||||||
|
log.log_level(DEFAULT_LOG_LEVEL)
|
||||||
|
local selection = {}
|
||||||
|
local known_files = {}
|
||||||
|
|
||||||
|
local job = dt.gui.create_job(_("select non existing images"), true, stop_job)
|
||||||
|
for key,image in ipairs(images) do
|
||||||
|
if(job.valid) then
|
||||||
|
if not known_files[image.path] then
|
||||||
|
known_files[image.path] = get_file_list(image.path)
|
||||||
|
end
|
||||||
|
if key % 10 == 0 then
|
||||||
|
job.percent = key/#images
|
||||||
|
end
|
||||||
|
local file_exists = known_files[image.path][image.filename]
|
||||||
|
log.msg(log.debug, image.path .. PS .. image.filename .." exists? => "..tostring(file_exists))
|
||||||
|
if (not file_exists) then
|
||||||
|
table.insert(selection, image)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stop_job(job)
|
||||||
|
|
||||||
|
return selection
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs.select.destroy_selection(MODULE)
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.gui.libs.select.register_selection(
|
||||||
|
MODULE,
|
||||||
|
_("select non existing"),
|
||||||
|
select_nonexisting_images,
|
||||||
|
_("select all non-existing images in the current images"))
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("select non existing"),
|
||||||
|
purpose = _("enable selection of non-existing images"),
|
||||||
|
author = "Dirk Dittmar",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
return script_data
|
||||||
@@ -20,18 +20,30 @@ Enable selection of untagged images (darktable|* tags are ignored)
|
|||||||
|
|
||||||
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("3.0.0", "select_untagged")
|
du.check_min_api_version("7.0.0", "select_untagged")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("select_untagged",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("select_untagged", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("select untagged"),
|
||||||
|
purpose = _("enable selection of untagged images"),
|
||||||
|
author = "Jannis_V",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function stop_job(job)
|
local function stop_job(job)
|
||||||
job.valid = false
|
job.valid = false
|
||||||
end
|
end
|
||||||
@@ -64,7 +76,15 @@ local function select_untagged_images(event, images)
|
|||||||
return selection
|
return selection
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs.select.destroy_selection("select_untagged")
|
||||||
|
end
|
||||||
|
|
||||||
dt.gui.libs.select.register_selection(
|
dt.gui.libs.select.register_selection(
|
||||||
_("select untagged"),
|
"select_untagged", _("select untagged"),
|
||||||
select_untagged_images,
|
select_untagged_images,
|
||||||
_("select all images containing no tags or only tags added by darktable"))
|
_("select all images containing no tags or only tags added by darktable"))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
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
|
||||||
@@ -27,20 +27,30 @@ USAGE
|
|||||||
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 gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local gettext = dt.gettext
|
du.check_min_api_version("7.0.0", "slideshowMusic")
|
||||||
|
|
||||||
du.check_min_api_version("2.0.2", "slideshowMusic")
|
|
||||||
|
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("slideshowMusic", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("slideshow music"),
|
||||||
|
purpose = _("play music during a slideshow"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local function playSlideshowMusic(_, old_view, new_view)
|
local function playSlideshowMusic(_, old_view, new_view)
|
||||||
local filename, playMusic
|
local filename, playMusic
|
||||||
|
|
||||||
@@ -48,7 +58,7 @@ local function playSlideshowMusic(_, old_view, new_view)
|
|||||||
playMusic = dt.preferences.read("slideshowMusic","PlaySlideshowMusic","bool")
|
playMusic = dt.preferences.read("slideshowMusic","PlaySlideshowMusic","bool")
|
||||||
|
|
||||||
if not df.check_if_bin_exists("rhythmbox-client") then
|
if not df.check_if_bin_exists("rhythmbox-client") then
|
||||||
dt.print_error(_("rhythmbox-client not found"))
|
dt.print_error("rhythmbox-client not found")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -64,20 +74,30 @@ local function playSlideshowMusic(_, old_view, new_view)
|
|||||||
if (old_view and old_view.id == "slideshow") then
|
if (old_view and old_view.id == "slideshow") then
|
||||||
stopCommand = "rhythmbox-client --pause"
|
stopCommand = "rhythmbox-client --pause"
|
||||||
--dt.print_error(stopCommand)
|
--dt.print_error(stopCommand)
|
||||||
dt.control.execute( stopCommand)
|
dt.control.execute(stopCommand)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function destroy()
|
||||||
|
dt.destroy_event("slideshow_music", "view-changed")
|
||||||
|
dt.preferences.destroy("slideshowMusic", "SlideshowMusic")
|
||||||
|
dt.preferences.destroy("slideshowMusic", "PlaySlideshowMusic")
|
||||||
|
end
|
||||||
|
|
||||||
-- Preferences
|
-- Preferences
|
||||||
dt.preferences.register("slideshowMusic", "SlideshowMusic", "file", _("Slideshow background music file"), "", "")
|
dt.preferences.register("slideshowMusic", "SlideshowMusic", "file", _("slideshow background music file"), "", "")
|
||||||
dt.preferences.register("slideshowMusic",
|
dt.preferences.register("slideshowMusic",
|
||||||
"PlaySlideshowMusic",
|
"PlaySlideshowMusic",
|
||||||
"bool",
|
"bool",
|
||||||
_("Play slideshow background music"),
|
_("play slideshow background music"),
|
||||||
_("Plays music with rhythmbox if a slideshow starts"),
|
_("plays music with rhythmbox if a slideshow starts"),
|
||||||
true)
|
true)
|
||||||
-- Register
|
-- Register
|
||||||
dt.register_event("view-changed",
|
dt.register_event("slideshow_music", "view-changed",
|
||||||
playSlideshowMusic)
|
playSlideshowMusic)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
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,24 +76,38 @@ 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("5.0.0", LIB_ID)
|
dtutils.check_min_api_version("7.0.0", LIB_ID)
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("transfer hierarchy"),
|
||||||
|
purpose = _("allows the moving or copying of images from one directory tree to another, while preserving the existing hierarchy"),
|
||||||
|
author = "August Schwerdfeger (august@schwerdfeger.name)",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local MKDIR_COMMAND = darktable.configuration.running_os == "windows" and "mkdir " or "mkdir -p "
|
local MKDIR_COMMAND = darktable.configuration.running_os == "windows" and "mkdir " or "mkdir -p "
|
||||||
local PATH_SEPARATOR = darktable.configuration.running_os == "windows" and "\\\\" or "/"
|
local PATH_SEPARATOR = darktable.configuration.running_os == "windows" and "\\\\" or "/"
|
||||||
local PATH_SEGMENT_REGEX = "(" .. PATH_SEPARATOR .. "?)([^" .. PATH_SEPARATOR .. "]+)"
|
local PATH_SEGMENT_REGEX = "(" .. PATH_SEPARATOR .. "?)([^" .. PATH_SEPARATOR .. "]+)"
|
||||||
local CURR_API_STRING = darktable.configuration.api_version_string
|
|
||||||
|
|
||||||
unpack = unpack or table.unpack
|
unpack = unpack or table.unpack
|
||||||
gmatch = string.gfind or string.gmatch
|
gmatch = string.gfind or string.gmatch
|
||||||
|
|
||||||
darktable.gettext.bindtextdomain(LIB_ID, darktable.configuration.config_dir .. PATH_SEPARATOR .. "lua" .. PATH_SEPARATOR .. "locale" .. PATH_SEPARATOR)
|
|
||||||
|
|
||||||
local function _(msgid)
|
|
||||||
return darktable.gettext.dgettext(LIB_ID, msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Header material: END
|
-- Header material: END
|
||||||
|
|
||||||
|
|
||||||
@@ -130,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
|
||||||
@@ -143,7 +157,7 @@ end
|
|||||||
-- Widgets and business logic: BEGIN
|
-- Widgets and business logic: BEGIN
|
||||||
|
|
||||||
local sourceTextBox = darktable.new_widget("entry") {
|
local sourceTextBox = darktable.new_widget("entry") {
|
||||||
tooltip = _("Lowest directory containing all selected images"),
|
tooltip = _("lowest directory containing all selected images"),
|
||||||
editable = false
|
editable = false
|
||||||
}
|
}
|
||||||
sourceTextBox.reset_callback = function() sourceTextBox.text = "" end
|
sourceTextBox.reset_callback = function() sourceTextBox.text = "" end
|
||||||
@@ -256,21 +270,21 @@ 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
|
||||||
darktable.print(_("transfer hierarchy: ERROR: could not create directory: " .. films[film]))
|
darktable.print(string.format(_("transfer hierarchy: ERROR: could not create directory: %s"), films[film]))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not pathIsDirectory(films[film]) then
|
if not pathIsDirectory(films[film]) then
|
||||||
darktable.print(_("transfer hierarchy: ERROR: not a directory: " .. films[film]))
|
darktable.print(string.format(_("transfer hierarchy: ERROR: not a directory: %s"), films[film]))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
destFilms[film] = darktable.films.new(films[film])
|
destFilms[film] = darktable.films.new(films[film])
|
||||||
if destFilms[film] == nil then
|
if destFilms[film] == nil then
|
||||||
darktable.print(_("transfer hierarchy: ERROR: could not create film: " .. film.path))
|
darktable.print(string.format(_("transfer hierarchy: ERROR: could not create film: %s"), film.path))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -279,7 +293,7 @@ local function doTransfer(transferFunc)
|
|||||||
srcFilms[img] = img.film
|
srcFilms[img] = img.film
|
||||||
end
|
end
|
||||||
|
|
||||||
local job = darktable.gui.create_job(string.format(_("transfer hierarchy") .. " (%d image" .. (#(darktable.gui.action_images) == 1 and "" or "s") .. ")", #(darktable.gui.action_images)), true, stopTransfer)
|
local job = darktable.gui.create_job(string.format(_("transfer hierarchy (%d image%s)"), #(darktable.gui.action_images), (#(darktable.gui.action_images) == 1 and "" or "s")), true, stopTransfer)
|
||||||
job.percent = 0.0
|
job.percent = 0.0
|
||||||
local pctIncrement = 1.0 / #(darktable.gui.action_images)
|
local pctIncrement = 1.0 / #(darktable.gui.action_images)
|
||||||
for _,img in ipairs(darktable.gui.action_images) do
|
for _,img in ipairs(darktable.gui.action_images) do
|
||||||
@@ -302,6 +316,13 @@ local function doCopy()
|
|||||||
doTransfer(darktable.database.copy_image)
|
doTransfer(darktable.database.copy_image)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
darktable.gui.libs[LIB_ID].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
darktable.gui.libs[LIB_ID].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -330,7 +351,7 @@ th.transfer_widget = darktable.new_widget("box") {
|
|||||||
},
|
},
|
||||||
darktable.new_widget("button") {
|
darktable.new_widget("button") {
|
||||||
label = _("copy"),
|
label = _("copy"),
|
||||||
tooltip = _("Copy all selected images"),
|
tooltip = _("copy all selected images"),
|
||||||
clicked_callback = doCopy
|
clicked_callback = doCopy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,7 +388,7 @@ if darktable.gui.current_view().id == "lighttable" then
|
|||||||
else
|
else
|
||||||
if not th.event_registered then
|
if not th.event_registered then
|
||||||
darktable.register_event(
|
darktable.register_event(
|
||||||
"view-changed",
|
LIB_ID, "view-changed",
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
install_module()
|
install_module()
|
||||||
@@ -377,3 +398,11 @@ else
|
|||||||
th.event_registered = true
|
th.event_registered = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
1066
contrib/ultrahdr.lua
Normal file
1066
contrib/ultrahdr.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -37,19 +37,33 @@ 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 dsys = require "lib/dtutils.system"
|
local dsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("5.0.0")
|
du.check_min_api_version("7.0.0", "video_ffmpeg")
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("video ffmpeg"),
|
||||||
|
purpose = _("timelapse video plugin based on ffmpeg"),
|
||||||
|
author = "Dominik Markiewicz",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contib/video_ffmpeg"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
local MODULE_NAME = "video_ffmpeg"
|
local MODULE_NAME = "video_ffmpeg"
|
||||||
|
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---- DECLARATIONS
|
---- DECLARATIONS
|
||||||
|
|
||||||
@@ -232,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)
|
||||||
@@ -277,7 +291,7 @@ local defaultVideoDir = ''
|
|||||||
if dt.configuration.running_os == "windows" then
|
if dt.configuration.running_os == "windows" then
|
||||||
defaultVideoDir = os.getenv("USERPROFILE")..PS .."videos"
|
defaultVideoDir = os.getenv("USERPROFILE")..PS .."videos"
|
||||||
elseif dt.configuration.running_os == "macos" then
|
elseif dt.configuration.running_os == "macos" then
|
||||||
defaultVideoDir = os.getenv("home")..PS.."Videos"
|
defaultVideoDir = os.getenv("HOME")..PS.."Videos"
|
||||||
else
|
else
|
||||||
local handle = io.popen("xdg-user-dir VIDEOS")
|
local handle = io.popen("xdg-user-dir VIDEOS")
|
||||||
defaultVideoDir = handle:read()
|
defaultVideoDir = handle:read()
|
||||||
@@ -285,7 +299,7 @@ else
|
|||||||
end
|
end
|
||||||
|
|
||||||
local output_directory_chooser = dt.new_widget("file_chooser_button"){
|
local output_directory_chooser = dt.new_widget("file_chooser_button"){
|
||||||
title = _("Select export path"),
|
title = _("select export path"),
|
||||||
is_directory = true,
|
is_directory = true,
|
||||||
tooltip =_("select the target directory for the timelapse. \nthe filename is created automatically."),
|
tooltip =_("select the target directory for the timelapse. \nthe filename is created automatically."),
|
||||||
value = string_pref_read("export_path", defaultVideoDir),
|
value = string_pref_read("export_path", defaultVideoDir),
|
||||||
@@ -366,7 +380,7 @@ local module_widget = dt.new_widget("box") {
|
|||||||
---- EXPORT & REGISTRATION
|
---- EXPORT & REGISTRATION
|
||||||
|
|
||||||
local function show_status(enf_storage, image, format, filename, number, total, high_quality, extra_data)
|
local function show_status(enf_storage, image, format, filename, number, total, high_quality, extra_data)
|
||||||
dt.print(_("export ")..tostring(number).." / "..tostring(total))
|
dt.print(string.format(_("export %d / %d", number), total))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function init_export(storage, img_format, images, high_quality, extra_data)
|
local function init_export(storage, img_format, images, high_quality, extra_data)
|
||||||
@@ -402,7 +416,7 @@ local function export(extra_data)
|
|||||||
local ffmpeg_path = df.check_if_bin_exists("ffmpeg")
|
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"]
|
||||||
@@ -434,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"))
|
||||||
@@ -448,9 +462,15 @@ local function finalize_export(storage, images_table, extra_data)
|
|||||||
df.rmdir(df.sanitize_filename(tmp_dir))
|
df.rmdir(df.sanitize_filename(tmp_dir))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- script_manager integration
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("module_video_ffmpeg")
|
||||||
|
end
|
||||||
|
|
||||||
dt.register_storage(
|
dt.register_storage(
|
||||||
"module_video_ffmpeg",
|
"module_video_ffmpeg",
|
||||||
_(MODULE_NAME),
|
_("video ffmpeg"),
|
||||||
show_status,
|
show_status,
|
||||||
finalize_export,
|
finalize_export,
|
||||||
nil,
|
nil,
|
||||||
@@ -458,3 +478,8 @@ dt.register_storage(
|
|||||||
module_widget
|
module_widget
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- script_manager integration
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
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
|
||||||
BIN
data/icons/blank20.png
Normal file
BIN
data/icons/blank20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
data/icons/path20.png
Normal file
BIN
data/icons/path20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
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
|
||||||
@@ -23,5 +23,36 @@ USAGE
|
|||||||
]]
|
]]
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
|
|
||||||
|
-- translation facilities
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msg)
|
||||||
|
return gettext(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
local result = dt.configuration.api_version_string
|
local result = dt.configuration.api_version_string
|
||||||
dt.print_error("API Version: "..result)
|
dt.print_log("API Version: " .. result)
|
||||||
|
dt.print("API " .. _("version") .. ": " .. result)
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("APIversion"),
|
||||||
|
purpose = _("display api_version example"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -40,23 +40,28 @@ local du = require "lib/dtutils"
|
|||||||
-- V E R S I O N C H E C K
|
-- V E R S I O N C H E C K
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
du.check_min_api_version("5.0.2", "darkroom_mode") -- darktable 3.0
|
du.check_min_api_version("5.0.2", "darkroom_demo") -- darktable 3.0
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- C O N S T A N T S
|
-- C O N S T A N T S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
local MODULE_NAME = "darkroom"
|
local MODULE_NAME = "darkroom_demo"
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- T R A N S L A T I O N S
|
-- T R A N S L A T I O N S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@@ -70,19 +75,29 @@ local sleep = dt.control.sleep
|
|||||||
|
|
||||||
local current_view = dt.gui.current_view()
|
local current_view = dt.gui.current_view()
|
||||||
|
|
||||||
|
-- check that there is an image selected, otherwise we can't activate darkroom viewe
|
||||||
|
|
||||||
|
local images = dt.gui.action_images
|
||||||
|
dt.print_log(#images .. " images selected")
|
||||||
|
if not images or #images == 0 then
|
||||||
|
dt.print_log("no images selected, creating selection")
|
||||||
|
dt.print_log("using image " .. dt.collection[1].filename)
|
||||||
|
dt.gui.selection({dt.collection[1]})
|
||||||
|
end
|
||||||
|
|
||||||
-- enter darkroom view
|
-- enter darkroom view
|
||||||
|
|
||||||
dt.gui.current_view(dt.gui.views.darkroom)
|
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
|
||||||
|
|
||||||
for i, img in ipairs(dt.collection) do
|
for i, img in ipairs(dt.collection) do
|
||||||
dt.print(_("Displaying image " .. i))
|
dt.print(string.format(_("displaying image "), i))
|
||||||
dt.gui.views.darkroom.display_image(img)
|
dt.gui.views.darkroom.display_image(img)
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
if i == max_images then
|
if i == max_images then
|
||||||
@@ -92,7 +107,22 @@ end
|
|||||||
|
|
||||||
-- return to lighttable view
|
-- return to lighttable view
|
||||||
|
|
||||||
dt.print(_("Restoring view"))
|
dt.print(_("restoring view"))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
dt.gui.current_view(current_view)
|
dt.gui.current_view(current_view)
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("darkroom demo"),
|
||||||
|
purpose = _("example demonstrating how to control image display in darkroom mode"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -58,22 +58,40 @@ local du = require "lib/dtutils"
|
|||||||
--check API version
|
--check API version
|
||||||
du.check_min_api_version("3.0.0", "gettextExample")
|
du.check_min_api_version("3.0.0", "gettextExample")
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
-- Not translated Text
|
-- Not translated Text
|
||||||
dt.print_error("Hello World!")
|
dt.print_error("Hello World!")
|
||||||
|
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
-- Translate a string using the darktable textdomain
|
-- Translate a string using the darktable textdomain
|
||||||
dt.print_error(gettext.gettext("image"))
|
dt.print_error(gettext("image"))
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
|
|
||||||
gettext.bindtextdomain("gettextExample",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
-- Translate a string using the specified textdomain
|
|
||||||
dt.print_error(gettext.dgettext("gettextExample", 'Hello World!'))
|
|
||||||
|
|
||||||
-- Define a local function called _ to make the code more readable and have it call dgettext
|
-- Define a local function called _ to make the code more readable and have it call dgettext
|
||||||
-- with the proper domain.
|
-- with the proper domain.
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("gettextExample", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
dt.print_error(_('Hello World!'))
|
|
||||||
|
dt.print_error(_("hello world!"))
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("gettext example"),
|
||||||
|
purpose = _("example of how translations works"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextExample"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
142
examples/gui_action.lua
Normal file
142
examples/gui_action.lua
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
local dt = require "darktable"
|
||||||
|
|
||||||
|
local NaN = 0/0
|
||||||
|
|
||||||
|
local wg = {}
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("gui action"),
|
||||||
|
purpose = _("example of how to use darktable.gui.action() calls"),
|
||||||
|
author = "Diederik ter Rahe",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gui_action"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
wg.action = dt.new_widget("entry"){
|
||||||
|
text = "lib/filter/view",
|
||||||
|
placeholder = _("action path"),
|
||||||
|
tooltip = _("enter the full path of an action, for example 'lib/filter/view'")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.instance = dt.new_widget("combobox"){
|
||||||
|
label = _("instance"),
|
||||||
|
tooltip = _("the instance of an image processing module to execute action on"),
|
||||||
|
"0", "+1", "-1", "+2", "-2", "+3", "-3", "+4", "-4", "+5", "-5", "+6", "-6", "+7", "-7", "+8", "-8", "+9", "-9"
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.element = dt.new_widget("entry"){
|
||||||
|
text = "",
|
||||||
|
placeholder = _("action element"),
|
||||||
|
tooltip = _("enter the element of an action, for example 'selection', or leave empty for default")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.effect = dt.new_widget("entry"){
|
||||||
|
text = "next",
|
||||||
|
placeholder = _("action effect"),
|
||||||
|
tooltip = _("enter the effect of an action, for example 'next', or leave empty for default")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.speed = dt.new_widget("entry"){
|
||||||
|
text = "1",
|
||||||
|
placeholder = _("action speed"),
|
||||||
|
tooltip = _("enter the speed to use in action execution, or leave empty to only read state")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.check = dt.new_widget("check_button"){
|
||||||
|
label = _('perform action'),
|
||||||
|
tooltip = _('perform action or only read return'),
|
||||||
|
clicked_callback = function()
|
||||||
|
wg.speed.sensitive = wg.check.value
|
||||||
|
end,
|
||||||
|
value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.return_value = dt.new_widget("entry"){
|
||||||
|
text = "",
|
||||||
|
sensitive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
dt.register_lib(
|
||||||
|
"execute_action", -- Module name
|
||||||
|
_("execute gui actions"), -- name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100},
|
||||||
|
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}},
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "vertical",
|
||||||
|
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
dt.new_widget("label"){label = _("action path"), halign = "start"},
|
||||||
|
wg.action
|
||||||
|
},
|
||||||
|
wg.instance,
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
dt.new_widget("label"){label = _("element"), halign = "start"},
|
||||||
|
wg.element
|
||||||
|
},
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
dt.new_widget("label"){label = _("effect"), halign = "start"},
|
||||||
|
wg.effect
|
||||||
|
},
|
||||||
|
wg.check,
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
dt.new_widget("label"){label = _("speed"), halign = "start"},
|
||||||
|
wg.speed
|
||||||
|
},
|
||||||
|
dt.new_widget("button")
|
||||||
|
{
|
||||||
|
label = _("execute action"),
|
||||||
|
tooltip = _("execute the action specified in the fields above"),
|
||||||
|
clicked_callback = function(_)
|
||||||
|
local sp = NaN
|
||||||
|
if wg.check.value then sp = wg.speed.text end
|
||||||
|
wg.return_value.text = dt.gui.action(wg.action.text, tonumber(wg.instance.value), wg.element.text, wg.effect.text, tonumber(sp))
|
||||||
|
end
|
||||||
|
},
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "horizontal",
|
||||||
|
dt.new_widget("label"){label = "return value:", halign = "start"},
|
||||||
|
wg.return_value
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["execute_action"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["execute_action"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -29,9 +29,38 @@ USAGE
|
|||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
-- translation facilities
|
||||||
|
|
||||||
du.check_min_api_version("2.0.0", "hello_world")
|
du.check_min_api_version("2.0.0", "hello_world")
|
||||||
|
|
||||||
dt.print("hello, world")
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msg)
|
||||||
|
return gettext(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.print(_("hello, world"))
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("hello world"),
|
||||||
|
purpose = _("example of how to print a message to the screen"),
|
||||||
|
author = "Tobias Ellinghaus",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
--
|
--
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -50,14 +50,21 @@ du.check_min_api_version("5.0.2", "lighttable_demo") -- darktable 3.0
|
|||||||
local MODULE_NAME = "lighttable"
|
local MODULE_NAME = "lighttable"
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- T R A N S L A T I O N S
|
-- T R A N S L A T I O N S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
|
dt.gettext.bindtextdomain("lighttable_demo", dt.configuration.config_dir .."/lua/locale/")
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@@ -75,6 +82,7 @@ local layouts = {
|
|||||||
"DT_LIGHTTABLE_LAYOUT_ZOOMABLE",
|
"DT_LIGHTTABLE_LAYOUT_ZOOMABLE",
|
||||||
"DT_LIGHTTABLE_LAYOUT_FILEMANAGER",
|
"DT_LIGHTTABLE_LAYOUT_FILEMANAGER",
|
||||||
"DT_LIGHTTABLE_LAYOUT_CULLING",
|
"DT_LIGHTTABLE_LAYOUT_CULLING",
|
||||||
|
"DT_LIGHTTABLE_LAYOUT_CULLING_DYNAMIC",
|
||||||
}
|
}
|
||||||
|
|
||||||
local sorts = {
|
local sorts = {
|
||||||
@@ -138,34 +146,41 @@ sleep(2000)
|
|||||||
|
|
||||||
for n, layout in ipairs(layouts) do
|
for n, layout in ipairs(layouts) do
|
||||||
dt.gui.libs.lighttable_mode.layout(layout)
|
dt.gui.libs.lighttable_mode.layout(layout)
|
||||||
dt.print(_("set lighttable layout to " .. layout))
|
dt.print(string.format(_("set lighttable layout to %s"), layout))
|
||||||
|
dt.print_log("set lighttable layout to " .. layout)
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
for i = 1, 10 do
|
for i = 1, 10 do
|
||||||
dt.gui.libs.lighttable_mode.zoom_level(i)
|
dt.gui.libs.lighttable_mode.zoom_level(i)
|
||||||
dt.print(_("Set zoom level to " .. i))
|
dt.print(string.format(_("set zoom level to %d"), i))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
end
|
end
|
||||||
for i = 9, 1, -1 do
|
for i = 9, 1, -1 do
|
||||||
dt.gui.libs.lighttable_mode.zoom_level(i)
|
dt.gui.libs.lighttable_mode.zoom_level(i)
|
||||||
dt.print(_("Set zoom level to " .. i))
|
dt.print(string.format(_("set zoom level to %d"), i))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
dt.print_log("finished layout and zoom level testing")
|
||||||
|
dt.print_log("starting sort demonstration")
|
||||||
-- cycle through sorts
|
-- cycle through sorts
|
||||||
|
|
||||||
dt.print(_("lighttable sorting demonstration"))
|
dt.print(_("lighttable sorting demonstration"))
|
||||||
|
dt.print_log("setting lighttable to filemanager mode")
|
||||||
dt.gui.libs.lighttable_mode.layout("DT_LIGHTTABLE_LAYOUT_FILEMANAGER")
|
dt.gui.libs.lighttable_mode.layout("DT_LIGHTTABLE_LAYOUT_FILEMANAGER")
|
||||||
|
sleep(500)
|
||||||
|
dt.print_log("setting lighttable to zoom level 5")
|
||||||
dt.gui.libs.lighttable_mode.zoom_level(5)
|
dt.gui.libs.lighttable_mode.zoom_level(5)
|
||||||
|
dt.print_log("starting sorts")
|
||||||
|
|
||||||
for n, sort in ipairs(sorts) do
|
for n, sort in ipairs(sorts) do
|
||||||
dt.gui.libs.filter.sort(sort)
|
dt.gui.libs.filter.sort(sort)
|
||||||
dt.print(_("set lighttable sort to " .. sort))
|
dt.print(string.format(_("set lighttable sort to %s"), sort))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
for m, sort_order in ipairs(sort_orders) do
|
for m, sort_order in ipairs(sort_orders) do
|
||||||
dt.gui.libs.filter.sort_order(sort_order)
|
dt.gui.libs.filter.sort_order(sort_order)
|
||||||
dt.print(_("sort order set to " .. sort_order))
|
dt.print(string.format(_("sort order set to %s"), sort_order))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -176,12 +191,12 @@ dt.print(_("lighttable filtering demonstration"))
|
|||||||
|
|
||||||
for n, rating in ipairs(ratings) do
|
for n, rating in ipairs(ratings) do
|
||||||
dt.gui.libs.filter.rating(rating)
|
dt.gui.libs.filter.rating(rating)
|
||||||
dt.print(_("set filter to " .. rating))
|
dt.print(string.format(_("set filter to %s"), rating))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
for m, rating_comparator in ipairs(rating_comparators) do
|
for m, rating_comparator in ipairs(rating_comparators) do
|
||||||
dt.gui.libs.filter.rating_comparator(rating_comparator)
|
dt.gui.libs.filter.rating_comparator(rating_comparator)
|
||||||
dt.print(_("set rating comparator to " .. rating_comparator))
|
dt.print(string.format(_("set rating comparator to %s"), rating_comparator))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -196,3 +211,19 @@ current_rating = dt.gui.libs.filter.rating(current_rating)
|
|||||||
current_rating_comparator = dt.gui.libs.filter.rating_comparator(current_rating_comparator)
|
current_rating_comparator = dt.gui.libs.filter.rating_comparator(current_rating_comparator)
|
||||||
current_sort =dt.gui.libs.filter.sort(current_sort)
|
current_sort =dt.gui.libs.filter.sort(current_sort)
|
||||||
current_sort_order = dt.gui.libs.filter.sort_order(current_sort_order)
|
current_sort_order = dt.gui.libs.filter.sort_order(current_sort_order)
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("lighttable demo"),
|
||||||
|
purpose = _("example demonstrating how to control lighttable display modes"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -33,20 +33,33 @@ https://www.darktable.org/lua-api/index.html.php#darktable_new_widget
|
|||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "moduleExample")
|
du.check_min_api_version("7.0.0", "moduleExample")
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
-- translation
|
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
|
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
gettext.bindtextdomain("moduleExample", dt.configuration.config_dir .. "/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("moduleExample", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = ("module example"),
|
||||||
|
purpose = _("example of how to create a lighttable module"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleExample"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
-- translation
|
||||||
|
|
||||||
-- declare a local namespace and a couple of variables we'll need to install the module
|
-- declare a local namespace and a couple of variables we'll need to install the module
|
||||||
local mE = {}
|
local mE = {}
|
||||||
mE.widgets = {}
|
mE.widgets = {}
|
||||||
@@ -62,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
|
||||||
@@ -72,9 +85,9 @@ local function install_module()
|
|||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
dt.new_widget("button")
|
dt.new_widget("button")
|
||||||
{
|
{
|
||||||
label = _("MyButton"),
|
label = _("my ") .. "button",
|
||||||
clicked_callback = function (_)
|
clicked_callback = function (_)
|
||||||
dt.print(_("Button clicked"))
|
dt.print(_("button clicked"))
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
table.unpack(mE.widgets),
|
table.unpack(mE.widgets),
|
||||||
@@ -86,11 +99,21 @@ local function install_module()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["exampleModule"].visible = false -- we haven't figured out how to destroy it yet, so we hide it for now
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["exampleModule"].visible = true -- the user wants to use it again, so we just make it visible and it shows up in the UI
|
||||||
|
end
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/types_lua_check_button.html
|
-- https://www.darktable.org/lua-api/types_lua_check_button.html
|
||||||
local check_button = dt.new_widget("check_button"){label = _("MyCheck_button"), value = true}
|
local check_button = dt.new_widget("check_button"){label = _("my ") .. "check_button", value = true}
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/types_lua_combobox.html
|
-- https://www.darktable.org/lua-api/types_lua_combobox.html
|
||||||
local combobox = dt.new_widget("combobox"){label = _("MyCombobox"), value = 2, "8", "16", "32"}
|
local combobox = dt.new_widget("combobox"){label = _("my ") .. "combobox", value = 2, "8", "16", "32"}
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/types_lua_entry.html
|
-- https://www.darktable.org/lua-api/types_lua_entry.html
|
||||||
local entry = dt.new_widget("entry")
|
local entry = dt.new_widget("entry")
|
||||||
@@ -99,21 +122,21 @@ local entry = dt.new_widget("entry")
|
|||||||
placeholder = _("placeholder"),
|
placeholder = _("placeholder"),
|
||||||
is_password = false,
|
is_password = false,
|
||||||
editable = true,
|
editable = true,
|
||||||
tooltip = _("Tooltip Text"),
|
tooltip = _("tooltip text"),
|
||||||
reset_callback = function(self) self.text = "text" end
|
reset_callback = function(self) self.text = "text" end
|
||||||
}
|
}
|
||||||
|
|
||||||
-- 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 = _("MyFile_chooser_button"), -- The title of the window when choosing a file
|
title = _("my ") .. "file_chooser_button", -- The title of the window when choosing a file
|
||||||
value = "", -- The currently selected file
|
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 = _("MyLabel") -- This is an alternative way to the "{}" syntax to set a property
|
label.label = _("my ") .. "label" -- This is an alternative way to the "{}" syntax to set a property
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/types_lua_separator.html
|
-- https://www.darktable.org/lua-api/types_lua_separator.html
|
||||||
local separator = dt.new_widget("separator"){}
|
local separator = dt.new_widget("separator"){}
|
||||||
@@ -121,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 = _("MySlider"),
|
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
|
||||||
@@ -148,7 +171,7 @@ else
|
|||||||
if not mE.event_registered then -- if we are not in lighttable view then register an event to signal when we might be
|
if not mE.event_registered then -- if we are not in lighttable view then register an event to signal when we might be
|
||||||
-- https://www.darktable.org/lua-api/index.html#darktable_register_event
|
-- https://www.darktable.org/lua-api/index.html#darktable_register_event
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"view-changed", -- we want to be informed when the view changes
|
"mdouleExample", "view-changed", -- we want to be informed when the view changes
|
||||||
function(event, old_view, new_view)
|
function(event, old_view, new_view)
|
||||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then -- if the view changes from darkroom to lighttable
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then -- if the view changes from darkroom to lighttable
|
||||||
install_module() -- register the lib
|
install_module() -- register the lib
|
||||||
@@ -159,5 +182,14 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart -- only required for lib modules until we figure out how to destroy them
|
||||||
|
script_data.destroy_method = "hide" -- tell script_manager that we are hiding the lib so it knows to use the restart function
|
||||||
|
script_data.show = restart -- if the script was "off" when darktable exited, the module is hidden, so force it to show on start
|
||||||
|
|
||||||
|
return script_data
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
-- kate: hl Lua;
|
-- kate: hl Lua;
|
||||||
|
|||||||
@@ -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
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -62,7 +62,6 @@ local dt = require "darktable"
|
|||||||
local du = require "lib/dtutils" -- utilities
|
local du = require "lib/dtutils" -- utilities
|
||||||
local df = require "lib/dtutils.file" -- file utilities
|
local df = require "lib/dtutils.file" -- file utilities
|
||||||
local dtsys = require "lib/dtutils.system" -- system utilities
|
local dtsys = require "lib/dtutils.system" -- system utilities
|
||||||
local CURR_API_STRING = dt.configuration.api_version_string
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
darktable is an international program, and it's user interface has been translated into
|
darktable is an international program, and it's user interface has been translated into
|
||||||
@@ -71,13 +70,10 @@ local CURR_API_STRING = dt.configuration.api_version_string
|
|||||||
translations, inserting this lays the groundwork for anyone who wants to translate the strings.
|
translations, inserting this lays the groundwork for anyone who wants to translate the strings.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("multi_os",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("multi_os", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -87,7 +83,7 @@ end
|
|||||||
screen stating that you couldn't load because the minimum api version wasn't met.
|
screen stating that you couldn't load because the minimum api version wasn't met.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
du.check_min_api_version("5.0.0", "multi_os")
|
du.check_min_api_version("7.0.0", "multi_os")
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image
|
copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image
|
||||||
@@ -143,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
|
||||||
@@ -156,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.
|
||||||
@@ -168,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)
|
||||||
@@ -188,15 +219,24 @@ local function extract_embedded_jpeg(images)
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
dt.print_error(image.filename .. " is not a raw file. No image can be extracted") -- print debugging error message
|
dt.print_error(image.filename .. " is not a raw file. No image can be extracted") -- print debugging error message
|
||||||
dt.print(image.filename .. " is not a raw file. No image can be extracted") -- print the error to the screen
|
dt.print(string.format(_("%s is not a raw file, no image can be extracted"), image.filename)) -- print the error to the screen
|
||||||
end
|
end
|
||||||
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
|
||||||
|
|
||||||
|
--[[
|
||||||
|
script_manager integration to allow a script to be removed
|
||||||
|
without restarting darktable
|
||||||
|
]]
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_event("multi_os", "shortcut") -- destroy the event since the callback will no longer be present
|
||||||
|
dt.gui.libs.image.destroy_action("multi_os") -- remove the button from the selected images module
|
||||||
|
end
|
||||||
--[[
|
--[[
|
||||||
Windows and MacOS don't place executables in the user's path so their location needs to be specified
|
Windows and MacOS don't place executables in the user's path so their location needs to be specified
|
||||||
so that the script can find them. An exception to this is packages installed on MacOS with homebrew. Those
|
so that the script can find them. An exception to this is packages installed on MacOS with homebrew. Those
|
||||||
@@ -208,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)
|
||||||
@@ -219,8 +259,8 @@ 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
|
||||||
)
|
)
|
||||||
@@ -231,18 +271,36 @@ end
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.register_action(
|
||||||
_("extract embedded jpeg"),
|
"multi_os", _("extract embedded jpeg"),
|
||||||
function(event, images) extract_embedded_jpeg(images) end,
|
function(event, images) extract_embedded_jpeg(images) end,
|
||||||
"extract embedded jpeg"
|
_("extract embedded jpeg")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Add a shortcut
|
Add a shortcut
|
||||||
]]
|
]]
|
||||||
|
|
||||||
dt.register_event(
|
dt.register_event(
|
||||||
"shortcut",
|
"multi_os", "shortcut",
|
||||||
function(event, shortcut) extract_embedded_jpeg(dt.gui.action_images) end,
|
function(event, shortcut) extract_embedded_jpeg(dt.gui.action_images) end,
|
||||||
"extract embedded jpeg"
|
_("extract embedded jpeg")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
set the destroy routine so that script_manager can call it when
|
||||||
|
it's time to destroy the script and then return the data to
|
||||||
|
script_manager
|
||||||
|
]]
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("multi OS"),
|
||||||
|
purpose = _("example module thet runs on different operating systems"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/multi_os"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -41,23 +41,28 @@ local du = require "lib/dtutils"
|
|||||||
-- V E R S I O N C H E C K
|
-- V E R S I O N C H E C K
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
du.check_min_api_version("5.0.2", "panels_demo") -- darktable 3.0
|
du.check_min_api_version("7.0.0", "panels_demo")
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- C O N S T A N T S
|
-- C O N S T A N T S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
local MODULE_NAME = "panels"
|
local MODULE_NAME = "panels_demo"
|
||||||
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
-- T R A N S L A T I O N S
|
-- T R A N S L A T I O N S
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@@ -88,29 +93,29 @@ 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
|
||||||
|
|
||||||
-- display left, then top, then right, then bottom
|
-- display left, then top, then right, then bottom
|
||||||
|
|
||||||
dt.print(_("Make panels visible, one at a time"))
|
dt.print(_("make panels visible, one at a time"))
|
||||||
sleep(1500)
|
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
|
||||||
|
|
||||||
-- hide all
|
-- hide all
|
||||||
|
|
||||||
dt.print(_("Hiding all panels"))
|
dt.print(_("hiding all panels"))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
dt.gui.panel_hide_all()
|
dt.gui.panel_hide_all()
|
||||||
@@ -118,7 +123,7 @@ sleep(1500)
|
|||||||
|
|
||||||
-- show all
|
-- show all
|
||||||
|
|
||||||
dt.print(_("Showing all panels"))
|
dt.print(_("showing all panels"))
|
||||||
sleep(1500)
|
sleep(1500)
|
||||||
|
|
||||||
dt.gui.panel_show_all()
|
dt.gui.panel_show_all()
|
||||||
@@ -126,7 +131,7 @@ sleep(1500)
|
|||||||
|
|
||||||
-- restore
|
-- restore
|
||||||
|
|
||||||
dt.print(_("Restoring panels to starting configuration"))
|
dt.print(_("restoring panels to starting configuration"))
|
||||||
for i = 1, #panels do
|
for i = 1, #panels do
|
||||||
if panel_status[i] then
|
if panel_status[i] then
|
||||||
dt.gui.panel_show(panels[i])
|
dt.gui.panel_show(panels[i])
|
||||||
@@ -134,3 +139,19 @@ for i = 1, #panels do
|
|||||||
dt.gui.panel_hide(panels[i])
|
dt.gui.panel_hide(panels[i])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("panels demo"),
|
||||||
|
purpose = _("example demonstrating how to contol panel visibility"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -24,60 +24,86 @@ USAGE
|
|||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local du = require "lib/dtutils"
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
-- translation facilities
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msg)
|
||||||
|
return gettext(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("preference examples"),
|
||||||
|
purpose = _("example to show the different preference types that are possible"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceExamples"
|
||||||
|
}
|
||||||
|
|
||||||
du.check_min_api_version("2.0.1", "preferenceExamples")
|
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.
|
||||||
"preferenceExamplesString", -- 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.
|
||||||
"preferenceExamplesString", -- 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
|
||||||
|
|
||||||
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
|
"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
|
||||||
0.5) -- step
|
0.5) -- step
|
||||||
|
|
||||||
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
|
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
|
||||||
"preferenceExamplesString", -- name
|
"preferenceExamplesFile", -- name
|
||||||
"file", -- type
|
"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.
|
||||||
"preferenceExamplesString", -- 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.
|
||||||
"preferenceExamplesString", -- 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
|
||||||
|
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -24,9 +24,23 @@ local du = require "lib/dtutils"
|
|||||||
|
|
||||||
du.check_min_api_version("5.0.0", "printExamples")
|
du.check_min_api_version("5.0.0", "printExamples")
|
||||||
|
|
||||||
|
-- translation facilities
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msg)
|
||||||
|
return gettext(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
-- Will print a string to the darktable control log (the long
|
-- Will print a string to the darktable control log (the long
|
||||||
-- overlayed window that appears over the main panel).
|
-- overlayed window that appears over the main panel).
|
||||||
dt.print("print")
|
dt.print(_("print"))
|
||||||
|
|
||||||
-- This function will print its parameter if the Lua logdomain is
|
-- This function will print its parameter if the Lua logdomain is
|
||||||
-- activated. Start darktable with the "-d lua" command line option
|
-- activated. Start darktable with the "-d lua" command line option
|
||||||
@@ -38,5 +52,20 @@ dt.print_error("print error")
|
|||||||
-- to enable the Lua logdomain.
|
-- to enable the Lua logdomain.
|
||||||
dt.print_log("print log")
|
dt.print_log("print log")
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("print examples"),
|
||||||
|
purpose = _("example showing the different types of printing messages"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printExamples"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
--
|
--
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -30,7 +30,37 @@ local du = require "lib/dtutils"
|
|||||||
|
|
||||||
du.check_min_api_version("5.0.0", "running_os")
|
du.check_min_api_version("5.0.0", "running_os")
|
||||||
|
|
||||||
dt.print("You are running: "..dt.configuration.running_os)
|
-- translation facilities
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msg)
|
||||||
|
return gettext(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- script_manager integration to allow a script to be removed
|
||||||
|
-- without restarting darktable
|
||||||
|
local function destroy()
|
||||||
|
-- nothing to destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.print(string.format(_("you are running: %s"), dt.configuration.running_os))
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("running OS"),
|
||||||
|
purpose = _("example of how to determine the operating system being used"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
--
|
--
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
345
examples/x-touch.lua
Normal file
345
examples/x-touch.lua
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
--[[
|
||||||
|
This file is part of darktable,
|
||||||
|
copyright (c) 2023 Diederik ter Rahe
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
X-Touch Mini flexible encoder shortcuts
|
||||||
|
|
||||||
|
This script will create virtual sliders that are mapped dynamically to
|
||||||
|
the most relevant sliders for the currently focused processing module.
|
||||||
|
Tailored modules are color zones, tone equalizer, color calibration and
|
||||||
|
mask manager properties. The script can easily be amended for other
|
||||||
|
devices or personal preferences. Virtual "toggle" buttons can be created
|
||||||
|
as well, that dynamically change meaning depending on current status.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* require this script from your main lua file
|
||||||
|
* restart darktable
|
||||||
|
* create shortcuts for each of the encoders on the x-touch mini
|
||||||
|
to a virtual slider under lua/x-touch
|
||||||
|
or import the following shortcutsrc file in the shortcuts dialog/preferences tab:
|
||||||
|
|
||||||
|
None;midi:CC1=lua/x-touch/knob 1
|
||||||
|
None;midi:CC2=lua/x-touch/knob 2
|
||||||
|
None;midi:CC3=lua/x-touch/knob 3
|
||||||
|
None;midi:CC4=lua/x-touch/knob 4
|
||||||
|
None;midi:CC5=lua/x-touch/knob 5
|
||||||
|
None;midi:CC6=lua/x-touch/knob 6
|
||||||
|
None;midi:CC7=lua/x-touch/knob 7
|
||||||
|
None;midi:CC8=lua/x-touch/knob 8
|
||||||
|
midi:E0=global/modifiers
|
||||||
|
midi:F0=global/modifiers;ctrl
|
||||||
|
midi:F#0=global/modifiers;alt
|
||||||
|
midi:G#-1=iop/blend/tools/show and edit mask elements
|
||||||
|
midi:A-1=iop/colorzones;focus
|
||||||
|
midi:A#-1=iop/toneequal;focus
|
||||||
|
midi:B-1=iop/colorbalancergb;focus
|
||||||
|
midi:C0=iop/channelmixerrgb;focus
|
||||||
|
midi:C#0=iop/colorequal;focus
|
||||||
|
midi:D0=iop/colorequal/page;previous
|
||||||
|
midi:D#0=iop/colorequal/page;next
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
To add a new module, simply add an entry to MODULE_PROFILES below.
|
||||||
|
Get action paths from darktable's shortcut interface.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
du.check_min_api_version("9.2.0", "x-touch")
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Constants
|
||||||
|
local NUM_KNOBS = 8
|
||||||
|
local NAN = 0/0
|
||||||
|
local darkroom_view = dt.gui.views.darkroom
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- USER CONFIGURATION
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Module Profiles: Define behavior when each module has focus
|
||||||
|
-- Structure: [module_name] = { knob1_action, knob2_action, ... }
|
||||||
|
local MODULE_PROFILES = {
|
||||||
|
|
||||||
|
-- Color Zones: Control individual color channels
|
||||||
|
colorzones = {
|
||||||
|
{"iop/colorzones/graph", "red"},
|
||||||
|
{"iop/colorzones/graph", "orange"},
|
||||||
|
{"iop/colorzones/graph", "yellow"},
|
||||||
|
{"iop/colorzones/graph", "green"},
|
||||||
|
{"iop/colorzones/graph", "aqua"},
|
||||||
|
{"iop/colorzones/graph", "blue"},
|
||||||
|
{"iop/colorzones/graph", "purple"},
|
||||||
|
{"iop/colorzones/graph", "magenta"}
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Color Equalizer: Control color channels
|
||||||
|
colorequal = {
|
||||||
|
{"iop/colorequal/graph", "red"},
|
||||||
|
{"iop/colorequal/graph", "orange"},
|
||||||
|
{"iop/colorequal/graph", "yellow"},
|
||||||
|
{"iop/colorequal/graph", "green"},
|
||||||
|
{"iop/colorequal/graph", "cyan"},
|
||||||
|
{"iop/colorequal/graph", "blue"},
|
||||||
|
{"iop/colorequal/graph", "lavender"},
|
||||||
|
{"iop/colorequal/graph", "magenta"}
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Sigmoid: Control primary color adjustments
|
||||||
|
sigmoid = {
|
||||||
|
{"iop/sigmoid/primaries/red attenuation"},
|
||||||
|
{"iop/sigmoid/primaries/red rotation"},
|
||||||
|
{"iop/sigmoid/primaries/green attenuation"},
|
||||||
|
{"iop/sigmoid/primaries/green rotation"},
|
||||||
|
{"iop/sigmoid/primaries/blue attenuation"},
|
||||||
|
{"iop/sigmoid/primaries/blue rotation"},
|
||||||
|
{"iop/sigmoid/primaries/recover purity"}
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Color Calibration (Primaries)
|
||||||
|
primaries = {
|
||||||
|
{"iop/primaries/red hue"},
|
||||||
|
{"iop/primaries/red purity"},
|
||||||
|
{"iop/primaries/green hue"},
|
||||||
|
{"iop/primaries/green purity"},
|
||||||
|
{"iop/primaries/blue hue"},
|
||||||
|
{"iop/primaries/blue purity"},
|
||||||
|
{"iop/primaries/tint hue"},
|
||||||
|
{"iop/primaries/tint purity"}
|
||||||
|
},
|
||||||
|
|
||||||
|
-- AgX
|
||||||
|
agx = {
|
||||||
|
{"iop/agx/white relative exposure"},
|
||||||
|
{"iop/agx/black relative exposure"},
|
||||||
|
{"iop/agx/curve/pivot relative exposure"},
|
||||||
|
{"iop/agx/curve/pivot target output"},
|
||||||
|
{"iop/agx/curve/contrast"},
|
||||||
|
{"iop/agx/curve/shoulder power"},
|
||||||
|
{"iop/agx/curve/toe power"},
|
||||||
|
{"iop/agx/curve/curve y gamma"}
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Tone Equalizer: Control EV bands
|
||||||
|
-- For 8 knobs, using offsets -8 to get bands
|
||||||
|
toneequal = {
|
||||||
|
{"iop/toneequal/simple/-8 EV"},
|
||||||
|
{"iop/toneequal/simple/-7 EV"},
|
||||||
|
{"iop/toneequal/simple/-6 EV"},
|
||||||
|
{"iop/toneequal/simple/-5 EV"},
|
||||||
|
{"iop/toneequal/simple/-4 EV"},
|
||||||
|
{"iop/toneequal/simple/-3 EV"},
|
||||||
|
{"iop/toneequal/simple/-2 EV"},
|
||||||
|
{"iop/toneequal/simple/-1 EV"}
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Color Balance RGB: Selected controls
|
||||||
|
colorbalancergb = {
|
||||||
|
nil, -- Knob 1: unused --> default
|
||||||
|
{"iop/colorbalancergb/global vibrance"},
|
||||||
|
{"iop/colorbalancergb/contrast"},
|
||||||
|
{"iop/colorbalancergb/global chroma"},
|
||||||
|
{"iop/colorbalancergb/global saturation"},
|
||||||
|
{"iop/colorbalancergb/global brilliance"},
|
||||||
|
nil, -- Knob 7: --> default
|
||||||
|
nil -- Knob 8: --> default
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Channel Mixer RGB (Color Calibration): Tab control and sliders
|
||||||
|
channelmixerrgb = {
|
||||||
|
nil, -- Knob 1-4: --> default
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
{"iop/channelmixerrgb/page", "CAT", "tab"}, -- Knob 5: Tab selector
|
||||||
|
{"iop/focus/sliders", "1st"}, -- Knob 6: First slider
|
||||||
|
{"iop/focus/sliders", "2nd"}, -- Knob 7: Second slider
|
||||||
|
{"iop/focus/sliders", "3rd"} -- Knob 8: Third slider
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
local DEFAULT_MAPPINGS = {
|
||||||
|
{"iop/exposure/exposure"},
|
||||||
|
{"iop/filmicrgb/white relative exposure"},
|
||||||
|
{"iop/filmicrgb/black relative exposure"},
|
||||||
|
{"iop/filmicrgb/contrast"},
|
||||||
|
{"iop/crop/left"},
|
||||||
|
{"iop/crop/right"},
|
||||||
|
{"iop/crop/top"},
|
||||||
|
{"iop/crop/bottom"}
|
||||||
|
}
|
||||||
|
-- Mask properties: Always checked first, regardless of module focus
|
||||||
|
local MASK_PROPERTIES = {
|
||||||
|
{"lib/masks/properties/opacity"},
|
||||||
|
{"lib/masks/properties/size"},
|
||||||
|
{"lib/masks/properties/feather"},
|
||||||
|
{"lib/masks/properties/hardness"},
|
||||||
|
{"lib/masks/properties/rotation"},
|
||||||
|
{"lib/masks/properties/curvature"},
|
||||||
|
{"lib/masks/properties/compression"}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Priority order for checking module focus (most commonly used first)
|
||||||
|
local MODULE_CHECK_ORDER = {
|
||||||
|
"toneequal",
|
||||||
|
"colorequal",
|
||||||
|
"colorbalancergb",
|
||||||
|
"agx",
|
||||||
|
"sigmoid",
|
||||||
|
"primaries",
|
||||||
|
"channelmixerrgb",
|
||||||
|
"colorzones"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- CORE LOGIC - No need to edit below unless changing architecture
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Helper functions
|
||||||
|
local function is_in_darkroom()
|
||||||
|
return dt.gui.current_view() == darkroom_view
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_valid(value)
|
||||||
|
return value == value -- NaN check
|
||||||
|
end
|
||||||
|
|
||||||
|
local function translate_tab_effect(effect)
|
||||||
|
if effect == "up" then
|
||||||
|
return "next"
|
||||||
|
elseif effect == "down" then
|
||||||
|
return "previous"
|
||||||
|
else
|
||||||
|
return "activate"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Execute an action configuration
|
||||||
|
local function execute_action(action_config, element, effect, size)
|
||||||
|
if not action_config then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = action_config[1]
|
||||||
|
local elem = action_config[2] or element
|
||||||
|
local action_type = action_config[3]
|
||||||
|
|
||||||
|
-- Handle special action types
|
||||||
|
if action_type == "tab" then
|
||||||
|
effect = translate_tab_effect(effect)
|
||||||
|
end
|
||||||
|
|
||||||
|
return dt.gui.action(path, elem, effect, size)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try mask properties first
|
||||||
|
local function try_mask_properties(knob_idx, element, effect, size)
|
||||||
|
if knob_idx >= NUM_KNOBS then
|
||||||
|
return NAN
|
||||||
|
end
|
||||||
|
|
||||||
|
local mask_action = MASK_PROPERTIES[knob_idx]
|
||||||
|
if not mask_action then
|
||||||
|
return NAN
|
||||||
|
end
|
||||||
|
|
||||||
|
local value = execute_action(mask_action, element, effect, size)
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Main knob handler
|
||||||
|
local function handle_knob_action(knob_idx, element, effect, size)
|
||||||
|
-- Early exit for non-darkroom view
|
||||||
|
if not is_in_darkroom() then
|
||||||
|
return NAN
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try mask properties first (no focus check needed)
|
||||||
|
local mask_value = try_mask_properties(knob_idx, element, effect, size)
|
||||||
|
if is_valid(mask_value) then
|
||||||
|
return mask_value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check focused modules in priority order
|
||||||
|
for _, module_name in ipairs(MODULE_CHECK_ORDER) do
|
||||||
|
local focus_status = dt.gui.action("iop/" .. module_name, "focus")
|
||||||
|
|
||||||
|
if focus_status ~= 0 then
|
||||||
|
-- Module has focus, get its profile
|
||||||
|
local profile = MODULE_PROFILES[module_name]
|
||||||
|
|
||||||
|
if profile then
|
||||||
|
local action_config = profile[knob_idx]
|
||||||
|
|
||||||
|
-- If profile has explicit mapping for this knob, use it
|
||||||
|
if action_config then
|
||||||
|
local result = execute_action(action_config, element, effect, size)
|
||||||
|
if result then
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Profile exists but this knob is nil -> use default
|
||||||
|
-- This avoids checking other modules when we know the focused one
|
||||||
|
local default_action = DEFAULT_MAPPINGS[knob_idx]
|
||||||
|
return execute_action(default_action, element, effect, size) or NAN
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If no profile exists for this module, continue checking others
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- No module has focus or no profile matched, fall back to default
|
||||||
|
local default_action = DEFAULT_MAPPINGS[knob_idx]
|
||||||
|
return execute_action(default_action, element, effect, size) or NAN
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Setup mimic sliders
|
||||||
|
local function setup_knobs()
|
||||||
|
for k = 1, NUM_KNOBS do
|
||||||
|
dt.gui.mimic("slider", "knob " .. k,
|
||||||
|
function(action, element, effect, size)
|
||||||
|
local knob_idx = tonumber(action:sub(-1))
|
||||||
|
return handle_knob_action(knob_idx, element, effect, size)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Script metadata
|
||||||
|
local script_data = {
|
||||||
|
metadata = {
|
||||||
|
name = _("x-touch"),
|
||||||
|
purpose = _("data-driven x-touch midi controller configuration"),
|
||||||
|
author = "Martin Straeten",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch"
|
||||||
|
},
|
||||||
|
destroy = function() end,
|
||||||
|
destroy_method = nil,
|
||||||
|
restart = nil,
|
||||||
|
show = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Initialize
|
||||||
|
setup_knobs()
|
||||||
|
|
||||||
|
return script_data
|
||||||
138
lib/dtutils.lua
138
lib/dtutils.lua
@@ -61,14 +61,50 @@ 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.")
|
||||||
|
dt.control.sleep(2000) -- allow time for the error to display before script_manager writes it's error message
|
||||||
error("Minimum API " .. min_api .. " not met for " .. script_name .. ".", 0)
|
error("Minimum API " .. min_api .. " not met for " .. script_name .. ".", 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["check_max_api_version"] = {
|
||||||
|
Name = [[check_max_api_version]],
|
||||||
|
Synopsis = [[check the maximum required api version against the current api version]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
local result = du.check_max_api_version(max_api, script_name)
|
||||||
|
max_api - string - the api version that the application was written for (example: "5.0.0")
|
||||||
|
script_name - string - the name of the script]],
|
||||||
|
Description = [[check_max_api_version compares the maximum api required for the appllication to
|
||||||
|
run against the current api version. This function is used when a part of the Lua API that
|
||||||
|
the script relies on is removed. If the maximum api version is not met, then an
|
||||||
|
error message is printed saying the script_name failed to load, then an error is thrown causing the
|
||||||
|
program to stop executing.]],
|
||||||
|
Return_Value = [[result - true if the maximum api version is available, false if not.]],
|
||||||
|
Limitations = [[When using the default handler on a script being executed from the luarc file, the error thrown
|
||||||
|
will stop the luarc file from executing any remaining statements. This limitation does not apply to script_manger.]],
|
||||||
|
Example = [[check_max_api_version("9.0.0") does nothing if the api is less than or equal to 9.0.0 otherwise an
|
||||||
|
error message is printed and an error is thrown stopping execution of the script.]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.check_max_api_version(max_api, script_name)
|
||||||
|
local current_api = dt.configuration.api_version_string
|
||||||
|
if dtutils.compare_api_versions(current_api, max_api) > 0 then
|
||||||
|
dt.print_error("This application is written for lua api version " .. max_api .. " or earlier.")
|
||||||
|
dt.print_error("The current lua api version is " .. current_api)
|
||||||
|
dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. max_api .. " or earlier required.")
|
||||||
|
dt.control.sleep(2000) -- allow time for the error to display before script_manager writes it's error message
|
||||||
|
error("Maximum API " .. max_api .. " not met for " .. script_name .. ".", 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
dtutils.libdoc.functions["split"] = {
|
dtutils.libdoc.functions["split"] = {
|
||||||
Name = [[split]],
|
Name = [[split]],
|
||||||
Synopsis = [[split a string on a specified separator]],
|
Synopsis = [[split a string on a specified separator]],
|
||||||
@@ -184,7 +220,8 @@ function dtutils.prequire(req_name)
|
|||||||
if status then
|
if status then
|
||||||
log.msg(log.info, "Loaded " .. req_name)
|
log.msg(log.info, "Loaded " .. req_name)
|
||||||
else
|
else
|
||||||
log.msg(log.info, "Error loading " .. req_name)
|
log.msg(log.error, "Error loading " .. req_name)
|
||||||
|
log.msg(log.error, "Error returned is " .. lib)
|
||||||
end
|
end
|
||||||
return status, lib
|
return status, lib
|
||||||
end
|
end
|
||||||
@@ -330,4 +367,101 @@ function dtutils.find_image_by_id(imgid)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["deprecated"] = {
|
||||||
|
Name = [[deprecated]],
|
||||||
|
Synopsis = [[print deprecation warning]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
du.deprecated(script_name, removal_string)
|
||||||
|
script_name - name of the script being deprecated
|
||||||
|
removal_string - a string explaining when the script will be removed]],
|
||||||
|
Description = [[deprecated prints an error message saying the script is deprecated and when it will be removed]],
|
||||||
|
Return_Value = [[]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[local du = require "lib/dtutils"
|
||||||
|
du.deprecated("contrib/rename-tags.lua", "darktable release 4.0")]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.deprecated(script_name, removal_string)
|
||||||
|
dt.print_toast("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
|
||||||
|
dt.print_error("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["gen_uuid"] = {
|
||||||
|
Name = [[gen_uuid]],
|
||||||
|
Synopsis = [[generate a UUID string]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
uuid = du.gen_uuid(case)
|
||||||
|
case - "upper" or "lower" to specify the case of the UUID string]],
|
||||||
|
Description = [[gen_uuid prints an error message saying the script is gen_uuid and when it will be removed]],
|
||||||
|
Return_Value = [[uuid - string - a hexidecimal string representing the UUID in the requested case]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[https://gist.github.com/jrus/3197011]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.gen_uuid(case)
|
||||||
|
local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||||||
|
|
||||||
|
-- seed with os.time in seconds and add an extra degree of random for multiple calls in the same second
|
||||||
|
math.randomseed(os.time(), math.random(0, 65536))
|
||||||
|
|
||||||
|
local uuid = string.gsub(template, '[xy]', function (c)
|
||||||
|
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
|
||||||
|
return string.format('%x', v)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
if case and case == "upper" then
|
||||||
|
uuid = string.upper(uuid)
|
||||||
|
end
|
||||||
|
|
||||||
|
return uuid
|
||||||
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["compare_api_versions"] = {
|
||||||
|
Name = [[compare_api_versions]],
|
||||||
|
Synopsis = [[compare two API version strings]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
local result = du.compare_api_versions(version1, version2)
|
||||||
|
version1 - string - the first version string to compare (example: "5.0.0")
|
||||||
|
version2 - string - the second version string to compare (example: "5.1.0")]],
|
||||||
|
Description = [[compare_api_versions compares two version strings and returns 1 if version1 is greater,
|
||||||
|
-1 if version2 is greater, and 0 if they are equal.]],
|
||||||
|
Return_Value = [[result - 1 if version1 is greater, -1 if version2 is greater, 0 if they are equal.]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[compare_api_versions("5.0.0", "5.1.0") returns -1]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.compare_api_versions(version1, version2)
|
||||||
|
local v1 = {}
|
||||||
|
for num in version1:gmatch("%d+") do table.insert(v1, tonumber(num)) end
|
||||||
|
local v2 = {}
|
||||||
|
for num in version2:gmatch("%d+") do table.insert(v2, tonumber(num)) end
|
||||||
|
|
||||||
|
for i = 1, math.max(#v1, #v2) do
|
||||||
|
local num1 = v1[i] or 0
|
||||||
|
local num2 = v2[i] or 0
|
||||||
|
if num1 > num2 then
|
||||||
|
return 1
|
||||||
|
elseif num1 < num2 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
return dtutils
|
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
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -191,7 +188,7 @@ end
|
|||||||
|
|
||||||
local function _search_for_bin_nix(bin)
|
local function _search_for_bin_nix(bin)
|
||||||
local result = false
|
local result = false
|
||||||
local p = io.popen("which " .. bin)
|
local p = io.popen("command -v " .. bin)
|
||||||
local output = p:read("*a")
|
local output = p:read("*a")
|
||||||
p:close()
|
p:close()
|
||||||
if string.len(output) > 0 then
|
if string.len(output) > 0 then
|
||||||
@@ -394,125 +391,32 @@ function dtutils_file.check_if_bin_exists(bin)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
dtutils_file.libdoc.functions["split_filepath"] = {
|
-- the following path, filename, etc functions have
|
||||||
Name = [[split_filepath]],
|
-- moved to the string library since they are string
|
||||||
Synopsis = [[split a filepath into parts]],
|
-- manipulation functions, and to prevent circular
|
||||||
Usage = [[local df = require "lib/dtutils.file"
|
-- library inclusiion.
|
||||||
|
|
||||||
local result = df.split_filepath(filepath)
|
-- these functions are left here for compatibility
|
||||||
filepath - string - path and filename]],
|
-- with older scripts
|
||||||
Description = [[split_filepath splits a filepath into the path, filename, basename and filetype and puts
|
|
||||||
that in a table]],
|
|
||||||
Return_Value = [[result - table - a table containing the path, filename, basename, and filetype]],
|
|
||||||
Limitations = [[]],
|
|
||||||
Example = [[]],
|
|
||||||
See_Also = [[]],
|
|
||||||
Reference = [[]],
|
|
||||||
License = [[]],
|
|
||||||
Copyright = [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
function dtutils_file.split_filepath(str)
|
function dtutils_file.split_filepath(str)
|
||||||
-- strip out single quotes from quoted pathnames
|
return ds.split_filepath(str)
|
||||||
str = string.gsub(str, "'", "")
|
|
||||||
str = string.gsub(str, '"', '')
|
|
||||||
local result = {}
|
|
||||||
-- Thank you Tobias Jakobs for the awesome regular expression, which I tweaked a little
|
|
||||||
result["path"], result["filename"], result["basename"], result["filetype"] = string.match(str, "(.-)(([^\\/]-)%.?([^%.\\/]*))$")
|
|
||||||
if result["basename"] == "" and result["filetype"]:len() > 1 then
|
|
||||||
result["basename"] = result["filetype"]
|
|
||||||
result["filetype"] = ""
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dtutils_file.libdoc.functions["get_path"] = {
|
|
||||||
Name = [[get_path]],
|
|
||||||
Synopsis = [[get the path from a file path]],
|
|
||||||
Usage = [[local df = require "lib/dtutils.file"
|
|
||||||
|
|
||||||
local result = df.get_path(filepath)
|
|
||||||
filepath - string - path and filename]],
|
|
||||||
Description = [[get_path strips the filename and filetype from a path and returns the path]],
|
|
||||||
Return_Value = [[result - string - the path]],
|
|
||||||
Limitations = [[]],
|
|
||||||
Example = [[]],
|
|
||||||
See_Also = [[]],
|
|
||||||
Reference = [[]],
|
|
||||||
License = [[]],
|
|
||||||
Copyright = [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
function dtutils_file.get_path(str)
|
function dtutils_file.get_path(str)
|
||||||
local parts = dtutils_file.split_filepath(str)
|
return ds.get_path(str)
|
||||||
return parts["path"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dtutils_file.libdoc.functions["get_filename"] = {
|
|
||||||
Name = [[get_filename]],
|
|
||||||
Synopsis = [[get the filename and extension from a file path]],
|
|
||||||
Usage = [[local df = require "lib/dtutils.file"
|
|
||||||
|
|
||||||
local result = df.get_filename(filepath)
|
|
||||||
filepath - string - path and filename]],
|
|
||||||
Description = [[get_filename strips the path from a filepath and returns the filename]],
|
|
||||||
Return_Value = [[result - string - the file name and type]],
|
|
||||||
Limitations = [[]],
|
|
||||||
Example = [[]],
|
|
||||||
See_Also = [[]],
|
|
||||||
Reference = [[]],
|
|
||||||
License = [[]],
|
|
||||||
Copyright = [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
function dtutils_file.get_filename(str)
|
function dtutils_file.get_filename(str)
|
||||||
local parts = dtutils_file.split_filepath(str)
|
return ds.get_filename(str)
|
||||||
return parts["filename"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dtutils_file.libdoc.functions["get_basename"] = {
|
|
||||||
Name = [[get_basename]],
|
|
||||||
Synopsis = [[get the filename without the path or extension]],
|
|
||||||
Usage = [[local df = require "lib/dtutils.file"
|
|
||||||
|
|
||||||
local result = df.get_basename(filepath)
|
|
||||||
filepath - string - path and filename]],
|
|
||||||
Description = [[get_basename returns the name of the file without the path or filetype
|
|
||||||
]],
|
|
||||||
Return_Value = [[result - string - the basename of the file]],
|
|
||||||
Limitations = [[]],
|
|
||||||
Example = [[]],
|
|
||||||
See_Also = [[]],
|
|
||||||
Reference = [[]],
|
|
||||||
License = [[]],
|
|
||||||
Copyright = [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
function dtutils_file.get_basename(str)
|
function dtutils_file.get_basename(str)
|
||||||
local parts = dtutils_file.split_filepath(str)
|
return ds.get_basename(str)
|
||||||
return parts["basename"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dtutils_file.libdoc.functions["get_filetype"] = {
|
|
||||||
Name = [[get_filetype]],
|
|
||||||
Synopsis = [[get the filetype from a filename]],
|
|
||||||
Usage = [[local df = require "lib/dtutils.file"
|
|
||||||
|
|
||||||
local result = df.get_filetype(filepath)
|
|
||||||
filepath - string - path and filename]],
|
|
||||||
Description = [[get_filetype returns the filetype from the supplied filepath]],
|
|
||||||
Return_Value = [[result - string - the filetype]],
|
|
||||||
Limitations = [[]],
|
|
||||||
Example = [[]],
|
|
||||||
See_Also = [[]],
|
|
||||||
Reference = [[]],
|
|
||||||
License = [[]],
|
|
||||||
Copyright = [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
function dtutils_file.get_filetype(str)
|
function dtutils_file.get_filetype(str)
|
||||||
local parts = dtutils_file.split_filepath(str)
|
return ds.get_filetype(str)
|
||||||
return parts["filetype"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -615,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
|
||||||
@@ -668,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
|
||||||
@@ -908,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
|
||||||
@@ -933,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"] = {
|
||||||
@@ -954,9 +858,6 @@ dtutils_file.libdoc.functions["create_tmp_file"] = {
|
|||||||
|
|
||||||
function dtutils_file.create_tmp_file()
|
function dtutils_file.create_tmp_file()
|
||||||
local tmp_file = os.tmpname()
|
local tmp_file = os.tmpname()
|
||||||
if dt.configuration.running_os == "windows" then
|
|
||||||
tmp_file = dt.configuration.tmp_dir .. tmp_file -- windows os.tmpname() defaults to root directory
|
|
||||||
end
|
|
||||||
|
|
||||||
local f = io.open(tmp_file, "w")
|
local f = io.open(tmp_file, "w")
|
||||||
if not f then
|
if not f then
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ dtutils_log.libdoc = {
|
|||||||
|
|
||||||
print out warning, error and success messages as code is running
|
print out warning, error and success messages as code is running
|
||||||
|
|
||||||
log.log_level(debug)
|
log.log_level(log.debug)
|
||||||
|
|
||||||
print out debugging messages too because this isnt working
|
print out debugging messages too because this isnt working
|
||||||
|
|
||||||
log.log_level(info)
|
log.log_level(log.info)
|
||||||
|
|
||||||
I want to make sure this is working ok
|
I want to make sure this is working ok
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ function dtutils_log.msg(level, ...)
|
|||||||
table.remove(args, 1)
|
table.remove(args, 1)
|
||||||
end
|
end
|
||||||
local log_msg = level.label
|
local log_msg = level.label
|
||||||
if level.engine ~= dt_screen and call_level ~= 0 then
|
if level.engine ~= dt_print and call_level ~= 0 then
|
||||||
log_msg = log_msg .. dtutils_log.caller(call_level, level.caller_info) .. " "
|
log_msg = log_msg .. dtutils_log.caller(call_level, level.caller_info) .. " "
|
||||||
elseif log_msg:len() > 2 then
|
elseif log_msg:len() > 2 then
|
||||||
log_msg = log_msg .. " "
|
log_msg = log_msg .. " "
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
local dtutils_system = {}
|
local dtutils_system = {}
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
|
local ds = require "lib/dtutils.string"
|
||||||
|
|
||||||
dtutils_system.libdoc = {
|
dtutils_system.libdoc = {
|
||||||
Name = [[dtutils.system]],
|
Name = [[dtutils.system]],
|
||||||
@@ -85,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)
|
||||||
@@ -118,6 +126,7 @@ dtutils_system.libdoc.functions["launch_default_app"] = {
|
|||||||
License = [[]],
|
License = [[]],
|
||||||
Copyright = [[]],
|
Copyright = [[]],
|
||||||
}
|
}
|
||||||
|
|
||||||
function dtutils_system.launch_default_app(path)
|
function dtutils_system.launch_default_app(path)
|
||||||
local open_cmd = "xdg-open "
|
local open_cmd = "xdg-open "
|
||||||
if (dt.configuration.running_os == "windows") then
|
if (dt.configuration.running_os == "windows") then
|
||||||
@@ -128,5 +137,4 @@ function dtutils_system.launch_default_app(path)
|
|||||||
return dtutils_system.external_command(open_cmd .. path)
|
return dtutils_system.external_command(open_cmd .. path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return dtutils_system
|
return dtutils_system
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
12
locale/de_DE/LC_MESSAGES/select_non_existing.po
Normal file
12
locale/de_DE/LC_MESSAGES/select_non_existing.po
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
|
||||||
|
msgid "select non existing images"
|
||||||
|
msgstr "nicht vorhandene Bilder auswählen"
|
||||||
|
|
||||||
|
msgid "select non existing"
|
||||||
|
msgstr "nicht vorhandene auswählen"
|
||||||
|
|
||||||
|
msgid "select all non-existing images in the current images"
|
||||||
|
msgstr "alle nicht vorhandenen Bilder in den aktuellen Bildern auswählen"
|
||||||
85
locale/fr_FR/LC_MESSAGES/OpenInExplorer.po
Normal file
85
locale/fr_FR/LC_MESSAGES/OpenInExplorer.po
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: 2022-06-27 17:22+0200\n"
|
||||||
|
"PO-Revision-Date: 2022-06-27 17:36+0200\n"
|
||||||
|
"Last-Translator: Manuel PINTOR <manu@lepassepartout.fr>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
"X-Poedit-Basepath: ../../../contrib\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-SearchPath-0: OpenInExplorer.lua\n"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:79
|
||||||
|
msgid ""
|
||||||
|
"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"
|
||||||
|
msgstr ""
|
||||||
|
"Le script OpenInExplorer ne fonctionne que sur Linux, macOS et Windows pour "
|
||||||
|
"le moment"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:95
|
||||||
|
msgid ""
|
||||||
|
"No links directory selected.\n"
|
||||||
|
"Please check the dt preferences (lua options)"
|
||||||
|
msgstr ""
|
||||||
|
"Aucun répertoire de liens sélectionné.\n"
|
||||||
|
"Voir dans les préférences dt > options lua"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:99
|
||||||
|
#, lua-format
|
||||||
|
msgid ""
|
||||||
|
"Links directory '%s' not found.\n"
|
||||||
|
"Please check the dt preferences (lua options)"
|
||||||
|
msgstr ""
|
||||||
|
"Répertoire de liens '%s' inexistant.\n"
|
||||||
|
"Voir dans les préférences dt > options lua"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:156
|
||||||
|
msgid "Failed to create links. Missing rights?"
|
||||||
|
msgstr "Erreur de création de liens. Droits insuffisants ?"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:172
|
||||||
|
msgid "Please select an image"
|
||||||
|
msgstr "Sélectionner une image SVP"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:178
|
||||||
|
msgid "Please select fewer images (max. 15)"
|
||||||
|
msgstr "Sélectionner moins d'images SVP (max. 15)"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:203
|
||||||
|
msgid "show in file explorer"
|
||||||
|
msgstr "afficher dans le gestionnaire de fichiers"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:205
|
||||||
|
msgid "Open the file manager at the selected image's location"
|
||||||
|
msgstr "Ouvre le gestionnaire de fichiers à l'endroit de l'image sélectionnée"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:212
|
||||||
|
msgid "OpenInExplorer: linked files directory"
|
||||||
|
msgstr "OpenInExplorer : répertoire des fichiers liés"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:213
|
||||||
|
msgid ""
|
||||||
|
"Directory to store the links to the file names. Requires restart to take "
|
||||||
|
"effect"
|
||||||
|
msgstr ""
|
||||||
|
"Répertoire dans lequel enregistrer les liens sur les fichiers. Nécessite un "
|
||||||
|
"redémarrage de dt"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:216
|
||||||
|
msgid "Select directory"
|
||||||
|
msgstr "Sélectionner un répertoire"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:222
|
||||||
|
msgid "OpenInExplorer: use links"
|
||||||
|
msgstr "OpenInExplorer : utiliser des liens"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:223
|
||||||
|
msgid "Use links instead of multiple windows. Requires restart to take effect"
|
||||||
|
msgstr ""
|
||||||
|
"Utiliser des liens plutôt que de multiples fenêtres. Nécessite un "
|
||||||
|
"redémarrage de dt"
|
||||||
117
locale/fr_FR/LC_MESSAGES/photils.po
Normal file
117
locale/fr_FR/LC_MESSAGES/photils.po
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# French messages for photils.lua
|
||||||
|
# Christophe Agathon <christophe.agathon@gmail.com>, 2021
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: 2021-08-13 09:04+0200\n"
|
||||||
|
"PO-Revision-Date: 2021-08-13 09:49+0200\n"
|
||||||
|
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.4.1\n"
|
||||||
|
"X-Poedit-Basepath: ../../../contrib\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-SearchPath-0: photils.lua\n"
|
||||||
|
|
||||||
|
#: photils.lua:117
|
||||||
|
msgid "get tags"
|
||||||
|
msgstr "trouver des mots-clés"
|
||||||
|
|
||||||
|
#: photils.lua:148
|
||||||
|
msgid "requires a restart to be applied"
|
||||||
|
msgstr "nécessite un redémarrage pour être pris en compte"
|
||||||
|
|
||||||
|
#: photils.lua:163
|
||||||
|
msgid "min confidence value"
|
||||||
|
msgstr "niveau de confiance minimum"
|
||||||
|
|
||||||
|
#: photils.lua:180
|
||||||
|
msgid ""
|
||||||
|
"The suggested tags were not generated\n"
|
||||||
|
" for the currently selected image!"
|
||||||
|
msgstr ""
|
||||||
|
"La proposition de mots-clés n'a pas été générée\n"
|
||||||
|
" pour l'image sélectionnée actuellement !"
|
||||||
|
|
||||||
|
#: photils.lua:186
|
||||||
|
#, lua-format
|
||||||
|
msgid " page %s of %s "
|
||||||
|
msgstr " page %s de %s "
|
||||||
|
|
||||||
|
#: photils.lua:237
|
||||||
|
msgid "Apply tag to image"
|
||||||
|
msgstr "Appliquer les mots-clés à l'image"
|
||||||
|
|
||||||
|
#: photils.lua:249
|
||||||
|
msgid "Tags successfully attached to image"
|
||||||
|
msgstr "Les mots-clés ont bien été ajoutés à l'image"
|
||||||
|
|
||||||
|
#: photils.lua:299
|
||||||
|
#, lua-format
|
||||||
|
msgid "%s found %d tags for your image"
|
||||||
|
msgstr "%s a trouvé %d mots-clés pour votre image"
|
||||||
|
|
||||||
|
#: photils.lua:315
|
||||||
|
msgid "No image selected."
|
||||||
|
msgstr "Pas d'image sélectionnée."
|
||||||
|
|
||||||
|
#: photils.lua:319
|
||||||
|
msgid "This plugin can only handle a single image."
|
||||||
|
msgstr "Ce plugin ne peut gérer qu'une seule image."
|
||||||
|
|
||||||
|
#: photils.lua:326
|
||||||
|
#, lua-format
|
||||||
|
msgid "%s failed, see terminal output for details"
|
||||||
|
msgstr ""
|
||||||
|
"%s a échoué, consultez les messages sur le terminal pour plus de détails"
|
||||||
|
|
||||||
|
#: photils.lua:334
|
||||||
|
#, lua-format
|
||||||
|
msgid "no tags where found"
|
||||||
|
msgstr "aucun mot-clé n'a été trouvé"
|
||||||
|
|
||||||
|
#: photils.lua:365
|
||||||
|
#, lua-format
|
||||||
|
msgid "attach %d tags"
|
||||||
|
msgstr "ajouter %d mots-clés"
|
||||||
|
|
||||||
|
#: photils.lua:429 photils.lua:430
|
||||||
|
msgid "photils-cli not found"
|
||||||
|
msgstr "photils-cli non trouvé"
|
||||||
|
|
||||||
|
#: photils.lua:432
|
||||||
|
msgid ""
|
||||||
|
"Select an image, click \"get tags\" and get \n"
|
||||||
|
"suggestions for tags."
|
||||||
|
msgstr ""
|
||||||
|
"Sélectionnez une image, cliquez sur « trouver des mots-clés » \n"
|
||||||
|
"pour obtenir une proposition de mots-clés."
|
||||||
|
|
||||||
|
#: photils.lua:467
|
||||||
|
msgid "photils: show confidence value"
|
||||||
|
msgstr "photils: afficher le niveau de confiance"
|
||||||
|
|
||||||
|
# Original message should begin with capital I
|
||||||
|
#: photils.lua:468
|
||||||
|
msgid "if enabled, the confidence value for each tag is displayed"
|
||||||
|
msgstr "Si activé, le niveau de confiance de chaque mot-clé est affiché"
|
||||||
|
|
||||||
|
#: photils.lua:474
|
||||||
|
msgid "photils: use exported image for tag request"
|
||||||
|
msgstr "photils: utiliser l'image exportée pour la recherche de mots-clés"
|
||||||
|
|
||||||
|
#: photils.lua:475
|
||||||
|
msgid ""
|
||||||
|
"If enabled, the image passed to photils for tag suggestion is based on the "
|
||||||
|
"exported, already edited image. Otherwise, the embedded thumbnail of the RAW "
|
||||||
|
"file will be used for tag suggestion.The embedded thumbnail could speedup "
|
||||||
|
"the tag suggestion but can fail if the RAW file is not supported."
|
||||||
|
msgstr ""
|
||||||
|
"Si activé, l'image passée à photils pour la proposition de mots-clés est "
|
||||||
|
"basée sur l'export de l'image déjà éditée. Sinon, c'est la vignette "
|
||||||
|
"encapsulée dans le fichier RAW qui sera utilisée. La vignette encapsulée "
|
||||||
|
"pourra accélérer la proposition des mots-clés mais la recherche échouera si "
|
||||||
|
"le fichier RAW n'est pas supporté."
|
||||||
62
locale/it_IT/LC_MESSAGES/executable_manager.po
Normal file
62
locale/it_IT/LC_MESSAGES/executable_manager.po
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Maurizio Paglia <mpaglia0@gmail.com>, 2022.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2022-07-11 22:19+0200\n"
|
||||||
|
"PO-Revision-Date: 2022-07-11 22:21+0200\n"
|
||||||
|
"Last-Translator: Maurizio Paglia <mpaglia0@gmail.com>\n"
|
||||||
|
"Language-Team: Italian <>\n"
|
||||||
|
"Language: it\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.12.3\n"
|
||||||
|
|
||||||
|
#: executable_manager.lua:144
|
||||||
|
msgid "No executable paths found, exiting..."
|
||||||
|
msgstr "Nessun percorso con eseguibili, esco..."
|
||||||
|
|
||||||
|
#: executable_manager.lua:160
|
||||||
|
msgid "select an executable"
|
||||||
|
msgstr "seleziona un eseguibile"
|
||||||
|
|
||||||
|
#: executable_manager.lua:160
|
||||||
|
msgid "search path for executable"
|
||||||
|
msgstr "cerca il percorso dell'eseguibile"
|
||||||
|
|
||||||
|
#: executable_manager.lua:169
|
||||||
|
msgid "select "
|
||||||
|
msgstr "seleziona"
|
||||||
|
|
||||||
|
#: executable_manager.lua:169
|
||||||
|
msgid " executable"
|
||||||
|
msgstr " eseguibile"
|
||||||
|
|
||||||
|
#: executable_manager.lua:190
|
||||||
|
msgid "select executable to modify"
|
||||||
|
msgstr "Seleziona l'eseguibile da modificare"
|
||||||
|
|
||||||
|
#: executable_manager.lua:205
|
||||||
|
msgid "current"
|
||||||
|
msgstr "Attuale"
|
||||||
|
|
||||||
|
#: executable_manager.lua:207
|
||||||
|
msgid "select"
|
||||||
|
msgstr "Seleziona"
|
||||||
|
|
||||||
|
#: executable_manager.lua:209
|
||||||
|
msgid "reset"
|
||||||
|
msgstr "Cancella"
|
||||||
|
|
||||||
|
#: executable_manager.lua:212
|
||||||
|
msgid "Clear path for "
|
||||||
|
msgstr "Cancella il percorso di "
|
||||||
|
|
||||||
|
#: executable_manager.lua:211
|
||||||
|
msgid "clear"
|
||||||
|
msgstr "Pulisci"
|
||||||
190
locale/it_IT/LC_MESSAGES/script_manager.po
Normal file
190
locale/it_IT/LC_MESSAGES/script_manager.po
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# French messages for script_manager.lua
|
||||||
|
# Christophe Agathon <christophe.agathon@gmail.com>, 2020.
|
||||||
|
# Maurizio Paglia <mpaglia0@gmail.com>, 2021, 2022.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: 2020-12-28 10:22+0100\n"
|
||||||
|
"PO-Revision-Date: 2022-07-11 22:03+0200\n"
|
||||||
|
"Last-Translator: Maurizio Paglia <mpaglia0@gmail.com>\n"
|
||||||
|
"Language-Team: Italian <>\n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Lokalize 21.12.3\n"
|
||||||
|
"X-Poedit-Basepath: ../../../tools\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-KeywordsList: _\n"
|
||||||
|
"X-Poedit-SearchPath-0: script_manager.lua\n"
|
||||||
|
|
||||||
|
#: script_manager.lua:190
|
||||||
|
msgid "Cant read from "
|
||||||
|
msgstr "Non posso leggere da "
|
||||||
|
|
||||||
|
#: script_manager.lua:204
|
||||||
|
msgid "Loaded "
|
||||||
|
msgstr "Script caricato "
|
||||||
|
|
||||||
|
#: script_manager.lua:207
|
||||||
|
msgid " failed to load"
|
||||||
|
msgstr "Non riesco a caricare lo script"
|
||||||
|
|
||||||
|
#: script_manager.lua:223
|
||||||
|
msgid " will not start when darktable is restarted"
|
||||||
|
msgstr " non verrà avviato al riavvio di darktable"
|
||||||
|
|
||||||
|
#: script_manager.lua:281 script_manager.lua:345
|
||||||
|
msgid "find command is "
|
||||||
|
msgstr "Il comando find è "
|
||||||
|
|
||||||
|
#: script_manager.lua:321
|
||||||
|
msgid "lua scripts successfully updated"
|
||||||
|
msgstr "Gli script lua sono stati correttamente aggiornati"
|
||||||
|
|
||||||
|
#: script_manager.lua:381
|
||||||
|
msgid "category "
|
||||||
|
msgstr "Categoria"
|
||||||
|
|
||||||
|
#: script_manager.lua:381
|
||||||
|
msgid " is already in use. Please specify a different category name."
|
||||||
|
msgstr " è già utilizzato. Prego specificare un nome differente."
|
||||||
|
|
||||||
|
#: script_manager.lua:410
|
||||||
|
msgid "scripts successfully installed into category "
|
||||||
|
msgstr "Gli script sono stati installati correttamente nella categoria "
|
||||||
|
|
||||||
|
#: script_manager.lua:426
|
||||||
|
msgid "No scripts found to install"
|
||||||
|
msgstr "Non trovo script da installare"
|
||||||
|
|
||||||
|
#: script_manager.lua:430
|
||||||
|
msgid "failed to download scripts"
|
||||||
|
msgstr "Non riesco a scaricare gli script"
|
||||||
|
|
||||||
|
# Peut-être que « activé » serait mieux, mais ce n'est pas la traduction de « started »
|
||||||
|
#: script_manager.lua:458
|
||||||
|
msgid " started"
|
||||||
|
msgstr " avviato"
|
||||||
|
|
||||||
|
# Peut-être que « désactivé » serait mieux, mais ce n'est pas la traduction de « stopped »
|
||||||
|
#: script_manager.lua:460
|
||||||
|
msgid " stopped"
|
||||||
|
msgstr " bloccato"
|
||||||
|
|
||||||
|
#: script_manager.lua:545
|
||||||
|
msgid "Page "
|
||||||
|
msgstr "Pag."
|
||||||
|
|
||||||
|
#: script_manager.lua:545
|
||||||
|
msgid " of "
|
||||||
|
msgstr " di "
|
||||||
|
|
||||||
|
#: script_manager.lua:674
|
||||||
|
msgid "scripts to update"
|
||||||
|
msgstr "Script da aggiornare"
|
||||||
|
|
||||||
|
#: script_manager.lua:675
|
||||||
|
msgid "select the scripts installation to update"
|
||||||
|
msgstr "Selezionare l'installazione di script da aggiornare"
|
||||||
|
|
||||||
|
#: script_manager.lua:684 script_manager.lua:742
|
||||||
|
msgid "update scripts"
|
||||||
|
msgstr "Aggiornare gli script"
|
||||||
|
|
||||||
|
#: script_manager.lua:685
|
||||||
|
msgid "update the lua scripts from the repository"
|
||||||
|
msgstr "Aggiornare gli script lua del repo"
|
||||||
|
|
||||||
|
#: script_manager.lua:696
|
||||||
|
msgid ""
|
||||||
|
"enter the URL of the git repository containing the scripts you wish to add"
|
||||||
|
msgstr "Immettere l'URL del repo git che contiene gli script da aggiungere"
|
||||||
|
|
||||||
|
#: script_manager.lua:701
|
||||||
|
msgid "name of new category"
|
||||||
|
msgstr "Nome della nuova categoria"
|
||||||
|
|
||||||
|
#: script_manager.lua:702
|
||||||
|
msgid "enter a category name for the additional scripts"
|
||||||
|
msgstr "Immettere il nome della categoria per altri script"
|
||||||
|
|
||||||
|
#: script_manager.lua:707
|
||||||
|
msgid "URL to download additional scripts from"
|
||||||
|
msgstr "URL dal quale scaricare ulteriori script"
|
||||||
|
|
||||||
|
#: script_manager.lua:709
|
||||||
|
msgid "new category to place scripts in"
|
||||||
|
msgstr "Nuova categoria che dovrà contenere gli script"
|
||||||
|
|
||||||
|
#: script_manager.lua:712
|
||||||
|
msgid "install additional scripts"
|
||||||
|
msgstr "Installare gli script aggiuntivi"
|
||||||
|
|
||||||
|
#: script_manager.lua:720
|
||||||
|
msgid "Enable \"Disable Scripts\" button"
|
||||||
|
msgstr "Abilita il pulsante \"Disabilita Script\""
|
||||||
|
|
||||||
|
#: script_manager.lua:730
|
||||||
|
msgid "Disable Scripts"
|
||||||
|
msgstr "Disabilita Script"
|
||||||
|
|
||||||
|
#: script_manager.lua:736
|
||||||
|
msgid "lua scripts will not run the next time darktable is started"
|
||||||
|
msgstr "Gli script lua non verranno avviati al prossimo riavvio di darktable"
|
||||||
|
|
||||||
|
#: script_manager.lua:745
|
||||||
|
msgid "add more scripts"
|
||||||
|
msgstr "Aggiungi altri script"
|
||||||
|
|
||||||
|
#: script_manager.lua:747
|
||||||
|
msgid "disable scripts"
|
||||||
|
msgstr "Disabilita script"
|
||||||
|
|
||||||
|
#: script_manager.lua:755
|
||||||
|
msgid "category"
|
||||||
|
msgstr "categoria"
|
||||||
|
|
||||||
|
#: script_manager.lua:756
|
||||||
|
msgid "select the script category"
|
||||||
|
msgstr "Seleziona la categoria"
|
||||||
|
|
||||||
|
#: script_manager.lua:780
|
||||||
|
msgid "Page:"
|
||||||
|
msgstr "Pag.:"
|
||||||
|
|
||||||
|
#: script_manager.lua:808
|
||||||
|
msgid "Scripts"
|
||||||
|
msgstr "Scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:817
|
||||||
|
msgid "scripts per page"
|
||||||
|
msgstr "Script per pagina"
|
||||||
|
|
||||||
|
#: script_manager.lua:818
|
||||||
|
msgid "select number of start/stop buttons to display"
|
||||||
|
msgstr "Selezionare il numero di pulsanti da visualizzare"
|
||||||
|
|
||||||
|
#: script_manager.lua:829
|
||||||
|
msgid "change number of buttons"
|
||||||
|
msgstr "Modifica la quantità di pulsanti"
|
||||||
|
|
||||||
|
#: script_manager.lua:837
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr "Configurazione"
|
||||||
|
|
||||||
|
#: script_manager.lua:858
|
||||||
|
msgid "action"
|
||||||
|
msgstr "Azione"
|
||||||
|
|
||||||
|
#: script_manager.lua:864
|
||||||
|
msgid "install/update scripts"
|
||||||
|
msgstr "Installa/aggiorna script"
|
||||||
|
|
||||||
|
#: script_manager.lua:864
|
||||||
|
msgid "configure"
|
||||||
|
msgstr "Configura"
|
||||||
|
|
||||||
|
#: script_manager.lua:864
|
||||||
|
msgid "start/stop scripts"
|
||||||
|
msgstr "Avvia/blocca script"
|
||||||
46
locale/pt_BR/LC_MESSAGES/AutoGrouper.po
Normal file
46
locale/pt_BR/LC_MESSAGES/AutoGrouper.po
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 19:54-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:144
|
||||||
|
msgid "auto group"
|
||||||
|
msgstr "grupo automático"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:173
|
||||||
|
msgid "group gap [sec.]"
|
||||||
|
msgstr "intervalo de agrupamento [seg]"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:174
|
||||||
|
msgid "minimum gap, in seconds, between groups"
|
||||||
|
msgstr "intervalo mínimo, em segundos, entre os grupos"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:187
|
||||||
|
msgid "auto group: selected"
|
||||||
|
msgstr "grupo automático: selecionado"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:188
|
||||||
|
msgid "auto group selected images"
|
||||||
|
msgstr "agrupa automaticamente as imagens selecionadas"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:192
|
||||||
|
msgid "auto group: collection"
|
||||||
|
msgstr "grupo automático: coleção"
|
||||||
|
|
||||||
|
#: ../contrib/AutoGrouper.lua:193
|
||||||
|
msgid "auto group the entire collection"
|
||||||
|
msgstr "agrupa automaticamente toda a coleção"
|
||||||
97
locale/pt_BR/LC_MESSAGES/CollectHelper.po
Normal file
97
locale/pt_BR/LC_MESSAGES/CollectHelper.po
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 20:02-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:74
|
||||||
|
msgid "Please select a single image"
|
||||||
|
msgstr "Por favor, selecione uma única imagem"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:124
|
||||||
|
msgid "select an image with an active color label"
|
||||||
|
msgstr "selecione uma imagem com uma etiqueta de cor ativa"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:205
|
||||||
|
msgid "collect: previous"
|
||||||
|
msgstr "colecionar: anterior"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:207
|
||||||
|
msgid "Sets the Collect parameters to be the previously active parameters"
|
||||||
|
msgstr "Define os parâmetros da coleção para os ativos anteriormente"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:211
|
||||||
|
msgid "collect: folder"
|
||||||
|
msgstr "colecionar: pasta"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:213
|
||||||
|
msgid "Sets the Collect parameters to be the selected images's folder"
|
||||||
|
msgstr "Define os parâmetros da coleção para a pasta de imagens selecionada"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:218
|
||||||
|
msgid "collect: color label(s)"
|
||||||
|
msgstr "colecionar: etiqueta(s) de cor"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:220
|
||||||
|
msgid "Sets the Collect parameters to be the selected images's color label(s)"
|
||||||
|
msgstr ""
|
||||||
|
"Define os parâmetros da coleção para a(s) etiqueta(s) de cor das imagens"
|
||||||
|
" selecionadas"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:225
|
||||||
|
msgid "collect: all (AND)"
|
||||||
|
msgstr "colecionar: tudo (E)"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:227
|
||||||
|
msgid ""
|
||||||
|
"Sets the Collect parameters based on all activated CollectHelper options"
|
||||||
|
msgstr "Define os parâmetros da coleção baseado em todas as opções do script"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:234
|
||||||
|
msgid "CollectHelper: All"
|
||||||
|
msgstr "CollectHelper: Tudo"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:235
|
||||||
|
msgid ""
|
||||||
|
"Will create a collect parameter set that utilizes all enabled CollectHelper "
|
||||||
|
"types (AND)"
|
||||||
|
msgstr ""
|
||||||
|
"Criará um conjunto de parâmetros de coleção que utilizará todos os tipos "
|
||||||
|
"habilitados do CollectHelper (E)"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:240
|
||||||
|
msgid "CollectHelper: Color Label(s)"
|
||||||
|
msgstr "CollectHelper: Etiqueta(s) de Cor"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:241
|
||||||
|
msgid ""
|
||||||
|
"Enable the button that allows you to swap to a collection based on selected "
|
||||||
|
"image's COLOR LABEL(S)"
|
||||||
|
msgstr ""
|
||||||
|
"Habilita o botão que permite que você alterna para uma coleção baseada na "
|
||||||
|
"ETIQUETA(S) DE COR da imagem selecionada"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:246
|
||||||
|
msgid "CollectHelper: Folder"
|
||||||
|
msgstr "CollectHelper: Pasta"
|
||||||
|
|
||||||
|
#: ../contrib/CollectHelper.lua:247
|
||||||
|
msgid ""
|
||||||
|
"Enable the button that allows you to swap to a collection based on selected "
|
||||||
|
"image's FOLDER location"
|
||||||
|
msgstr ""
|
||||||
|
"Habilita o botão que permite que você alterne para uma coleção baseada na "
|
||||||
|
"PASTA da imagem selecionada"
|
||||||
163
locale/pt_BR/LC_MESSAGES/HDRMerge.po
Normal file
163
locale/pt_BR/LC_MESSAGES/HDRMerge.po
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-10-01 19:40-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:175
|
||||||
|
msgid "please update you binary location"
|
||||||
|
msgstr "por favor, atualize a localização do binário"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:194
|
||||||
|
msgid "update successful"
|
||||||
|
msgstr "atualização bem sucedida"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:196
|
||||||
|
msgid "update unsuccessful, please try again"
|
||||||
|
msgstr "atualização mau sucedida, tente de novo"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:210
|
||||||
|
msgid "HDRMerge install issue"
|
||||||
|
msgstr "problema com a instalação do HDRMerge"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:211
|
||||||
|
msgid "HDRMerge install issue, please ensure the binary path is proper"
|
||||||
|
msgstr ""
|
||||||
|
"problema com a instalação do HDRMerge, certifique-se de que o caminho do"
|
||||||
|
" binário esteja correto"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:216
|
||||||
|
msgid "not enough images selected, select at least 2 images to merge"
|
||||||
|
msgstr ""
|
||||||
|
"imagens selecionadas insuficientes, selecione apenas 2 imagens para mesclar"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:277
|
||||||
|
msgid "HDRMerge completed successfully"
|
||||||
|
msgstr "HDRMerge completado com sucesso"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:279 ../contrib/HDRMerge.lua:280
|
||||||
|
msgid "HDRMerge failed"
|
||||||
|
msgstr "HDRMerge falhou"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:289
|
||||||
|
msgid "HDRMerge"
|
||||||
|
msgstr "HDRMerge"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:312
|
||||||
|
msgid "HDRMerge options"
|
||||||
|
msgstr "opções do HDRMerge"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:317
|
||||||
|
msgid "bits per sample"
|
||||||
|
msgstr "bits por amostra"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:318
|
||||||
|
msgid "number of bits per sample in the output image"
|
||||||
|
msgstr "número de bits por amostra na imagem de saída"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:334
|
||||||
|
msgid "embedded preview size"
|
||||||
|
msgstr "tamanho da pré-visualização embutida"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:335
|
||||||
|
msgid "size of the embedded preview in output image"
|
||||||
|
msgstr "tamanho da pré-visualização embutida na imagem de saída"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:337 ../contrib/HDRMerge.lua:382
|
||||||
|
msgid "none"
|
||||||
|
msgstr "nenhum"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:337
|
||||||
|
msgid "half"
|
||||||
|
msgstr "metade"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:337
|
||||||
|
msgid "full"
|
||||||
|
msgstr "completo"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:349
|
||||||
|
msgid "batch mode"
|
||||||
|
msgstr "modo em lote"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:351
|
||||||
|
msgid ""
|
||||||
|
"enable batch mode operation \n"
|
||||||
|
"NOTE: resultant files will NOT be auto-imported"
|
||||||
|
msgstr ""
|
||||||
|
"ativar a operação em modo em lote\n"
|
||||||
|
"NOTA: arquivos resultantes NÃO serão importados automaticamente"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:361
|
||||||
|
msgid "batch gap [sec.]"
|
||||||
|
msgstr "intervalo do lote [seg.]"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:362
|
||||||
|
msgid "gap, in seconds, between batch mode groups"
|
||||||
|
msgstr "intervalo, em segundos, entre os grupos do modo em lote"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:376
|
||||||
|
msgid "import options"
|
||||||
|
msgstr "opções de importação"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:379
|
||||||
|
msgid "apply style on import"
|
||||||
|
msgstr "aplicar estilo ao importar"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:380
|
||||||
|
msgid "Apply selected style on auto-import to newly created image"
|
||||||
|
msgstr ""
|
||||||
|
"Aplica o estilo selecionado ao importar automaticamente para novas imagens"
|
||||||
|
" criadas"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:400
|
||||||
|
msgid "copy tags"
|
||||||
|
msgstr "copiar etiquetas"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:402
|
||||||
|
msgid "copy tags from first source image"
|
||||||
|
msgstr "copia etiquetas da primeira imagem fonte"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:409
|
||||||
|
msgid ""
|
||||||
|
"Additional tags to be added on import. Seperate with commas, all spaces will "
|
||||||
|
"be removed"
|
||||||
|
msgstr ""
|
||||||
|
"Etiquetas adicionais a serem anexadas ao importar. Separar com vírgulas,"
|
||||||
|
" todos os espaços serão removidos"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:411
|
||||||
|
msgid "Enter tags, seperated by commas"
|
||||||
|
msgstr "Insira as etiquetas, separadas por vírgulas"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:415
|
||||||
|
msgid "merge"
|
||||||
|
msgstr "mesclar"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:416
|
||||||
|
msgid "run HDRMerge with the above specified settings"
|
||||||
|
msgstr "rodar o HDRMerge com as configurações especificadas acima"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:420
|
||||||
|
msgid "Select HDRmerge executable"
|
||||||
|
msgstr "Selecionar o executável do HDRmerge"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:425
|
||||||
|
msgid "update"
|
||||||
|
msgstr "atualizar"
|
||||||
|
|
||||||
|
#: ../contrib/HDRMerge.lua:426
|
||||||
|
msgid "update the binary path with current value"
|
||||||
|
msgstr "atualiza o caminho do binário com o valor atual"
|
||||||
86
locale/pt_BR/LC_MESSAGES/OpenInExplorer.po
Normal file
86
locale/pt_BR/LC_MESSAGES/OpenInExplorer.po
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-10-01 23:40-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:79
|
||||||
|
msgid ""
|
||||||
|
"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"
|
||||||
|
msgstr ""
|
||||||
|
"Atualmente, o plugin OpenInExplorer somente suporta Linux, macOS e Windows"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:95
|
||||||
|
msgid ""
|
||||||
|
"No links directory selected.\n"
|
||||||
|
"Please check the dt preferences (lua options)"
|
||||||
|
msgstr ""
|
||||||
|
"Nenhum link para pasta selecionado.\n"
|
||||||
|
"Verifique as preferências do darktable (opções lua)"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:99
|
||||||
|
#, lua-format
|
||||||
|
msgid ""
|
||||||
|
"Links directory '%s' not found.\n"
|
||||||
|
"Please check the dt preferences (lua options)"
|
||||||
|
msgstr ""
|
||||||
|
"Links para pasta '%s' não encontrados.\n"
|
||||||
|
"Verifique as preferências do darktable (opções lua)"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:156
|
||||||
|
msgid "Failed to create links. Missing rights?"
|
||||||
|
msgstr "Falha ao criar links. Possui permissões adequadas?"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:172
|
||||||
|
msgid "Please select an image"
|
||||||
|
msgstr "Por favor, selecione uma imagem"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:178
|
||||||
|
msgid "Please select fewer images (max. 15)"
|
||||||
|
msgstr "Por favor, selecione menos imagens (máx 15)"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:203
|
||||||
|
msgid "show in file explorer"
|
||||||
|
msgstr "mostrar no navegador de arquivos"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:205
|
||||||
|
msgid "Open the file manager at the selected image's location"
|
||||||
|
msgstr "Abre o gerenciador de arquivos na localização das imagens selecionadas"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:212
|
||||||
|
msgid "OpenInExplorer: linked files directory"
|
||||||
|
msgstr "OpenInExplorer: pasta de arquivos linkada"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:213
|
||||||
|
msgid ""
|
||||||
|
"Directory to store the links to the file names. Requires restart to take "
|
||||||
|
"effect"
|
||||||
|
msgstr ""
|
||||||
|
"Pasta para armazenar os links para os nomes de arquivo. Precisa reiniciar"
|
||||||
|
" para ter efeito"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:216
|
||||||
|
msgid "Select directory"
|
||||||
|
msgstr "Selecione a pasta"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:222
|
||||||
|
msgid "OpenInExplorer: use links"
|
||||||
|
msgstr "OpenInExplorer: usar links"
|
||||||
|
|
||||||
|
#: ../contrib/OpenInExplorer.lua:223
|
||||||
|
msgid "Use links instead of multiple windows. Requires restart to take effect"
|
||||||
|
msgstr ""
|
||||||
|
"Usa links ao invés de janelas múltiplas. Precisa reiniciar para ter efeito"
|
||||||
74
locale/pt_BR/LC_MESSAGES/RL_out_sharp.po
Normal file
74
locale/pt_BR/LC_MESSAGES/RL_out_sharp.po
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-10-02 05:31-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:119
|
||||||
|
msgid "GMic executable not configured"
|
||||||
|
msgstr "executável GMic não configurado"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:139
|
||||||
|
msgid "sharpening image "
|
||||||
|
msgstr "melhorar nitidez da imagem "
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:154
|
||||||
|
msgid "sharpening error"
|
||||||
|
msgstr "erro ao melhorar nitidez"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:163
|
||||||
|
msgid "finished exporting"
|
||||||
|
msgstr "exportação finalizada"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:175 ../contrib/RL_out_sharp.lua:176
|
||||||
|
msgid "select output folder"
|
||||||
|
msgstr "selecionar pasta de saída"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:185
|
||||||
|
msgid "sigma"
|
||||||
|
msgstr "sigma"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:186
|
||||||
|
msgid "controls the width of the blur that's applied"
|
||||||
|
msgstr "controla a largura do desfoque que é aplicada"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:197
|
||||||
|
msgid "iterations"
|
||||||
|
msgstr "iterações"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:198
|
||||||
|
msgid "increase for better sharpening, but slower"
|
||||||
|
msgstr "aumente para melhor efeito, mas diminui velocidade"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:209
|
||||||
|
msgid "output jpg quality"
|
||||||
|
msgstr "qualidade do jpg de saída"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:210
|
||||||
|
msgid "quality of the output jpg file"
|
||||||
|
msgstr "qualidade do arquivo jpg de saída"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:229
|
||||||
|
msgid "RL output sharpen"
|
||||||
|
msgstr "melhoria de nitidez na saída RL"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:233
|
||||||
|
msgid "executable for GMic CLI"
|
||||||
|
msgstr "executável para o GMic CLI"
|
||||||
|
|
||||||
|
#: ../contrib/RL_out_sharp.lua:234
|
||||||
|
msgid "select executable for GMic command line version"
|
||||||
|
msgstr "selecione o executável para a versão de linha de comando do GMic"
|
||||||
30
locale/pt_BR/LC_MESSAGES/clear_GPS.po
Normal file
30
locale/pt_BR/LC_MESSAGES/clear_GPS.po
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 19:53-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/clear_GPS.lua:81
|
||||||
|
msgid "clear GPS data"
|
||||||
|
msgstr "limpar dados de GPS"
|
||||||
|
|
||||||
|
#: ../contrib/clear_GPS.lua:83
|
||||||
|
msgid "Clear GPS data from selected images"
|
||||||
|
msgstr "Limpa os dados de GPS das imagens selecionadas"
|
||||||
|
|
||||||
|
#: ../contrib/clear_GPS.lua:89
|
||||||
|
msgid "Clear GPS data"
|
||||||
|
msgstr "Limpar dados de GPS"
|
||||||
90
locale/pt_BR/LC_MESSAGES/color_profile_manager.po
Normal file
90
locale/pt_BR/LC_MESSAGES/color_profile_manager.po
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-30 19:05-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:122
|
||||||
|
msgid "added color profile "
|
||||||
|
msgstr "perfil de cor adicionado "
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:128
|
||||||
|
msgid "removed color profile "
|
||||||
|
msgstr "perfil de cor removido "
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:146
|
||||||
|
msgid "ERROR: color profile must be an icc or icm file"
|
||||||
|
msgstr "ERRO: perfil de cor deve ser um arquivo icm ou icc"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:229
|
||||||
|
msgid "color profile manager"
|
||||||
|
msgstr "gerenciador de perfil de cor"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:270
|
||||||
|
msgid "initialize color profiles"
|
||||||
|
msgstr "inicializar perfis de cor"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:271
|
||||||
|
msgid "create the directory structure to contain the color profiles"
|
||||||
|
msgstr "cria a estrutura de pasta para conter os perfis de cor"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:286
|
||||||
|
msgid "select profile set"
|
||||||
|
msgstr "selecionar definição de perfil"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:287
|
||||||
|
msgid "select input or output profiles"
|
||||||
|
msgstr "selecionar perfis de entrada ou saída"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:294
|
||||||
|
msgid "input"
|
||||||
|
msgstr "entrada"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:294
|
||||||
|
msgid "output"
|
||||||
|
msgstr "saída"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:298
|
||||||
|
msgid "select color profile to add"
|
||||||
|
msgstr "selecionar perfil de cor a adicionar"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:299
|
||||||
|
msgid "select the .icc or .icm file to add"
|
||||||
|
msgstr "selecionar arquivo icc ou icm a adicionar"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:306
|
||||||
|
msgid "add profile"
|
||||||
|
msgstr "adicionar perfil"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:309
|
||||||
|
msgid "add selected color profile"
|
||||||
|
msgstr "adicionar perfil de cor selecionado"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:310
|
||||||
|
msgid "add selected file to profiles"
|
||||||
|
msgstr "adicionar arquivo selecionado aos perfis"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:320
|
||||||
|
msgid "remove profile"
|
||||||
|
msgstr "remover perfil"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:323
|
||||||
|
msgid "remove selected profile(s)"
|
||||||
|
msgstr "remover perfil(s) selecionado(s)"
|
||||||
|
|
||||||
|
#: ../contrib/color_profile_manager.lua:324
|
||||||
|
msgid "remove the checked profile(s)"
|
||||||
|
msgstr "remover o(s) perfil(s) marcado(s)"
|
||||||
86
locale/pt_BR/LC_MESSAGES/copy_attach_detach_tags.po
Normal file
86
locale/pt_BR/LC_MESSAGES/copy_attach_detach_tags.po
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 19:52-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:101
|
||||||
|
msgid "Image tags copied ..."
|
||||||
|
msgstr "Etiquetas de imagem copiadas ..."
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:123
|
||||||
|
msgid "No tag to attach, please copy tags first."
|
||||||
|
msgstr "Nenhuma etiqueta anexada, por favor copie as etiquetas primeiro."
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:147
|
||||||
|
msgid "Tags attached ..."
|
||||||
|
msgstr "Etiquetas anexadas ..."
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:163
|
||||||
|
msgid "Tags removed from image(s)."
|
||||||
|
msgstr "Etiquetas removidas da(s) imagem(ns) "
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:169
|
||||||
|
msgid "Tags replaced"
|
||||||
|
msgstr "Etiquetas substituídas"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:174
|
||||||
|
msgid "tagging addon"
|
||||||
|
msgstr "addon de etiquetas"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:204
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:241
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:294
|
||||||
|
msgid "copy tags from selected image(s)"
|
||||||
|
msgstr "copiar etiquetas da(s) imagem(ns) selecionada(s)"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:209
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:244
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:299
|
||||||
|
msgid "paste tags to selected image(s)"
|
||||||
|
msgstr "colar etiquetas para a(s) imagem(ns) selecionada(s)"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:214
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:257
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:304
|
||||||
|
msgid "remove tags from selected image(s)"
|
||||||
|
msgstr "remover etiquetas da(s) imagem(ns) selecionada(s)"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:219
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:253
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:309
|
||||||
|
msgid "replace tags from selected image(s)"
|
||||||
|
msgstr "substituir etiquetas a partir da(s) imagem(ns) selecionada(s)"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:232
|
||||||
|
msgid "tag clipboard"
|
||||||
|
msgstr "área de transferência de etiqueta"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:240
|
||||||
|
msgid "multi copy tags"
|
||||||
|
msgstr "copiar etiquetas múltiplas"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:245
|
||||||
|
msgid "paste tags"
|
||||||
|
msgstr "colar etiquetas"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:252
|
||||||
|
msgid "replace tags"
|
||||||
|
msgstr "substituir etiquetas"
|
||||||
|
|
||||||
|
#: ../contrib/copy_attach_detach_tags.lua:256
|
||||||
|
msgid "remove all tags"
|
||||||
|
msgstr "remover todas as etiquetas"
|
||||||
34
locale/pt_BR/LC_MESSAGES/copy_paste_metadata.po
Normal file
34
locale/pt_BR/LC_MESSAGES/copy_paste_metadata.po
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 14:57-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 19:38-0300\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:131
|
||||||
|
msgid "copy metadata"
|
||||||
|
msgstr "copiar metadados"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:133
|
||||||
|
msgid "copy metadata of the first selected image"
|
||||||
|
msgstr "copiar metadados da primeira imagem selecionada"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:139
|
||||||
|
msgid "paste metadata"
|
||||||
|
msgstr "colar metadados"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:141
|
||||||
|
msgid "paste metadata to the selected images"
|
||||||
|
msgstr "colar metadados para as imagens selecionadas"
|
||||||
61
locale/pt_BR/LC_MESSAGES/enfuse.po
Normal file
61
locale/pt_BR/LC_MESSAGES/enfuse.po
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 15:19-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 19:39-0300\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: enfuse.lua:187
|
||||||
|
#, lua-format
|
||||||
|
msgid "Error writing to `%s`"
|
||||||
|
msgstr "Erro ao salvar `%s`"
|
||||||
|
|
||||||
|
#: enfuse.lua:210
|
||||||
|
#, lua-format
|
||||||
|
msgid "Converting raw file '%s' to tiff..."
|
||||||
|
msgstr "Converter arquivo raw `%s` para tiff..."
|
||||||
|
|
||||||
|
#: enfuse.lua:220
|
||||||
|
#, lua-format
|
||||||
|
msgid "Skipping %s..."
|
||||||
|
msgstr "Pulando %s..."
|
||||||
|
|
||||||
|
#: enfuse.lua:227
|
||||||
|
msgid "No suitable images selected, nothing to do for enfuse"
|
||||||
|
msgstr "Nenhuma imagem adequada selecionado, nada a fazer para o script"
|
||||||
|
|
||||||
|
#: enfuse.lua:233
|
||||||
|
#, lua-format
|
||||||
|
msgid "%d image(s) skipped"
|
||||||
|
msgstr "%d imagem(ns) pulada(s)"
|
||||||
|
|
||||||
|
#: enfuse.lua:253
|
||||||
|
msgid "Enfuse failed, see terminal output for details"
|
||||||
|
msgstr "Script falhou, veja a saída do terminal para detalhes"
|
||||||
|
|
||||||
|
#: enfuse.lua:265
|
||||||
|
msgid "enfuse was successful, resulting image has been imported"
|
||||||
|
msgstr "script foi bem sucedido, imagem resultante foi importada"
|
||||||
|
|
||||||
|
#: enfuse.lua:267
|
||||||
|
#, lua-format
|
||||||
|
msgid "enfuse: done, resulting image '%s' has been imported with id %d"
|
||||||
|
msgstr "enfuse: feito, imagem '%s' resultante foi importada com o id %d"
|
||||||
|
|
||||||
|
#: enfuse.lua:301
|
||||||
|
msgid "Could not find enfuse executable. Not loading enfuse exporter..."
|
||||||
|
msgstr ""
|
||||||
|
"Não foi possível encontrar o executável do enfuse. Exportador do enfuse não"
|
||||||
|
" carregado..."
|
||||||
555
locale/pt_BR/LC_MESSAGES/enfuseAdvanced.po
Normal file
555
locale/pt_BR/LC_MESSAGES/enfuseAdvanced.po
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-10-01 19:03-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:250
|
||||||
|
msgid "please update your binary locations"
|
||||||
|
msgstr "por favor, atualiza suas localizações de binários"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:262
|
||||||
|
msgid "issue with "
|
||||||
|
msgstr "problema com "
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:262
|
||||||
|
msgid " executable"
|
||||||
|
msgstr " executável"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:271
|
||||||
|
msgid "update successful"
|
||||||
|
msgstr "atualização bem sucedida"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:273
|
||||||
|
msgid "update unsuccessful, please try again"
|
||||||
|
msgstr "atualização mau sucedida, tente novamente"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:379
|
||||||
|
msgid "saved to "
|
||||||
|
msgstr "salvar para "
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:395
|
||||||
|
msgid "loaded from "
|
||||||
|
msgstr "carregado de "
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:430
|
||||||
|
msgid "export for image fusion "
|
||||||
|
msgstr "exportar para fusão de imagem "
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:435
|
||||||
|
msgid "too few images selected, please select at least 2 images"
|
||||||
|
msgstr "muito poucas imagens selecionadas, selecione pelo menos 2 imagens"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:438
|
||||||
|
msgid "installation error, please verify binary paths are proper"
|
||||||
|
msgstr ""
|
||||||
|
"erro de instalação, verifique se as localizações dos binários estão corretas"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:458 ../contrib/enfuseAdvanced.lua:459
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:485 ../contrib/enfuseAdvanced.lua:486
|
||||||
|
msgid " failed"
|
||||||
|
msgstr " falhou"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:534
|
||||||
|
msgid "image align options"
|
||||||
|
msgstr "opções de alinhamento de imagem"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:537
|
||||||
|
msgid "align images"
|
||||||
|
msgstr "alinhar imagens"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:539
|
||||||
|
msgid "automatically align images prior to enfuse"
|
||||||
|
msgstr "alinha automaticamente as imagens antes de fundir"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:549
|
||||||
|
msgid "optimize radial distortion"
|
||||||
|
msgstr "otimizar distorção radial"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:551
|
||||||
|
msgid ""
|
||||||
|
"optimize radial distortion for all images, \n"
|
||||||
|
"except for first"
|
||||||
|
msgstr ""
|
||||||
|
"otimiza a distorção radial para todas as imagens, \n"
|
||||||
|
"exceto para a primeira"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:556
|
||||||
|
msgid "optimize field of view"
|
||||||
|
msgstr "otimizar o campo de visão"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:558
|
||||||
|
msgid ""
|
||||||
|
"optimize field of view for all images, except for first. \n"
|
||||||
|
"Useful for aligning focus stacks (DFF) with slightly \n"
|
||||||
|
"different magnification."
|
||||||
|
msgstr ""
|
||||||
|
"otimizar o campo de visão para todas as imagens, exceto para a primeira. \n"
|
||||||
|
"Útil para alinhar foco empilhado (DFF, sigla em inglês) com ampliação \n"
|
||||||
|
"ligeiramente diferente."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:563
|
||||||
|
msgid "optimize image center shift"
|
||||||
|
msgstr "otimizar deslocamento do centro da imagem"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:565
|
||||||
|
msgid ""
|
||||||
|
"optimize image center shift for all images, \n"
|
||||||
|
"except for first."
|
||||||
|
msgstr ""
|
||||||
|
"otimiza o deslocamento do centro da imagem para\n"
|
||||||
|
"todas as imagens, exceto a primeira."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:570
|
||||||
|
msgid "auto crop"
|
||||||
|
msgstr "recorte automático"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:572
|
||||||
|
msgid "auto crop the image to the area covered by all images."
|
||||||
|
msgstr ""
|
||||||
|
"recorta automaticamente a imagem para a área coberta por todas\n"
|
||||||
|
"as imagens."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:577
|
||||||
|
msgid "load distortion from lens database"
|
||||||
|
msgstr "carregar distorção a partir da base de dados de lentes"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:579
|
||||||
|
msgid "try to load distortion information from lens database"
|
||||||
|
msgstr "tenta carregar informações de distorção da base de dados de lentes"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:584
|
||||||
|
msgid "use gpu"
|
||||||
|
msgstr "usar gpu"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:586
|
||||||
|
msgid "use gpu during alignment"
|
||||||
|
msgstr "usa a gpu durante o alinhamento"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:593
|
||||||
|
msgid "image grid size"
|
||||||
|
msgstr "tamanho da grade de imagem"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:594
|
||||||
|
msgid ""
|
||||||
|
"break image into a rectangular grid \n"
|
||||||
|
"and attempt to find num control points in each section.\n"
|
||||||
|
"default: (5x5)"
|
||||||
|
msgstr ""
|
||||||
|
"quebra a imagem em uma grade retangular e \n"
|
||||||
|
"tenta encontrar um número de pontos de controle\n"
|
||||||
|
"em cada seção.\n"
|
||||||
|
"padrão: (5x5)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:610
|
||||||
|
msgid "control points/grid"
|
||||||
|
msgstr "pontos de controle/grade"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:611
|
||||||
|
msgid ""
|
||||||
|
"number of control points (per grid, see option -g) \n"
|
||||||
|
"to create between adjacent images \n"
|
||||||
|
"default: (8)."
|
||||||
|
msgstr ""
|
||||||
|
"número de pontos de controle (por grade, veja a opção -g)\n"
|
||||||
|
"para criar entre imagens adjacentes \n"
|
||||||
|
"padrão: (8)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:627
|
||||||
|
msgid "remove control points with error"
|
||||||
|
msgstr "remover pontos de controle com erro"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:628
|
||||||
|
msgid ""
|
||||||
|
"remove all control points with an error higher \n"
|
||||||
|
"than num pixels \n"
|
||||||
|
"default: (3)"
|
||||||
|
msgstr ""
|
||||||
|
"remove todos os pontos de controle com um erro superior\n"
|
||||||
|
"que um número de pixels\n"
|
||||||
|
"padrão: (3)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:644
|
||||||
|
msgid "correlation threshold for control points"
|
||||||
|
msgstr "limiar de correlação para pontos de controle"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:645
|
||||||
|
msgid ""
|
||||||
|
"correlation threshold for identifying \n"
|
||||||
|
"control points \n"
|
||||||
|
"default: (0.9)."
|
||||||
|
msgstr ""
|
||||||
|
"limiar de correlação para identificar pontos\n"
|
||||||
|
"de controle\n"
|
||||||
|
"padrão: (0,9)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:659
|
||||||
|
msgid "image fusion options"
|
||||||
|
msgstr "opções de fusão de imagem"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:664
|
||||||
|
msgid "exposure weight"
|
||||||
|
msgstr "peso da exposição"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:665
|
||||||
|
msgid ""
|
||||||
|
"set the relative weight of the well-exposedness criterion \n"
|
||||||
|
"as defined by the chosen exposure weight function. \n"
|
||||||
|
"increasing this weight relative to the others will\n"
|
||||||
|
" make well-exposed pixels contribute more to\n"
|
||||||
|
" the final output. \n"
|
||||||
|
"default: (1.0)"
|
||||||
|
msgstr ""
|
||||||
|
"configura o peso relativo do critério de boa exposição como\n"
|
||||||
|
"definido pela função de peso de exposição.\n"
|
||||||
|
"aumentar este peso relativo para outros fará com que\n"
|
||||||
|
"os pixels bem expostos contribuam mais para a saída\n"
|
||||||
|
"final.\n"
|
||||||
|
"padrão: (1,0)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:676
|
||||||
|
msgid "saturation weight"
|
||||||
|
msgstr "peso da saturação"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:677
|
||||||
|
msgid ""
|
||||||
|
"set the relative weight of high-saturation pixels. \n"
|
||||||
|
"increasing this weight makes pixels with high \n"
|
||||||
|
"saturation contribute more to the final output. \n"
|
||||||
|
"default: (0.2)"
|
||||||
|
msgstr ""
|
||||||
|
"configura o peso relativo dos pixels altamente saturados.\n"
|
||||||
|
"aumentar este peso fará com que os pixels com alta saturação\n"
|
||||||
|
"contribuam mais para a saída final. "
|
||||||
|
"padrão: (0,2)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:688
|
||||||
|
msgid "contrast weight"
|
||||||
|
msgstr "peso do contraste"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:689
|
||||||
|
msgid ""
|
||||||
|
"sets the relative weight of high local-contrast pixels. \n"
|
||||||
|
"default: (0.0)."
|
||||||
|
msgstr ""
|
||||||
|
"configura o peso relativo dos pixels com alto contraste local.\n"
|
||||||
|
"padrão: (0,0)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:700
|
||||||
|
msgid "exposure optimum"
|
||||||
|
msgstr "exposição ótima"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:701
|
||||||
|
msgid ""
|
||||||
|
"determine at what normalized exposure value\n"
|
||||||
|
" the optimum exposure of the input images\n"
|
||||||
|
" is. this is, set the position of the maximum\n"
|
||||||
|
" of the exposure weight curve. use this \n"
|
||||||
|
"option to fine-tune exposure weighting. \n"
|
||||||
|
"default: (0.5)"
|
||||||
|
msgstr ""
|
||||||
|
"determina qual valor de exposição normalizado\n"
|
||||||
|
"é a exposição ótima da imagem de entrada.\n"
|
||||||
|
"isto é, define a posição do máximo de exposição\n"
|
||||||
|
"da curva de peso. use esta opção para ajustar o\n"
|
||||||
|
"peso da exposição.\n"
|
||||||
|
"padrão: (0,5)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:712
|
||||||
|
msgid "exposure width"
|
||||||
|
msgstr "largura da exposição"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:713
|
||||||
|
msgid ""
|
||||||
|
"set the characteristic width (FWHM) of the exposure \n"
|
||||||
|
"weight function. low numbers give less weight to \n"
|
||||||
|
"pixels that are far from the user-defined \n"
|
||||||
|
"optimum and vice versa. use this option to \n"
|
||||||
|
"fine-tune exposure weighting. \n"
|
||||||
|
"default: (0.2)"
|
||||||
|
msgstr ""
|
||||||
|
"configura a largura característica (FWHM) da função de peso\n"
|
||||||
|
"da exposição. números baixos atribuem menos peso para os\n"
|
||||||
|
"pixels que estão longe do ótimo definido pelo usuário e vice-\n"
|
||||||
|
"versa. use esta opção para ajustar o peso da exposição.\n"
|
||||||
|
"padrão: (0,2)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:722
|
||||||
|
msgid "hard mask"
|
||||||
|
msgstr "máscara dura"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:724
|
||||||
|
msgid ""
|
||||||
|
"force hard blend masks on the finest scale. this avoids \n"
|
||||||
|
"averaging of fine details (only), at the expense \n"
|
||||||
|
"of increasing the noise. this improves the \n"
|
||||||
|
"sharpness of focus stacks considerably.\n"
|
||||||
|
"default (soft mask)"
|
||||||
|
msgstr ""
|
||||||
|
"força máscaras de mesclagem duras na escala mais fina. isto\n"
|
||||||
|
"previne aproximação dos detalhes mais finos (somente), ao\n"
|
||||||
|
"custo de aumentar o ruído. isto melhora a nitidez do empilha-\n"
|
||||||
|
"mento de foco consideravelmente.\n"
|
||||||
|
"padrão (máscara suave)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:729
|
||||||
|
msgid "save masks"
|
||||||
|
msgstr "salvar máscaras"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:731
|
||||||
|
msgid ""
|
||||||
|
"Save the generated weight masks to your home directory,\n"
|
||||||
|
"enblend saves masks as 8 bit grayscale, \n"
|
||||||
|
"i.e. single channel images. \n"
|
||||||
|
"for accuracy we recommend to choose a lossless format."
|
||||||
|
msgstr ""
|
||||||
|
"Salva as máscaras de peso geradas para sua pasta pessoal,\n"
|
||||||
|
"mescla máscaras salvas como tons de cinza de 8 bits, isto é,\n"
|
||||||
|
"imagens de canal simples.\n"
|
||||||
|
"para maior precisão, nós recomendamos escolher um formato\n"
|
||||||
|
"sem perdas."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:738
|
||||||
|
msgid "contrast window size"
|
||||||
|
msgstr "tamanho da janela de contraste"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:739
|
||||||
|
msgid ""
|
||||||
|
"set the window size for local contrast analysis. \n"
|
||||||
|
"the window will be a square of size × size pixels. \n"
|
||||||
|
"if given an even size, Enfuse will \n"
|
||||||
|
"automatically use the next odd number.\n"
|
||||||
|
"for contrast analysis size values larger \n"
|
||||||
|
"than 5 pixels might result in a \n"
|
||||||
|
"blurry composite image. values of 3 and \n"
|
||||||
|
"5 pixels have given good results on \n"
|
||||||
|
"focus stacks. \n"
|
||||||
|
"default: (5) pixels"
|
||||||
|
msgstr ""
|
||||||
|
"configura o tamanho da janela para a análise de contraste local.\n"
|
||||||
|
"a janela será um quadrado de tamanho x tamanho em pixels.\n"
|
||||||
|
"se fornecido um tamanho par, o script usará automaticamente\n"
|
||||||
|
"o próximo número ímpar.\n"
|
||||||
|
"valores de tamanho para análise de contraste maiores que\n"
|
||||||
|
"5 pixels podem resultar em uma imagem composta borrada.\n"
|
||||||
|
"valores de 3 e 5 pixels produzem bons resultados para empi-\n"
|
||||||
|
"lhamento de foco.\n"
|
||||||
|
"padrão: (5) pixels"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:755
|
||||||
|
msgid "contrast edge scale"
|
||||||
|
msgstr "escala de borda de contraste"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:756
|
||||||
|
msgid ""
|
||||||
|
"a non-zero value for EDGE-SCALE switches on the \n"
|
||||||
|
"Laplacian-of-Gaussian (LoG) edge detection algorithm.\n"
|
||||||
|
" edage-scale is the radius of the Gaussian used \n"
|
||||||
|
"in the search for edges. a positive LCE-SCALE \n"
|
||||||
|
"turns on local contrast enhancement (LCE) \n"
|
||||||
|
"before the LoG edge detection. \n"
|
||||||
|
"Default: (0.0) pixels."
|
||||||
|
msgstr ""
|
||||||
|
"um valor diferente de zero para a ESCALA DE BORDA alterna\n"
|
||||||
|
"o algoritmo de detecção de borda Laplaciano-de-Gaussiano (LoG).\n"
|
||||||
|
"a escala de borda é o raio do Gaussiano usado na busca por bordas.\n"
|
||||||
|
"uma escala LCE positiva ativa o melhoramento de contraste local (LCE)\n"
|
||||||
|
"antes da detecção de borda LoG.\n"
|
||||||
|
"Padrão: (0,0) pixels"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:772
|
||||||
|
msgid "contrast min curvature [%]"
|
||||||
|
msgstr "curvatura mín de contraste [%]"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:773
|
||||||
|
msgid ""
|
||||||
|
"define the minimum curvature for the LoG edge detection. Append a ‘%’ to "
|
||||||
|
"specify the minimum curvature relative to maximum pixel value in the source "
|
||||||
|
"image. Default: (0.0%)"
|
||||||
|
msgstr ""
|
||||||
|
"configura a curvatura mínima para a detecção de borda LoG. Adicionar um '%'"
|
||||||
|
" para "
|
||||||
|
"definir a curvatura mínima em relação ao valor máximo de pixel na imagem"
|
||||||
|
" fonte. "
|
||||||
|
"Padrão: (0,0%)"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:787
|
||||||
|
msgid "target file"
|
||||||
|
msgstr "arquivo alvo"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:792
|
||||||
|
msgid "tiff compression"
|
||||||
|
msgstr "compressão tiff"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:793
|
||||||
|
msgid "compression method for tiff files"
|
||||||
|
msgstr "método de compressão para arquivos tiff"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:809
|
||||||
|
msgid "jpeg compression"
|
||||||
|
msgstr "compressão jpeg"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:810
|
||||||
|
msgid "jpeg compression level"
|
||||||
|
msgstr "nível de compressão jpeg"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:832
|
||||||
|
msgid "file format"
|
||||||
|
msgstr "formato de arquivo"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:833
|
||||||
|
msgid "file format of the enfused final image"
|
||||||
|
msgstr "formato de arquivo da imagem final fundida"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:853
|
||||||
|
msgid "bit depth"
|
||||||
|
msgstr "profundidade de bits"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:854
|
||||||
|
msgid "bit depth of the enfused file"
|
||||||
|
msgstr "profundidade de bits do arquivo fundido"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:868
|
||||||
|
msgid "directory"
|
||||||
|
msgstr "pasta"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:877
|
||||||
|
msgid ""
|
||||||
|
"select the target directory for the fused image. \n"
|
||||||
|
"the filename is created automatically."
|
||||||
|
msgstr ""
|
||||||
|
"seleciona a pasta alvo para a imagem fundida.\n"
|
||||||
|
"o nome do arquivo é criado automaticamente."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:882
|
||||||
|
msgid "save to source image location"
|
||||||
|
msgstr "salvar para a localização da imagem fonte"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:884
|
||||||
|
msgid ""
|
||||||
|
"If checked ignores the location above and saves output image(s) to the same "
|
||||||
|
"location as the source images."
|
||||||
|
msgstr ""
|
||||||
|
"Se selecionado, ignora a localização acima e salva a(s) imagem(ns) de saída"
|
||||||
|
" na mesma "
|
||||||
|
"localização das imagens fonte."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:891
|
||||||
|
msgid "on conflict"
|
||||||
|
msgstr "no caso de conflito"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:905
|
||||||
|
msgid "auto import"
|
||||||
|
msgstr "importar automaticamente"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:907
|
||||||
|
msgid "import the image into darktable database when enfuse completes"
|
||||||
|
msgstr ""
|
||||||
|
"importa a imagem para a base de dados do darktable quando a fusão for"
|
||||||
|
" completada"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:918
|
||||||
|
msgid "Apply Style on Import"
|
||||||
|
msgstr "Aplicar estilo ao importar"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:919
|
||||||
|
msgid "Apply selected style on auto-import to newly created blended image"
|
||||||
|
msgstr ""
|
||||||
|
"Aplica o estilo selecionado ao importar automaticamente para a nova imagem"
|
||||||
|
" fundida criada"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:938
|
||||||
|
msgid "copy tags"
|
||||||
|
msgstr "copiar etiquetas"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:940
|
||||||
|
msgid "Copy tags from first image."
|
||||||
|
msgstr "Copia as etiquetas da primeira imagem."
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:947
|
||||||
|
msgid ""
|
||||||
|
"Additional tags to be added on import. Seperate with commas, all spaces will "
|
||||||
|
"be removed"
|
||||||
|
msgstr ""
|
||||||
|
"Etiquetas adicionais a serem adicionadas ao importar. Separe-as com vírgulas,"
|
||||||
|
" todos "
|
||||||
|
"os espaços serão removidos"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:955
|
||||||
|
msgid "active preset"
|
||||||
|
msgstr "predefinição ativa"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:956
|
||||||
|
msgid "preset to be loaded from or saved to"
|
||||||
|
msgstr "predefinição a ser carregada de ou salvada para"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:970
|
||||||
|
msgid "load fusion preset"
|
||||||
|
msgstr "carregar predefinição de fusão"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:971
|
||||||
|
msgid "load current fusion parameters from selected preset"
|
||||||
|
msgstr ""
|
||||||
|
"carrega parâmetros de fusão atuais a partir da predefinição selecionada"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:975
|
||||||
|
msgid "save to fusion preset"
|
||||||
|
msgstr "salvar para predefinição de fusão"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:976
|
||||||
|
msgid "save current fusion parameters to selected preset"
|
||||||
|
msgstr "salva os parâmetros de fusão atuais para a predefinição selecionada"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:980
|
||||||
|
msgid "create image variants from presets"
|
||||||
|
msgstr "criar variantes da imagem a partir das predefinições"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:982
|
||||||
|
msgid ""
|
||||||
|
"create multiple image variants based on the three different presets of the "
|
||||||
|
"specified type"
|
||||||
|
msgstr ""
|
||||||
|
"cria múltiplas variantes da imagem baseadas nas três diferentes predefinições"
|
||||||
|
" do tipo "
|
||||||
|
"especificado"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:999
|
||||||
|
msgid "create variants type"
|
||||||
|
msgstr "criar tipos de variantes"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:1000
|
||||||
|
msgid "preset type to be used when creating image variants"
|
||||||
|
msgstr "tipo de predefinição a ser usado ao criar variantes de imagens"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:1039
|
||||||
|
msgid "update"
|
||||||
|
msgstr "atualizar"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:1040
|
||||||
|
msgid "update the binary paths with current values"
|
||||||
|
msgstr "atualiza os caminhos dos binários com os valores atuais"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:1120
|
||||||
|
msgid "show options"
|
||||||
|
msgstr "mostrar opções"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:1121
|
||||||
|
msgid "show options for specified aspect of output"
|
||||||
|
msgstr "mostra as opções para o aspecto de saída especificado"
|
||||||
|
|
||||||
|
#: ../contrib/enfuseAdvanced.lua:1142
|
||||||
|
msgid "DRI or DFF image"
|
||||||
|
msgstr "imagem DRI ou DFF"
|
||||||
58
locale/pt_BR/LC_MESSAGES/executable_manager.po
Normal file
58
locale/pt_BR/LC_MESSAGES/executable_manager.po
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 11:50-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-28 19:39-0300\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: executable_manager.lua:144
|
||||||
|
msgid "No executable paths found, exiting..."
|
||||||
|
msgstr "Caminho do executável não encontrado, saindo..."
|
||||||
|
|
||||||
|
#: executable_manager.lua:160
|
||||||
|
msgid "select an executable"
|
||||||
|
msgstr "selecione um executável"
|
||||||
|
|
||||||
|
#: executable_manager.lua:160
|
||||||
|
msgid "search path for executable"
|
||||||
|
msgstr "caminho de pesquisa por executável"
|
||||||
|
|
||||||
|
#: executable_manager.lua:169
|
||||||
|
msgid "select "
|
||||||
|
msgstr "selecionar "
|
||||||
|
|
||||||
|
#: executable_manager.lua:169
|
||||||
|
msgid " executable"
|
||||||
|
msgstr " executável"
|
||||||
|
|
||||||
|
#: executable_manager.lua:190
|
||||||
|
msgid "select executable to modify"
|
||||||
|
msgstr "selecione executável a modificar"
|
||||||
|
|
||||||
|
#: executable_manager.lua:205
|
||||||
|
msgid "current"
|
||||||
|
msgstr "atual"
|
||||||
|
|
||||||
|
#: executable_manager.lua:207
|
||||||
|
msgid "select"
|
||||||
|
msgstr "selecionar"
|
||||||
|
|
||||||
|
#: executable_manager.lua:209
|
||||||
|
msgid "reset"
|
||||||
|
msgstr "reiniciar"
|
||||||
|
|
||||||
|
#: executable_manager.lua:212
|
||||||
|
msgid "Clear path for "
|
||||||
|
msgstr "Limpar caminho para "
|
||||||
62
locale/pt_BR/LC_MESSAGES/exportLUT.po
Normal file
62
locale/pt_BR/LC_MESSAGES/exportLUT.po
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Marcus Gama <marcus.gama@gmail.com>, 2021.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2021-09-28 19:37-0300\n"
|
||||||
|
"PO-Revision-Date: 2021-09-30 19:25-0300\n"
|
||||||
|
"Last-Translator: Marcus Gama <marcus.gama@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese <darktable-dev@lists.darktable.org>\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Generator: Lokalize 21.08.1\n"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:66
|
||||||
|
msgid "Identity_file_chooser"
|
||||||
|
msgstr "Selecionar_arquiv_identidade"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:72
|
||||||
|
msgid "Export_location_chooser"
|
||||||
|
msgstr "Selecionar_localização_exportação"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:78
|
||||||
|
msgid "choose the identity haldclut file"
|
||||||
|
msgstr "selecione o arquivo halclut de identidade"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:82
|
||||||
|
msgid "choose the output location"
|
||||||
|
msgstr "selecione a localização de saída"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:86
|
||||||
|
msgid "WARNING: files may be silently overwritten"
|
||||||
|
msgstr "AVISO: os arquivos podem ser sobrescritos sem confirmação"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:108
|
||||||
|
msgid "Invalid identity lut file"
|
||||||
|
msgstr "Arquivo lut de identidade inválido"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:110
|
||||||
|
msgid "Exporting styles as haldCLUTs"
|
||||||
|
msgstr "Exportar estilos como haldCLUTs"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:129
|
||||||
|
msgid "Exported: "
|
||||||
|
msgstr "Exportado: "
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:131
|
||||||
|
msgid "Done exporting haldCLUTs"
|
||||||
|
msgstr "Exportação de haldCLUTs feita"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:141
|
||||||
|
msgid "export haldclut"
|
||||||
|
msgstr "exportar haldclut"
|
||||||
|
|
||||||
|
#: ../contrib/exportLUT.lua:166
|
||||||
|
msgid "export"
|
||||||
|
msgstr "exportar"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user