Compare commits
729 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1629ee719 | ||
|
|
b32216d94e | ||
|
|
a55de740e5 | ||
|
|
dc31b221ef | ||
|
|
34b88490c2 | ||
|
|
d9b77bb3a2 | ||
|
|
cc198d6ca1 | ||
|
|
84582cbf29 | ||
|
|
9e7a18e290 | ||
|
|
8ff81fcfa4 | ||
|
|
b649e7a840 | ||
|
|
efa06ee34c | ||
|
|
77959422e0 | ||
|
|
0bff346b05 | ||
|
|
d05c494147 | ||
|
|
9cbbae9621 | ||
|
|
fb2aade198 | ||
|
|
7ffd8bdc27 | ||
|
|
b689dbc7ee | ||
|
|
027135a257 | ||
|
|
643057deaf | ||
|
|
a00f71f533 | ||
|
|
36d6db24e7 | ||
|
|
f62a5a817f | ||
|
|
44b40b305b | ||
|
|
97dade06cf | ||
|
|
30167631e7 | ||
|
|
8235de1f0a | ||
|
|
d67ada8300 | ||
|
|
c6788e4370 | ||
|
|
d238fcee61 | ||
|
|
28d71c989f | ||
|
|
74f020c809 | ||
|
|
80769a753a | ||
|
|
427359769e | ||
|
|
5e5b073cec | ||
|
|
c771c5fc1b | ||
|
|
7cdf1d4c7f | ||
|
|
b6ea6211db | ||
|
|
4d3924939b | ||
|
|
80ff1ef0aa | ||
|
|
af362315e8 | ||
|
|
28f3e9f6a2 | ||
|
|
4f05672809 | ||
|
|
8d8f295c77 | ||
|
|
a81ab437cf | ||
|
|
a94a0c2c90 | ||
|
|
0eda1b6ac5 | ||
|
|
58bc3a69df | ||
|
|
35398c55e5 | ||
|
|
1762b372b1 | ||
|
|
1907f9a1f5 | ||
|
|
b1dd3eca5a | ||
|
|
732d2788ca | ||
|
|
04908e3f0b | ||
|
|
3ba3facad2 | ||
|
|
cb209d1d56 | ||
|
|
36533b1004 | ||
|
|
cbbf0f413f | ||
|
|
181aea5bc2 | ||
|
|
94c6dff245 | ||
|
|
7a61d77c27 | ||
|
|
2635e261a0 | ||
|
|
d197851478 | ||
|
|
6f519b3ce8 | ||
|
|
03eb79e83a | ||
|
|
fb03b49a93 | ||
|
|
c0865c37bb | ||
|
|
182a2c96c3 | ||
|
|
06522802d8 | ||
|
|
ad263fed11 | ||
|
|
ced60016fb | ||
|
|
1d6d0695fe | ||
|
|
fbe504b47a | ||
|
|
7da38b3665 | ||
|
|
08fe1ba410 | ||
|
|
088c79e140 | ||
|
|
f06eebe316 | ||
|
|
d6a1cec9fa | ||
|
|
e194ad2bc7 | ||
|
|
22e3c59d69 | ||
|
|
eb743e4b3b | ||
|
|
7a8f3afc40 | ||
|
|
2b3910c698 | ||
|
|
c584ee05c9 | ||
|
|
bb3a74feac | ||
|
|
90d2764a5a | ||
|
|
b0373f7166 | ||
|
|
c6002e7eb7 | ||
|
|
a47de82afd | ||
|
|
7f09e55525 | ||
|
|
f83ac1366a | ||
|
|
6cda27172e | ||
|
|
df78522a79 | ||
|
|
6e64d6db6f | ||
|
|
7f00cb8d6c | ||
|
|
dfeff73fcd | ||
|
|
d75f00daa0 | ||
|
|
3e430ee2be | ||
|
|
3856ae3449 | ||
|
|
d54a9f8b7e | ||
|
|
8f6cc850fb | ||
|
|
e9f18a3d36 | ||
|
|
f3a89a89bd | ||
|
|
d075fb7128 | ||
|
|
7f4bae4234 | ||
|
|
071a3527c1 | ||
|
|
22c9a7cd6e | ||
|
|
12ccefcc3b | ||
|
|
fa6014fefa | ||
|
|
e831f377bf | ||
|
|
d696e6e3f2 | ||
|
|
db505f1e0a | ||
|
|
57d15a3fa6 | ||
|
|
3c131c41d1 | ||
|
|
4d54b5eaba | ||
|
|
ffa125b134 | ||
|
|
dbf0c5d853 | ||
|
|
193a615772 | ||
|
|
056f616283 | ||
|
|
447980ddc4 | ||
|
|
8af5476f8d | ||
|
|
c95547caa7 | ||
|
|
b4d3b2901f | ||
|
|
cb6b3161d8 | ||
|
|
2d82eea237 | ||
|
|
3beee93af4 | ||
|
|
68291414f3 | ||
|
|
5662519314 | ||
|
|
28ab065293 | ||
|
|
aed3275943 | ||
|
|
d7aeb43340 | ||
|
|
ea79d10b11 | ||
|
|
e4001f984f | ||
|
|
453d744b69 | ||
|
|
b9cc0dccce | ||
|
|
84fea20ee5 | ||
|
|
e054bfc59d | ||
|
|
8cd10dce68 | ||
|
|
f8d49ccd01 | ||
|
|
25afb41924 | ||
|
|
3001a0dc1a | ||
|
|
62e932990e | ||
|
|
980fe3962c | ||
|
|
0043769946 | ||
|
|
0cb9a8872e | ||
|
|
5309ff1726 | ||
|
|
c6eee15d75 | ||
|
|
8c2d0ddf2e | ||
|
|
87d18549a6 | ||
|
|
6fdd78114f | ||
|
|
f20721373a | ||
|
|
9c0f8d63e6 | ||
|
|
ea2596ae13 | ||
|
|
a9191cf084 | ||
|
|
59b5039579 | ||
|
|
e070a1462c | ||
|
|
faa3302a37 | ||
|
|
115dbbec32 | ||
|
|
9bf7efe71f | ||
|
|
3ec9dc4963 | ||
|
|
cddb86ded0 | ||
|
|
2624338100 | ||
|
|
067fe29f76 | ||
|
|
926272b1cd | ||
|
|
363524fbaf | ||
|
|
edeceb268e | ||
|
|
252cbc96f2 | ||
|
|
b70fdc0ecf | ||
|
|
96457bc598 | ||
|
|
cd31d18075 | ||
|
|
4e140f10d1 | ||
|
|
593f8049a0 | ||
|
|
89973b3f9a | ||
|
|
deca366ce4 | ||
|
|
914a17fb4e | ||
|
|
1e33ea4192 | ||
|
|
9f3fb16161 | ||
|
|
3f064783b7 | ||
|
|
0ab4b0cd6d | ||
|
|
e2f5c993b8 | ||
|
|
3def8b43d9 | ||
|
|
7b2942ca29 | ||
|
|
bfed38f1db | ||
|
|
6d16a8778b | ||
|
|
3c67c0c693 | ||
|
|
1ba488d073 | ||
|
|
3bdbfd12bf | ||
|
|
77c405882d | ||
|
|
ecc3fb29f5 | ||
|
|
a09140c03c | ||
|
|
95e2e4924e | ||
|
|
72ce0b0f93 | ||
|
|
e4da226661 | ||
|
|
a3404b64de | ||
|
|
13ae486ab3 | ||
|
|
268aa7df42 | ||
|
|
ea7ac8e474 | ||
|
|
2a4452cd20 | ||
|
|
749db97dd5 | ||
|
|
84a1c1ddfe | ||
|
|
3791b47914 | ||
|
|
f6a1d1c20b | ||
|
|
118bbad5cb | ||
|
|
44e442e7eb | ||
|
|
08726427aa | ||
|
|
39f63cc8c5 | ||
|
|
99c3548950 | ||
|
|
7e8340c526 | ||
|
|
2e95c4837b | ||
|
|
fe6d262d9b | ||
|
|
794b12305c | ||
|
|
1a3a7f17ff | ||
|
|
496f8e704d | ||
|
|
28f87566e1 | ||
|
|
865d29fb40 | ||
|
|
1986f4e148 | ||
|
|
b963eaa38c | ||
|
|
3b520d6291 | ||
|
|
87ca8bde27 | ||
|
|
d27df0c0ed | ||
|
|
9bceb79a15 | ||
|
|
ed9f2bb0d0 | ||
|
|
b5c93e55d1 | ||
|
|
9bfda93b51 | ||
|
|
daa0877b4c | ||
|
|
da275c2a66 | ||
|
|
3fa913d68a | ||
|
|
e4e194b0ec | ||
|
|
c70ebf4297 | ||
|
|
7e3c6471ff | ||
|
|
a39f6feba7 | ||
|
|
ee7e92019a | ||
|
|
96b7ba24bc | ||
|
|
8581af29d2 | ||
|
|
ab532a724c | ||
|
|
b1e6bb8daa | ||
|
|
26e797d5e5 | ||
|
|
3941c7ab52 | ||
|
|
b7c8759af3 | ||
|
|
c5711880d3 | ||
|
|
e7dbd6e369 | ||
|
|
1b5cb1ae6e | ||
|
|
2953cf7e91 | ||
|
|
5e4ba9da5e | ||
|
|
3a1f6bbc7a | ||
|
|
21ec2883fe | ||
|
|
a0af01884d | ||
|
|
8e129d3825 | ||
|
|
d123634679 | ||
|
|
13f210bec9 | ||
|
|
8e4767c42a | ||
|
|
300bb9d443 | ||
|
|
4f8df88fec | ||
|
|
44da339202 | ||
|
|
0e37a287ca | ||
|
|
2af2530e45 | ||
|
|
99eb595f4c | ||
|
|
31cdd3f24a | ||
|
|
631d89e581 | ||
|
|
526e97a4e1 | ||
|
|
a835ab89bc | ||
|
|
da7674ca51 | ||
|
|
0e535b850c | ||
|
|
64840089e9 | ||
|
|
d754e37cf4 | ||
|
|
f6cb8091e7 | ||
|
|
e359567620 | ||
|
|
36a6a54364 | ||
|
|
ecff920537 | ||
|
|
842f2fca66 | ||
|
|
ee9384b9a7 | ||
|
|
272c35499a | ||
|
|
4165d46b33 | ||
|
|
5bed7c914c | ||
|
|
684178b27e | ||
|
|
a72abe0ad0 | ||
|
|
8863f6ade7 | ||
|
|
e0463eba83 | ||
|
|
3a2ff7a7b9 | ||
|
|
ce7ddfa472 | ||
|
|
3fb00586c3 | ||
|
|
167620fadf | ||
|
|
f0bdc890c1 | ||
|
|
abdfc9de0b | ||
|
|
c65e070a76 | ||
|
|
05c57c0247 | ||
|
|
5be3d6986e | ||
|
|
f0e3627800 | ||
|
|
ea08557a0e | ||
|
|
ac43afd8c9 | ||
|
|
ee98f96a4f | ||
|
|
a33eeddbd6 | ||
|
|
746c6a07c4 | ||
|
|
96fee48ef2 | ||
|
|
6c686128d2 | ||
|
|
6ca8f0034e | ||
|
|
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 | ||
|
|
f272aeb9fe | ||
|
|
cf26b82d87 | ||
|
|
2041fb34c9 | ||
|
|
4b63240ab9 | ||
|
|
9d59479049 | ||
|
|
350cdfa436 | ||
|
|
15de64dcf5 | ||
|
|
d9c2519a83 | ||
|
|
9885e34f82 | ||
|
|
ed0adfbae1 | ||
|
|
beafb6c21b | ||
|
|
7810791991 | ||
|
|
1d10d5234f | ||
|
|
9900f46be4 | ||
|
|
0d2c4dfb3a | ||
|
|
752e27d97f | ||
|
|
33523fbe36 | ||
|
|
db47dd5110 | ||
|
|
a71097a8c9 | ||
|
|
931e448ebd | ||
|
|
abd8bf4a60 | ||
|
|
aee1f82d04 | ||
|
|
8112472df5 | ||
|
|
c9fa13b97b | ||
|
|
73cd301ff4 | ||
|
|
aa33ae0706 | ||
|
|
4e9c45c162 | ||
|
|
f35cea75d3 | ||
|
|
4053ceb9e5 | ||
|
|
448da35327 | ||
|
|
6d9c4c90ba | ||
|
|
5b8282e933 | ||
|
|
b1498aa664 | ||
|
|
2b28f021ee | ||
|
|
dc4938c377 | ||
|
|
69b846a856 | ||
|
|
1460396d4f | ||
|
|
b9804d9d48 | ||
|
|
ade7008127 | ||
|
|
da0235ab27 | ||
|
|
c17e832572 | ||
|
|
c12d6f01e7 | ||
|
|
778ad2ce20 | ||
|
|
33ef58ac97 | ||
|
|
8091ba9398 | ||
|
|
8e0bf9e991 | ||
|
|
daee82aa6a | ||
|
|
359a1fa3a9 | ||
|
|
021b32c1d4 | ||
|
|
1d1eb6a768 | ||
|
|
a9fd0718b0 | ||
|
|
6341a0a459 | ||
|
|
952b8b2eb2 | ||
|
|
b08d7f27d2 | ||
|
|
a71889021f | ||
|
|
367631af99 | ||
|
|
62753d1a50 | ||
|
|
dfe589e843 | ||
|
|
d09574bb17 | ||
|
|
2be12e5b37 | ||
|
|
d2086b6480 | ||
|
|
132668d04c | ||
|
|
0a4aa95965 | ||
|
|
af1e774d1f | ||
|
|
e5271d79a0 | ||
|
|
dfd714540e | ||
|
|
a0fd22be19 | ||
|
|
c0dc55b18e | ||
|
|
258def1cf8 | ||
|
|
b2095fd947 | ||
|
|
e51278f76e | ||
|
|
9af60063d8 | ||
|
|
37b523f989 | ||
|
|
622a9ebab4 | ||
|
|
b26ec58f9f | ||
|
|
976ad4ab17 | ||
|
|
8dcf19834c | ||
|
|
43315bb9cc | ||
|
|
4ab20d8088 | ||
|
|
485de32bb4 | ||
|
|
536b9726b2 | ||
|
|
41ae43e741 | ||
|
|
b5b49245fa | ||
|
|
d172ac7049 | ||
|
|
8808306a5f | ||
|
|
00dac577f6 | ||
|
|
85c218b350 | ||
|
|
6f71ef4d6b | ||
|
|
394cbdb454 | ||
|
|
62e2a7969d | ||
|
|
d9c284b54d | ||
|
|
bd2bc3659c | ||
|
|
f76fa01240 | ||
|
|
45d62f418c | ||
|
|
650325257b | ||
|
|
a0a8a61d56 | ||
|
|
998732287b | ||
|
|
509c7da384 | ||
|
|
771e03ce76 | ||
|
|
7d9b8dbdda | ||
|
|
912ae93d10 | ||
|
|
b613cc266a | ||
|
|
dfb8d4e7c6 | ||
|
|
b8e3ec4302 | ||
|
|
d4e21b7877 | ||
|
|
d10cf968f1 | ||
|
|
880ec7fc58 | ||
|
|
432b6b5dd7 | ||
|
|
5f5ad426bf | ||
|
|
52acdb9036 | ||
|
|
138b23674a | ||
|
|
8a710798f7 | ||
|
|
be5557080b | ||
|
|
994b9c6472 | ||
|
|
d05536bed4 | ||
|
|
cc4f1d7251 | ||
|
|
2ddd0965cd | ||
|
|
4e68e420cc | ||
|
|
50be8a9240 | ||
|
|
d1e330eee0 | ||
|
|
6aa5893b5f | ||
|
|
797d668ebb | ||
|
|
b23595ff54 | ||
|
|
6903f8385b | ||
|
|
e8205c6435 | ||
|
|
c114856699 | ||
|
|
57a9a8495c | ||
|
|
53bb95e6fd | ||
|
|
a40fe3099d | ||
|
|
8d38ea0cf2 | ||
|
|
ac81c95f7e | ||
|
|
48b915b0b2 | ||
|
|
593917db9a | ||
|
|
2d49b49bec | ||
|
|
323c8b6845 | ||
|
|
e5ab30006b | ||
|
|
14b97cdbd9 | ||
|
|
8f0f0a5298 | ||
|
|
497dbc8eaa | ||
|
|
11baf14280 | ||
|
|
948e8690dd | ||
|
|
802c17d590 | ||
|
|
71e72b688f | ||
|
|
877b56ad67 | ||
|
|
87502fb17f | ||
|
|
ed3e141601 | ||
|
|
f66d3ed3aa | ||
|
|
5488e45c78 | ||
|
|
72bd66d3f5 | ||
|
|
1378441dfa | ||
|
|
26d97b8cdf | ||
|
|
2e27b812d6 | ||
|
|
fd97ac18da | ||
|
|
b9d14a2711 | ||
|
|
1c9042c496 | ||
|
|
60fa4b9ee0 | ||
|
|
33f91af1af | ||
|
|
05d86e89da | ||
|
|
a02176fc63 | ||
|
|
23131a301b | ||
|
|
aef7363bee | ||
|
|
4455347930 | ||
|
|
5709cfebd4 | ||
|
|
01184bee72 | ||
|
|
286ef77663 | ||
|
|
28b9073010 | ||
|
|
fe97e29654 | ||
|
|
792e782e8c | ||
|
|
33b99a97e4 | ||
|
|
a8e5a9b453 | ||
|
|
2d69536489 |
184
ChangeLog.md
Normal file
184
ChangeLog.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
## Changes from most recent to oldest
|
||||||
|
|
||||||
|
**06 Jun 2024 - wpferguson**
|
||||||
|
* fix fujifilm_ratings running on all images
|
||||||
|
* added string library functions to sanitize windows io.popen and os.execute functions
|
||||||
|
* added database maintenance script
|
||||||
|
|
||||||
|
**05 Jun 2024 - wpferguson**
|
||||||
|
* added fix for executable_manager not being visible on windows
|
||||||
|
|
||||||
|
**30 May 2024 - kkotowicz**
|
||||||
|
* open in explorer now uses applescript on macos to open multiple files
|
||||||
|
|
||||||
|
**20 May 2024 - wpferguson**
|
||||||
|
* added string variable substitution to the string library
|
||||||
|
|
||||||
|
**16 May 2024 - wpferguson**
|
||||||
|
* fix crash in script_manager
|
||||||
|
|
||||||
|
**15 May 2024 - wpferguson**
|
||||||
|
* added metadata to scripts (name, author, purpose, help url)
|
||||||
|
|
||||||
|
**06 May 2024 - christian.sueltrop**
|
||||||
|
* added passport_guide_germany script
|
||||||
|
|
||||||
|
**08 Apr 2024 - wpferguson**
|
||||||
|
* made script_manager aware of library modules in other directories besides lib
|
||||||
|
|
||||||
|
**29 Mar 2024 - wpferguson**
|
||||||
|
* updated examples/gui_action to use NaN instead of nan
|
||||||
|
|
||||||
|
**29 Mar 2024 - dterrahe**
|
||||||
|
* add lua action script example
|
||||||
|
|
||||||
|
**28 Jan 2024 - wpferguson**
|
||||||
|
* fix script_manager crash when script existed in top level lua directory
|
||||||
|
|
||||||
|
**24 Jan 2024 - wpferguson**
|
||||||
|
* don't set lib visibility unless we are in lighttable mode
|
||||||
|
|
||||||
|
**15 Jan 2024 - MStraeten**
|
||||||
|
* update x-touch.lua with support for primaries slider
|
||||||
|
|
||||||
|
**14 Jan 2024 - wpferguson**
|
||||||
|
* added cycle_group_leader script
|
||||||
|
* added hif_group_leader script
|
||||||
|
* added jpg_group_leader script
|
||||||
|
|
||||||
|
**28 Oct 2023 - ddittmar**
|
||||||
|
* Added select non existing image script
|
||||||
|
|
||||||
|
**17 Oct 2023 - wpferguson**
|
||||||
|
* script_manager wrap username in quotes to handle spaces in username
|
||||||
|
|
||||||
|
**20 Sep 2023 - wpferguson**
|
||||||
|
* script_manager explicitly set stopped scripts to false
|
||||||
|
|
||||||
|
**18 Aug 2023 - wpferguson**
|
||||||
|
* swap the position of executable_manager and script_manager due to windows gtk bug
|
||||||
|
|
||||||
|
**17 Jul 2023 - wpferguson**
|
||||||
|
* update check for update instructions
|
||||||
|
* update flatpak readme
|
||||||
|
|
||||||
|
**16 Jul 2023 - wpferguson**
|
||||||
|
* added script_data.show function to contrib/gpx_export
|
||||||
|
|
||||||
|
**15 Jul 2023 - wpferguson**
|
||||||
|
* script_manager updates
|
||||||
|
* added check_max_api_version for the case where the API no longer supports the required
|
||||||
|
functions
|
||||||
|
|
||||||
|
**14 Jul 2023 - spaceChRis**
|
||||||
|
* add tooltip to filter manager
|
||||||
|
|
||||||
|
**25 Mar 2023 - wpferguson**
|
||||||
|
* Added script_manager darktable shortcut integration
|
||||||
|
* Moved filename/path/extension string functions to string library
|
||||||
|
|
||||||
|
**06 Mar 2023 - wpferguson - Added option to disable script_manager update check**
|
||||||
|
|
||||||
|
**12 Jan 2022 - wpferguson - Documented contrib/rename_images.lua**
|
||||||
|
|
||||||
|
**28 Dec 2021 - wpferguson - Replaced deprecated _which_ command with _command -v_ in lib/dtutils/file.lua**
|
||||||
|
|
||||||
|
**12 Dec 2021 - wpferguson**
|
||||||
|
* Added deprecated function to library
|
||||||
|
* Added deprecation warning to contrib/rename-tags.lua
|
||||||
|
|
||||||
|
**22 Oct 2021 - wpferguson for Volker Bödker - make sure sequence is 4 digits in rename_images.lua**
|
||||||
|
|
||||||
|
**31 Aug 2021 - wpferguson - remove styles hiding from AutoGrouper.lua**
|
||||||
|
|
||||||
|
**02 Jul 2021 - wpferguson - merged API-7.0.0-dev branch to master**
|
||||||
|
* API-7.0.0 is darktable 3.6
|
||||||
|
* breaking changes
|
||||||
|
* register_event argments changed
|
||||||
|
* register_action arguments changed
|
||||||
|
* register_selection arguments changed
|
||||||
|
* register_event arguments changed
|
||||||
|
* scripts updated to API-7.0.0 compatibility
|
||||||
|
* script_manger updated to be API aware and check out the proper branch
|
||||||
|
based on darktable API version
|
||||||
|
|
||||||
|
**01 Jul 2021 - wpferguson - created branch for API-6.1.0 - darktable 3.4**
|
||||||
|
|
||||||
|
**20 Jun 2021 - wpferguson - created branches for older API versions**
|
||||||
|
* API-6.0.0 - darktable 3.2.1
|
||||||
|
* API-5.0.2 - darktable 3.0
|
||||||
|
* API-5.0.1 - darktable 2.6.1
|
||||||
|
* API-5.0.0 - darktable 2.4
|
||||||
|
* API-4.0.0 - darktable 2.2
|
||||||
|
* API-3.0.0 - darktable 2.0
|
||||||
|
|
||||||
|
**19 Jun 2021 - wpferguson - fix issue 312, image_path_in_ui**
|
||||||
|
|
||||||
|
**02 Jun 2021 - wpferguson - fix contrib/quicktag**
|
||||||
|
* set new entry field is_password to false so entry
|
||||||
|
is visible to user while typing.
|
||||||
|
|
||||||
|
**19 Mar 2021 - wpferguson - fixed crash in contrib/HDRmerge.lua**
|
||||||
|
* Made generated filename routine gracefully handle names that
|
||||||
|
are not in the expected format.
|
||||||
|
|
||||||
|
**15 Mar 2021 - scheckmedia - updated contrib/photils.lua**
|
||||||
|
* refactor print method
|
||||||
|
* add option to apply selected tags from a single image to multiple images
|
||||||
|
* add setting parameter to enable/disable the export of an image before tag suggestion
|
||||||
|
|
||||||
|
**25 Feb 2021 - wpferguson - added detached mode to contrib/gimp.lua**
|
||||||
|
|
||||||
|
* Added run_detached checkbox to the exporter GUI. Selecting run_detached
|
||||||
|
let's GIMP keep running and accepting additional images. It does not return
|
||||||
|
the edited images to darktable.
|
||||||
|
|
||||||
|
**24 Feb 2021 - Mark64 - make ext_editor lib visible in darkroom view**
|
||||||
|
|
||||||
|
**17 Feb 2021 - wpferguson - API 6.2.3 register_action changes**
|
||||||
|
|
||||||
|
* Added check for API version and supplied a name argument if the
|
||||||
|
API version was greater than or equal to 6.2.3
|
||||||
|
|
||||||
|
**10 Feb 2021 - wpferguson - bugfix select_untagged**
|
||||||
|
|
||||||
|
* Fixed callback to return a list of images as expected instead of
|
||||||
|
doing the selection in the callback
|
||||||
|
|
||||||
|
**10 Feb 2021 - wpferguson - bugfix API 6.2.1 compatibility**
|
||||||
|
|
||||||
|
* The inline check for API version didn't handle argument return
|
||||||
|
correctly so added a transition library with a register_event function
|
||||||
|
override to check the API version and process the arguments correctly.
|
||||||
|
|
||||||
|
**9 Feb 2021 - wpferguson - bugfix API 6.2.2 compatibility**
|
||||||
|
|
||||||
|
* The inline check for API version didn't handle argument return
|
||||||
|
correctly so changed it to a full if/else block
|
||||||
|
|
||||||
|
**4 Reb 2021 - wpferguson - API 6.2.2 compatibililty**
|
||||||
|
|
||||||
|
* Added check for API version and supplied a name argument to register_selection
|
||||||
|
if the API version was greater than or eqal to 6.2.2
|
||||||
|
|
||||||
|
**1 Feb 2021 - wpferguson - API 6.2.1 compatibility**
|
||||||
|
|
||||||
|
* Added check for API version and supplied a name argument to register_event
|
||||||
|
if the API version was greater than or eqal to 6.2.1
|
||||||
|
|
||||||
|
**21 Jan 2021 - wpferguson - Modified dtutils function find_image_by_id**
|
||||||
|
|
||||||
|
* For users with API 6.2.0 or greater - Enabled use of new API function
|
||||||
|
darktable.database_get_image() in find_image_by_id().
|
||||||
|
|
||||||
|
**19 Jan 2021 - schwerdf - Added dtutils library function find_image_by_id()**
|
||||||
|
|
||||||
|
* Added new library function to retrieve an image from the library based on it's ID instead
|
||||||
|
of it's row number in the database
|
||||||
|
|
||||||
|
**10 Jan 2021 - chrisaga - copy_attach_detach_tags localization**
|
||||||
|
|
||||||
|
**7 Jan 2021 - dtorop - add contrib/fujifilm_dynamic_range**
|
||||||
|
|
||||||
|
* add a new contrib script, fujifilm_dynamic_range to adjust exposure
|
||||||
|
based on the exposure bias camera setting
|
||||||
199
README.md
199
README.md
@@ -7,23 +7,25 @@ efforts of the darktable developers, maintainers, contributors and community. Th
|
|||||||
contained in the repository, whether they can be run by themselves (Standalone - Yes) or depend on other
|
contained in the repository, whether they can be run by themselves (Standalone - Yes) or depend on other
|
||||||
scripts (Standalone - No), what operating systems they are known to work on (L - Linux, M - MacOS, W - Windows), and their purpose.
|
scripts (Standalone - No), what operating systems they are known to work on (L - Linux, M - MacOS, W - Windows), and their purpose.
|
||||||
|
|
||||||
|
For the latest changes, see the [ChangeLog](ChangeLog.md)
|
||||||
|
|
||||||
### Official Scripts
|
### Official Scripts
|
||||||
|
|
||||||
These scripts are written primarily by the darktable developers and maintained by the authors and/or repository maintainers. They are located in the official/ directory.
|
These scripts are written primarily by the darktable developers and maintained by the authors and/or repository maintainers. They are located in the official/ directory.
|
||||||
|
|
||||||
Name|Standalone|OS |Purpose
|
Name|Standalone|OS |Purpose
|
||||||
----|:--------:|:---:|-------
|
----|:--------:|:---:|-------
|
||||||
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|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|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|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|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|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|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|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|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|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|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
|
||||||
@@ -32,36 +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|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|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|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|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|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|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|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
|
||||||
ext_editor|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors
|
[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
|
||||||
face_recognition|No|LM|Identify and tag images using facial recognition
|
[cr2hdr](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO
|
||||||
fujifilm_ratings|No|LM|Support importing Fujifilm ratings
|
cycle_group_leader|Yes|LMW|cycle through images of a group making each the group leader in turn
|
||||||
geoJSON_export|No|L|Create a geo JSON script with thumbnails for use in ...
|
dbmaint|Yes|LMW|find and remove database entries for missing film rolls and images
|
||||||
geoToolbox|No|LMW|A toolbox of geo functions
|
[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
|
||||||
gimp|No|LMW|Open an image in GIMP for editing and return the result
|
[exportLUT](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it
|
||||||
gpx_export|No|LMW|Export a GPX track file from selected images GPS data
|
[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
|
||||||
HDRMerge|No|LMW|Combind the selected images into an HDR DNG 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
|
||||||
hugin|No|LMW|Combine selected images into a panorama and return the result
|
[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
|
||||||
image_stack|No|LMW|Combine a stack of images to remove noise or transient objects
|
[fujifilm_ratings](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings
|
||||||
kml_export|No|L|Export photos with a KML file for usage in Google Earth
|
[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 ...
|
||||||
LabelsToTags|Yes|LMW|Apply tags based on color labels and ratings
|
[geoToolbox](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions
|
||||||
OpenInExplorer|No|LMW|Open the selected images in the system file manager
|
[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
|
||||||
passport_guide|Yes|LMW|Add passport cropping guide to darkroom crop tool
|
[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
|
||||||
pdf_slideshow|No|LM|Export images to a PDF slideshow
|
harmonic_armature|Yes|LMW|provide a harmonic armature guide
|
||||||
quicktag|Yes|LMW|Create shortcuts for quickly applying tags
|
hif_group_leader|Yes|LMW|change the group leader in a raw+heif image pair to the heif image
|
||||||
rate_group|Yes|LMW|Apply or remove a star rating from grouped images
|
[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
|
||||||
rename-tags|Yes|LMW|Change a tag name
|
[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
|
||||||
RL_out_sharp|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
|
[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
|
||||||
select_untagged|Yes|LMW|Enable selection of untagged images
|
[image_time](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time
|
||||||
slideshowMusic|No|L|Play music during a slideshow
|
jpg_group_leader|Yes|LMW|change the group leader for a raw+jpg pair to the jpg image
|
||||||
transfer_hierarchy|Yes|LMW|Image move/copy preserving directory hierarchy
|
[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
|
||||||
video_ffmpeg|No|LMW|Export video from darktable
|
[LabelsToTags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings
|
||||||
|
[OpenInExplorer](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager
|
||||||
|
[passport_guide](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool
|
||||||
|
passport_guide_germany|Yes|LMW|Add passport cropping guide for German passports to darkroom crop tool
|
||||||
|
[pdf_slideshow](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow
|
||||||
|
[photils](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images
|
||||||
|
[quicktag](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags
|
||||||
|
[rate_group](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images
|
||||||
|
[rename_images](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images
|
||||||
|
[rename-tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name
|
||||||
|
[RL_out_sharp](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm)
|
||||||
|
[select_non_existing](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images
|
||||||
|
[select_untagged](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images
|
||||||
|
[slideshowMusic](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow
|
||||||
|
[transfer_hierarchy](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy
|
||||||
|
[video_ffmpeg](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable
|
||||||
|
website_gallery_export|No|LMW|Export a website gallery for selected images
|
||||||
|
|
||||||
### Example Scripts
|
### Example Scripts
|
||||||
|
|
||||||
@@ -69,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|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|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|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|Yes|LMW|Prints hello world when darktable starts
|
gui_actions|Yes|LMW|demonstrate controlling the GUI using darktable.gui.action calls
|
||||||
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|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|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|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|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|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|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
|
||||||
|
|
||||||
@@ -87,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|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|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|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|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|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
|
||||||
|
|
||||||
@@ -108,6 +128,9 @@ The following third-party projects are listed for information only. Think of thi
|
|||||||
* [arru/darktable-scripts](https://github.com/arru/darktable-scripts)
|
* [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
|
||||||
|
|
||||||
@@ -116,14 +139,24 @@ are met as well as providing an easy update path. Single scripts listed as stand
|
|||||||
|
|
||||||
### snap packages
|
### snap packages
|
||||||
|
|
||||||
The snap version of darktable comes with lua included starting with version 2.4.3snap2. It is currently in the edge channel, but should reach the stable channel soon.
|
The snap version of darktable comes with lua included starting with version 2.4.3snap2.
|
||||||
|
|
||||||
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
|
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
|
||||||
|
|
||||||
cd ~/snap/darktable/current
|
cd ~/snap/darktable/current
|
||||||
git clone https://github.com/darktable-org/lua-scripts.git lua
|
git clone https://github.com/darktable-org/lua-scripts.git lua
|
||||||
|
|
||||||
### flatpak and appimage packages
|
### flatpak packages
|
||||||
|
|
||||||
|
Flatpak packages now use the internal lua interpreter.
|
||||||
|
|
||||||
|
|
||||||
|
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
|
||||||
|
|
||||||
|
cd ~/.var/app/org.darktable.Darktable/config/darktable
|
||||||
|
git clone https://github.com/darktable-org/lua-scripts.git lua
|
||||||
|
|
||||||
|
### appimage packages
|
||||||
|
|
||||||
These packages run in their own environment and don't have access to a lua interpreter, therefore the scripts can't run. The packagers could enable the internal interpreter, or allow the package to link the interpreter from the operating system, or bundle a copy of lua with the package. If you use one of these packages and wish to use the lua scripts, please contact the package maintainer and suggest the above fixes.
|
These packages run in their own environment and don't have access to a lua interpreter, therefore the scripts can't run. The packagers could enable the internal interpreter, or allow the package to link the interpreter from the operating system, or bundle a copy of lua with the package. If you use one of these packages and wish to use the lua scripts, please contact the package maintainer and suggest the above fixes.
|
||||||
|
|
||||||
@@ -138,8 +171,6 @@ Ensure git is installed on your system. If it isn't, use the package manager to
|
|||||||
|
|
||||||
Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run:
|
Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run:
|
||||||
|
|
||||||
Open a command prompt.
|
|
||||||
|
|
||||||
cd %LOCALAPPDATA%\darktable
|
cd %LOCALAPPDATA%\darktable
|
||||||
git clone https://github.com/darktable-org/lua-scripts.git lua
|
git clone https://github.com/darktable-org/lua-scripts.git lua
|
||||||
|
|
||||||
@@ -149,6 +180,24 @@ If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least o
|
|||||||
|
|
||||||
When darktable starts it looks for a file name `~/.config/darktable/luarc` (`%LOCALAPPDATA%\darktable\luarc` for windows) and reads it to see which scripts to include. The file is a plain text file with entries of the form `require "<directory>/<name>"` where directory is the directory containing the scripts, from the above list, and name is the name from the above list. To include GIMP the line would be `require "contrib/gimp"`.
|
When darktable starts it looks for a file name `~/.config/darktable/luarc` (`%LOCALAPPDATA%\darktable\luarc` for windows) and reads it to see which scripts to include. The file is a plain text file with entries of the form `require "<directory>/<name>"` where directory is the directory containing the scripts, from the above list, and name is the name from the above list. To include GIMP the line would be `require "contrib/gimp"`.
|
||||||
|
|
||||||
|
The recommended way to enable and disable specific scripts is using the [script manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/script_manager/) module. To use script manager do the following:
|
||||||
|
|
||||||
|
### Linux or MacOS
|
||||||
|
|
||||||
|
echo 'require "tools/script_manager"' > ~/.config/darktable/luarc
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
echo require "tools/script_manager" > %LOCALAPPDATA%\darktable\luarc
|
||||||
|
|
||||||
|
### Snap
|
||||||
|
|
||||||
|
echo 'require "tools/script_manager"' > ~/snap/darktable/current/luarc
|
||||||
|
|
||||||
|
### Flatpak
|
||||||
|
|
||||||
|
echo 'require "tools/script_manager"' > ~/.var/app/org.darktable.Darktable/config/darktable/luarc
|
||||||
|
|
||||||
You can also create or add lines to the luarc file from the command line:
|
You can also create or add lines to the luarc file from the command line:
|
||||||
|
|
||||||
`echo 'require "contrib/gimp"' > ~/.config/darktable/luarc` to create the file with a gimp entry\
|
`echo 'require "contrib/gimp"' > ~/.config/darktable/luarc` to create the file with a gimp entry\
|
||||||
@@ -172,6 +221,12 @@ To update the script repository, open a terminal or command prompt and do the fo
|
|||||||
cd ~/snap/darktable/current/lua
|
cd ~/snap/darktable/current/lua
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
|
|
||||||
|
### Flatpak
|
||||||
|
|
||||||
|
cd ~/.var/app/org.darktable.Darktable/config/darktable/lua
|
||||||
|
git pull
|
||||||
|
|
||||||
### Linux and MacOS
|
### Linux and MacOS
|
||||||
|
|
||||||
cd ~/.config/darktable/lua/
|
cd ~/.config/darktable/lua/
|
||||||
@@ -184,15 +239,19 @@ To update the script repository, open a terminal or command prompt and do the fo
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Each script includes its own documentation and usage in its header, please refer to them.
|
The [Lua Scripts Manual](https://docs.darktable.org/lua/stable/lua.scripts.manual/) provides documentation
|
||||||
|
for the scripts transcribed from the header comments. Each script also contains comments and usage instructions
|
||||||
|
in the header comments.
|
||||||
|
|
||||||
Lua-script libraries documentation may be generated using the tools in the tools/ directory.
|
The [Lua Scripts Library API Manual](https://docs.darktable.org/lua/stable/lua.scripts.api.manual/) provides
|
||||||
|
documentation of the libraries and functions. Lua-script libraries documentation may also be generated using
|
||||||
|
the tools in the tools/ directory.
|
||||||
|
|
||||||
More information about the scripting with Lua can be found in the darktable user manual:
|
More information about the scripting with Lua can be found in the darktable user manual:
|
||||||
https://www.darktable.org/usermanual/en/lua_chapter.html
|
[Scripting with Lua](https://darktable.org.github.io/dtdocs/lua/)
|
||||||
|
|
||||||
The darktable Lua API documentation is here:
|
The [Lua API Manual](https://docs.darktable.org/lua/stable/lua.api.manual/) provides docuemntation of the
|
||||||
https://www.darktable.org/lua-api/
|
darktable Lua API.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -202,13 +261,17 @@ Running darktable with Lua debugging enabled provides more information about wha
|
|||||||
|
|
||||||
Open a terminal and start darktable with the command `snap run darktable -d lua`. This provides debugging information to give you insight into what is happening.
|
Open a terminal and start darktable with the command `snap run darktable -d lua`. This provides debugging information to give you insight into what is happening.
|
||||||
|
|
||||||
### Linux and MacOS
|
### Linux
|
||||||
|
|
||||||
Open a terminal and start darktable with the command `darktable -d lua`. This provides debugging information to give you insight into what is happening.
|
Open a terminal and start darktable with the command `darktable -d lua`. This provides debugging information to give you insight into what is happening.
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
|
||||||
|
Open a terminal and start darktable with the command `/Applications/darktable.app/Contents/MacOS/darktable -d lua`. This provides debugging information to give you insight into what is happening.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Open the Git Bash Shell. Start darktable with the command `/c/Program\ Files/darktable/bin/darktable -d lua`. This provides debugging information to give you insight into what is happening.
|
Open a command prompt. Start darktable with the command "C:\Program Files\darktable\bin\darktable" -d lua > log.txt. This provides debugging information to give you insight into what is happening.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,158 +1,225 @@
|
|||||||
--[[AutoGrouper plugin for darktable
|
--[[AutoGrouper plugin for darktable
|
||||||
|
|
||||||
copyright (c) 2019 Kevin Ertel
|
copyright (c) 2019 Kevin Ertel
|
||||||
|
|
||||||
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
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
darktable is distributed in the hope that it will be useful,
|
darktable is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[About this Plugin
|
--[[About this Plugin
|
||||||
This plugin adds the module "Auto Group" to darktable's lighttable view
|
This plugin adds the module "Auto Group" to darktable's lighttable view
|
||||||
|
|
||||||
----REQUIRED SOFTWARE----
|
----REQUIRED SOFTWARE----
|
||||||
None
|
None
|
||||||
|
|
||||||
----USAGE----
|
----USAGE----
|
||||||
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
||||||
1) Copy this file in to your "lua/contrib" folder where all other scripts reside.
|
1) Copy this file in to your "lua/contrib" folder where all other scripts reside.
|
||||||
2) Require this file in your luarc file, as with any other dt plug-in
|
2) Require this file in your luarc file, as with any other dt plug-in
|
||||||
|
|
||||||
Set a gap amount in second which will be used to determine when images should no
|
Set a gap amount in second which will be used to determine when images should no
|
||||||
longer be added to a group. If an image is more then the specified amount of time
|
longer be added to a group. If an image is more then the specified amount of time
|
||||||
from the last image in the group it will not be added. Images without timestamps
|
from the last image in the group it will not be added. Images without timestamps
|
||||||
in exif data will be ignored.
|
in exif data will be ignored.
|
||||||
|
|
||||||
There are two buttons. One allows the grouping to be performed only on the currently
|
There are two buttons. One allows the grouping to be performed only on the currently
|
||||||
selected images, the other button performs grouping on the entire active collection
|
selected images, the other button performs grouping on the entire active collection
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local MOD = 'autogrouper'
|
local du = require "lib/dtutils"
|
||||||
local gettext = dt.gettext
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
du.check_min_api_version("7.0.0", "AutoGrouper")
|
||||||
gettext.bindtextdomain("AutoGrouper",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
local function _(msgid)
|
local MOD = 'autogrouper'
|
||||||
return gettext.dgettext("AutoGrouper", msgid)
|
|
||||||
end
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
|
local function _(msgid)
|
||||||
if test >= low and test <= high then
|
return gettext(msgid)
|
||||||
return true
|
end
|
||||||
else
|
|
||||||
return false
|
-- return data structure for script_manager
|
||||||
end
|
|
||||||
end
|
local script_data = {}
|
||||||
|
|
||||||
local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second
|
script_data.metadata = {
|
||||||
first_time = first.exif_datetime_taken
|
name = _("auto group"),
|
||||||
if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end
|
purpose = _("automatically group images by time interval"),
|
||||||
first_time = tonumber(string.gsub(first_time, '[^0-9]*',''))
|
author = "Kevin Ertel",
|
||||||
second_time = second.exif_datetime_taken
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/AutoGrouper/"
|
||||||
if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end
|
}
|
||||||
second_time = tonumber(string.gsub(second_time, '[^0-9]*',''))
|
|
||||||
return first_time < second_time
|
script_data.destroy = nil -- function to destory the script
|
||||||
end
|
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 function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
local cleaned = string.gsub(str, '[^%d]',':')
|
|
||||||
cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss
|
local Ag = {}
|
||||||
local year = string.sub(cleaned,1,4)
|
Ag.module_installed = false
|
||||||
local month = string.sub(cleaned,6,7)
|
Ag.event_registered = false
|
||||||
local day = string.sub(cleaned,9,10)
|
|
||||||
local hour = string.sub(cleaned,12,13)
|
local GUI = {
|
||||||
local min = string.sub(cleaned,15,16)
|
gap = {},
|
||||||
local sec = string.sub(cleaned,18,19)
|
selected = {},
|
||||||
return {year = year, month = month, day = day, hour = hour, min = min, sec = sec}
|
collection = {}
|
||||||
end
|
}
|
||||||
|
|
||||||
local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image
|
|
||||||
local curr_time = SeperateTime(curr_image.exif_datetime_taken)
|
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
|
||||||
local prev_time = SeperateTime(prev_image.exif_datetime_taken)
|
if test >= low and test <= high then
|
||||||
return os.time(curr_time)-os.time(prev_time)
|
return true
|
||||||
end
|
else
|
||||||
|
return false
|
||||||
local function main(on_collection)
|
end
|
||||||
local images = {}
|
end
|
||||||
if on_collection then
|
|
||||||
local col_images = dt.collection
|
local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second
|
||||||
for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection
|
first_time = first.exif_datetime_taken
|
||||||
table.insert(images,i,image)
|
if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end
|
||||||
end
|
first_time = tonumber(string.gsub(first_time, '[^0-9]*',''))
|
||||||
else
|
second_time = second.exif_datetime_taken
|
||||||
images = dt.gui.selection()
|
if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end
|
||||||
end
|
second_time = tonumber(string.gsub(second_time, '[^0-9]*',''))
|
||||||
dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value)
|
return first_time < second_time
|
||||||
if #images < 2 then
|
end
|
||||||
dt.print('please select at least 2 images')
|
|
||||||
return
|
local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations
|
||||||
end
|
local cleaned = string.gsub(str, '[^%d]',':')
|
||||||
table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp
|
cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss
|
||||||
|
local year = string.sub(cleaned,1,4)
|
||||||
for i, image in ipairs(images) do
|
local month = string.sub(cleaned,6,7)
|
||||||
if i == 1 then
|
local day = string.sub(cleaned,9,10)
|
||||||
prev_image = image
|
local hour = string.sub(cleaned,12,13)
|
||||||
elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group
|
local min = string.sub(cleaned,15,16)
|
||||||
local curr_image = image
|
local sec = string.sub(cleaned,18,19)
|
||||||
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
|
return {year = year, month = month, day = day, hour = hour, min = min, sec = sec}
|
||||||
images[i]:group_with(images[i-1])
|
end
|
||||||
end
|
|
||||||
prev_image = curr_image
|
local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image
|
||||||
end
|
local curr_time = SeperateTime(curr_image.exif_datetime_taken)
|
||||||
end
|
local prev_time = SeperateTime(prev_image.exif_datetime_taken)
|
||||||
end
|
return os.time(curr_time)-os.time(prev_time)
|
||||||
|
end
|
||||||
-- GUI --
|
|
||||||
GUI = {
|
local function main(on_collection)
|
||||||
gap = {},
|
local images = {}
|
||||||
selected = {},
|
if on_collection then
|
||||||
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
|
||||||
temp = dt.preferences.read(MOD, 'active_gap', 'integer')
|
table.insert(images,i,image)
|
||||||
if not InRange(temp, 1, 86400) then temp = 3 end
|
end
|
||||||
GUI.gap = dt.new_widget('slider'){
|
else
|
||||||
label = _('group gap [sec.]'),
|
images = dt.gui.selection()
|
||||||
tooltip = _('minimum gap, in seconds, between groups'),
|
end
|
||||||
soft_min = 1,
|
dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value)
|
||||||
soft_max = 60,
|
if #images < 2 then
|
||||||
hard_min = 1,
|
dt.print('please select at least 2 images')
|
||||||
hard_max = 86400,
|
return
|
||||||
step = 1,
|
end
|
||||||
digits = 0,
|
table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp
|
||||||
value = temp,
|
|
||||||
reset_callback = function(self)
|
for i, image in ipairs(images) do
|
||||||
self.value = 3
|
if i == 1 then
|
||||||
end
|
prev_image = image
|
||||||
}
|
image:make_group_leader()
|
||||||
GUI.selected = dt.new_widget("button"){
|
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
|
||||||
label = _('auto group: selected'),
|
local curr_image = image
|
||||||
tooltip =_('auto group selected images'),
|
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
|
||||||
clicked_callback = function() main(false) end
|
images[i]:group_with(images[i-1].group_leader)
|
||||||
}
|
end
|
||||||
GUI.collection = dt.new_widget("button"){
|
prev_image = curr_image
|
||||||
label = _('auto group: collection'),
|
end
|
||||||
tooltip =_('auto group the entire collection'),
|
end
|
||||||
clicked_callback = function() main(true) end
|
end
|
||||||
}
|
|
||||||
dt.register_lib(
|
local function install_module()
|
||||||
'AutoGroup_Lib', -- Module name
|
if not Ag.module_installed then
|
||||||
_('auto group'), -- name
|
dt.print_log("installing module")
|
||||||
true, -- expandable
|
dt.register_lib(
|
||||||
true, -- resetable
|
MOD, -- Module name
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers
|
_('auto group'), -- name
|
||||||
dt.new_widget("box"){
|
true, -- expandable
|
||||||
orientation = "vertical",
|
true, -- resetable
|
||||||
GUI.gap,
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers
|
||||||
GUI.selected,
|
dt.new_widget("box"){
|
||||||
GUI.collection
|
orientation = "vertical",
|
||||||
}
|
GUI.gap,
|
||||||
)
|
GUI.selected,
|
||||||
|
GUI.collection
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Ag.module_installed = true
|
||||||
|
dt.print_log("module installed")
|
||||||
|
dt.print_log("styles module visibility is " .. tostring(dt.gui.libs["styles"].visible))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MOD].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MOD].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- GUI --
|
||||||
|
temp = dt.preferences.read(MOD, 'active_gap', 'integer')
|
||||||
|
if not InRange(temp, 1, 86400) then temp = 3 end
|
||||||
|
GUI.gap = dt.new_widget('slider'){
|
||||||
|
label = _('group gap [sec.]'),
|
||||||
|
tooltip = _('minimum gap, in seconds, between groups'),
|
||||||
|
soft_min = 1,
|
||||||
|
soft_max = 60,
|
||||||
|
hard_min = 1,
|
||||||
|
hard_max = 86400,
|
||||||
|
step = 1,
|
||||||
|
digits = 0,
|
||||||
|
value = temp,
|
||||||
|
reset_callback = function(self)
|
||||||
|
self.value = 3
|
||||||
|
end
|
||||||
|
}
|
||||||
|
GUI.selected = dt.new_widget("button"){
|
||||||
|
label = _('auto group: selected'),
|
||||||
|
tooltip =_('auto group selected images'),
|
||||||
|
clicked_callback = function() main(false) end
|
||||||
|
}
|
||||||
|
GUI.collection = dt.new_widget("button"){
|
||||||
|
label = _('auto group: collection'),
|
||||||
|
tooltip =_('auto group the entire collection'),
|
||||||
|
clicked_callback = function() main(true) end
|
||||||
|
}
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not Ag.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"AutoGrouper", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
Ag.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -1,221 +1,258 @@
|
|||||||
--[[Collect Helper plugin for darktable
|
--[[Collect Helper plugin for darktable
|
||||||
|
|
||||||
copyright (c) 2019 Kevin Ertel
|
copyright (c) 2019 Kevin Ertel
|
||||||
|
|
||||||
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
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
darktable is distributed in the hope that it will be useful,
|
darktable is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[About this plugin
|
--[[About this plugin
|
||||||
This plugin adds the button(s) to the "Selected Images" module:
|
This plugin adds the button(s) to the "Selected Images" module:
|
||||||
1) Return to Previous Collection
|
1) Return to Previous Collection
|
||||||
2) Collect on image's Folder
|
2) Collect on image's Folder
|
||||||
3) Collect on image's Color Label(s)
|
3) Collect on image's Color Label(s)
|
||||||
4) Collect on All (AND)
|
4) Collect on All (AND)
|
||||||
|
|
||||||
It also adds 3 preferences to the lua options dialog box which allow the user to activate/deactivate the 3 "Collect on" buttons.
|
It also adds 3 preferences to the lua options dialog box which allow the user to activate/deactivate the 3 "Collect on" buttons.
|
||||||
|
|
||||||
Button Behavior:
|
Button Behavior:
|
||||||
1) Return to Previous Collection - Will reset the collect parameters to the previously active settings
|
1) Return to Previous Collection - Will reset the collect parameters to the previously active settings
|
||||||
2) Collect on image's Folder - Will change the collect parameters to be "Folder" with a value of the selected image's folder location
|
2) Collect on image's Folder - Will change the collect parameters to be "Folder" with a value of the selected image's folder location
|
||||||
3) Collect on image's Color Label(s) - Will change the collect parameter to be "Color" with a value of the selected images color labels, will apply multiple parameters with AND logic if multiple exist
|
3) Collect on image's Color Label(s) - Will change the collect parameter to be "Color" with a value of the selected images color labels, will apply multiple parameters with AND logic if multiple exist
|
||||||
4) Collect on All (AND) - Will collect on all parameters activated by the preferences dialog, as such this button is redundant if you only have one of the two other options enabled
|
4) Collect on All (AND) - Will collect on all parameters activated by the preferences dialog, as such this button is redundant if you only have one of the two other options enabled
|
||||||
|
|
||||||
----REQUIRED SOFTWARE----
|
----REQUIRED SOFTWARE----
|
||||||
NA
|
NA
|
||||||
|
|
||||||
----USAGE----
|
----USAGE----
|
||||||
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
||||||
1) Copy this file in to your "lua/contrib" folder where all other scripts reside.
|
1) Copy this file in to your "lua/contrib" folder where all other scripts reside.
|
||||||
2) Require this file in your luarc file, as with any other dt plug-in
|
2) Require this file in your luarc file, as with any other dt plug-in
|
||||||
|
|
||||||
Select the photo you wish to change you collection based on.
|
Select the photo you wish to change you collection based on.
|
||||||
In the "Selected Images" module click on "Collect on this Image"
|
In the "Selected Images" module click on "Collect on this Image"
|
||||||
|
|
||||||
----KNOWN ISSUES----
|
----KNOWN ISSUES----
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local gettext = dt.gettext
|
local du = require "lib/dtutils"
|
||||||
local previous = nil
|
local gettext = dt.gettext.gettext
|
||||||
local all_active = false
|
local previous = nil
|
||||||
|
local all_active = false
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("CollectHelper",dt.configuration.config_dir.."/lua/locale/")
|
du.check_min_api_version("7.0.0", "CollectHelper")
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("CollectHelper", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- FUNCTION --
|
-- return data structure for script_manager
|
||||||
local function CheckSingleImage(selection)
|
|
||||||
if #selection ~= 1 then
|
local script_data = {}
|
||||||
dt.print(_("Please select a single image"))
|
|
||||||
return true
|
script_data.metadata = {
|
||||||
end
|
name = _("collection helper"),
|
||||||
return false
|
purpose = _("add collection helper buttons"),
|
||||||
end
|
author = "Kevin Ertel",
|
||||||
local function CheckHasColorLabel(selection)
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/CollectHelper/"
|
||||||
local ret = false
|
}
|
||||||
for _,image in pairs(selection) do
|
|
||||||
if image.red then ret = true end
|
script_data.destroy = nil -- function to destory the script
|
||||||
if image.blue then ret = true end
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
if image.green then ret = true end
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
if image.yellow then ret = true end
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
if image.purple then ret = true end
|
|
||||||
end
|
-- FUNCTION --
|
||||||
return ret
|
local function CheckSingleImage(selection)
|
||||||
end
|
if #selection ~= 1 then
|
||||||
local function PreviousCollection()
|
dt.print(_("please select a single image"))
|
||||||
if previous ~= nil then
|
return true
|
||||||
previous = dt.gui.libs.collect.filter(previous)
|
end
|
||||||
end
|
return false
|
||||||
end
|
end
|
||||||
local function CollectOnFolder(all_rules, all_active)
|
|
||||||
local images = dt.gui.selection()
|
local function CheckHasColorLabel(selection)
|
||||||
if CheckSingleImage(images) then
|
local ret = false
|
||||||
return
|
for _,image in pairs(selection) do
|
||||||
end
|
if image.red then ret = true end
|
||||||
local rules = {}
|
if image.blue then ret = true end
|
||||||
local rule = dt.gui.libs.collect.new_rule()
|
if image.green then ret = true end
|
||||||
rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
if image.yellow then ret = true end
|
||||||
rule.data = images[1].path
|
if image.purple then ret = true end
|
||||||
rule.item = "DT_COLLECTION_PROP_FOLDERS"
|
end
|
||||||
table.insert(rules, rule)
|
return ret
|
||||||
if all_active then
|
end
|
||||||
for _,active_rule in pairs(rules) do
|
local function PreviousCollection()
|
||||||
table.insert(all_rules, active_rule)
|
if previous ~= nil then
|
||||||
end
|
previous = dt.gui.libs.collect.filter(previous)
|
||||||
return all_rules
|
end
|
||||||
else
|
end
|
||||||
previous = dt.gui.libs.collect.filter(rules)
|
|
||||||
end
|
local function CollectOnFolder(all_rules, all_active)
|
||||||
end
|
local images = dt.gui.selection()
|
||||||
local function CollectOnColors(all_rules, all_active)
|
if CheckSingleImage(images) then
|
||||||
local images = dt.gui.selection()
|
return
|
||||||
if CheckSingleImage(images) then
|
end
|
||||||
return
|
local rules = {}
|
||||||
end
|
local rule = dt.gui.libs.collect.new_rule()
|
||||||
if not CheckHasColorLabel(images) then
|
rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||||
dt.print(_('select an image with an active color label'))
|
rule.data = images[1].path
|
||||||
return
|
rule.item = "DT_COLLECTION_PROP_FOLDERS"
|
||||||
end
|
table.insert(rules, rule)
|
||||||
for _,image in pairs(images) do
|
if all_active then
|
||||||
local rules = {}
|
for _,active_rule in pairs(rules) do
|
||||||
if image.red then
|
table.insert(all_rules, active_rule)
|
||||||
local red_rule = dt.gui.libs.collect.new_rule()
|
end
|
||||||
red_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
return all_rules
|
||||||
red_rule.data = "red"
|
else
|
||||||
red_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
previous = dt.gui.libs.collect.filter(rules)
|
||||||
table.insert(rules, red_rule)
|
end
|
||||||
end
|
end
|
||||||
if image.blue then
|
|
||||||
local blue_rule = dt.gui.libs.collect.new_rule()
|
local function CollectOnColors(all_rules, all_active)
|
||||||
blue_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
local images = dt.gui.selection()
|
||||||
blue_rule.data = "blue"
|
if CheckSingleImage(images) then
|
||||||
blue_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
return
|
||||||
table.insert(rules, blue_rule)
|
end
|
||||||
end
|
if not CheckHasColorLabel(images) then
|
||||||
if image.green then
|
dt.print(_('select an image with an active color label'))
|
||||||
local green_rule = dt.gui.libs.collect.new_rule()
|
return
|
||||||
green_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
end
|
||||||
green_rule.data = "green"
|
for _,image in pairs(images) do
|
||||||
green_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
local rules = {}
|
||||||
table.insert(rules, green_rule)
|
if image.red then
|
||||||
end
|
local red_rule = dt.gui.libs.collect.new_rule()
|
||||||
if image.yellow then
|
red_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||||
local yellow_rule = dt.gui.libs.collect.new_rule()
|
red_rule.data = "red"
|
||||||
yellow_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
red_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
||||||
yellow_rule.data = "yellow"
|
table.insert(rules, red_rule)
|
||||||
yellow_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
end
|
||||||
table.insert(rules, yellow_rule)
|
if image.blue then
|
||||||
end
|
local blue_rule = dt.gui.libs.collect.new_rule()
|
||||||
if image.purple then
|
blue_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||||
local purple_rule = dt.gui.libs.collect.new_rule()
|
blue_rule.data = "blue"
|
||||||
purple_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
blue_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
||||||
purple_rule.data = "purple"
|
table.insert(rules, blue_rule)
|
||||||
purple_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
end
|
||||||
table.insert(rules, purple_rule)
|
if image.green then
|
||||||
end
|
local green_rule = dt.gui.libs.collect.new_rule()
|
||||||
if all_active then
|
green_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||||
for _,active_rule in pairs(rules) do
|
green_rule.data = "green"
|
||||||
table.insert(all_rules, active_rule)
|
green_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
||||||
end
|
table.insert(rules, green_rule)
|
||||||
return all_rules
|
end
|
||||||
else
|
if image.yellow then
|
||||||
previous = dt.gui.libs.collect.filter(rules)
|
local yellow_rule = dt.gui.libs.collect.new_rule()
|
||||||
end
|
yellow_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||||
end
|
yellow_rule.data = "yellow"
|
||||||
end
|
yellow_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
||||||
local function CollectOnAll_AND()
|
table.insert(rules, yellow_rule)
|
||||||
local images = dt.gui.selection()
|
end
|
||||||
if CheckSingleImage(images) then
|
if image.purple then
|
||||||
return
|
local purple_rule = dt.gui.libs.collect.new_rule()
|
||||||
end
|
purple_rule.mode = "DT_LIB_COLLECT_MODE_AND"
|
||||||
local rules = {}
|
purple_rule.data = "purple"
|
||||||
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
purple_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
|
||||||
rules = CollectOnFolder(rules, true)
|
table.insert(rules, purple_rule)
|
||||||
end
|
end
|
||||||
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
if all_active then
|
||||||
rules = CollectOnColors(rules, true)
|
for _,active_rule in pairs(rules) do
|
||||||
end
|
table.insert(all_rules, active_rule)
|
||||||
previous = dt.gui.libs.collect.filter(rules)
|
end
|
||||||
end
|
return all_rules
|
||||||
|
else
|
||||||
-- GUI --
|
previous = dt.gui.libs.collect.filter(rules)
|
||||||
dt.gui.libs.image.register_action(
|
end
|
||||||
_("collect: previous"),
|
end
|
||||||
function() PreviousCollection() end,
|
end
|
||||||
_("Sets the Collect parameters to be the previously active parameters")
|
|
||||||
)
|
local function CollectOnAll_AND()
|
||||||
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
local images = dt.gui.selection()
|
||||||
dt.gui.libs.image.register_action(
|
if CheckSingleImage(images) then
|
||||||
_("collect: folder"),
|
return
|
||||||
function() CollectOnFolder(_ , false) end,
|
end
|
||||||
_("Sets the Collect parameters to be the selected images's folder")
|
local rules = {}
|
||||||
)
|
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
||||||
end
|
rules = CollectOnFolder(rules, true)
|
||||||
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
end
|
||||||
dt.gui.libs.image.register_action(
|
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
||||||
_("collect: color label(s)"),
|
rules = CollectOnColors(rules, true)
|
||||||
function() CollectOnColors(_ , false) end,
|
end
|
||||||
_("Sets the Collect parameters to be the selected images's color label(s)")
|
previous = dt.gui.libs.collect.filter(rules)
|
||||||
)
|
end
|
||||||
end
|
|
||||||
if dt.preferences.read('module_CollectHelper','all_and','bool') then
|
local function destroy()
|
||||||
dt.gui.libs.image.register_action(
|
dt.gui.libs.image.destroy_action("CollectHelper_prev")
|
||||||
_("collect: all (AND)"),
|
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
||||||
function() CollectOnAll_AND() end,
|
dt.gui.libs.image.destroy_action("CollectHelper_folder")
|
||||||
_("Sets the Collect parameters based on all activated CollectHelper options")
|
end
|
||||||
)
|
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
||||||
end
|
dt.gui.libs.image.destroy_action("CollectHelper_labels")
|
||||||
|
end
|
||||||
-- PREFERENCES --
|
if dt.preferences.read('module_CollectHelper','all_and','bool') then
|
||||||
dt.preferences.register("module_CollectHelper", "all_and", -- name
|
dt.gui.libs.image.destroy_action("CollectHelper_and")
|
||||||
"bool", -- type
|
end
|
||||||
_('CollectHelper: All'), -- label
|
end
|
||||||
_('Will create a collect parameter set that utelizes all enabled CollectHelper types (AND)'), -- tooltip
|
|
||||||
true -- default
|
-- GUI --
|
||||||
)
|
dt.gui.libs.image.register_action(
|
||||||
dt.preferences.register("module_CollectHelper", "colors", -- name
|
"CollectHelper_prev", _("collect: previous"),
|
||||||
"bool", -- type
|
function() PreviousCollection() end,
|
||||||
_('CollectHelper: Color Label(s)'), -- label
|
_("sets the collect parameters to be the previously active parameters")
|
||||||
_('Enable the button that allows you to swap to a collection based on selected image\'s COLOR LABEL(S)'), -- tooltip
|
)
|
||||||
true -- default
|
if dt.preferences.read('module_CollectHelper','folder','bool') then
|
||||||
)
|
dt.gui.libs.image.register_action(
|
||||||
dt.preferences.register("module_CollectHelper", "folder", -- name
|
"CollectHelper_folder", _("collect: folder"),
|
||||||
"bool", -- type
|
function() CollectOnFolder(_ , false) end,
|
||||||
_('CollectHelper: Folder'), -- label
|
_("sets the collect parameters to be the selected images's folder")
|
||||||
_('Enable the button that allows you to swap to a collection based on selected image\'s FOLDER location'), -- tooltip
|
)
|
||||||
true -- default
|
end
|
||||||
)
|
if dt.preferences.read('module_CollectHelper','colors','bool') then
|
||||||
|
dt.gui.libs.image.register_action(
|
||||||
|
"CollectHelper_labels", _("collect: color label(s)"),
|
||||||
|
function() CollectOnColors(_ , false) end,
|
||||||
|
_("sets the collect parameters to be the selected images's color label(s)")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if dt.preferences.read('module_CollectHelper','all_and','bool') then
|
||||||
|
dt.gui.libs.image.register_action(
|
||||||
|
"CollectHelper_and", _("collect: all (AND)"),
|
||||||
|
function() CollectOnAll_AND() end,
|
||||||
|
_("sets the collect parameters based on all activated CollectHelper options")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PREFERENCES --
|
||||||
|
dt.preferences.register("module_CollectHelper", "all_and", -- name
|
||||||
|
"bool", -- type
|
||||||
|
_('CollectHelper: all'), -- label
|
||||||
|
_('creates a collect parameter set that utilizes all enabled CollectHelper types (and)'), -- tooltip
|
||||||
|
true -- default
|
||||||
|
)
|
||||||
|
dt.preferences.register("module_CollectHelper", "colors", -- name
|
||||||
|
"bool", -- type
|
||||||
|
_('CollectHelper: color label(s)'), -- label
|
||||||
|
_('enable the button that allows you to swap to a collection based on selected image\'s color label(s)'), -- tooltip
|
||||||
|
true -- default
|
||||||
|
)
|
||||||
|
dt.preferences.register("module_CollectHelper", "folder", -- name
|
||||||
|
"bool", -- type
|
||||||
|
_('CollectHelper: folder'), -- label
|
||||||
|
_('enable the button that allows you to swap to a collection based on selected image\'s folder location'), -- tooltip
|
||||||
|
true -- default
|
||||||
|
)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -1,424 +1,486 @@
|
|||||||
--[[HDRMerge plugin for darktable
|
--[[HDRMerge plugin for darktable
|
||||||
|
|
||||||
copyright (c) 2018 Kevin Ertel
|
copyright (c) 2018 Kevin Ertel
|
||||||
|
|
||||||
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
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
darktable is distributed in the hope that it will be useful,
|
darktable is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[About this Plugin
|
--[[About this Plugin
|
||||||
This plugin adds the module 'HDRMerge' to darktable's lighttable view
|
This plugin adds the module 'HDRMerge' to darktable's lighttable view
|
||||||
|
|
||||||
----REQUIRED SOFTWARE----
|
----REQUIRED SOFTWARE----
|
||||||
HDRMerge ver. 4.5 or greater
|
HDRMerge ver. 4.5 or greater
|
||||||
|
|
||||||
----USAGE----
|
----USAGE----
|
||||||
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
||||||
1) Copy this file in to your 'lua/contrib' folder where all other scripts reside.
|
1) Copy this file in to your 'lua/contrib' folder where all other scripts reside.
|
||||||
2) Require this file in your luarc file, as with any other dt plug-in
|
2) Require this file in your luarc file, as with any other dt plug-in
|
||||||
On the initial startup go to darktable settings > lua options and set your executable paths and other preferences, then restart darktable
|
On the initial startup go to darktable settings > lua options and set your executable paths and other preferences, then restart darktable
|
||||||
|
|
||||||
Select bracketed images and press the Run HDRMerge button. The resulting DNG will be auto-imported into darktable.
|
Select bracketed images and press the Run HDRMerge button. The resulting DNG will be auto-imported into darktable.
|
||||||
Additional tags or style can be applied on auto import as well, if you desire.
|
Additional tags or style can be applied on auto import as well, if you desire.
|
||||||
|
|
||||||
Base Options:
|
Base Options:
|
||||||
Select your desired BPS (bits per sample and Embedded Preview Size.
|
Select your desired BPS (bits per sample and Embedded Preview Size.
|
||||||
|
|
||||||
Batch Options:
|
Batch Options:
|
||||||
Select if you want to run in batch mode or not
|
Select if you want to run in batch mode or not
|
||||||
Select the gap, in seconds, between images for auto grouping in batch mode
|
Select the gap, in seconds, between images for auto grouping in batch mode
|
||||||
|
|
||||||
See HDRMerge manual for further detail: http://jcelaya.github.io/hdrmerge/documentation/2014/07/11/user-manual.html
|
See HDRMerge manual for further detail: http://jcelaya.github.io/hdrmerge/documentation/2014/07/11/user-manual.html
|
||||||
|
|
||||||
Auto-import Options:
|
Auto-import Options:
|
||||||
Select a style, whether you want tags to be copied from the original, and any additional tags you desire added when the new image is auto-imported
|
Select a style, whether you want tags to be copied from the original, and any additional tags you desire added when the new image is auto-imported
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require 'darktable'
|
local dt = require 'darktable'
|
||||||
local df = require 'lib/dtutils.file'
|
local du = require "lib/dtutils"
|
||||||
local dsys = require 'lib/dtutils.system'
|
local df = require 'lib/dtutils.file'
|
||||||
local mod = 'module_HDRMerge'
|
local dsys = require 'lib/dtutils.system'
|
||||||
local os_path_seperator = '/'
|
|
||||||
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
du.check_min_api_version("7.0.0", "HDRmerge")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
gettext.bindtextdomain('HDRMerge',dt.configuration.config_dir..'/lua/locale/')
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext('HDRMerge', msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
local temp
|
-- return data structure for script_manager
|
||||||
local HDRM = { --HDRMerge Program Table
|
|
||||||
name = 'HDRMerge',
|
local script_data = {}
|
||||||
bin = '',
|
|
||||||
first_run = true,
|
script_data.metadata = {
|
||||||
install_error = false,
|
name = _("HDR merge"),
|
||||||
arg_string = '',
|
purpose = _("merge bracketed images into an HDR DNG image"),
|
||||||
images_string = '',
|
author = "Kevin Ertel",
|
||||||
args = {
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/HDRmerge"
|
||||||
bps ={text = '-b ', style = 'integer'},
|
}
|
||||||
size ={text = '-p ', style = 'string'},
|
|
||||||
batch ={text = '-B ', style = 'bool'},
|
script_data.destroy = nil -- function to destory the script
|
||||||
gap ={text = '-g ', style = 'integer'}
|
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 GUI = { --GUI Elements Table
|
|
||||||
HDR = {
|
|
||||||
bps ={},
|
local mod = 'module_HDRMerge'
|
||||||
size ={},
|
local os_path_seperator = '/'
|
||||||
batch ={},
|
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
||||||
gap ={}
|
local CURR_API_STRING = dt.configuration.api_version_string
|
||||||
},
|
|
||||||
Target = {
|
local temp
|
||||||
style ={},
|
local HDRM = { --HDRMerge Program Table
|
||||||
copy_tags ={},
|
name = 'HDRMerge',
|
||||||
add_tags ={}
|
bin = '',
|
||||||
},
|
first_run = true,
|
||||||
run = {},
|
install_error = false,
|
||||||
stack = {},
|
arg_string = '',
|
||||||
options = {},
|
images_string = '',
|
||||||
exes = {
|
args = {
|
||||||
HDRMerge = {},
|
bps = {text = '-b ', style = 'integer'},
|
||||||
update = {},
|
size = {text = '-p ', style = 'string'},
|
||||||
}
|
batch = {text = '-B ', style = 'bool'},
|
||||||
}
|
gap = {text = '-g ', style = 'integer'}
|
||||||
|
}
|
||||||
--Detect User Styles--
|
}
|
||||||
local styles = dt.styles
|
local GUI = { --GUI Elements Table
|
||||||
local styles_count = 1 -- 'none' = 1
|
HDR = {
|
||||||
for _,i in pairs(dt.styles) do
|
bps ={},
|
||||||
if type(i) == 'userdata' then styles_count = styles_count + 1 end
|
size ={},
|
||||||
end
|
batch ={},
|
||||||
|
gap ={}
|
||||||
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
|
Target = {
|
||||||
return true
|
style ={},
|
||||||
else
|
copy_tags ={},
|
||||||
return false
|
add_tags ={}
|
||||||
end
|
},
|
||||||
end
|
run = {},
|
||||||
|
stack = {},
|
||||||
local function GetFileName(full_path) --Parses a full path (path/filename_identifier.extension) into individual parts
|
options = {},
|
||||||
--[[Input: Folder1/Folder2/Folder3/Img_0001.CR2
|
exes = {
|
||||||
|
HDRMerge = {},
|
||||||
Returns:
|
update = {},
|
||||||
path: Folder1/Folder2/Folder3/
|
}
|
||||||
filename: Img_0001
|
}
|
||||||
identifier: 0001
|
|
||||||
extension: .CR2
|
HDRM.module_installed = false
|
||||||
|
HDRM.event_registered = false
|
||||||
EX:
|
|
||||||
path_1, file_1, id_1, ext_1 = GetFileName(full_path_1)
|
|
||||||
]]
|
--Detect User Styles--
|
||||||
local path = string.match(full_path, '.*[\\/]')
|
local styles = dt.styles
|
||||||
local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' )
|
local styles_count = 1 -- 'none' = 1
|
||||||
local identifier = string.match(filename, '%d*$')
|
for _,i in pairs(dt.styles) do
|
||||||
local extension = string.match(full_path, '%.%w*')
|
if type(i) == 'userdata' then styles_count = styles_count + 1 end
|
||||||
return path, filename, identifier, extension
|
end
|
||||||
end
|
|
||||||
|
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
|
||||||
local function CleanSpaces(text) --removes spaces from the front and back of passed in text
|
if test >= low and test <= high then
|
||||||
text = string.gsub(text,'^%s*','')
|
return true
|
||||||
text = string.gsub(text,'%s*$','')
|
else
|
||||||
return text
|
return false
|
||||||
end
|
end
|
||||||
|
end
|
||||||
local function BuildExecuteCmd(prog_table) --creates a program command using elements of the passed in program table
|
|
||||||
local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string)
|
local function GetFileName(full_path) --Parses a full path (path/filename_identifier.extension) into individual parts
|
||||||
return result
|
--[[Input: Folder1/Folder2/Folder3/Img_0001.CR2
|
||||||
end
|
|
||||||
|
Returns:
|
||||||
local function PreCall(prog_tbl) --looks to see if this is the first call, if so checks to see if program is installed properly
|
path: Folder1/Folder2/Folder3/
|
||||||
for _,prog in pairs(prog_tbl) do
|
filename: Img_0001
|
||||||
if prog.first_run then
|
identifier: 0001
|
||||||
prog.bin = df.check_if_bin_exists(prog.name)
|
extension: .CR2
|
||||||
if not prog.bin then
|
|
||||||
prog.install_error = true
|
EX:
|
||||||
dt.preferences.write(mod, 'bin_exists', 'bool', false)
|
path_1, file_1, id_1, ext_1 = GetFileName(full_path_1)
|
||||||
else
|
]]
|
||||||
prog.bin = CleanSpaces(prog.bin)
|
local path = string.match(full_path, '.*[\\/]')
|
||||||
end
|
local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' )
|
||||||
prog.first_run = false
|
local identifier = string.match(filename, '%d*$')
|
||||||
end
|
local extension = string.match(full_path, '%.%w*')
|
||||||
end
|
return path, filename, identifier, extension
|
||||||
if not dt.preferences.read(mod, 'bin_exists', 'bool') then
|
end
|
||||||
GUI.stack.active = 2
|
|
||||||
dt.print(_('please update you binary locatoin'))
|
local function CleanSpaces(text) --removes spaces from the front and back of passed in text
|
||||||
end
|
text = string.gsub(text,'^%s*','')
|
||||||
end
|
text = string.gsub(text,'%s*$','')
|
||||||
|
return text
|
||||||
local function ExeUpdate(prog_tbl)
|
end
|
||||||
dt.preferences.write(mod, 'bin_exists', 'bool', true)
|
|
||||||
for _,prog in pairs(prog_tbl) do
|
local function BuildExecuteCmd(prog_table) --creates a program command using elements of the passed in program table
|
||||||
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
|
local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string)
|
||||||
prog.bin = df.check_if_bin_exists(prog.name)
|
return result
|
||||||
if not prog.bin then
|
end
|
||||||
prog.install_error = true
|
|
||||||
dt.preferences.write(mod, 'bin_exists', 'bool', false)
|
local function PreCall(prog_tbl) --looks to see if this is the first call, if so checks to see if program is installed properly
|
||||||
else
|
for _,prog in pairs(prog_tbl) do
|
||||||
prog.bin = CleanSpaces(prog.bin)
|
if prog.first_run then
|
||||||
end
|
prog.bin = df.check_if_bin_exists(prog.name)
|
||||||
prog.first_run = false
|
if not prog.bin then
|
||||||
end
|
prog.install_error = true
|
||||||
if dt.preferences.read(mod, 'bin_exists', 'bool') then
|
dt.preferences.write(mod, 'bin_exists', 'bool', false)
|
||||||
GUI.stack.active = 1
|
else
|
||||||
dt.print(_('update successful'))
|
prog.bin = CleanSpaces(prog.bin)
|
||||||
else
|
end
|
||||||
dt.print(_('update unsuccessful, please try again'))
|
prog.first_run = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if not dt.preferences.read(mod, 'bin_exists', 'bool') then
|
||||||
local function UpdateActivePreference() --sliders & entry boxes do not have a click/changed callback, so their values must be saved to the active preference
|
GUI.stack.active = 2
|
||||||
temp = GUI.HDR.gap.value
|
dt.print(_('please update you binary location'))
|
||||||
dt.preferences.write(mod, 'active_gap', 'integer', temp)
|
end
|
||||||
temp = GUI.Target.add_tags.text
|
end
|
||||||
dt.preferences.write(mod, 'active_add_tags', 'string', temp)
|
|
||||||
end
|
local function ExeUpdate(prog_tbl)
|
||||||
|
dt.preferences.write(mod, 'bin_exists', 'bool', true)
|
||||||
local function main()
|
for _,prog in pairs(prog_tbl) do
|
||||||
PreCall({HDRM}) --check if furst run then check if install OK
|
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
|
||||||
if HDRM.install_error then
|
prog.bin = df.check_if_bin_exists(prog.name)
|
||||||
dt.print_error(_('HDRMerge install issue'))
|
if not prog.bin then
|
||||||
dt.print(_('HDRMerge install issue, please ensure the binary path is proper'))
|
prog.install_error = true
|
||||||
return
|
dt.preferences.write(mod, 'bin_exists', 'bool', false)
|
||||||
end
|
else
|
||||||
images = dt.gui.selection() --get selected images
|
prog.bin = CleanSpaces(prog.bin)
|
||||||
if #images < 2 then --ensure enough images selected
|
end
|
||||||
dt.print(_('not enough images selected, select at least 2 images to merge'))
|
prog.first_run = false
|
||||||
return
|
end
|
||||||
end
|
if dt.preferences.read(mod, 'bin_exists', 'bool') then
|
||||||
|
GUI.stack.active = 1
|
||||||
UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup
|
dt.print(_('update successful'))
|
||||||
|
else
|
||||||
--create image string and output path
|
dt.print(_('update unsuccessful, please try again'))
|
||||||
HDRM.images_string = ''
|
end
|
||||||
local out_path = ''
|
end
|
||||||
local smallest_id = math.huge
|
|
||||||
local smallest_name = ''
|
local function UpdateActivePreference() --sliders & entry boxes do not have a click/changed callback, so their values must be saved to the active preference
|
||||||
local largest_id = 0
|
temp = GUI.HDR.gap.value
|
||||||
local source_raw = {}
|
dt.preferences.write(mod, 'active_gap', 'integer', temp)
|
||||||
for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng)
|
temp = GUI.Target.add_tags.text
|
||||||
local curr_image = image.path..os_path_seperator..image.filename
|
dt.preferences.write(mod, 'active_add_tags', 'string', temp)
|
||||||
HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' '
|
end
|
||||||
out_path = image.path
|
|
||||||
_, source_name, source_id = GetFileName(image.filename)
|
local function main()
|
||||||
source_id = tonumber(source_id)
|
PreCall({HDRM}) --check if furst run then check if install OK
|
||||||
if source_id < smallest_id then
|
if HDRM.install_error then
|
||||||
smallest_id = source_id
|
dt.print_error('HDRMerge install issue')
|
||||||
smallest_name = source_name
|
dt.print(_('HDRMerge install issue, please ensure the binary path is correct'))
|
||||||
source_raw = image
|
return
|
||||||
end
|
end
|
||||||
if source_id > largest_id then largest_id = source_id end
|
images = dt.gui.selection() --get selected images
|
||||||
end
|
if #images < 2 then --ensure enough images selected
|
||||||
out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng'
|
dt.print(_('not enough images selected, select at least 2 images to merge'))
|
||||||
out_path = df.create_unique_filename(out_path)
|
return
|
||||||
|
end
|
||||||
--create argument string
|
|
||||||
HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' '
|
UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup
|
||||||
if GUI.HDR.batch.value then
|
|
||||||
HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a'
|
--create image string and output path
|
||||||
else
|
HDRM.images_string = ''
|
||||||
HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path)
|
local out_path = ''
|
||||||
end
|
local smallest_id = math.huge
|
||||||
|
local smallest_name = ''
|
||||||
-- create run command and execute
|
local largest_id = 0
|
||||||
local run_cmd = BuildExecuteCmd(HDRM)
|
local source_raw = {}
|
||||||
resp = dsys.external_command(run_cmd)
|
for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng)
|
||||||
|
local curr_image = image.path..os_path_seperator..image.filename
|
||||||
if resp == 0 and not GUI.HDR.batch.value then
|
HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' '
|
||||||
local imported = dt.database.import(out_path) -- import the new file
|
out_path = image.path
|
||||||
if GUI.Target.style.selected > 1 then -- apply selected style
|
_unused, source_name, source_id = GetFileName(image.filename)
|
||||||
local set_style = styles[GUI.Target.style.selected - 1]
|
source_id = tonumber(source_id) or 0
|
||||||
dt.styles.apply(set_style , imported)
|
if source_id < smallest_id then
|
||||||
end
|
smallest_id = source_id
|
||||||
if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags)
|
smallest_name = source_name
|
||||||
local all_tags = dt.tags.get_tags(source_raw)
|
source_raw = image
|
||||||
for _,tag in pairs(all_tags) do
|
end
|
||||||
if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end
|
if source_id > largest_id then largest_id = source_id end
|
||||||
end
|
end
|
||||||
end
|
out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng'
|
||||||
local set_tag = GUI.Target.add_tags.text
|
out_path = df.create_unique_filename(out_path)
|
||||||
if set_tag ~= nil then -- add additional user-specified tags
|
|
||||||
for tag in string.gmatch(set_tag, '[^,]+') do
|
--create argument string
|
||||||
tag = CleanSpaces(tag)
|
HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' '
|
||||||
tag = dt.tags.create(tag)
|
if GUI.HDR.batch.value then
|
||||||
dt.tags.attach(tag, imported)
|
HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a'
|
||||||
end
|
else
|
||||||
end
|
HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path)
|
||||||
dt.print(_('HDRMerge completed successfully'))
|
end
|
||||||
else
|
|
||||||
dt.print_error(_('HDRMerge failed'))
|
-- create run command and execute
|
||||||
dt.print(_('HDRMerge failed'))
|
local run_cmd = BuildExecuteCmd(HDRM)
|
||||||
end
|
resp = dsys.external_command(run_cmd)
|
||||||
|
|
||||||
end
|
if resp == 0 and not GUI.HDR.batch.value then
|
||||||
|
local imported = dt.database.import(out_path) -- import the new file
|
||||||
-- GUI Elements --
|
if GUI.Target.style.selected > 1 then -- apply selected style
|
||||||
local lbl_hdr = dt.new_widget('section_label'){
|
local set_style = styles[GUI.Target.style.selected - 1]
|
||||||
label = _('HDRMerge options')
|
dt.styles.apply(set_style , imported)
|
||||||
}
|
end
|
||||||
temp = dt.preferences.read(mod, 'active_bps_ind', 'integer')
|
if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags)
|
||||||
if not InRange(temp, 1, 3) then temp = 3 end
|
local all_tags = dt.tags.get_tags(source_raw)
|
||||||
GUI.HDR.bps = dt.new_widget('combobox'){
|
for _,tag in pairs(all_tags) do
|
||||||
label = _('bits per sample'),
|
if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end
|
||||||
tooltip =_('number of bits per sample in the output image'),
|
end
|
||||||
selected = temp,
|
end
|
||||||
'16','24','32',
|
local set_tag = GUI.Target.add_tags.text
|
||||||
changed_callback = function(self)
|
if set_tag ~= nil then -- add additional user-specified tags
|
||||||
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
|
for tag in string.gmatch(set_tag, '[^,]+') do
|
||||||
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
|
tag = CleanSpaces(tag)
|
||||||
end,
|
tag = dt.tags.create(tag)
|
||||||
reset_callback = function(self)
|
dt.tags.attach(tag, imported)
|
||||||
self.selected = 3
|
end
|
||||||
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
|
end
|
||||||
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
|
dt.print(_('HDRMerge completed successfully'))
|
||||||
end
|
else
|
||||||
}
|
dt.print_error('HDRMerge failed')
|
||||||
temp = dt.preferences.read(mod, 'active_size_ind', 'integer')
|
dt.print(_('HDRMerge failed'))
|
||||||
if not InRange(temp, 1, 3) then temp = 2 end
|
end
|
||||||
GUI.HDR.size = dt.new_widget('combobox'){
|
|
||||||
label = _('embedded preview size'),
|
end
|
||||||
tooltip =_('size of the embedded preview in output image'),
|
|
||||||
selected = temp,
|
local function install_module()
|
||||||
_('none'),_('half'),_('full'),
|
if not HDRM.module_installed then
|
||||||
changed_callback = function(self)
|
dt.register_lib( -- register HDRMerge module
|
||||||
dt.preferences.write(mod, 'active_size', 'string', self.value)
|
'HDRMerge_Lib', -- Module name
|
||||||
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
|
_('HDRMerge'), -- name
|
||||||
end,
|
true, -- expandable
|
||||||
reset_callback = function(self)
|
true, -- resetable
|
||||||
self.selected = 2
|
{[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers
|
||||||
dt.preferences.write(mod, 'active_size', 'string', self.value)
|
dt.new_widget('box'){
|
||||||
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
|
orientation = 'vertical',
|
||||||
end
|
GUI.stack
|
||||||
}
|
}
|
||||||
GUI.HDR.batch = dt.new_widget('check_button'){
|
)
|
||||||
label = _('batch mode'),
|
HDRM.module_installed = true
|
||||||
value = dt.preferences.read(mod, 'active_batch', 'bool'),
|
end
|
||||||
tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'),
|
end
|
||||||
clicked_callback = function(self)
|
|
||||||
dt.preferences.write(mod, 'active_batch', 'bool', self.value)
|
local function destroy()
|
||||||
GUI.HDR.gap.sensitive = self.value
|
dt.gui.libs["HDRMerge_Lib"].visible = false
|
||||||
end,
|
end
|
||||||
reset_callback = function(self) self.value = false end
|
|
||||||
}
|
local function restart()
|
||||||
temp = dt.preferences.read(mod, 'active_gap', 'integer')
|
dt.gui.libs["HDRMerge_Lib"].visible = true
|
||||||
if not InRange(temp, 1, 3600) then temp = 3 end
|
end
|
||||||
GUI.HDR.gap = dt.new_widget('slider'){
|
|
||||||
label = _('batch gap [sec.]'),
|
-- GUI Elements --
|
||||||
tooltip = _('gap, in seconds, between batch mode groups'),
|
local lbl_hdr = dt.new_widget('section_label'){
|
||||||
soft_min = 1,
|
label = _('HDRMerge options')
|
||||||
soft_max = 30,
|
}
|
||||||
hard_min = 1,
|
temp = dt.preferences.read(mod, 'active_bps_ind', 'integer')
|
||||||
hard_max = 3600,
|
if not InRange(temp, 1, 3) then temp = 3 end
|
||||||
step = 1,
|
GUI.HDR.bps = dt.new_widget('combobox'){
|
||||||
digits = 0,
|
label = _('bits per sample'),
|
||||||
value = temp,
|
tooltip =_('number of bits per sample in the output image'),
|
||||||
sensitive = GUI.HDR.batch.value,
|
selected = temp,
|
||||||
reset_callback = function(self)
|
'16','24','32',
|
||||||
self.value = 3
|
changed_callback = function(self)
|
||||||
end
|
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
|
||||||
}
|
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
|
||||||
local lbl_import = dt.new_widget('section_label'){
|
end,
|
||||||
label = _('import options')
|
reset_callback = function(self)
|
||||||
}
|
self.selected = 3
|
||||||
GUI.Target.style = dt.new_widget('combobox'){
|
dt.preferences.write(mod, 'active_bps', 'integer', self.value)
|
||||||
label = _('apply style on import'),
|
dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected)
|
||||||
tooltip = _('Apply selected style on auto-import to newly created image'),
|
end
|
||||||
selected = 1,
|
}
|
||||||
_('none'),
|
temp = dt.preferences.read(mod, 'active_size_ind', 'integer')
|
||||||
changed_callback = function(self)
|
if not InRange(temp, 1, 3) then temp = 2 end
|
||||||
dt.preferences.write(mod, 'active_style', 'string', self.value)
|
GUI.HDR.size = dt.new_widget('combobox'){
|
||||||
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
|
label = _('embedded preview size'),
|
||||||
end,
|
tooltip =_('size of the embedded preview in output image'),
|
||||||
reset_callback = function(self)
|
selected = temp,
|
||||||
self.selected = 1
|
_('none'),_('half'),_('full'),
|
||||||
dt.preferences.write(mod, 'active_style', 'string', self.value)
|
changed_callback = function(self)
|
||||||
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
|
dt.preferences.write(mod, 'active_size', 'string', self.value)
|
||||||
end
|
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
|
||||||
}
|
end,
|
||||||
for k=1, (styles_count-1) do
|
reset_callback = function(self)
|
||||||
GUI.Target.style[k+1] = styles[k].name
|
self.selected = 2
|
||||||
end
|
dt.preferences.write(mod, 'active_size', 'string', self.value)
|
||||||
temp = dt.preferences.read(mod, 'active_style_ind', 'integer')
|
dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected)
|
||||||
if not InRange(temp, 1, styles_count) then temp = 1 end
|
end
|
||||||
GUI.Target.style.selected = temp
|
}
|
||||||
GUI.Target.copy_tags = dt.new_widget('check_button'){
|
GUI.HDR.batch = dt.new_widget('check_button'){
|
||||||
label = _('copy tags'),
|
label = _('batch mode'),
|
||||||
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
|
value = dt.preferences.read(mod, 'active_batch', 'bool'),
|
||||||
tooltip = _('copy tags from first source image'),
|
tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'),
|
||||||
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
|
clicked_callback = function(self)
|
||||||
reset_callback = function(self) self.value = true end
|
dt.preferences.write(mod, 'active_batch', 'bool', self.value)
|
||||||
}
|
GUI.HDR.gap.sensitive = self.value
|
||||||
temp = dt.preferences.read(mod, 'active_add_tags', 'string')
|
end,
|
||||||
if temp == '' then temp = nil end
|
reset_callback = function(self) self.value = false end
|
||||||
GUI.Target.add_tags = dt.new_widget('entry'){
|
}
|
||||||
tooltip = _('Additional tags to be added on import. Seperate with commas, all spaces will be removed'),
|
temp = dt.preferences.read(mod, 'active_gap', 'integer')
|
||||||
text = temp,
|
if not InRange(temp, 1, 3600) then temp = 3 end
|
||||||
placeholder = _('Enter tags, seperated by commas'),
|
GUI.HDR.gap = dt.new_widget('slider'){
|
||||||
editable = true
|
label = _('batch gap [sec.]'),
|
||||||
}
|
tooltip = _('gap, in seconds, between batch mode groups'),
|
||||||
GUI.run = dt.new_widget('button'){
|
soft_min = 1,
|
||||||
label = _('merge'),
|
soft_max = 30,
|
||||||
tooltip =_('run HDRMerge with the above specified settings'),
|
hard_min = 1,
|
||||||
clicked_callback = function() main() end
|
hard_max = 3600,
|
||||||
}
|
step = 1,
|
||||||
GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){
|
digits = 0,
|
||||||
title = _('Select HDRmerge executable'),
|
value = temp,
|
||||||
value = df.get_executable_path_preference(HDRM.name),
|
sensitive = GUI.HDR.batch.value,
|
||||||
is_directory = false
|
reset_callback = function(self)
|
||||||
}
|
self.value = 3
|
||||||
GUI.exes.update = dt.new_widget('button'){
|
end
|
||||||
label = _('update'),
|
}
|
||||||
tooltip =_('update the binary path with current value'),
|
local lbl_import = dt.new_widget('section_label'){
|
||||||
clicked_callback = function() ExeUpdate({HDRM}) end
|
label = _('import options')
|
||||||
}
|
}
|
||||||
GUI.options = dt.new_widget('box'){
|
GUI.Target.style = dt.new_widget('combobox'){
|
||||||
orientation = 'vertical',
|
label = _('apply style on import'),
|
||||||
lbl_hdr,
|
tooltip = _('apply selected style on auto-import to newly created image'),
|
||||||
GUI.HDR.bps,
|
selected = 1,
|
||||||
GUI.HDR.size,
|
_('none'),
|
||||||
GUI.HDR.batch,
|
changed_callback = function(self)
|
||||||
GUI.HDR.gap,
|
dt.preferences.write(mod, 'active_style', 'string', self.value)
|
||||||
lbl_import,
|
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
|
||||||
GUI.Target.style,
|
end,
|
||||||
GUI.Target.copy_tags,
|
reset_callback = function(self)
|
||||||
GUI.Target.add_tags,
|
self.selected = 1
|
||||||
GUI.run
|
dt.preferences.write(mod, 'active_style', 'string', self.value)
|
||||||
}
|
dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected)
|
||||||
local exes_box = dt.new_widget('box'){
|
end
|
||||||
orientation = 'vertical',
|
}
|
||||||
GUI.exes.HDRMerge,
|
for k=1, (styles_count-1) do
|
||||||
GUI.exes.update
|
GUI.Target.style[k+1] = styles[k].name
|
||||||
}
|
end
|
||||||
GUI.stack = dt.new_widget('stack'){
|
temp = dt.preferences.read(mod, 'active_style_ind', 'integer')
|
||||||
GUI.options,
|
if not InRange(temp, 1, styles_count) then temp = 1 end
|
||||||
exes_box
|
GUI.Target.style.selected = temp
|
||||||
}
|
GUI.Target.copy_tags = dt.new_widget('check_button'){
|
||||||
if dt.preferences.read(mod, 'bin_exists', 'bool') then
|
label = _('copy tags'),
|
||||||
GUI.stack.active = 1
|
value = dt.preferences.read(mod, 'active_copy_tags', 'bool'),
|
||||||
else
|
tooltip = _('copy tags from first source image'),
|
||||||
GUI.stack.active = 2
|
clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end,
|
||||||
end
|
reset_callback = function(self) self.value = true end
|
||||||
|
}
|
||||||
dt.register_lib( -- register HDRMerge module
|
temp = dt.preferences.read(mod, 'active_add_tags', 'string')
|
||||||
'HDRMerge_Lib', -- Module name
|
if temp == '' then temp = nil end
|
||||||
_('HDRMerge'), -- name
|
GUI.Target.add_tags = dt.new_widget('entry'){
|
||||||
true, -- expandable
|
tooltip = _('additional tags to be added on import, separate with commas, all spaces will be removed'),
|
||||||
true, -- resetable
|
text = temp,
|
||||||
{[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers
|
placeholder = _('enter tags, separated by commas'),
|
||||||
dt.new_widget('box'){
|
editable = true
|
||||||
orientation = 'vertical',
|
}
|
||||||
GUI.stack
|
GUI.run = dt.new_widget('button'){
|
||||||
}
|
label = _('merge'),
|
||||||
)
|
tooltip =_('run HDRMerge with the above settings'),
|
||||||
|
clicked_callback = function() main() end
|
||||||
|
}
|
||||||
|
GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){
|
||||||
|
title = _('select HDRmerge executable'),
|
||||||
|
value = df.get_executable_path_preference(HDRM.name),
|
||||||
|
is_directory = false
|
||||||
|
}
|
||||||
|
GUI.exes.update = dt.new_widget('button'){
|
||||||
|
label = _('update'),
|
||||||
|
tooltip =_('update the binary path with current value'),
|
||||||
|
clicked_callback = function() ExeUpdate({HDRM}) end
|
||||||
|
}
|
||||||
|
GUI.options = dt.new_widget('box'){
|
||||||
|
orientation = 'vertical',
|
||||||
|
lbl_hdr,
|
||||||
|
GUI.HDR.bps,
|
||||||
|
GUI.HDR.size,
|
||||||
|
GUI.HDR.batch,
|
||||||
|
GUI.HDR.gap,
|
||||||
|
lbl_import,
|
||||||
|
GUI.Target.style,
|
||||||
|
GUI.Target.copy_tags,
|
||||||
|
GUI.Target.add_tags,
|
||||||
|
GUI.run
|
||||||
|
}
|
||||||
|
local exes_box = dt.new_widget('box'){
|
||||||
|
orientation = 'vertical',
|
||||||
|
GUI.exes.HDRMerge,
|
||||||
|
GUI.exes.update
|
||||||
|
}
|
||||||
|
GUI.stack = dt.new_widget('stack'){
|
||||||
|
GUI.options,
|
||||||
|
exes_box
|
||||||
|
}
|
||||||
|
if dt.preferences.read(mod, 'bin_exists', 'bool') then
|
||||||
|
GUI.stack.active = 1
|
||||||
|
else
|
||||||
|
GUI.stack.active = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not HDRM.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"HDRmerge", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
HDRM.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -50,12 +50,38 @@
|
|||||||
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
|
||||||
|
|
||||||
local LIB_ID = "LabelsToTags"
|
local ltt = {}
|
||||||
|
ltt.module_installed = false
|
||||||
|
ltt.event_registered = false
|
||||||
|
|
||||||
|
local LIB_ID = _("LabelsToTags")
|
||||||
|
|
||||||
-- Helper functions: BEGIN
|
-- Helper functions: BEGIN
|
||||||
|
|
||||||
@@ -96,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 = {}
|
||||||
@@ -127,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()
|
||||||
@@ -157,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)
|
||||||
|
|
||||||
@@ -167,29 +193,29 @@ 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
|
||||||
job.valid = false
|
job.valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
local my_widget = darktable.new_widget("box") {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,20 +229,37 @@ 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
|
||||||
mappingComboBox.reset_callback(mappingComboBox)
|
mappingComboBox.reset_callback(mappingComboBox)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not ltt.module_installed then
|
||||||
|
darktable.register_lib(LIB_ID,_("labels to tags"),true,true,{
|
||||||
|
[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20},
|
||||||
|
},ltt.my_widget,nil,nil)
|
||||||
|
ltt.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
darktable.gui.libs[LIB_ID].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
darktable.gui.libs[LIB_ID].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
darktable.register_tag_mapping("Example",
|
darktable.register_tag_mapping("Example",
|
||||||
{ ["+----*"] = { "Red", "Only red" },
|
{ ["+----*"] = { "Red", "Only red" },
|
||||||
@@ -229,6 +272,25 @@ darktable.register_tag_mapping("Example",
|
|||||||
["*****R"] = { "Rejected" } })
|
["*****R"] = { "Rejected" } })
|
||||||
]]
|
]]
|
||||||
|
|
||||||
darktable.register_lib(LIB_ID,"labels to tags",true,true,{
|
if darktable.gui.current_view().id == "lighttable" then
|
||||||
[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20},
|
install_module()
|
||||||
},my_widget,nil,nil)
|
else
|
||||||
|
if not ltt.event_registered then
|
||||||
|
darktable.register_event(
|
||||||
|
LIB_ID, "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
ltt.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -1,111 +1,261 @@
|
|||||||
--[[
|
--[[
|
||||||
OpenInExplorer plugin for darktable
|
OpenInExplorer plugin for darktable
|
||||||
|
|
||||||
copyright (c) 2018 Kevin Ertel
|
copyright (c) 2018 Kevin Ertel
|
||||||
Update 2020 and macOS support by Volker Lenhardt
|
Update 2020 and macOS support by Volker Lenhardt
|
||||||
Linux support 2020 by Bill Ferguson
|
Linux support 2020 by Bill Ferguson
|
||||||
|
|
||||||
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
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
darktable is distributed in the hope that it will be useful,
|
darktable is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[About this plugin
|
--[[About this plugin
|
||||||
This plugin adds the module "OpenInExplorer" to darktable's lighttable view.
|
This plugin adds the module "OpenInExplorer" to darktable's lighttable view.
|
||||||
|
|
||||||
----REQUIRED SOFTWARE----
|
----REQUIRED SOFTWARE----
|
||||||
Apple macOS, Microsoft Windows or Linux
|
Apple macOS, Microsoft Windows or Linux
|
||||||
|
|
||||||
----USAGE----
|
----USAGE----
|
||||||
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
Install: (see here for more detail: https://github.com/darktable-org/lua-scripts )
|
||||||
1) Copy this file into your "lua/contrib" folder where all other scripts reside.
|
1) Copy this file into your "lua/contrib" folder where all other scripts reside.
|
||||||
2) Require this file in your luarc file, as with any other dt plug-in
|
2) Require this file in your luarc file, as with any other dt plug-in
|
||||||
|
|
||||||
Select the photo(s) you wish to find in your operating system's file manager and press "show in file explorer" in the "selected images" section.
|
Select the photo(s) you wish to find in your operating system's file manager and press "show in file explorer" in the "selected images" section.
|
||||||
|
|
||||||
- Nautilus (Linux), Explorer (Windows), and Finder (macOS before Catalina) will open one window for each selected image at the file's location. The file name will be highlighted.
|
- Nautilus (Linux), Explorer (Windows), and Finder (macOS prior to Mojave) will open one window for each selected image at the file's location. The file name will be highlighted.
|
||||||
|
|
||||||
- On macOS Catalina the Finder will open one window for each different directory. In these windows only the last one of the corresponding files will be highlighted (bug or feature?).
|
- On macOS Mojave and Catalina the Finder will open one window for each different directory. In these windows only the last one of the corresponding files will be highlighted (bug or feature?).
|
||||||
|
|
||||||
- Dolphin (Linux) will open one window with tabs for the different directories. All the selected images' file names are highlighted in their respective directories.
|
- Dolphin (Linux) will open one window with tabs for the different directories. All the selected images' file names are highlighted in their respective directories.
|
||||||
|
|
||||||
----KNOWN ISSUES----
|
As an alternative option you can choose to show the image file names as symbolic links in an arbitrary directory. Go to preferences|Lua options. This option is not available for Windows users as on Windows solely admins are allowed to create links.
|
||||||
]]
|
|
||||||
|
- Pros: You do not clutter up your display with multiple windows. So there is no need to limit the number of selections.
|
||||||
local dt = require "darktable"
|
|
||||||
local du = require "lib/dtutils"
|
- Cons: If you want to work with the files you are one step behind the original data.
|
||||||
local df = require "lib/dtutils.file"
|
|
||||||
local dsys = require "lib/dtutils.system"
|
----KNOWN ISSUES----
|
||||||
local gettext = dt.gettext
|
]]
|
||||||
|
|
||||||
--Check API version
|
local dt = require "darktable"
|
||||||
du.check_min_api_version("5.0.0", "OpenInExplorer")
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
gettext.bindtextdomain("OpenInExplorer",dt.configuration.config_dir.."/lua/locale/")
|
local dsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext("OpenInExplorer", msgid)
|
--Check API version
|
||||||
end
|
du.check_min_api_version("7.0.0", "OpenInExplorer")
|
||||||
|
|
||||||
local act_os = dt.configuration.running_os
|
local function _(msgid)
|
||||||
local PS = act_os == "windows" and "\\" or "/"
|
return gettext(msgid)
|
||||||
|
end
|
||||||
--Detect OS and quit if it is not supported--
|
|
||||||
local proper_install = true
|
-- return data structure for script_manager
|
||||||
if act_os ~= "macos" and act_os ~= "windows" and act_os ~= "linux" then
|
|
||||||
proper_install = false
|
local script_data = {}
|
||||||
dt.print_error(_('OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time'))
|
|
||||||
return
|
script_data.metadata = {
|
||||||
end
|
name = _("open in explorer"),
|
||||||
|
purpose = _("open a selected file in the system file manager"),
|
||||||
--Format strings for the commands to open the corresponding OS' file manager
|
author = "Kevin Ertel",
|
||||||
local fmng_cmd = {}
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/OpenInExplorer"
|
||||||
fmng_cmd.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s""]]
|
}
|
||||||
fmng_cmd.macos = 'open -Rn %s'
|
|
||||||
fmng_cmd.windows = 'explorer.exe /select, %s'
|
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
|
||||||
--The working function that opens the file manager windows with the selected image file names highlighted.
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
local function open_in_fmanager(op_sys, fmcmd)
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
local images = dt.gui.selection()
|
|
||||||
local curr_image, run_cmd, file_uris = '', '', ''
|
local act_os = dt.configuration.running_os
|
||||||
if #images == 0 then
|
local PS = act_os == "windows" and "\\" or "/"
|
||||||
dt.print(_('Please select an image'))
|
|
||||||
elseif #images <= 15 then
|
--Detect OS and quit if it is not supported.
|
||||||
for _,image in pairs(images) do
|
if act_os ~= "macos" and act_os ~= "windows" and act_os ~= "linux" then
|
||||||
curr_image = image.path..PS..image.filename
|
dt.print(_("OpenInExplorer plug-in only supports linux, macos, and windows at this time"))
|
||||||
if op_sys == 'linux' then
|
dt.print_error("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time")
|
||||||
file_uris = file_uris .. df.sanitize_filename("file://" .. curr_image) .. " "
|
return
|
||||||
dt.print_log("file_uris is " .. file_uris)
|
end
|
||||||
else
|
|
||||||
run_cmd = string.format(fmcmd, df.sanitize_filename(curr_image))
|
local use_links, links_dir = false, ""
|
||||||
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
|
if act_os ~= "windows" then
|
||||||
dsys.external_command(run_cmd)
|
use_links = dt.preferences.read("OpenInExplorer", "use_links", "bool")
|
||||||
end
|
links_dir = dt.preferences.read("OpenInExplorer", "linked_image_files_dir", "string")
|
||||||
end
|
end
|
||||||
if op_sys == 'linux' then
|
|
||||||
run_cmd = string.format(fmcmd, #images, file_uris)
|
--Check if the directory exists that was chosen for the file links. Return boolean.
|
||||||
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
|
local function check_if_links_dir_exists()
|
||||||
dsys.external_command(run_cmd)
|
local dir_exists = true
|
||||||
end
|
if not links_dir then
|
||||||
else
|
--Just for paranoic reasons. I tried, but I couldn't devise a setting for a nil value.
|
||||||
dt.print(_('Please select fewer images (max 15)'))
|
dt.print(_("no links directory selected\nplease check the dt preferences (lua options)"))
|
||||||
end
|
dt.print_error("OpenInExplorer: No links directory selected")
|
||||||
end
|
dir_exists = false
|
||||||
|
elseif not df.check_if_file_exists(links_dir) then
|
||||||
-- GUI --
|
dt.print(string.format(_("links directory '%s' not found\nplease check the dt preferences (lua options)"), links_dir))
|
||||||
if proper_install then
|
dt.print_error(string.format("OpenInExplorer: Links directory '%s' not found", links_dir))
|
||||||
dt.gui.libs.image.register_action(
|
dir_exists = false
|
||||||
_("show in file explorer"),
|
end
|
||||||
function() open_in_fmanager(act_os, fmng_cmd[act_os]) end,
|
return dir_exists
|
||||||
_("Opens the file manager at the selected image's location")
|
end
|
||||||
)
|
|
||||||
end
|
--Format strings for the commands to open the corresponding OS's file manager.
|
||||||
|
local open_dir = {}
|
||||||
|
open_dir.windows = "explorer.exe /n, %s"
|
||||||
|
open_dir.macos = "open %s"
|
||||||
|
open_dir.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowFolders ass 1 %s ""]]
|
||||||
|
|
||||||
|
local open_files = {}
|
||||||
|
open_files.windows = "explorer.exe /select, %s"
|
||||||
|
open_files.macos = "osascript -e 'tell application \"Finder\" to (reveal {%s}) activate'"
|
||||||
|
open_files.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s ""]]
|
||||||
|
|
||||||
|
reveal_file_osx_cmd = "\"%s\" as POSIX file"
|
||||||
|
|
||||||
|
--Call the osx Finder with each selected image selected.
|
||||||
|
--For images in multiple folders, Finder will open a separate window for each
|
||||||
|
local function call_list_of_files_osx(selected_images)
|
||||||
|
local cmds = {}
|
||||||
|
for _, image in pairs(selected_images) do
|
||||||
|
current_image = image.path..PS..image.filename
|
||||||
|
-- AppleScript needs double quoted strings, and the whole command is wrapped in single quotes.
|
||||||
|
table.insert(cmds, string.format(reveal_file_osx_cmd, string.gsub(string.gsub(current_image, "\"", "\\\""), "'", "'\"'\"'")))
|
||||||
|
end
|
||||||
|
reveal_cmd = table.concat(cmds, ",")
|
||||||
|
run_cmd = string.format(open_files.macos, reveal_cmd)
|
||||||
|
dt.print_log("OSX run_cmd = "..run_cmd)
|
||||||
|
dsys.external_command(run_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
--Call the file mangager for each selected image on Linux.
|
||||||
|
--There is one call to busctl containing a list of all the image file names.
|
||||||
|
local function call_list_of_files(selected_images)
|
||||||
|
local current_image, file_uris, run_cmd = "", "", ""
|
||||||
|
for _, image in pairs(selected_images) do
|
||||||
|
current_image = image.path..PS..image.filename
|
||||||
|
file_uris = file_uris .. df.sanitize_filename("file://" .. current_image) .. " "
|
||||||
|
dt.print_log("file_uris is " .. file_uris)
|
||||||
|
end
|
||||||
|
run_cmd = string.format(open_files.linux, #selected_images, file_uris)
|
||||||
|
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
|
||||||
|
dsys.external_command(run_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
--Call the file manager for each selected image on Windows and macOS.
|
||||||
|
local function call_file_by_file(selected_images)
|
||||||
|
local current_image, run_cmd = "", ""
|
||||||
|
for _, image in pairs(selected_images) do
|
||||||
|
current_image = image.path..PS..image.filename
|
||||||
|
run_cmd = string.format(open_files[act_os], df.sanitize_filename(current_image))
|
||||||
|
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
|
||||||
|
dsys.external_command(run_cmd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--Create a link for each selected image, and finally call the file manager.
|
||||||
|
local function set_links(selected_images)
|
||||||
|
local current_image, link_target, run_cmd, k = "", "", "", nil
|
||||||
|
for k, image in pairs(selected_images) do
|
||||||
|
current_image = image.path..PS..image.filename
|
||||||
|
link_target = df.create_unique_filename(links_dir .. PS .. image.filename)
|
||||||
|
run_cmd = string.format("ln -s %s %s", df.sanitize_filename(current_image), df.sanitize_filename(link_target))
|
||||||
|
--[[
|
||||||
|
In case Windows will allow normal users to create soft links:
|
||||||
|
if act_os == "windows" then
|
||||||
|
run_cmd = string.format("mklink %s %s", df.sanitize_filename(link_target), df.sanitize_filename(current_image))
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
if dsys.external_command(run_cmd) ~= 0 then
|
||||||
|
dt.print(_("failed to create links, missing rights?"))
|
||||||
|
dt.print_error("OpenInExplorer: Failed to create links")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--The URI format is necessary only for the Linux busctl command.
|
||||||
|
--But it is accepted by the Windows Explorer and macOS's Finder all the same.
|
||||||
|
run_cmd = string.format(open_dir[act_os], df.sanitize_filename("file://"..links_dir))
|
||||||
|
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
|
||||||
|
dsys.external_command(run_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
--The working function that starts the particular task.
|
||||||
|
local function open_in_fmanager()
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
if #images == 0 then
|
||||||
|
dt.print(_("please select an image"))
|
||||||
|
else
|
||||||
|
if use_links and not check_if_links_dir_exists() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if #images > 15 and not use_links then
|
||||||
|
dt.print(_("please select fewer images (max. 15)"))
|
||||||
|
elseif use_links then
|
||||||
|
set_links(images)
|
||||||
|
else
|
||||||
|
if act_os == "linux" then
|
||||||
|
call_list_of_files(images)
|
||||||
|
elseif act_os == "macos" then
|
||||||
|
call_list_of_files_osx(images)
|
||||||
|
else
|
||||||
|
call_file_by_file(images)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs.image.destroy_action("OpenInExplorer")
|
||||||
|
dt.destroy_event("OpenInExplorer", "shortcut")
|
||||||
|
if act_os ~= "windows" then
|
||||||
|
dt.preferences.destroy("OpenInExplorer", "linked_image_files_dir")
|
||||||
|
end
|
||||||
|
dt.preferences.destroy("OpenInExplorer", "use_links")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- GUI --
|
||||||
|
dt.gui.libs.image.register_action(
|
||||||
|
"OpenInExplorer", _("show in file explorer"),
|
||||||
|
function() open_in_fmanager() end,
|
||||||
|
_("open the file manager at the selected image's location")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if act_os ~= "windows" then
|
||||||
|
dt.preferences.register("OpenInExplorer", "linked_image_files_dir", -- name
|
||||||
|
"directory", -- type
|
||||||
|
_("OpenInExplorer: linked files directory"), -- label
|
||||||
|
_("directory to store the links to the file names, requires restart to take effect"), -- tooltip
|
||||||
|
"Links to image files", -- default
|
||||||
|
dt.new_widget("file_chooser_button"){
|
||||||
|
title = _("select directory"),
|
||||||
|
is_directory = true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
dt.preferences.register("OpenInExplorer", "use_links", -- name
|
||||||
|
"bool", -- type
|
||||||
|
_("OpenInExplorer: use links"), -- label
|
||||||
|
_("use links instead of multiple windows, requires restart to take effect"), -- tooltip
|
||||||
|
false, -- default
|
||||||
|
""
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event(
|
||||||
|
"OpenInExplorer", "shortcut",
|
||||||
|
function(event, shortcut) open_in_fmanager() end,
|
||||||
|
"OpenInExplorer"
|
||||||
|
)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -1,10 +1,29 @@
|
|||||||
--[[
|
--[[
|
||||||
|
Richardson-Lucy output sharpening for darktable using GMic
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
RL_out_sharp.lua - Richardson-Lucy output sharpening using GMic
|
RL_out_sharp.lua - Richardson-Lucy output sharpening using GMic
|
||||||
|
|
||||||
This script provides a new target storage "RL output sharpen".
|
This script provides a new target storage "RL output sharpen".
|
||||||
Images exported will be sharpened using GMic (RL deblur algorithm)
|
Images exported will be sharpened using GMic (RL deblur algorithm)
|
||||||
|
|
||||||
|
REQUIRED SOFTWARE
|
||||||
|
GMic command line interface (CLI) https://gmic.eu/download.shtml
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
* require this script from main lua file
|
* require this script from main lua file
|
||||||
@@ -33,7 +52,6 @@
|
|||||||
|
|
||||||
CHANGES
|
CHANGES
|
||||||
* 20200308 - initial version
|
* 20200308 - initial version
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
@@ -45,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
|
end
|
||||||
|
|
||||||
|
-- preserve original image metadata in the output image -----------------------
|
||||||
|
local function preserve_metadata(original, sharpened)
|
||||||
|
local exiftool = df.check_if_bin_exists("exiftool")
|
||||||
|
|
||||||
|
if exiftool then
|
||||||
|
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
|
||||||
|
else
|
||||||
|
dt.print_log(MODULE_NAME .. " exiftool not found, metadata not preserved")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- setup export ---------------------------------------------------------------
|
-- setup export ---------------------------------------------------------------
|
||||||
@@ -110,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)
|
||||||
@@ -127,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)
|
||||||
@@ -137,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"){
|
||||||
@@ -156,7 +210,7 @@ sigma_slider = dt.new_widget("slider"){
|
|||||||
soft_max = 2.0,
|
soft_max = 2.0,
|
||||||
hard_min = 0.0,
|
hard_min = 0.0,
|
||||||
hard_max = 3.0,
|
hard_max = 3.0,
|
||||||
step = 0.1,
|
step = 0.05,
|
||||||
digits = 2,
|
digits = 2,
|
||||||
value = 1.0
|
value = 1.0
|
||||||
}
|
}
|
||||||
@@ -206,6 +260,12 @@ sigma_slider.value = dt.preferences.read(MODULE_NAME, "sigma", "float")
|
|||||||
iterations_slider.value = dt.preferences.read(MODULE_NAME, "iterations", "float")
|
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,101 +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
|
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
|
|
||||||
-- First find the style (we have its name)
|
|
||||||
local styles= darktable.styles
|
|
||||||
local style
|
|
||||||
for _,s in ipairs(styles) do
|
|
||||||
if s.name == style_name then
|
|
||||||
style=s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (not style) then
|
|
||||||
darktable.print("style not found for autostyle: " .. style_name)
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply the style to image, if it is tagged
|
local have_not_printed_config_message = true
|
||||||
local ok,auto_dr_attr= pcall(exiftool_attribute,image.path .. '/' .. image.filename,tag)
|
|
||||||
--darktable.print_error("dr_attr:" .. auto_dr_attr)
|
|
||||||
-- If the lookup fails, stop here
|
|
||||||
if (not ok) then
|
|
||||||
darktable.print("Couldn't get attribute " .. auto_dr_attr .. " from exiftool's output")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
if auto_dr_attr==value then
|
|
||||||
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. darktable.preferences.read("autostyle","exif_tag","string") )
|
|
||||||
darktable.styles.apply(style,image)
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. darktable.preferences.read("autostyle","exif_tag","string") .. " not matched: " .. auto_dr_attr)
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("auto style"),
|
||||||
|
purpose = _("automatically apply a style based on image EXIF tag"),
|
||||||
|
author = "Marc Cousin <cousinmarc@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle/"
|
||||||
|
}
|
||||||
|
|
||||||
function autostyle_apply( shortcut)
|
script_data.destroy = nil -- function to destory the script
|
||||||
local images = darktable.gui.action_images
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
local images_processed =0
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
local images_submitted =0
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
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)
|
||||||
@@ -150,12 +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",autostyle_apply_one_image_event)
|
darktable.register_event("autostyle", "post-import-image",
|
||||||
|
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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
--[[
|
--[[
|
||||||
|
|
||||||
clear_GPS.lua - export and edit with GIMP
|
clear_GPS.lua - plugin for Darktable
|
||||||
|
|
||||||
Copyright (C) 2016 Bill Ferguson <wpferguson@gmail.com>.
|
Copyright (C) 2016 Bill Ferguson <wpferguson@gmail.com>.
|
||||||
|
|
||||||
@@ -38,20 +38,33 @@
|
|||||||
|
|
||||||
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")
|
||||||
|
|
||||||
|
|
||||||
-- 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
|
||||||
@@ -62,15 +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"
|
_("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,17 +40,35 @@ 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")
|
||||||
|
|
||||||
-- 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 = {}
|
||||||
|
cadt.module_installed = false
|
||||||
|
cadt.event_registered = false
|
||||||
|
cadt.widget_table = {}
|
||||||
|
|
||||||
|
|
||||||
local image_tags = {}
|
local image_tags = {}
|
||||||
|
|
||||||
@@ -85,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 = ""
|
||||||
@@ -107,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 tags to attached, please copy tags first.'))
|
dt.print(_('no tag to attach, please copy tags first.'))
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -131,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()
|
||||||
@@ -147,13 +165,68 @@ 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
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not cadt.module_installed then
|
||||||
|
dt.register_lib("tagging_addon",_('tagging addon'),true,true,{
|
||||||
|
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",500}
|
||||||
|
},
|
||||||
|
dt.new_widget("box") {
|
||||||
|
-- orientation = "vertical",
|
||||||
|
reset_callback = function()
|
||||||
|
taglist_label.label = ""
|
||||||
|
image_tags = {}
|
||||||
|
end,
|
||||||
|
table.unpack(cadt.widget_table),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
cadt.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_event("cadt_ct", "shortcut")
|
||||||
|
dt.destroy_event("cadt_at", "shortcut")
|
||||||
|
dt.destroy_event("cadt_dt", "shortcut")
|
||||||
|
dt.destroy_event("cadt_rt", "shortcut")
|
||||||
|
dt.gui.libs["tagging_addon"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
-- shortcut for copy
|
||||||
|
dt.register_event("cadt_ct", "shortcut",
|
||||||
|
mcopy_tags,
|
||||||
|
_('copy tags from selected image(s)'))
|
||||||
|
|
||||||
|
-- shortcut for attach
|
||||||
|
dt.register_event("cadt_at", "shortcut",
|
||||||
|
attach_tags,
|
||||||
|
_('paste tags to selected image(s)'))
|
||||||
|
|
||||||
|
-- shortcut for detaching tags
|
||||||
|
dt.register_event("cadt_dt", "shortcut",
|
||||||
|
detach_tags,
|
||||||
|
_('remove tags from selected image(s)'))
|
||||||
|
|
||||||
|
-- shortcut for replace tags
|
||||||
|
dt.register_event("cadt_rt", "shortcut",
|
||||||
|
replace_tags,
|
||||||
|
_('replace tags from selected image(s)'))
|
||||||
|
dt.gui.libs["tagging_addon"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs["tagging_addon"].visible = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create modul Tagging addons
|
-- create modul Tagging addons
|
||||||
@@ -169,73 +242,82 @@ local taglabel = dt.new_widget("label") {
|
|||||||
local box1 = dt.new_widget("box"){
|
local box1 = dt.new_widget("box"){
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("button") {
|
dt.new_widget("button") {
|
||||||
label = _('multi copy tags'),
|
label = _('multi copy tags'),
|
||||||
clicked_callback = mcopy_tags},
|
tooltip = _('copy tags from selected image(s)'),
|
||||||
|
clicked_callback = mcopy_tags},
|
||||||
dt.new_widget("button") {
|
dt.new_widget("button") {
|
||||||
label = _('paste tags'),
|
tooltip = _('paste tags to selected image(s)'),
|
||||||
clicked_callback = attach_tags}
|
label = _('paste tags'),
|
||||||
|
clicked_callback = attach_tags}
|
||||||
}
|
}
|
||||||
|
|
||||||
local box2 = dt.new_widget("box"){
|
local box2 = dt.new_widget("box"){
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
dt.new_widget("button") {
|
dt.new_widget("button") {
|
||||||
label = _('replace tags'),
|
label = _('replace tags'),
|
||||||
clicked_callback = replace_tags},
|
tooltip = _('replace tags from selected image(s)'),
|
||||||
|
clicked_callback = replace_tags},
|
||||||
dt.new_widget("button") {
|
dt.new_widget("button") {
|
||||||
label = _('remove all tags'),
|
label = _('remove all tags'),
|
||||||
clicked_callback = detach_tags}
|
tooltip = _('remove tags from selected image(s)'),
|
||||||
|
clicked_callback = detach_tags}
|
||||||
}
|
}
|
||||||
|
|
||||||
local sep = dt.new_widget("separator"){}
|
local sep = dt.new_widget("separator"){}
|
||||||
|
|
||||||
-- pack elements into widget table for a nicer layout
|
-- pack elements into widget table for a nicer layout
|
||||||
local widget_table = {}
|
|
||||||
|
|
||||||
widget_table[1] = box1
|
cadt.widget_table[1] = box1
|
||||||
widget_table[#widget_table+1] = box2
|
cadt.widget_table[#cadt.widget_table+1] = box2
|
||||||
|
|
||||||
widget_table[#widget_table+1] = sep
|
cadt.widget_table[#cadt.widget_table+1] = sep
|
||||||
widget_table[#widget_table+1] = taglabel
|
cadt.widget_table[#cadt.widget_table+1] = taglabel
|
||||||
widget_table[#widget_table+1] = taglist_label
|
cadt.widget_table[#cadt.widget_table+1] = taglist_label
|
||||||
|
|
||||||
|
|
||||||
-- create modul
|
-- create modul
|
||||||
dt.register_lib("tagging_addon","Tagging addon",true,true,{
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",500}
|
install_module()
|
||||||
},
|
else
|
||||||
dt.new_widget("box") {
|
if not cadt.event_registered then
|
||||||
-- orientation = "vertical",
|
dt.register_event(
|
||||||
reset_callback = function()
|
"cadt", "view-changed",
|
||||||
taglist_label.label = ""
|
function(event, old_view, new_view)
|
||||||
image_tags = {}
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
end,
|
install_module()
|
||||||
table.unpack(widget_table),
|
end
|
||||||
},
|
end
|
||||||
nil,
|
)
|
||||||
nil
|
cadt.event_registered = true
|
||||||
)
|
end
|
||||||
|
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,8 +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 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 = {}
|
||||||
@@ -79,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
|
||||||
@@ -90,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 = {}
|
||||||
@@ -107,8 +128,21 @@ local function convert_action_images(shortcut)
|
|||||||
convert_images()
|
convert_images()
|
||||||
end
|
end
|
||||||
|
|
||||||
darktable.register_event("shortcut", convert_action_images, "Run cr2hdr (Magic Lantern DualISO converter) on selected images")
|
local function destroy()
|
||||||
darktable.register_event("post-import-image", file_imported)
|
darktable.destroy_event("cr2hdr", "shortcut")
|
||||||
darktable.register_event("post-import-film", film_imported)
|
darktable.destroy_event("cr2hdr", "post-import-image")
|
||||||
|
darktable.destroy_event("cr2hdr", "post-import-film")
|
||||||
|
end
|
||||||
|
|
||||||
darktable.preferences.register("cr2hdr", "onimport", "bool", "Invoke on import", "If true then cr2hdr will try to proccess every file during importing. Warning: cr2hdr is quite slow even in figuring out on whether the file is Dual ISO or not.", false)
|
darktable.register_event("cr2hdr", "shortcut",
|
||||||
|
convert_action_images, _("run cr2hdr (Magic Lantern DualISO converter) on selected images"))
|
||||||
|
darktable.register_event("cr2hdr", "post-import-image",
|
||||||
|
file_imported)
|
||||||
|
darktable.register_event("cr2hdr", "post-import-film",
|
||||||
|
film_imported)
|
||||||
|
|
||||||
|
darktable.preferences.register("cr2hdr", "onimport", "bool", _("invoke on import"), _("if true then cr2hdr will try to proccess every file during importing\nwarning: cr2hdr is quite slow even in figuring out on whether the file is dual ISO or not."), false)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
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',
|
||||||
@@ -180,6 +196,11 @@ for _,i in pairs(dt.styles) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- FUNCTION --
|
-- FUNCTION --
|
||||||
|
|
||||||
|
local function sanitize_decimals(cmd) -- make sure decimal separator is a '.'
|
||||||
|
return string.gsub(cmd, '(%d),(%d)', "%1.%2")
|
||||||
|
end
|
||||||
|
|
||||||
local function InRange(test, low, high) -- tests if test value is within range of low and high (inclusive)
|
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
|
||||||
@@ -240,13 +261,13 @@ end
|
|||||||
|
|
||||||
local function ExeUpdate(prog_tbl) --updates executable paths and verifies them
|
local function ExeUpdate(prog_tbl) --updates executable paths and verifies them
|
||||||
dt.preferences.write(mod, 'bin_exists', 'bool', true)
|
dt.preferences.write(mod, 'bin_exists', 'bool', true)
|
||||||
for _,prog in pairs(prog_tbl) do
|
for x,prog in pairs(prog_tbl) do
|
||||||
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
|
dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value)
|
||||||
prog.bin = df.check_if_bin_exists(prog.name)
|
prog.bin = df.check_if_bin_exists(prog.name)
|
||||||
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
|
||||||
@@ -267,7 +288,13 @@ local function GetArgsFromPreference(prog_table, prefix) --for each arg in a pro
|
|||||||
local temp = dt.preferences.read(mod, prefix..argument, arg_data.style)
|
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)
|
||||||
@@ -311,7 +338,7 @@ local function UpdateENFargs(image_table, prefix) --updates the Enfuse arguments
|
|||||||
local largest_id = 0
|
local largest_id = 0
|
||||||
local first_raw = {}
|
local first_raw = {}
|
||||||
for raw, temp_image in pairs(image_table) do
|
for raw, temp_image in pairs(image_table) do
|
||||||
_, source_name, source_id = GetFileName(raw.filename)
|
local _, source_name, source_id = GetFileName(raw.filename)
|
||||||
source_id = tonumber(source_id)
|
source_id = tonumber(source_id)
|
||||||
if source_id < smallest_id then
|
if source_id < smallest_id then
|
||||||
smallest_id = source_id
|
smallest_id = source_id
|
||||||
@@ -324,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
|
||||||
@@ -363,7 +390,7 @@ local function SaveToPreference(preset) --save the present values of enfuse GUI
|
|||||||
dt.preferences.write(mod, preset..argument, arg_data.style, temp)
|
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
|
||||||
@@ -379,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
|
||||||
@@ -414,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)
|
||||||
@@ -422,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 = ''
|
||||||
@@ -435,12 +462,15 @@ local function main(storage, image_table, extra_data)
|
|||||||
local job = dt.gui.create_job('aligning images')
|
local job = dt.gui.create_job('aligning images')
|
||||||
image_table, images_to_remove, AIS.images_string = UpdateAISargs(image_table, images_to_remove)
|
image_table, images_to_remove, AIS.images_string = UpdateAISargs(image_table, images_to_remove)
|
||||||
local run_cmd = BuildExecuteCmd(AIS)
|
local run_cmd = BuildExecuteCmd(AIS)
|
||||||
|
dt.print_log("AIS run command is " .. run_cmd)
|
||||||
|
run_cmd = sanitize_decimals(run_cmd)
|
||||||
|
dt.print_log("AIS decimaal sanitized command is " .. run_cmd)
|
||||||
local resp = dsys.external_command(run_cmd)
|
local resp = dsys.external_command(run_cmd)
|
||||||
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
|
||||||
@@ -455,26 +485,35 @@ local function main(storage, image_table, extra_data)
|
|||||||
local image_num = 0
|
local image_num = 0
|
||||||
local job = dt.gui.create_job('blending '..#variants..' image(s)', true) --create a GUI job bar to display enfuse progress
|
local job = dt.gui.create_job('blending '..#variants..' image(s)', true) --create a GUI job bar to display enfuse progress
|
||||||
UpdateActivePreference() --load current GUI values into active preference (only applies to elements without a clicked/changed callback)
|
UpdateActivePreference() --load current GUI values into active preference (only applies to elements without a clicked/changed callback)
|
||||||
for _,prefix in pairs(variants) do --for each image to be created load in the preference values, build arguments string, output image, and run command then execute.
|
for x,prefix in pairs(variants) do --for each image to be created load in the preference values, build arguments string, output image, and run command then execute.
|
||||||
job.percent = image_num /(#variants)
|
job.percent = image_num /(#variants)
|
||||||
image_num = image_num+1
|
image_num = image_num+1
|
||||||
ENF.images_string, final_image, source_raw = UpdateENFargs(image_table, prefix)
|
ENF.images_string, final_image, source_raw = UpdateENFargs(image_table, prefix)
|
||||||
local run_cmd = BuildExecuteCmd(ENF)
|
local run_cmd = BuildExecuteCmd(ENF)
|
||||||
|
dt.print_log("ENF run command is " .. run_cmd)
|
||||||
|
run_cmd = sanitize_decimals(run_cmd)
|
||||||
|
dt.print_log("ENF decimaal sanitized command is " .. run_cmd)
|
||||||
local resp = dsys.external_command(run_cmd)
|
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
|
||||||
|
|
||||||
--copy exif data from original file
|
--copy exif data from original file
|
||||||
run_cmd = EXF.bin..' -TagsFromFile '..df.sanitize_filename(source_raw.path..os_path_seperator..source_raw.filename)..' -exif:all --subifd:all -overwrite_original '..df.sanitize_filename(final_image)
|
run_cmd = EXF.bin..' -TagsFromFile '..df.sanitize_filename(source_raw.path..os_path_seperator..source_raw.filename)..' -exif:all --subifd:all -overwrite_original '..df.sanitize_filename(final_image)
|
||||||
|
-- replace comma decimal separator with period
|
||||||
|
dt.print_log("EXF run command is " .. run_cmd)
|
||||||
|
run_cmd = sanitize_decimals(run_cmd)
|
||||||
|
dt.print_log("EXF decimaal sanitized command is " .. run_cmd)
|
||||||
resp = dsys.external_command(run_cmd)
|
resp = dsys.external_command(run_cmd)
|
||||||
|
|
||||||
|
|
||||||
if GUI.Target.auto_import.value then --import image into dt if specified
|
if GUI.Target.auto_import.value then --import image into dt if specified
|
||||||
local imported = dt.database.import(final_image)
|
local imported = dt.database.import(final_image)
|
||||||
|
dt.print_log("image imported")
|
||||||
if GUI.Target.apply_style.selected > 1 then --apply specified style to imported image
|
if GUI.Target.apply_style.selected > 1 then --apply specified style to imported image
|
||||||
local set_style = styles[GUI.Target.apply_style.selected - 1]
|
local set_style = styles[GUI.Target.apply_style.selected - 1]
|
||||||
dt.styles.apply(set_style , imported)
|
dt.styles.apply(set_style , imported)
|
||||||
@@ -500,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'){
|
||||||
@@ -700,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
|
||||||
}
|
}
|
||||||
@@ -853,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
|
||||||
}
|
}
|
||||||
@@ -887,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)
|
||||||
@@ -909,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')
|
||||||
@@ -939,7 +982,7 @@ GUI.Presets.current_preset = dt.new_widget('combobox'){
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
GUI.Presets.load = dt.new_widget('button'){
|
GUI.Presets.load = dt.new_widget('button'){
|
||||||
label = _('laod fusion preset'),
|
label = _('load fusion preset'),
|
||||||
tooltip = _('load current fusion parameters from selected preset'),
|
tooltip = _('load current fusion parameters from selected preset'),
|
||||||
clicked_callback = function() LoadFromPreference(GUI.Presets.current_preset.value) end
|
clicked_callback = function() LoadFromPreference(GUI.Presets.current_preset.value) end
|
||||||
}
|
}
|
||||||
@@ -985,7 +1028,7 @@ GUI.Presets.variants_type = dt.new_widget('combobox'){
|
|||||||
GUI.Presets.variants_type.sensitive = GUI.Presets.variants.value
|
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,
|
||||||
@@ -993,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,
|
||||||
@@ -1001,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,
|
||||||
@@ -1126,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
|
||||||
|
|||||||
204
contrib/exportLUT.lua
Normal file
204
contrib/exportLUT.lua
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
--[[
|
||||||
|
This file is part of darktable,
|
||||||
|
copyright (c) 2020 Noah Clarke
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
Add the following line to .config/darktable/luarc to enable this lightable module:
|
||||||
|
require "contrib/exportLut"
|
||||||
|
|
||||||
|
Given a haldCLUT identity file this script generates haldCLUTS from all the user's
|
||||||
|
styles and exports them to a location of their choosing.
|
||||||
|
|
||||||
|
Warning: during export if a naming collision occurs the older file is automatically
|
||||||
|
overwritten silently.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require("lib/dtutils.file")
|
||||||
|
local ds = require("lib/dtutils.system")
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "exportLUT")
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("export LUT"),
|
||||||
|
purpose = _("export a style as a LUT"),
|
||||||
|
author = "Noah Clarke",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportLUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
du.check_min_api_version("5.0.0", "exportLUT")
|
||||||
|
|
||||||
|
local eL = {}
|
||||||
|
eL.module_installed = false
|
||||||
|
eL.event_registered = false
|
||||||
|
eL.widgets = {}
|
||||||
|
|
||||||
|
-- Thanks Kevin Ertel for this bit
|
||||||
|
local os_path_seperator = '/'
|
||||||
|
if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end
|
||||||
|
local mkdir_command = 'mkdir -p '
|
||||||
|
if dt.configuration.running_os == 'windows' then mkdir_command = 'mkdir ' end
|
||||||
|
|
||||||
|
local file_chooser_button = dt.new_widget("file_chooser_button"){
|
||||||
|
title = _("choose the identity file"),
|
||||||
|
value = "",
|
||||||
|
is_directory = false
|
||||||
|
}
|
||||||
|
|
||||||
|
local export_chooser_button = dt.new_widget("file_chooser_button"){
|
||||||
|
title = _("choose the export location"),
|
||||||
|
value = "",
|
||||||
|
is_directory = true
|
||||||
|
}
|
||||||
|
|
||||||
|
local identity_label = dt.new_widget("label"){
|
||||||
|
label = _("choose the identity haldclut file")
|
||||||
|
}
|
||||||
|
|
||||||
|
local output_label = dt.new_widget("label"){
|
||||||
|
label = _("choose the output location")
|
||||||
|
}
|
||||||
|
|
||||||
|
local warning_label = dt.new_widget("label"){
|
||||||
|
label = _("WARNING: files may be silently overwritten")
|
||||||
|
}
|
||||||
|
|
||||||
|
local function end_job(job)
|
||||||
|
job.valid = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function output_path(style_name, job)
|
||||||
|
local output_location = export_chooser_button.value .. os_path_seperator .. style_name .. ".png"
|
||||||
|
output_location = string.gsub(output_location, "|", os_path_seperator)
|
||||||
|
local output_dir = string.reverse(output_location)
|
||||||
|
output_dir = string.gsub(output_dir, ".-" .. os_path_seperator, os_path_seperator, 1)
|
||||||
|
output_dir = string.reverse(output_dir)
|
||||||
|
if(output_dir ~= "") then
|
||||||
|
df.mkdir(df.sanitize_filename(output_dir))
|
||||||
|
end
|
||||||
|
return output_location
|
||||||
|
end
|
||||||
|
|
||||||
|
local function export_luts()
|
||||||
|
local identity = dt.database.import(file_chooser_button.value)
|
||||||
|
if(type(identity) ~= "userdata") then
|
||||||
|
dt.print(_("invalid identity lut file"))
|
||||||
|
else
|
||||||
|
local job = dt.gui.create_job(_('exporting styles as haldCLUTs'), true, end_job)
|
||||||
|
|
||||||
|
local size = 1
|
||||||
|
|
||||||
|
for style_num, style in ipairs(dt.styles) do
|
||||||
|
size = size + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for style_num, style in ipairs(dt.styles) do
|
||||||
|
|
||||||
|
identity:reset()
|
||||||
|
dt.styles.apply(style, identity)
|
||||||
|
local io_lut = dt.new_format("png")
|
||||||
|
io_lut.bpp = 8
|
||||||
|
|
||||||
|
io_lut:write_image(identity, output_path(style.name, job))
|
||||||
|
count = count + 1
|
||||||
|
job.percent = count / size
|
||||||
|
dt.print(string.format(_("exported: %s"), output_path(style.name, job)))
|
||||||
|
end
|
||||||
|
dt.print(_("done exporting haldCLUTs"))
|
||||||
|
job.valid = false
|
||||||
|
identity:reset()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not eL.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
"export haldclut",
|
||||||
|
_("export haldclut"),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
|
||||||
|
dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "vertical",
|
||||||
|
table.unpack(eL.widgets),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
eL.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["export haldclut"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["export haldclut"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local export_button = dt.new_widget("button"){
|
||||||
|
label = _("export"),
|
||||||
|
clicked_callback = export_luts
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(eL.widgets, identity_label)
|
||||||
|
table.insert(eL.widgets, file_chooser_button)
|
||||||
|
table.insert(eL.widgets, output_label)
|
||||||
|
table.insert(eL.widgets, export_chooser_button)
|
||||||
|
table.insert(eL.widgets, warning_label)
|
||||||
|
table.insert(eL.widgets, export_button)
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not eL.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"exportLUT", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
eL.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -1,64 +1,67 @@
|
|||||||
--[[
|
--[[
|
||||||
|
ext_editor.lua - edit images with external editors
|
||||||
|
|
||||||
DESCRIPTION
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
ext_editor.lua - edit images with external editors
|
ext_editor.lua - edit images with external editors
|
||||||
|
|
||||||
This script provides helpers to edit image files with programs external to darktable.
|
This script provides helpers to edit image files with programs external to darktable. It adds:
|
||||||
It adds:
|
- a new target storage "collection". Image exported will be reimported to collection for further edit with external programs
|
||||||
- a new target storage "collection". Image exported will be reimported to collection for
|
- a new module "external editors", visible in lightable and darkroom, to select a program from a list
|
||||||
further edit with external programs
|
- of up to 9 external editors and run it on a selected image (adjust this limit by changing MAX_EDITORS)
|
||||||
- a new lighttable module "external editors", to select a program from a list of up to
|
- a set of lua preferences in order to configure name and path of up to 9 external editors
|
||||||
- 9 external editors and run it on a selected image (adjust this limit by changing MAX_EDITORS)
|
- a set of lua shortcuts in order to quick launch the external editors
|
||||||
- a set of lua preferences in order to configure name and path of up to 9 external editors
|
|
||||||
- a set of lua shortcuts in order to quick launch the external editors
|
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
* require this script from main lua file
|
* require this script from main lua file
|
||||||
|
|
||||||
-- setup --
|
-- setup --
|
||||||
* in "preferences/lua options" configure name and path/command of external programs
|
* in "preferences/lua options" configure name and path/command of external programs
|
||||||
* note that if a program name is left empty, that and all following entries will be ignored
|
* note that if a program name is left empty, that and all following entries will be ignored
|
||||||
* in "preferences/shortcuts/lua" configure shortcuts for external programs (optional)
|
* in "preferences/shortcuts/lua" configure shortcuts for external programs (optional)
|
||||||
* whenever programs preferences are changed, in lighttable/external editors, press "update list"
|
* whenever programs preferences are changed, in external editors GUI, press "update list"
|
||||||
|
|
||||||
-- use --
|
-- use --
|
||||||
* in the export dialog choose "collection" and select the format and bit depth for the
|
* in the export dialog choose "collection" and select the format and bit depth for the
|
||||||
exported image
|
exported image
|
||||||
* press "export"
|
* press "export"
|
||||||
* the exported image will be imported into collection and grouped with the original image
|
* the exported image will be imported into collection and grouped with the original image
|
||||||
|
|
||||||
|
* in lighttable, select an image for editing with en external program
|
||||||
|
* (or in darkroom for the image being edited):
|
||||||
|
* in external editors GUI, select program and press "edit"
|
||||||
|
* edit the image with the external editor, overwite the file, quit the external program
|
||||||
|
* the selected image will be updated
|
||||||
|
or
|
||||||
|
* in external editors GUI, select program and press "edit a copy"
|
||||||
|
* edit the image with the external editor, overwite the file, quit the external program
|
||||||
|
* a copy of the selected image will be created and updated
|
||||||
|
or
|
||||||
|
* use the shortcut to edit the current image with the corresponding external editor
|
||||||
|
* overwite the file, quit the external program
|
||||||
|
* the image will be updated
|
||||||
|
|
||||||
* select an image for editing with en external program, and:
|
* warning: mouseover on lighttable/filmstrip will prevail on current image
|
||||||
* in lighttable/external editors, select program and press "edit"
|
* this is the default DT behavior, not a bug of this script
|
||||||
* edit the image with the external editor, overwite the file, quit the external program
|
|
||||||
* the selected image will be updated
|
|
||||||
or
|
|
||||||
* in lighttable/external editors, select program and press "edit a copy"
|
|
||||||
* edit the image with the external editor, overwite the file, quit the external program
|
|
||||||
* a copy of the selected image will be created and updated
|
|
||||||
or
|
|
||||||
* in lighttable select target storage "collection"
|
|
||||||
* enter in darkroom
|
|
||||||
* to create an export or a copy press CRTL+E
|
|
||||||
* use the shortcut to edit the current image with the corresponding external editor
|
|
||||||
* overwite the file, quit the external program
|
|
||||||
* the darkroom view will be updated
|
|
||||||
|
|
||||||
* warning: mouseover on lighttable/filmstrip will prevail on current image
|
|
||||||
* this is the default DT behavior, not a bug of this script
|
|
||||||
|
|
||||||
CAVEATS
|
CAVEATS
|
||||||
* MAC compatibility not tested
|
* MAC compatibility not tested
|
||||||
|
|
||||||
TODO
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
* send multiple images to the same program, maybe
|
|
||||||
|
|
||||||
BUGS, COMMENTS, SUGGESTIONS
|
|
||||||
* send to Marco Carrarini, marco.carrarini@gmail.com
|
* send to Marco Carrarini, marco.carrarini@gmail.com
|
||||||
|
|
||||||
CHANGES
|
|
||||||
* 20191224 - initial version
|
|
||||||
* 20191227 - added button "update list", better error handling, fixed bug with groups/tags in "edit"
|
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|
||||||
@@ -70,22 +73,40 @@ local dtsys = require "lib/dtutils.system"
|
|||||||
|
|
||||||
-- module name
|
-- module name
|
||||||
local MODULE_NAME = "ext_editor"
|
local MODULE_NAME = "ext_editor"
|
||||||
|
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.0
|
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 "/"
|
||||||
|
|
||||||
|
-- namespace
|
||||||
|
local ee = {}
|
||||||
|
ee.module_installed = false
|
||||||
|
ee.event_registered = false
|
||||||
|
ee.widgets = {}
|
||||||
|
|
||||||
-- translation
|
|
||||||
local gettext = dt.gettext
|
|
||||||
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
|
|
||||||
local function _(msgid)
|
|
||||||
return gettext.dgettext(MODULE_NAME, msgid)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- maximum number of external programs, can be increased to necessity
|
-- maximum number of external programs, can be increased to necessity
|
||||||
local MAX_EDITORS = 9
|
local MAX_EDITORS = 9
|
||||||
@@ -98,10 +119,11 @@ local n_entries
|
|||||||
local allowed_file_types = {"JPG", "jpg", "JPEG", "jpeg", "TIF", "tif", "TIFF", "tiff", "EXR", "exr", "PNG", "png"}
|
local allowed_file_types = {"JPG", "jpg", "JPEG", "jpeg", "TIF", "tif", "TIFF", "tiff", "EXR", "exr", "PNG", "png"}
|
||||||
|
|
||||||
|
|
||||||
|
-- last used editor initialization
|
||||||
if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then
|
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
|
||||||
|
|
||||||
|
|
||||||
@@ -126,23 +148,23 @@ 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
|
end
|
||||||
|
|
||||||
lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer")
|
lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer")
|
||||||
if lastchoice == 0 and n_entries > 0 then lastchoice = 1 end
|
if lastchoice == 0 and n_entries > 0 then lastchoice = 1 end
|
||||||
if lastchoice > n_entries then lastchoice = n_entries end
|
if lastchoice > n_entries then lastchoice = n_entries end
|
||||||
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice)
|
dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice)
|
||||||
|
|
||||||
-- widgets enabled if there is at least one program configured
|
-- widgets enabled if there is at least one program configured
|
||||||
combobox.selected = lastchoice
|
combobox.selected = lastchoice
|
||||||
local active = n_entries > 0
|
local active = n_entries > 0
|
||||||
combobox.sensitive = active
|
combobox.sensitive = active
|
||||||
button_edit.sensitive = active
|
button_edit.sensitive = active
|
||||||
button_edit_copy.sensitive = active
|
button_edit_copy.sensitive = active
|
||||||
|
|
||||||
if update_button_pressed then dt.print(n_entries.._(" editors configured")) end
|
if update_button_pressed then dt.print(string.format(_("%d editors configured"), n_entries)) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- callback for buttons "edit" and "edit a copy" ------------------------------
|
-- callback for buttons "edit" and "edit a copy" ------------------------------
|
||||||
@@ -152,22 +174,18 @@ 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]
|
||||||
|
|
||||||
-- check if external program executable exists, return if not
|
if dt.configuration.running_os == "macos" then bin = "open -W -a "..bin end
|
||||||
if not df.check_if_bin_exists(bin) then
|
|
||||||
dt.print(friendly_name.._(" not found"))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- image to be edited
|
-- image to be edited
|
||||||
local image
|
local image
|
||||||
@@ -175,24 +193,24 @@ local function OpenWith(images, choice, copy)
|
|||||||
local name = image.path..PS..image.filename
|
local name = image.path..PS..image.filename
|
||||||
|
|
||||||
-- check if image format is allowed
|
-- check if image format is allowed
|
||||||
local file_ext = df.get_filetype (image.filename)
|
local file_ext = df.get_filetype(image.filename)
|
||||||
local allowed = false
|
local allowed = false
|
||||||
for i,v in pairs(allowed_file_types) do
|
for i,v in pairs(allowed_file_types) do
|
||||||
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
|
||||||
@@ -207,28 +225,24 @@ local function OpenWith(images, choice, copy)
|
|||||||
if copy then
|
if copy then
|
||||||
|
|
||||||
-- create unique filename
|
-- create unique filename
|
||||||
while df.check_if_file_exists(df.sanitize_filename(new_name)) do
|
new_name = df.create_unique_filename(new_name)
|
||||||
new_name = df.filename_increment(new_name)
|
|
||||||
-- limit to 50 more exports of the original export
|
|
||||||
if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 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
|
||||||
@@ -253,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
|
||||||
@@ -266,21 +280,17 @@ local function OpenWith(images, choice, copy)
|
|||||||
if image.local_copy then image:drop_cache() end -- to fix fail to allocate cache error
|
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
|
||||||
-- refresh darkroom view
|
end
|
||||||
if dt.gui.current_view() == dt.gui.views.darkroom then
|
|
||||||
dt.gui.views.darkroom.display_image(new_image)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- restore image tags, rating and color, must be put after refresh darkroom view
|
-- restore image tags, rating and color
|
||||||
for i, tag in ipairs(tags) do dt.tags.attach(tag, new_image) end
|
for i, tag in ipairs(tags) do dt.tags.attach(tag, new_image) end
|
||||||
new_image.rating = rating
|
new_image.rating = rating
|
||||||
new_image.red = red
|
new_image.red = red
|
||||||
@@ -292,21 +302,25 @@ local function OpenWith(images, choice, copy)
|
|||||||
-- select the new image
|
-- select the new image
|
||||||
local selection = {}
|
local selection = {}
|
||||||
table.insert(selection, new_image)
|
table.insert(selection, new_image)
|
||||||
dt.gui.selection (selection)
|
dt.gui.selection(selection)
|
||||||
|
|
||||||
|
-- refresh darkroom view
|
||||||
|
if dt.gui.current_view().id == "darkroom" then
|
||||||
|
dt.gui.views.darkroom.display_image(new_image)
|
||||||
end
|
end
|
||||||
|
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 -----------------------------------
|
||||||
local function export2collection(storage, image_table, extra_data)
|
local function export2collection(storage, image_table, extra_data)
|
||||||
|
|
||||||
local new_name, new_image, result
|
local temp_name, new_name, new_image, move_success
|
||||||
|
|
||||||
for image, temp_name in pairs(image_table) do
|
for image, temp_name in pairs(image_table) do
|
||||||
|
|
||||||
@@ -314,32 +328,72 @@ local function export2collection(storage, image_table, extra_data)
|
|||||||
|
|
||||||
-- create unique filename
|
-- create unique filename
|
||||||
new_name = image.path..PS..df.get_filename(temp_name)
|
new_name = image.path..PS..df.get_filename(temp_name)
|
||||||
while df.check_if_file_exists(df.sanitize_filename(new_name)) do
|
new_name = df.create_unique_filename(new_name)
|
||||||
new_name = df.filename_increment(new_name)
|
|
||||||
-- limit to 50 more exports of the original export
|
|
||||||
if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 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
|
||||||
|
|
||||||
|
|
||||||
|
-- install the module in the UI -----------------------------------------------
|
||||||
|
local function install_module(dr)
|
||||||
|
|
||||||
|
local views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 90}}
|
||||||
|
if dr then
|
||||||
|
views = {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 90},
|
||||||
|
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not ee.module_installed then
|
||||||
|
-- register new module "external editors" in lighttable and darkroom ----
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE_NAME,
|
||||||
|
_("external editors"),
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
views,
|
||||||
|
dt.new_widget("box") {
|
||||||
|
orientation = "vertical",
|
||||||
|
table.unpack(ee.widgets),
|
||||||
|
},
|
||||||
|
nil, -- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
ee.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
for i = 1, MAX_EDITORS do
|
||||||
|
dt.destroy_event(MODULE_NAME .. i, "shortcut")
|
||||||
|
end
|
||||||
|
dt.destroy_storage("exp2coll")
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
-- register new storage -------------------------------------------------------
|
local function restart()
|
||||||
-- note that placing this declaration later makes the export selected module
|
for i = 1, MAX_EDITORS do
|
||||||
-- not to remember the choice "collection" when restarting DT, don't know why
|
dt.register_event(MODULE_NAME .. i, "shortcut",
|
||||||
dt.register_storage("exp2coll", _("collection"), null, export2collection)
|
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 ----------------------------------
|
||||||
@@ -350,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 ----------------------------------------------------------------
|
||||||
@@ -360,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 ---------------------------------------------------------
|
||||||
@@ -370,8 +424,8 @@ local button_edit_copy = dt.new_widget("button") {
|
|||||||
tooltip = _("create a copy of the selected image and open it in external editor"),
|
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 ---------------------------------------------------------
|
||||||
@@ -380,58 +434,81 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- register new module "external editors" in lighttable ------------------------
|
-- table with all the widgets --------------------------------------------------
|
||||||
dt.register_lib(
|
table.insert(ee.widgets, combobox)
|
||||||
MODULE_NAME,
|
table.insert(ee.widgets, box1)
|
||||||
_("external editors"),
|
|
||||||
true, -- expandable
|
|
||||||
false, -- resetable
|
-- register new module, but only when in lighttable ----------------------------
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}},
|
local show_dr = dt.preferences.read(MODULE_NAME, "show_in_darkrooom", "bool")
|
||||||
dt.new_widget("box") {
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
orientation = "vertical",
|
install_module(show_dr)
|
||||||
combobox,
|
else
|
||||||
box1
|
if not ee.event_registered then
|
||||||
},
|
dt.register_event(
|
||||||
nil, -- view_enter
|
MODULE_NAME, "view-changed",
|
||||||
nil -- view_leave
|
function(event, old_view, new_view)
|
||||||
)
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module(show_dr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
ee.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- initialize list of programs and widgets ------------------------------------
|
-- initialize list of programs and widgets ------------------------------------
|
||||||
UpdateProgramList(combobox, button_edit, button_edit_copy, false)
|
UpdateProgramList(combobox, button_edit, button_edit_copy, false)
|
||||||
|
|
||||||
|
|
||||||
|
-- register new storage -------------------------------------------------------
|
||||||
|
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",
|
||||||
|
_("show external editors in darkroom"),
|
||||||
|
_("check to show external editors module also in darkroom (requires restart)"), false)
|
||||||
|
|
||||||
|
|
||||||
-- register the new shortcuts -------------------------------------------------
|
-- register the new shortcuts -------------------------------------------------
|
||||||
for i = 1, MAX_EDITORS do
|
for i = 1, MAX_EDITORS do
|
||||||
dt.register_event("shortcut", program_shortcut, _("edit with program ")..string.format("%02d", i))
|
dt.register_event(MODULE_NAME .. i, "shortcut",
|
||||||
end
|
program_shortcut, string.format(_("edit with program %02d"), i))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
-- end of script --------------------------------------------------------------
|
-- end of script --------------------------------------------------------------
|
||||||
|
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ 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
|
||||||
|
|
||||||
-- constants
|
-- constants
|
||||||
|
|
||||||
@@ -52,19 +52,33 @@ 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"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
local fc = {}
|
local fc = {}
|
||||||
|
fc.module_installed = false
|
||||||
-- ensure we meet the minimum api
|
fc.event_registered = false
|
||||||
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 = {}
|
||||||
@@ -125,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
|
||||||
@@ -195,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
|
||||||
|
|
||||||
@@ -247,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)
|
||||||
|
|
||||||
@@ -255,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
|
||||||
|
|
||||||
@@ -337,6 +351,30 @@ local function face_recognition ()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not fc.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
MODULE, -- Module name
|
||||||
|
_("face recognition"), -- Visible name
|
||||||
|
true, -- expandable
|
||||||
|
true, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers
|
||||||
|
fc.widget,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
fc.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- build the interface
|
-- build the interface
|
||||||
|
|
||||||
fc.unknown_tag = dt.new_widget("entry"){
|
fc.unknown_tag = dt.new_widget("entry"){
|
||||||
@@ -365,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,
|
||||||
@@ -448,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)
|
||||||
@@ -466,16 +504,21 @@ fc.widget = dt.new_widget("box"){
|
|||||||
table.unpack(widgets),
|
table.unpack(widgets),
|
||||||
}
|
}
|
||||||
|
|
||||||
dt.register_lib(
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
"face_recognition", -- Module name
|
install_module()
|
||||||
_("face recognition"), -- Visible name
|
else
|
||||||
true, -- expandable
|
if not fc.event_registered then
|
||||||
true, -- resetable
|
dt.register_event(
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers
|
MODULE, "view-changed",
|
||||||
fc.widget,
|
function(event, old_view, new_view)
|
||||||
nil,-- view_enter
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
nil -- view_leave
|
install_module()
|
||||||
)
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
fc.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float")
|
fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float")
|
||||||
|
|
||||||
@@ -487,5 +530,11 @@ if not dt.preferences.read(MODULE, "initialized", "bool") then
|
|||||||
dt.preferences.write(MODULE, "initialized", "bool", true)
|
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
|
||||||
|
|||||||
156
contrib/fujifilm_dynamic_range.lua
Normal file
156
contrib/fujifilm_dynamic_range.lua
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
--[[ fujifilm_dynamic_range-0.1
|
||||||
|
|
||||||
|
Compensate for Fujifilm raw files made using "dynamic range".
|
||||||
|
|
||||||
|
Copyright (C) 2021, 2022 Dan Torop <dant@pnym.net>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[About this Plugin
|
||||||
|
Support for adjusting darktable exposure by Fujifilm raw exposure
|
||||||
|
bias. This corrects for a DR100/DR200/DR400 "dynamic range" setting.
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- exiftool (https://www.sno.phy.queensu.ca/~phil/exiftool/)
|
||||||
|
|
||||||
|
Based upon fujifilm_ratings by Ben Mendis
|
||||||
|
|
||||||
|
The relevant tag is RawExposureBias (0x9650). This appears to
|
||||||
|
represent the shift in EV for the chosen DR setting (whether manual or
|
||||||
|
automatic). Note that even at 100DR ("standard") there is an EV shift:
|
||||||
|
|
||||||
|
100 DR -> -0.72 EV
|
||||||
|
200 DR -> -1.72 EV
|
||||||
|
400 DR -> -2.72 EV
|
||||||
|
|
||||||
|
The ideal would be to use exiv2 to read this tag, as this is the same
|
||||||
|
code which darktable import uses. Unfortunately, exiv2 as of v0.27.3
|
||||||
|
can't read this tag. As it is encoded as a 4-byte ratio of two signed
|
||||||
|
shorts -- a novel data type -- it will require some attention to fix
|
||||||
|
this.
|
||||||
|
|
||||||
|
There is an exiv2-readable DevelopmentDynamicRange tag which maps to
|
||||||
|
RawExposureBias as above. DevelopmentDynamicRange is only present
|
||||||
|
when tag DynamicRangeSetting (0x1402) is Manual/Raw (0x0001). When it
|
||||||
|
is Auto (0x0000), the equivalent data is tag AutoDynamicRange
|
||||||
|
(0x140b). But exiv2 currently can't read that tag either.
|
||||||
|
|
||||||
|
Hence for now this code uses exiftool to read RawExposureBias, as a
|
||||||
|
more general solution. As exiftool is approx. 10x slower than exiv2
|
||||||
|
(Perl vs. C++), this may slow large imports.
|
||||||
|
|
||||||
|
These tags have been checked on a Fujifilm X100S and X100V. Other
|
||||||
|
cameras may behave in other ways.
|
||||||
|
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "fujifilm_dynamic_range")
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("fujifilm dynamic range"),
|
||||||
|
purpose = _("compensate for Fujifilm raw files made using \"dynamic range\""),
|
||||||
|
author = "Dan Torop <dant@pnym.net>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
local function detect_dynamic_range(event, image)
|
||||||
|
if image.exif_maker ~= "FUJIFILM" then
|
||||||
|
dt.print_log("[fujifilm_dynamic_range] ignoring non-Fujifilm image")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- it would be nice to check image.is_raw but this appears to not yet be set
|
||||||
|
if not string.match(image.filename, "%.RAF$") then
|
||||||
|
dt.print_log("[fujifilm_dynamic_range] ignoring non-raw image")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local command = df.check_if_bin_exists("exiftool")
|
||||||
|
if not command then
|
||||||
|
dt.print_error("[fujifilm_dynamic_range] exiftool not found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local RAF_filename = df.sanitize_filename(tostring(image))
|
||||||
|
-- without -n flag, exiftool will round to the nearest tenth
|
||||||
|
command = command .. " -RawExposureBias -n -t " .. RAF_filename
|
||||||
|
dt.print_log(command)
|
||||||
|
output = io.popen(command)
|
||||||
|
local raf_result = output:read("*all")
|
||||||
|
output:close()
|
||||||
|
if #raf_result == 0 then
|
||||||
|
dt.print_error("[fujifilm_dynamic_range] no output returned by exiftool")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
raf_result = string.match(raf_result, "\t(.*)")
|
||||||
|
if not raf_result then
|
||||||
|
dt.print_error("[fujifilm_dynamic_range] could not parse exiftool output")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if image.exif_exposure_bias ~= image.exif_exposure_bias then
|
||||||
|
-- is NAN (this is unlikely as RAFs should have ExposureBiasValue set)
|
||||||
|
image.exif_exposure_bias = 0
|
||||||
|
end
|
||||||
|
-- this should be auto-applied if plugins/darkroom/workflow is scene-referred
|
||||||
|
-- note that scene-referred workflow exposure preset also pushes exposure up by 0.5 EV
|
||||||
|
image.exif_exposure_bias = image.exif_exposure_bias + tonumber(raf_result)
|
||||||
|
dt.print_log("[fujifilm_dynamic_range] raw exposure bias " .. tostring(raf_result))
|
||||||
|
-- handle any duplicates
|
||||||
|
if #image:get_group_members() > 1 then
|
||||||
|
local basename = df.get_basename(image.filename)
|
||||||
|
local grouped_images = image:get_group_members()
|
||||||
|
for _, img in ipairs(grouped_images) do
|
||||||
|
if string.match(img.filename, basename) and img.duplicate_index > 0 then
|
||||||
|
-- its a duplicate
|
||||||
|
img.exif_exposure_bias = img.exif_exposure_bias + tonumber(raf_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_event("fujifilm_dr", "post-import-image")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not df.check_if_bin_exists("exiftool") then
|
||||||
|
dt.print_log("Please install exiftool to use fujifilm_dynamic_range")
|
||||||
|
error "[fujifilm_dynamic_range] exiftool not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event("fujifilm_dr", "post-import-image",
|
||||||
|
detect_dynamic_range)
|
||||||
|
|
||||||
|
dt.print_log("[fujifilm_dynamic_range] loaded")
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -26,46 +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 gettext = dt.gettext
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("4.0.0", "fujifilm_ratings")
|
du.check_min_api_version("7.0.0", "fujifilm_ratings")
|
||||||
|
|
||||||
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 df.check_if_bin_exists("exiftool") then
|
if not string.match(image.filename, "%.RAF$") and not string.match(image.filename, "%.raf$") then
|
||||||
dt.print_error(_("exiftool not found"))
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local RAF_filename = tostring(image)
|
if not df.check_if_bin_exists("exiftool") then
|
||||||
|
dt.print_error("exiftool not found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local RAF_filename = df.sanitize_filename(tostring(image))
|
||||||
local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG")
|
local 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", detect_rating)
|
local function destroy()
|
||||||
|
dt.destroy_event("fujifilm_rat", "post-import-image")
|
||||||
|
end
|
||||||
|
|
||||||
print(_("fujifilm_ratings loaded."))
|
dt.register_event("fujifilm_rat", "post-import-image",
|
||||||
|
detect_rating)
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -35,18 +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"
|
||||||
require "official/yield"
|
local dtsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
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
|
||||||
@@ -72,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
|
||||||
|
|
||||||
@@ -280,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
|
||||||
@@ -292,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")
|
||||||
@@ -321,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,37 +29,68 @@ 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"
|
||||||
require "official/yield"
|
local dtsys = require "lib/dtutils.system"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "geoToolbox")
|
du.check_min_api_version("7.0.0", "geoToolbox")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
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 = {}
|
||||||
|
gT.module_installed = false
|
||||||
|
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()
|
||||||
@@ -148,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
|
||||||
@@ -187,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
|
||||||
@@ -223,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
|
||||||
@@ -270,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
|
||||||
@@ -297,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
|
||||||
@@ -324,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
|
||||||
@@ -351,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
|
||||||
|
|
||||||
@@ -367,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
|
||||||
@@ -417,7 +449,7 @@ local function get_distance(lat1, lon1, ele1, lat2, lon2, ele2)
|
|||||||
math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) *
|
math.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
|
||||||
@@ -442,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
|
||||||
@@ -474,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()
|
||||||
@@ -502,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;
|
||||||
@@ -526,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
|
||||||
@@ -568,10 +600,53 @@ 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
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not gT.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
"geoToolbox", -- Module name
|
||||||
|
_("geo toolbox"), -- name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||||
|
gT.widget,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
gT.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["geoToolbox"].visible = false
|
||||||
|
dt.destroy_event("geoToolbox_cd", "shortcut")
|
||||||
|
dt.destroy_event("geoToolbox", "mouse-over-image-changed")
|
||||||
|
dt.destroy_event("geoToolbox_wg", "shortcut")
|
||||||
|
dt.destroy_event("geoToolbox_ng", "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.register_event("geoToolbox_cd", "shortcut",
|
||||||
|
print_calc_distance, _("calculate the distance from latitude and longitude in km"))
|
||||||
|
dt.register_event("geoToolbox", "mouse-over-image-changed",
|
||||||
|
toolbox_calc_distance)
|
||||||
|
|
||||||
|
dt.register_event("geoToolbox_wg", "shortcut",
|
||||||
|
select_with_gps, _("select all images with GPS information"))
|
||||||
|
dt.register_event("geoToolbox_ng", "shortcut",
|
||||||
|
select_without_gps, _("select all images without GPS information"))
|
||||||
|
|
||||||
|
dt.gui.libs["geoToolbox"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs["geoToolbox"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local separator = dt.new_widget("separator"){}
|
local separator = dt.new_widget("separator"){}
|
||||||
local separator2 = dt.new_widget("separator"){}
|
local separator2 = dt.new_widget("separator"){}
|
||||||
@@ -579,41 +654,47 @@ local separator3 = dt.new_widget("separator"){}
|
|||||||
local separator4 = dt.new_widget("separator"){}
|
local separator4 = dt.new_widget("separator"){}
|
||||||
local separator5 = dt.new_widget("separator"){}
|
local separator5 = dt.new_widget("separator"){}
|
||||||
|
|
||||||
dt.register_lib(
|
gT.widget = dt.new_widget("box")
|
||||||
"geoToolbox", -- Module name
|
|
||||||
"geo toolbox", -- name
|
|
||||||
true, -- expandable
|
|
||||||
false, -- resetable
|
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
|
||||||
dt.new_widget("box")
|
|
||||||
{
|
{
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
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,--------------------------------------------------------
|
||||||
@@ -643,14 +724,14 @@ dt.register_lib(
|
|||||||
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,--------------------------------------------------------
|
||||||
@@ -660,29 +741,53 @@ dt.register_lib(
|
|||||||
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
|
||||||
},
|
}
|
||||||
nil,-- view_enter
|
|
||||||
nil -- view_leave
|
|
||||||
)
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not gT.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"geoToolbox", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
gT.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Preferences
|
-- Preferences
|
||||||
dt.preferences.register("geoToolbox",
|
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", print_calc_distance, _("Calculate the distance from latitude and longitude in km"))
|
dt.register_event("geoToolbox_cd", "shortcut",
|
||||||
dt.register_event("mouse-over-image-changed", toolbox_calc_distance)
|
print_calc_distance, _("calculate the distance from latitude and longitude in km"))
|
||||||
|
dt.register_event("geoToolbox", "mouse-over-image-changed",
|
||||||
|
toolbox_calc_distance)
|
||||||
|
|
||||||
dt.register_event("shortcut", select_with_gps, _("Select all images with GPS information"))
|
dt.register_event("geoToolbox_wg", "shortcut",
|
||||||
dt.register_event("shortcut", select_without_gps, _("Select all images without GPS information"))
|
select_with_gps, _("select all images with GPS information"))
|
||||||
|
dt.register_event("geoToolbox_ng", "shortcut",
|
||||||
|
select_without_gps, _("select all images without GPS information"))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
||||||
-- kate: hl Lua;
|
-- kate: hl Lua;
|
||||||
|
|||||||
119
contrib/gimp.lua
119
contrib/gimp.lua
@@ -41,7 +41,9 @@
|
|||||||
* require this script from your main lua file
|
* require this script from your main lua file
|
||||||
* select an image or images for editing with GIMP
|
* select an image or images for editing with GIMP
|
||||||
* in the export dialog select "Edit with GIMP" and select the format and bit depth for the
|
* in the export dialog select "Edit with GIMP" and select the format and bit depth for the
|
||||||
exported image
|
exported image. Check the "run_detached" button to run GIMP in detached mode. Images
|
||||||
|
will not be returned to darktable in this mode, but additional images can be sent to
|
||||||
|
GIMP without stopping it.
|
||||||
* Press "export"
|
* Press "export"
|
||||||
* Edit the image with GIMP then save the changes with File->Overwrite....
|
* Edit the image with GIMP then save the changes with File->Overwrite....
|
||||||
* Exit GIMP
|
* Exit GIMP
|
||||||
@@ -67,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
|
||||||
@@ -98,20 +113,26 @@ 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
|
||||||
|
|
||||||
|
local run_detached = dt.preferences.read("gimp", "run_detached", "bool")
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
if dt.configuration.running_os == "macos" then
|
if dt.configuration.running_os == "macos" then
|
||||||
gimp_executable = "open -W -a " .. gimp_executable
|
if run_detached then
|
||||||
|
gimp_executable = "open -a " .. gimp_executable
|
||||||
|
else
|
||||||
|
gimp_executable = "open -W -a " .. gimp_executable
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- list of exported images
|
-- list of exported images
|
||||||
@@ -125,60 +146,80 @@ 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
|
||||||
|
|
||||||
|
if run_detached then
|
||||||
|
if dt.configuration.running_os == "windows" then
|
||||||
|
gimpStartCommand = "start /b \"\" " .. gimpStartCommand
|
||||||
|
else
|
||||||
|
gimpStartCommand = gimpStartCommand .. " &"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
dt.print_log(gimpStartCommand)
|
dt.print_log(gimpStartCommand)
|
||||||
|
|
||||||
dtsys.external_command(gimpStartCommand)
|
dtsys.external_command(gimpStartCommand)
|
||||||
|
|
||||||
-- for each of the image, exported image pairs
|
if not run_detached then
|
||||||
-- move the exported image into the directory with the original
|
|
||||||
-- then import the image into the database which will group it with the original
|
|
||||||
-- and then copy over any tags other than darktable tags
|
|
||||||
|
|
||||||
for image,exported_image in pairs(image_table) do
|
-- for each of the image, exported image pairs
|
||||||
|
-- move the exported image into the directory with the original
|
||||||
|
-- then import the image into the database which will group it with the original
|
||||||
|
-- and then copy over any tags other than darktable tags
|
||||||
|
|
||||||
local myimage_name = image.path .. "/" .. df.get_filename(exported_image)
|
for image,exported_image in pairs(image_table) do
|
||||||
|
|
||||||
while df.check_if_file_exists(myimage_name) do
|
local myimage_name = image.path .. "/" .. df.get_filename(exported_image)
|
||||||
myimage_name = df.filename_increment(myimage_name)
|
|
||||||
-- limit to 99 more exports of the original export
|
while df.check_if_file_exists(myimage_name) do
|
||||||
if string.match(df.get_basename(myimage_name), "_(d-)$") == "99" then
|
myimage_name = df.filename_increment(myimage_name)
|
||||||
break
|
-- limit to 99 more exports of the original export
|
||||||
|
if string.match(df.get_basename(myimage_name), "_(d-)$") == "99" then
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
dt.print_log("moving " .. exported_image .. " to " .. myimage_name)
|
dt.print_log("moving " .. exported_image .. " to " .. myimage_name)
|
||||||
local result = df.file_move(exported_image, myimage_name)
|
local result = df.file_move(exported_image, myimage_name)
|
||||||
|
|
||||||
if result then
|
if result then
|
||||||
dt.print_log("importing file")
|
dt.print_log("importing file")
|
||||||
local myimage = dt.database.import(myimage_name)
|
local myimage = dt.database.import(myimage_name)
|
||||||
|
|
||||||
group_if_not_member(image, myimage)
|
group_if_not_member(image, myimage)
|
||||||
|
|
||||||
for _,tag in pairs(dt.tags.get_tags(image)) do
|
for _,tag in pairs(dt.tags.get_tags(image)) do
|
||||||
if not (string.sub(tag.name,1,9) == "darktable") then
|
if not (string.sub(tag.name,1,9) == "darktable") then
|
||||||
dt.print_log("attaching tag")
|
dt.print_log("attaching tag")
|
||||||
dt.tags.attach(tag,myimage)
|
dt.tags.attach(tag,myimage)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.destroy_storage("module_gimp")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Register
|
-- Register
|
||||||
|
|
||||||
local executables = {"gimp"}
|
gimp_widget = dt.new_widget("check_button"){
|
||||||
|
label = _("run detached"),
|
||||||
|
tooltip = _("don't import resulting image back into darktable"),
|
||||||
|
value = dt.preferences.read("gimp", "run_detached", "bool"),
|
||||||
|
clicked_callback = function(this)
|
||||||
|
dt.preferences.write("gimp", "run_detached", "bool", this.value)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
if dt.configuration.running_os ~= "linux" then
|
dt.register_storage("module_gimp", _("edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
|
||||||
gimp_widget = df.executable_path_widget(executables)
|
|
||||||
end
|
|
||||||
|
|
||||||
dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -25,17 +25,34 @@ For each source folder, a separate <trk> is generated in the gpx file.
|
|||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
local df = require "lib/dtutils.file"
|
local df = require "lib/dtutils.file"
|
||||||
local dl = require "lib/dtutils"
|
local dl = require "lib/dtutils"
|
||||||
local gettext = dt.gettext
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
dl.check_min_api_version("3.0.0", "gpx-export")
|
dl.check_min_api_version("7.0.0", "gpx_export")
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("gpx_export",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
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 = {}
|
||||||
|
gpx.module_installed = false
|
||||||
|
gpx.event_registered = false
|
||||||
|
|
||||||
local path_entry = dt.new_widget("entry")
|
local path_entry = dt.new_widget("entry")
|
||||||
{
|
{
|
||||||
text = dt.preferences.read("gpx_exporter", "gpxExportPath", "string"),
|
text = dt.preferences.read("gpx_exporter", "gpxExportPath", "string"),
|
||||||
@@ -119,39 +136,78 @@ 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
|
||||||
|
|
||||||
dt.register_lib(
|
local function install_module()
|
||||||
"gpx_exporter",
|
if not gpx.module_installed then
|
||||||
"gpx export",
|
dt.register_lib(
|
||||||
true, -- expandable
|
"gpx_exporter",
|
||||||
true, -- resetable
|
_("gpx export"),
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
true, -- expandable
|
||||||
|
true, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||||
|
gpx.widget,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
gpx.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["gpx_exporter"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["gpx_exporter"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
gpx.widget = dt.new_widget("box")
|
||||||
|
{
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("button")
|
||||||
|
{
|
||||||
|
label = _("export"),
|
||||||
|
tooltip = _("export gpx file"),
|
||||||
|
clicked_callback = create_gpx_file
|
||||||
|
},
|
||||||
dt.new_widget("box")
|
dt.new_widget("box")
|
||||||
{
|
{
|
||||||
orientation = "vertical",
|
orientation = "horizontal",
|
||||||
dt.new_widget("button")
|
dt.new_widget("label")
|
||||||
{
|
{
|
||||||
label = _("export"),
|
label = _("file:"),
|
||||||
tooltip = _("export gpx file"),
|
|
||||||
clicked_callback = create_gpx_file
|
|
||||||
},
|
|
||||||
dt.new_widget("box")
|
|
||||||
{
|
|
||||||
orientation = "horizontal",
|
|
||||||
dt.new_widget("label")
|
|
||||||
{
|
|
||||||
label = _("file:"),
|
|
||||||
},
|
|
||||||
path_entry
|
|
||||||
},
|
},
|
||||||
|
path_entry
|
||||||
},
|
},
|
||||||
nil,-- view_enter
|
}
|
||||||
nil -- view_leave
|
|
||||||
)
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not gpx.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"gpx_export", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
gpx.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
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,8 +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"
|
||||||
require "official/yield"
|
local gettext = dt.gettext.gettext
|
||||||
local gettext = dt.gettext
|
|
||||||
|
|
||||||
local namespace = 'module_hugin'
|
local namespace = 'module_hugin'
|
||||||
local user_pref_str = 'prefer_gui'
|
local user_pref_str = 'prefer_gui'
|
||||||
@@ -55,15 +54,28 @@ local executable_table = {"hugin", "hugin_executor", "pto_gen"}
|
|||||||
local PQ = dt.configuration.running_os == "windows" and '"' or "'"
|
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)
|
||||||
@@ -71,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
|
||||||
@@ -132,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
|
||||||
|
|
||||||
@@ -193,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
|
||||||
@@ -206,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)
|
||||||
@@ -215,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
|
||||||
@@ -225,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
|
||||||
|
|||||||
592
contrib/image_time.lua
Normal file
592
contrib/image_time.lua
Normal file
@@ -0,0 +1,592 @@
|
|||||||
|
--[[
|
||||||
|
|
||||||
|
image_time.lua - synchronize image time for images shot with different cameras
|
||||||
|
|
||||||
|
Copyright (C) 2019, 2020 Bill Ferguson <wpferguson@gmail.com>.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
]]
|
||||||
|
--[[
|
||||||
|
image_time - non-destructively modify the image time
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
|
||||||
|
image_time non destructively adjusts image times by modifying the
|
||||||
|
database image exif_datetime_taken field. There are 4 modes: adjust time,
|
||||||
|
set time, synchronize time, and reset time.
|
||||||
|
|
||||||
|
ADJUST TIME
|
||||||
|
|
||||||
|
adjust time mode lets you chose an offset in terms of years, months,
|
||||||
|
days, hours, minutes, and seconds. The adjustment can be added or
|
||||||
|
subtracted.
|
||||||
|
|
||||||
|
WARNING: When adding and subtracting months the result will usually
|
||||||
|
be what is expected unless the time being adjusted is at the end of
|
||||||
|
the month. This is because a month is a variable amount of time that
|
||||||
|
can be 28, 29, 30 or 31 days depending on the month. Example: It's
|
||||||
|
March 31st and I subtract a month which not sets the time to February
|
||||||
|
31st. When that gets set to a valid time, then the date changes to
|
||||||
|
March 3rd.
|
||||||
|
|
||||||
|
SET TIME
|
||||||
|
|
||||||
|
set time mode allows you to pick a date and time and set the image
|
||||||
|
time accordingly. Fields may be left out. This is useful when
|
||||||
|
importing scanned images that don't have an embedded date.
|
||||||
|
|
||||||
|
SYNCHRONIZE TIME
|
||||||
|
|
||||||
|
I recently purchased a 7DmkII to replace my aging 7D. My 7D was still
|
||||||
|
serviceable, so I bought a remote control and figured I'd try shooting
|
||||||
|
events from 2 different perspectives. I didn't think to synchonize the
|
||||||
|
time between the 2 cameras, so when I loaded the images and sorted by
|
||||||
|
time it was a disaster. I hacked a script together with hard coded values
|
||||||
|
to adjust the exif_datetime_taken value in the database for the 7D images
|
||||||
|
so that everything sorted properly. I've tried shooting with 2 cameras
|
||||||
|
several times since that first attempt. I've gotten better at getting the
|
||||||
|
camera times close, but still haven't managed to get them to sync. So I
|
||||||
|
decided to think the problem through and write a proper script to take
|
||||||
|
care of the problem.
|
||||||
|
|
||||||
|
RESET TIME
|
||||||
|
|
||||||
|
Select the images and click reset.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
|
||||||
|
ADJUST TIME
|
||||||
|
|
||||||
|
Change the year, month, day, hour, minute, second dropdowns to the amount
|
||||||
|
of change desired. Select add or subtract. Select the images. Click
|
||||||
|
adjust.
|
||||||
|
|
||||||
|
SET TIME
|
||||||
|
|
||||||
|
Set the time fields to the desired time. Select the images to change. Click
|
||||||
|
set.
|
||||||
|
|
||||||
|
SYNCHRONIZE TIME
|
||||||
|
|
||||||
|
Select 2 images, one from each camera, of the same moment in time. Click
|
||||||
|
the Calculate button to calculate the time difference. The difference is
|
||||||
|
displayed in the difference entry. You can manually adjust it by changing
|
||||||
|
the value if necessary.
|
||||||
|
|
||||||
|
Select the images that need their time adjusted. Determine which way to adjust
|
||||||
|
adjust the time (add or subtract) and select the appropriate choice.
|
||||||
|
|
||||||
|
If the image times get messed up and you just want to start over, select reset time
|
||||||
|
from the mode and reset the image times.
|
||||||
|
|
||||||
|
RESET TIME
|
||||||
|
|
||||||
|
Select the images and click reset.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE REQUIRED
|
||||||
|
* exiv2
|
||||||
|
|
||||||
|
BUGS, COMMENTS, SUGGESTIONS
|
||||||
|
* Send to Bill Ferguson, wpferguson@gmail.com
|
||||||
|
|
||||||
|
CHANGES
|
||||||
|
|
||||||
|
]]
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local ds = require "lib/dtutils.string"
|
||||||
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local img_time = {}
|
||||||
|
img_time.module_installed = false
|
||||||
|
img_time.event_registered = false
|
||||||
|
|
||||||
|
du.check_min_api_version("7.0.0", "image_time")
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("image time"),
|
||||||
|
purpose = _("synchronize image time for images shot with different cameras or adjust or set image time"),
|
||||||
|
author = "Bill Ferguson <wpferguson@gmail.com>",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local PS = dt.configuration.runnin_os == "windows" and "\\" or "/"
|
||||||
|
local ERROR = -1
|
||||||
|
|
||||||
|
-- function to convert from exif time to system time
|
||||||
|
local function exiftime2systime(exiftime)
|
||||||
|
local yr,mo,dy,h,m,s = string.match(exiftime, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d+)")
|
||||||
|
return(os.time{year=yr, month=mo, day=dy, hour=h, min=m, sec=s})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function to convert from systime to exif time
|
||||||
|
local function systime2exiftime(systime)
|
||||||
|
local t = os.date("*t", systime)
|
||||||
|
return(string.format("%4d:%02d:%02d %02d:%02d:%02d", t.year, t.month, t.day, t.hour, t.min, t.sec))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function vars2exiftime(year, month, day, hour, min, sec)
|
||||||
|
local y = tonumber(year) and string.format("%4d", year) or "0000"
|
||||||
|
local mo = tonumber(month) and string.format("%02d", month) or "00"
|
||||||
|
local d = tonumber(day) and string.format("%02d", day) or "00"
|
||||||
|
local h = tonumber(hour) and string.format("%02d", hour) or "00"
|
||||||
|
local m = tonumber(min) and string.format("%02d", min) or "00"
|
||||||
|
local s = tonumber(sec) and string.format("%02d", sec) or "00"
|
||||||
|
return(y .. ":" .. mo .. ":" .. d .. " " .. h .. ":" .. m .. ":" .. s)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exiftime2vars(exiftime)
|
||||||
|
return string.match(exiftime, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function calc_time_difference(image1, image2)
|
||||||
|
return math.abs(exiftime2systime(image1.exif_datetime_taken) - exiftime2systime(image2.exif_datetime_taken))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function adjust_image_time(image, difference)
|
||||||
|
image.exif_datetime_taken = systime2exiftime(exiftime2systime(image.exif_datetime_taken) + difference)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function calculate_difference(images)
|
||||||
|
if #images == 2 then
|
||||||
|
img_time.diff_entry.text = calc_time_difference(images[1], images[2])
|
||||||
|
img_time.btn.sensitive = true
|
||||||
|
else
|
||||||
|
dt.print(_("ERROR: 2 images must be selected"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function synchronize_times(images, difference)
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
adjust_image_time(image, difference)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function synchronize_time(images)
|
||||||
|
local sign = 1
|
||||||
|
if img_time.sdir.value == _("subtract") then
|
||||||
|
sign = -1
|
||||||
|
end
|
||||||
|
synchronize_times(images, tonumber(img_time.diff_entry.text) * sign)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_time(images)
|
||||||
|
synchronize_times(images, tonumber(img_time.diff_entry.text))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function year_month2months(year, month)
|
||||||
|
year_months = tonumber(year) and year * 12 or 0
|
||||||
|
local months = tonumber(month) and tonumber(month) or 0
|
||||||
|
|
||||||
|
return year_months + months
|
||||||
|
end
|
||||||
|
|
||||||
|
local function months2year_month(months)
|
||||||
|
local year = math.floor(months / 12)
|
||||||
|
local month = months - (year * 12)
|
||||||
|
|
||||||
|
return year, month
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_image_taken_time(image)
|
||||||
|
-- get original image time
|
||||||
|
local datetime = nil
|
||||||
|
|
||||||
|
local exiv2 = df.check_if_bin_exists("exiv2")
|
||||||
|
if exiv2 then
|
||||||
|
p = io.popen(exiv2 .. " -K Exif.Image.DateTime " .. df.sanitize_filename(image.path .. PS .. image.filename))
|
||||||
|
if p then
|
||||||
|
for line in p:lines() do
|
||||||
|
if string.match(line, "Exif.Image.DateTime") then
|
||||||
|
datetime = string.match(line, "(%d-:%d-:%d- %d-:%d-:%d+)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
p:close()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
dt.print(_("unable to detect exiv2"))
|
||||||
|
datetime = ERROR
|
||||||
|
end
|
||||||
|
return datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _get_windows_image_file_creation_time(image)
|
||||||
|
local datetime = nil
|
||||||
|
local p = io.popen("dir " .. image.path .. PS .. image.filename)
|
||||||
|
if p then
|
||||||
|
for line in p:lines() do
|
||||||
|
if string.match(line, ds.sanitize_lua(image.filename)) then
|
||||||
|
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
|
||||||
|
dt.print(string.format(_("unable to get information for %s"), image.filename))
|
||||||
|
datetime = ERROR
|
||||||
|
end
|
||||||
|
return datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _get_nix_image_file_creation_time(image)
|
||||||
|
local datetime = nil
|
||||||
|
local p = io.popen("ls -lL --time-style=full-iso " .. df.sanitize_filename(image.path .. PS .. image.filename))
|
||||||
|
if p then
|
||||||
|
for line in p:lines() do
|
||||||
|
if string.match(line, ds.sanitize_lua(image.filename)) then
|
||||||
|
datetime = vars2exiftime(string.match(line, "(%d+)%-(%d-)%-(%d-) (%d-):(%d-):(%d+)."))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
p:close()
|
||||||
|
else
|
||||||
|
dt.print(string.format(_("unable to get information for %s"), image.filename))
|
||||||
|
datetime = ERROR
|
||||||
|
end
|
||||||
|
return datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_image_file_creation_time(image)
|
||||||
|
-- no exif time in the image file so get the creation time
|
||||||
|
local datetime = nil
|
||||||
|
if dt.configuration.running_os == "windows" then
|
||||||
|
datetime = _get_windows_image_file_creation_time(image)
|
||||||
|
else
|
||||||
|
datetime = _get_nix_image_file_creation_time(image)
|
||||||
|
end
|
||||||
|
return datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_original_image_time(image)
|
||||||
|
local image_time = image.exif_datetime_taken
|
||||||
|
local reset_time = nil
|
||||||
|
|
||||||
|
reset_time = get_image_taken_time(image)
|
||||||
|
|
||||||
|
if reset_time then
|
||||||
|
if reset_time == ERROR then
|
||||||
|
return image_time
|
||||||
|
else
|
||||||
|
return reset_time
|
||||||
|
end
|
||||||
|
else
|
||||||
|
reset_time = get_image_file_creation_time(image)
|
||||||
|
|
||||||
|
if reset_time then
|
||||||
|
if reset_time == ERROR then
|
||||||
|
return image_time
|
||||||
|
else
|
||||||
|
return reset_time
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reset_time(images)
|
||||||
|
if #images > 0 then
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
image.exif_datetime_taken = get_original_image_time(image)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
dt.print_error("reset time: no images selected")
|
||||||
|
dt.print(_("please select the images that need their time reset"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function adjust_time(images)
|
||||||
|
local SEC_MIN = 60
|
||||||
|
local SEC_HR = SEC_MIN * 60
|
||||||
|
local SEC_DY = SEC_HR * 24
|
||||||
|
|
||||||
|
local offset = nil
|
||||||
|
local sign = 1
|
||||||
|
|
||||||
|
if #images < 1 then
|
||||||
|
dt.print(_("please select some images and try again"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if img_time.adir.value == _("subtract") then
|
||||||
|
sign = -1
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
local y, mo, d, h, m, s = exiftime2vars(image.exif_datetime_taken)
|
||||||
|
local image_months = year_month2months(y, mo)
|
||||||
|
local months_diff = year_month2months(img_time.ayr.value, img_time.amo.value)
|
||||||
|
y, mo = months2year_month(image_months + (months_diff * sign))
|
||||||
|
local exif_new = vars2exiftime(y, mo, d, h, m, s)
|
||||||
|
offset = img_time.ady.value * SEC_DY
|
||||||
|
offset = offset + img_time.ahr.value * SEC_HR
|
||||||
|
offset = offset + img_time.amn.value * SEC_MIN
|
||||||
|
offset = offset + img_time.asc.value
|
||||||
|
offset = offset * sign
|
||||||
|
image.exif_datetime_taken = systime2exiftime(exiftime2systime(exif_new) + offset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_time(images)
|
||||||
|
if #images < 1 then
|
||||||
|
dt.print(_("please select some images and try again"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local y = img_time.syr.value
|
||||||
|
local mo = img_time.smo.value
|
||||||
|
local d = img_time.sdy.value
|
||||||
|
local h = img_time.shr.value
|
||||||
|
local m = img_time.smn.value
|
||||||
|
local s = img_time.ssc.value
|
||||||
|
|
||||||
|
for _, image in ipairs(images) do
|
||||||
|
image.exif_datetime_taken = vars2exiftime(y, mo, d, h, m, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function seq(first, last)
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
local num = first
|
||||||
|
|
||||||
|
while num <= last do
|
||||||
|
table.insert(result, num)
|
||||||
|
num = num + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.unpack(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reset_widgets()
|
||||||
|
dt.print_log("took the reset function")
|
||||||
|
img_time.ayr.selected = 1
|
||||||
|
img_time.amo.selected = 1
|
||||||
|
img_time.ady.selected = 1
|
||||||
|
img_time.ahr.selected = 1
|
||||||
|
img_time.ayr.selected = 1
|
||||||
|
img_time.amn.selected = 1
|
||||||
|
img_time.asc.selected = 1
|
||||||
|
img_time.adir.selected = 1
|
||||||
|
img_time.syr.selected = #img_time.syr
|
||||||
|
img_time.smo.selected = 1
|
||||||
|
img_time.sdy.selected = 1
|
||||||
|
img_time.shr.selected = 1
|
||||||
|
img_time.smn.selected = 1
|
||||||
|
img_time.ssc.selected = 1
|
||||||
|
img_time.adir.selected = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not img_time.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
"image_time", -- Module name
|
||||||
|
_("image time"), -- Visible name
|
||||||
|
true, -- expandable
|
||||||
|
true, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||||
|
img_time.widget,
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
img_time.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["image_time"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["image_time"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- widgets
|
||||||
|
|
||||||
|
img_time.widgets = {
|
||||||
|
-- name, type, tooltip, placeholder,
|
||||||
|
{"ayr", "combobox", _("years"), _("years to adjust by, 0 - ?"), {seq(0,20)}, 1},
|
||||||
|
{"amo", "combobox", _("months"), _("months to adjust by, 0-12"), {seq(0,12)}, 1},
|
||||||
|
{"ady", "combobox", _("days"), _("days to adjust by, 0-31"), {seq(0,31)}, 1},
|
||||||
|
{"ahr", "combobox", _("hours"), _("hours to adjust by, 0-23"), {seq(0,23)}, 1},
|
||||||
|
{"amn", "combobox", _("minutes"), _("minutes to adjust by, 0-59"), {seq(0,59)}, 1},
|
||||||
|
{"asc", "combobox", _("seconds"), _("seconds to adjust by, 0-59"), {seq(0,59)}, 1},
|
||||||
|
{"adir", "combobox", _("add/subtract"), _("add or subtract time"), {_("add"), _("subtract")}, 1},
|
||||||
|
{"syr", "combobox", _("year"), _("year to set, 1900 - now"), {" ", seq(1900,os.date("*t", os.time()).year)}, 1},
|
||||||
|
{"smo", "combobox", _("month"), _("month to set, 1-12"), {" ", seq(1,12)}, 1},
|
||||||
|
{"sdy", "combobox", _("day"), _("day to set, 1-31"), {" ", seq(1,31)}, 1},
|
||||||
|
{"shr", "combobox", _("hour"), _("hour to set, 0-23"), {" ", seq(0,23)}, 1},
|
||||||
|
{"smn", "combobox", _("minute"), _("minutes to set, 0-59"), {" ", seq(0, 59)}, 1},
|
||||||
|
{"ssc", "combobox", _("seconds"), _("seconds to set, 0-59"), {" ", seq(0,59)}, 1},
|
||||||
|
{"sdir", "combobox", _("add/subtract"), _("add or subtract time"), {_("add"), _("subtract")}, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, widget in ipairs(img_time.widgets) do
|
||||||
|
img_time[widget[1]] = dt.new_widget(widget[2]){
|
||||||
|
label = widget[3],
|
||||||
|
tooltip = widget[4],
|
||||||
|
selected = widget[6],
|
||||||
|
table.unpack(widget[5])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
img_time.syr.selected = #img_time.syr
|
||||||
|
|
||||||
|
img_time.diff_entry = dt.new_widget("entry"){
|
||||||
|
tooltip = _("time difference between images in seconds"),
|
||||||
|
placeholder = _("select 2 images and use the calculate button"),
|
||||||
|
text = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
img_time.calc_btn = dt.new_widget("button"){
|
||||||
|
label = _("calculate"),
|
||||||
|
tooltip = _("calculate time difference between 2 images"),
|
||||||
|
clicked_callback = function()
|
||||||
|
calculate_difference(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
img_time.btn = dt.new_widget("button"){
|
||||||
|
label = _("synchronize image times"),
|
||||||
|
tooltip = _("apply the time difference from selected images"),
|
||||||
|
sensitive = false,
|
||||||
|
clicked_callback = function()
|
||||||
|
synchronize_time(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
img_time.stack = dt.new_widget("stack"){
|
||||||
|
dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("label"){label = _("adjust time")},
|
||||||
|
dt.new_widget("section_label"){label = _("days, months, years")},
|
||||||
|
img_time.ady,
|
||||||
|
img_time.amo,
|
||||||
|
img_time.ayr,
|
||||||
|
dt.new_widget("section_label"){label = _("hours, minutes, seconds")},
|
||||||
|
img_time.ahr,
|
||||||
|
img_time.amn,
|
||||||
|
img_time.asc,
|
||||||
|
dt.new_widget("section_label"){label = _("adjustment direction")},
|
||||||
|
img_time.adir,
|
||||||
|
dt.new_widget("button"){
|
||||||
|
label = _("adjust"),
|
||||||
|
clicked_callback = function()
|
||||||
|
adjust_time(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("label"){label = _("set time")},
|
||||||
|
dt.new_widget("section_label"){label = _("date:")},
|
||||||
|
img_time.sdy,
|
||||||
|
img_time.smo,
|
||||||
|
img_time.syr,
|
||||||
|
dt.new_widget("section_label"){label = _("time:")},
|
||||||
|
img_time.shr,
|
||||||
|
img_time.smn,
|
||||||
|
img_time.ssc,
|
||||||
|
dt.new_widget("button"){
|
||||||
|
label = _("set"),
|
||||||
|
clicked_callback = function()
|
||||||
|
set_time(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("label"){label = _("synchronize image time")},
|
||||||
|
dt.new_widget("section_label"){label = _("calculate difference between images")},
|
||||||
|
img_time.diff_entry,
|
||||||
|
img_time.calc_btn,
|
||||||
|
dt.new_widget("section_label"){label = _("apply difference")},
|
||||||
|
img_time.sdir,
|
||||||
|
img_time.btn,
|
||||||
|
},
|
||||||
|
dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("label"){label = _("reset to original time")},
|
||||||
|
dt.new_widget("separator"){},
|
||||||
|
dt.new_widget("button"){
|
||||||
|
label = _("reset"),
|
||||||
|
clicked_callback = function()
|
||||||
|
reset_time(dt.gui.action_images)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
img_time.mode = dt.new_widget("combobox"){
|
||||||
|
label = _("mode"),
|
||||||
|
tooltip = _("select mode"),
|
||||||
|
selected = 1,
|
||||||
|
changed_callback = function(this)
|
||||||
|
img_time.stack.active = this.selected
|
||||||
|
end,
|
||||||
|
_("adjust time"),
|
||||||
|
_("set time"),
|
||||||
|
_("synchronize time"),
|
||||||
|
_("reset time")
|
||||||
|
}
|
||||||
|
|
||||||
|
img_time.widget = dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
reset_callback = function(this)
|
||||||
|
reset_widgets()
|
||||||
|
end,
|
||||||
|
img_time.mode,
|
||||||
|
img_time.stack,
|
||||||
|
}
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not img_time.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"image_time", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
img_time.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
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
|
||||||
@@ -41,23 +41,35 @@ format (all fields can be the empty string):
|
|||||||
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"
|
||||||
require "official/yield"
|
|
||||||
|
|
||||||
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",
|
||||||
@@ -165,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)
|
||||||
@@ -219,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
|
||||||
@@ -229,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
|
||||||
@@ -244,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
|
||||||
|
|||||||
507
contrib/photils.lua
Normal file
507
contrib/photils.lua
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
--[[ photils Auto Tagging plugin
|
||||||
|
copyright (c) 2020 Tobias Scheck
|
||||||
|
|
||||||
|
darktable is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
darktable is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
--]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
A darktable plugin that tries to predict keywords based on the selected image.
|
||||||
|
This plugin uses photils-cli to handle this task. Photils-cli is an application
|
||||||
|
that passes the image through a neural network, classifies it, and extracts the
|
||||||
|
suggested tags. Everything happens offline without the need that your data are
|
||||||
|
sent over the internet.
|
||||||
|
|
||||||
|
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||||
|
* photils-cli - https://github.com/scheckmedia/photils-cli at the moment only
|
||||||
|
available for Linux and MacOS
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* require this script from your main lua file
|
||||||
|
To do this add this line to the file .config/darktable/luarc:
|
||||||
|
require "contrib/photils"
|
||||||
|
* Select an image
|
||||||
|
* Press "get tags"
|
||||||
|
* Select the tags you want from a list of suggestions
|
||||||
|
* Press "Attach .. Tags" to add the selected tags to your image
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local dt = require "darktable"
|
||||||
|
local du = require "lib/dtutils"
|
||||||
|
local df = require "lib/dtutils.file"
|
||||||
|
local dtsys = require "lib/dtutils.system"
|
||||||
|
|
||||||
|
local MODULE_NAME = "photils"
|
||||||
|
du.check_min_api_version("7.0.0", MODULE_NAME)
|
||||||
|
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("photils"),
|
||||||
|
purpose = _("suggest tags based on image classification"),
|
||||||
|
author = "Tobias Scheck",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
|
||||||
|
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||||
|
|
||||||
|
local exporter = dt.new_format("jpeg")
|
||||||
|
exporter.quality = 80
|
||||||
|
exporter.max_height = 224
|
||||||
|
exporter.max_width = 224
|
||||||
|
|
||||||
|
-- helper functions
|
||||||
|
|
||||||
|
local function num_keys(tbl)
|
||||||
|
local num = 0
|
||||||
|
for _ in pairs(tbl) do num = num + 1 end
|
||||||
|
return num
|
||||||
|
end
|
||||||
|
|
||||||
|
local function has_key(tbl, value)
|
||||||
|
for k, _ in pairs(tbl) do
|
||||||
|
if k == value then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local photils_installed = df.check_if_bin_exists("photils-cli")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
local state object
|
||||||
|
|
||||||
|
maybe per_page is a preference variable but I think 10
|
||||||
|
is a good value for the ui
|
||||||
|
]]
|
||||||
|
local PHOTILS = {
|
||||||
|
tags = {},
|
||||||
|
confidences = {},
|
||||||
|
page = 1,
|
||||||
|
per_page = 10,
|
||||||
|
selected_tags = {},
|
||||||
|
in_pagination = false,
|
||||||
|
tagged_image = "",
|
||||||
|
module_installed = false,
|
||||||
|
event_registered = false,
|
||||||
|
plugin_display_views = {
|
||||||
|
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100},
|
||||||
|
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local GUI = {
|
||||||
|
container = dt.new_widget("box") {
|
||||||
|
orientation = "vertical",
|
||||||
|
sensitive = true,
|
||||||
|
dt.new_widget("button") {
|
||||||
|
label = _("get tags"),
|
||||||
|
sensitive = photils_installed,
|
||||||
|
clicked_callback = function() PHOTILS.on_tags_clicked() end
|
||||||
|
},
|
||||||
|
reset_callback = function() PHOTILS.on_reset(true) end
|
||||||
|
},
|
||||||
|
stack = dt.new_widget("stack"),
|
||||||
|
prev_button = dt.new_widget("button") {
|
||||||
|
label = "<",
|
||||||
|
sensitive = false,
|
||||||
|
clicked_callback = function()
|
||||||
|
PHOTILS.page = PHOTILS.page - 1
|
||||||
|
PHOTILS.paginate()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
next_button = dt.new_widget("button") {
|
||||||
|
label = ">",
|
||||||
|
sensitive = false,
|
||||||
|
clicked_callback = function()
|
||||||
|
PHOTILS.page = PHOTILS.page + 1
|
||||||
|
PHOTILS.paginate()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
tag_box = dt.new_widget("box") {orientation = "vertical"},
|
||||||
|
tag_view = dt.new_widget("box") {orientation = "vertical"},
|
||||||
|
page_label = dt.new_widget("label") {label = ""},
|
||||||
|
error_view = dt.new_widget("box") {orientation = "vertical"},
|
||||||
|
warning_label = dt.new_widget("label") {
|
||||||
|
label = ""
|
||||||
|
},
|
||||||
|
restart_required_label = dt.new_widget("label") {
|
||||||
|
label = _("requires a restart to be applied")
|
||||||
|
},
|
||||||
|
attach_button = dt.new_widget("button") {
|
||||||
|
label = "",
|
||||||
|
sensitive = false,
|
||||||
|
clicked_callback = function(self) PHOTILS.attach_tags() end
|
||||||
|
},
|
||||||
|
confidence_slider = dt.new_widget("slider") {
|
||||||
|
step = 1,
|
||||||
|
digits = 0,
|
||||||
|
value = 90,
|
||||||
|
hard_max = 100,
|
||||||
|
hard_min = 0,
|
||||||
|
soft_max = 100,
|
||||||
|
soft_min = 0,
|
||||||
|
label = _("min confidence value")
|
||||||
|
},
|
||||||
|
warning = dt.new_widget("label")
|
||||||
|
}
|
||||||
|
|
||||||
|
function PHOTILS.image_changed()
|
||||||
|
local current_image = tostring(dt.gui.selection()[1])
|
||||||
|
if current_image ~= PHOTILS.tagged_image then
|
||||||
|
if PHOTILS.tagged_image ~= "" then
|
||||||
|
PHOTILS.tagged_image_has_changed()
|
||||||
|
end
|
||||||
|
|
||||||
|
PHOTILS.tagged_image = tostring(current_image)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.tagged_image_has_changed()
|
||||||
|
GUI.warning.label = _("the suggested tags were not generated\n for the currently selected image!")
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.paginate()
|
||||||
|
PHOTILS.in_pagination = true
|
||||||
|
local num_pages = math.ceil(#PHOTILS.tags / PHOTILS.per_page)
|
||||||
|
GUI.page_label.label = string.format(_(" page %s of %s "), PHOTILS.page,
|
||||||
|
num_pages)
|
||||||
|
|
||||||
|
if PHOTILS.page <= 1 then
|
||||||
|
PHOTILS.page = 1
|
||||||
|
GUI.prev_button.sensitive = false
|
||||||
|
else
|
||||||
|
GUI.prev_button.sensitive = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if PHOTILS.page > num_pages - 1 then
|
||||||
|
PHOTILS.page = num_pages
|
||||||
|
GUI.next_button.sensitive = false
|
||||||
|
else
|
||||||
|
GUI.next_button.sensitive = true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
calculates the start positon in the tag array based on the current page
|
||||||
|
and takes N tags from that array to show these in darktable
|
||||||
|
e.g. page 1 goes from 1 to 10, page 2 from 11 to 20 a.s.o.
|
||||||
|
the paginaton approach is related to a problem with the dynamic addition
|
||||||
|
of mutliple widgets https://github.com/darktable-org/darktable/issues/4934#event-3318100463
|
||||||
|
]]--
|
||||||
|
local offset = ((PHOTILS.page - 1) * PHOTILS.per_page) + 1
|
||||||
|
local tag_index = 1
|
||||||
|
for i = offset, offset + PHOTILS.per_page - 1, 1 do
|
||||||
|
local tag = PHOTILS.tags[i]
|
||||||
|
local conf = PHOTILS.confidences[i]
|
||||||
|
|
||||||
|
GUI.tag_box[tag_index].value = has_key(PHOTILS.selected_tags, tag)
|
||||||
|
|
||||||
|
if tag then
|
||||||
|
if dt.preferences.read(MODULE_NAME, "show_confidence", "bool") then
|
||||||
|
tag = tag .. string.format(" (%.3f)", conf)
|
||||||
|
end
|
||||||
|
|
||||||
|
GUI.tag_box[tag_index].label = tag
|
||||||
|
GUI.tag_box[tag_index].sensitive = true
|
||||||
|
else
|
||||||
|
GUI.tag_box[tag_index].label = ""
|
||||||
|
GUI.tag_box[tag_index].sensitive = false
|
||||||
|
end
|
||||||
|
tag_index = tag_index + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
PHOTILS.in_pagination = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.attach_tags()
|
||||||
|
local num_selected = #dt.gui.selection()
|
||||||
|
local job = dt.gui.create_job(_("apply tag to image"), true)
|
||||||
|
|
||||||
|
for i = 1, num_selected, 1 do
|
||||||
|
local image = dt.gui.selection()[i]
|
||||||
|
for tag, _ in pairs(PHOTILS.selected_tags) do
|
||||||
|
local dt_tag = dt.tags.create(tag)
|
||||||
|
dt.tags.attach(dt_tag, image)
|
||||||
|
end
|
||||||
|
|
||||||
|
job.percent = i / num_selected
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.print(_("tags successfully attached to image"))
|
||||||
|
job.valid = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.get_tags(image, with_export)
|
||||||
|
|
||||||
|
local tmp_file = df.create_tmp_file()
|
||||||
|
local in_arg = df.sanitize_filename(tostring(image))
|
||||||
|
local out_arg = df.sanitize_filename(tmp_file)
|
||||||
|
local executable = photils_installed
|
||||||
|
|
||||||
|
if with_export then
|
||||||
|
dt.print_log("use export to for prediction")
|
||||||
|
local export_file = df.create_tmp_file()
|
||||||
|
exporter:write_image(image, export_file)
|
||||||
|
in_arg = df.sanitize_filename(tostring(export_file))
|
||||||
|
end
|
||||||
|
|
||||||
|
local command = executable .. " -c " .. " -i " .. in_arg .. " -o " .. out_arg
|
||||||
|
|
||||||
|
local ret = dtsys.external_command(command)
|
||||||
|
if ret > 0 then
|
||||||
|
dt.print_error(string.format("command %s returned error code %d", command, ret))
|
||||||
|
os.remove(tmp_file)
|
||||||
|
|
||||||
|
-- try to export the image and run tagging
|
||||||
|
if not with_export then
|
||||||
|
return PHOTILS.get_tags(image, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = #PHOTILS.tags, 1, -1 do
|
||||||
|
PHOTILS.tags[i] = nil
|
||||||
|
PHOTILS.confidences[i] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for tag in io.lines(tmp_file) do
|
||||||
|
local splitted = du.split(tag, ":")
|
||||||
|
if 100 * tonumber(splitted[2]) >= GUI.confidence_slider.value then
|
||||||
|
PHOTILS.tags[#PHOTILS.tags + 1] = splitted[1]
|
||||||
|
PHOTILS.confidences[#PHOTILS.confidences+1] = splitted[2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.print(string.format(_("%s found %d tags for your image"), MODULE_NAME,
|
||||||
|
#PHOTILS.tags))
|
||||||
|
os.remove(tmp_file)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.on_tags_clicked()
|
||||||
|
PHOTILS.page = 1
|
||||||
|
GUI.warning.label = ""
|
||||||
|
|
||||||
|
PHOTILS.on_reset(false)
|
||||||
|
|
||||||
|
local images = dt.gui.selection()
|
||||||
|
|
||||||
|
if #images == 0 then
|
||||||
|
dt.print(_("no image selected."))
|
||||||
|
dt.control.sleep(2000)
|
||||||
|
else
|
||||||
|
if #images > 1 then
|
||||||
|
dt.print(_("this plugin can only handle a single image."))
|
||||||
|
dt.gui.selection({images[1]})
|
||||||
|
dt.control.sleep(2000)
|
||||||
|
end
|
||||||
|
|
||||||
|
with_export = dt.preferences.read(MODULE_NAME, "export_image_before_for_tags", "bool")
|
||||||
|
if not PHOTILS.get_tags(images[1], with_export) then
|
||||||
|
local msg = string.format(_("%s failed, see terminal output for details"), MODULE_NAME)
|
||||||
|
GUI.warning_label.label = msg
|
||||||
|
GUI.stack.active = GUI.error_view
|
||||||
|
dt.print(msg)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if #PHOTILS.tags == 0 then
|
||||||
|
local msg = string.format(_("no tags were found"), MODULE_NAME)
|
||||||
|
GUI.warning_label.label = msg
|
||||||
|
GUI.stack.active = GUI.error_view
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
GUI.stack.active = GUI.tag_view
|
||||||
|
PHOTILS.paginate()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.tag_selected(tag_button)
|
||||||
|
if PHOTILS.in_pagination then return end
|
||||||
|
|
||||||
|
if tag_button.value then
|
||||||
|
local tag = tag_button.label
|
||||||
|
if dt.preferences.read(MODULE_NAME, "show_confidence", "bool") then
|
||||||
|
local idx = string.find(tag, "%(") - 2
|
||||||
|
tag = string.sub(tag, 0, idx)
|
||||||
|
end
|
||||||
|
|
||||||
|
PHOTILS.selected_tags[tag] = tag
|
||||||
|
else
|
||||||
|
PHOTILS.selected_tags[tag_button.label] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local num_selected = num_keys(PHOTILS.selected_tags)
|
||||||
|
if num_selected == 0 then
|
||||||
|
GUI.attach_button.label = ""
|
||||||
|
GUI.attach_button.sensitive = false
|
||||||
|
else
|
||||||
|
GUI.attach_button.label = string.format(_("attach %d tags"),
|
||||||
|
num_selected)
|
||||||
|
GUI.attach_button.sensitive = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function PHOTILS.on_reset(with_view)
|
||||||
|
if with_view then GUI.stack.active = 1 end
|
||||||
|
|
||||||
|
for k, _ in pairs(PHOTILS.selected_tags) do
|
||||||
|
PHOTILS.selected_tags[k] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in ipairs(GUI.tag_box) do
|
||||||
|
v.value = false
|
||||||
|
end
|
||||||
|
|
||||||
|
GUI.attach_button.label = ""
|
||||||
|
GUI.attach_button.sensitive = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not PHOTILS.module_installed then
|
||||||
|
dt.register_lib(MODULE_NAME,
|
||||||
|
_("photils auto-tagger"),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
PHOTILS.plugin_display_views,
|
||||||
|
GUI.container,
|
||||||
|
nil,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
PHOTILS.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = false
|
||||||
|
dt.destroy_event("photils", "mouse-over-image-changed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
dt.register_event("photils", "mouse-over-image-changed",
|
||||||
|
PHOTILS.image_changed)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs[MODULE_NAME].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- add a fix number of buttons
|
||||||
|
for _ = 1, PHOTILS.per_page, 1 do
|
||||||
|
local btn_tag = dt.new_widget("check_button") {
|
||||||
|
label = "",
|
||||||
|
sensitive = false,
|
||||||
|
clicked_callback = PHOTILS.tag_selected
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(GUI.tag_box, btn_tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not photils_installed then
|
||||||
|
GUI.warning_label.label = _("photils-cli not found")
|
||||||
|
dt.print_log("photils-cli not found")
|
||||||
|
else
|
||||||
|
GUI.warning_label.label = _("select an image, click \"get tags\" and get \nsuggestions for tags.")
|
||||||
|
end
|
||||||
|
|
||||||
|
GUI.pagination = dt.new_widget("box") {
|
||||||
|
orientation = "horizontal",
|
||||||
|
GUI.prev_button,
|
||||||
|
GUI.page_label,
|
||||||
|
GUI.next_button
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table.insert(GUI.error_view, GUI.warning_label)
|
||||||
|
if not photils_installed then
|
||||||
|
table.insert(GUI.error_view, df.executable_path_widget({"photils-cli"}))
|
||||||
|
table.insert(GUI.error_view, GUI.restart_required_label)
|
||||||
|
end
|
||||||
|
table.insert(GUI.stack, GUI.error_view)
|
||||||
|
table.insert(GUI.stack, GUI.tag_view)
|
||||||
|
|
||||||
|
table.insert(GUI.tag_view, GUI.pagination)
|
||||||
|
table.insert(GUI.tag_view, GUI.tag_box)
|
||||||
|
table.insert(GUI.tag_view, GUI.attach_button)
|
||||||
|
table.insert(GUI.tag_view, GUI.warning)
|
||||||
|
|
||||||
|
table.insert(GUI.container, GUI.confidence_slider)
|
||||||
|
table.insert(GUI.container, GUI.stack)
|
||||||
|
|
||||||
|
GUI.stack.active = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- uses photils: prefix because script settings are all together and not seperated by script
|
||||||
|
dt.preferences.register(MODULE_NAME,
|
||||||
|
"show_confidence",
|
||||||
|
"bool",
|
||||||
|
_("photils: show confidence value"),
|
||||||
|
_("if enabled, the confidence value for each tag is displayed"),
|
||||||
|
true)
|
||||||
|
|
||||||
|
dt.preferences.register(MODULE_NAME,
|
||||||
|
"export_image_before_for_tags",
|
||||||
|
"bool",
|
||||||
|
_("photils: use exported image for tag request"),
|
||||||
|
_("if enabled, the image passed to photils for tag suggestion is based on the exported, already edited image. " ..
|
||||||
|
"otherwise, the embedded thumbnail of the RAW file will be used for tag suggestion." ..
|
||||||
|
"the embedded thumbnail could speedup the tag suggestion but can fail if the RAW file is not supported."),
|
||||||
|
true)
|
||||||
|
|
||||||
|
dt.register_event("photils", "mouse-over-image-changed",
|
||||||
|
PHOTILS.image_changed)
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not PHOTILS.event_registered then
|
||||||
|
dt.register_event(
|
||||||
|
"photils", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
PHOTILS.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = show
|
||||||
|
|
||||||
|
return script_data
|
||||||
@@ -47,18 +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
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
du.check_min_api_version("3.0.0", "quicktag")
|
|
||||||
|
|
||||||
-- Tell gettext where to find the .mo file translating messages for a particular domain
|
|
||||||
gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/")
|
|
||||||
|
|
||||||
local function _(msgid)
|
local function _(msgid)
|
||||||
return gettext.dgettext("quicktag", msgid)
|
return gettext(msgid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("quick tag"),
|
||||||
|
purpose = _("use buttons to quickly apply tags assigned to them"),
|
||||||
|
author = "Christian Kanzian",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
|
||||||
|
local qt = {}
|
||||||
|
qt.module_installed = false
|
||||||
|
qt.event_registered = false
|
||||||
|
qt.widget_table = {}
|
||||||
|
|
||||||
-- maximum length of button labels
|
-- maximum length of button labels
|
||||||
dt.preferences.register("quickTag",
|
dt.preferences.register("quickTag",
|
||||||
"labellength",
|
"labellength",
|
||||||
@@ -156,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}
|
||||||
@@ -177,12 +193,56 @@ local function update_quicktag_list()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not qt.module_installed then
|
||||||
|
dt.register_lib(
|
||||||
|
"quicktag", -- Module name
|
||||||
|
_("quick tag"), -- name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
|
||||||
|
|
||||||
|
dt.new_widget("box"){
|
||||||
|
orientation = "vertical",
|
||||||
|
table.unpack(qt.widget_table),
|
||||||
|
|
||||||
|
},
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
qt.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
dt.gui.libs["quicktag"].visible = false
|
||||||
|
for i=1,qnr do
|
||||||
|
dt.destroy_event("quicktag " .. tostring(i), "shortcut")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
dt.gui.libs["quicktag"].visible = true
|
||||||
|
for i = 1 ,qnr do
|
||||||
|
dt.register_event("quicktag", "shortcut",
|
||||||
|
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
|
||||||
|
string.format(_("quicktag %i"),i))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show()
|
||||||
|
dt.gui.libs["quicktag"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
update_quicktag_list()
|
update_quicktag_list()
|
||||||
|
|
||||||
local new_quicktag = dt.new_widget("entry"){
|
local new_quicktag = dt.new_widget("entry"){
|
||||||
text = "",
|
text = "",
|
||||||
placeholder = _("new tag"),
|
placeholder = _("new tag"),
|
||||||
is_password = true,
|
is_password = false,
|
||||||
editable = true,
|
editable = true,
|
||||||
tooltip = _("enter your tag here")
|
tooltip = _("enter your tag here")
|
||||||
}
|
}
|
||||||
@@ -213,40 +273,46 @@ local new_qt_widget = dt.new_widget ("box") {
|
|||||||
|
|
||||||
-- back UI elements in a table
|
-- back UI elements in a table
|
||||||
-- thanks to wpferguson for the hint
|
-- thanks to wpferguson for the hint
|
||||||
local widget_table = {}
|
|
||||||
|
|
||||||
for i=1,qnr do
|
for i=1,qnr do
|
||||||
widget_table[#widget_table + 1] = button[i]
|
qt.widget_table[#qt.widget_table + 1] = button[i]
|
||||||
end
|
end
|
||||||
|
|
||||||
widget_table[#widget_table + 1] = dt.new_widget("separator"){}
|
qt.widget_table[#qt.widget_table + 1] = dt.new_widget("separator"){}
|
||||||
widget_table[#widget_table + 1] = old_quicktag
|
qt.widget_table[#qt.widget_table + 1] = old_quicktag
|
||||||
widget_table[#widget_table + 1] = new_qt_widget
|
qt.widget_table[#qt.widget_table + 1] = new_qt_widget
|
||||||
|
|
||||||
|
|
||||||
--create module
|
--create module
|
||||||
dt.register_lib(
|
if dt.gui.current_view().id == "lighttable" then
|
||||||
"quicktag", -- Module name
|
install_module()
|
||||||
"quicktag", -- name
|
else
|
||||||
true, -- expandable
|
if not qt.event_registered then
|
||||||
false, -- resetable
|
dt.register_event(
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
|
"quicktag", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
dt.new_widget("box"){
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
orientation = "vertical",
|
install_module()
|
||||||
table.unpack(widget_table),
|
end
|
||||||
|
end
|
||||||
},
|
)
|
||||||
nil,-- view_enter
|
qt.event_registered = true
|
||||||
nil -- view_leave
|
end
|
||||||
)
|
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,7 +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 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
|
||||||
@@ -53,36 +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",function(event, shortcut)
|
local function destroy()
|
||||||
|
dt.destroy_event("rg_reject", "shortcut")
|
||||||
|
dt.destroy_event("rg0", "shortcut")
|
||||||
|
dt.destroy_event("rg1", "shortcut")
|
||||||
|
dt.destroy_event("rg2", "shortcut")
|
||||||
|
dt.destroy_event("rg3", "shortcut")
|
||||||
|
dt.destroy_event("rg4", "shortcut")
|
||||||
|
dt.destroy_event("rg5", "shortcut")
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.register_event("rg_reject", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(-1)
|
apply_rating(-1)
|
||||||
end, "Reject group")
|
end, _("reject group"))
|
||||||
|
|
||||||
dt.register_event("shortcut",function(event, shortcut)
|
dt.register_event("rg0", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(0)
|
apply_rating(0)
|
||||||
end, "Rate group 0")
|
end, string.format(_("rate group %d"), 0)
|
||||||
|
)
|
||||||
|
|
||||||
dt.register_event("shortcut",function(event, shortcut)
|
dt.register_event("rg1", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(1)
|
apply_rating(1)
|
||||||
end, "Rate group 1")
|
end, string.format(_("rate group %d"), 1))
|
||||||
|
|
||||||
dt.register_event("shortcut",function(event, shortcut)
|
dt.register_event("rg2", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(2)
|
apply_rating(2)
|
||||||
end, "Rate group 2")
|
end, string.format(_("rate group %d"), 2))
|
||||||
|
|
||||||
dt.register_event("shortcut",function(event, shortcut)
|
dt.register_event("rg3", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(3)
|
apply_rating(3)
|
||||||
end, "Rate group 3")
|
end, string.format(_("rate group %d"), 3))
|
||||||
|
|
||||||
dt.register_event("shortcut",function(event, shortcut)
|
dt.register_event("rg4", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(4)
|
apply_rating(4)
|
||||||
end, "Rate group 4")
|
end, string.format(_("rate group %d"), 4))
|
||||||
|
|
||||||
dt.register_event("shortcut",function(event, shortcut)
|
dt.register_event("rg5", "shortcut",
|
||||||
|
function(event, shortcut)
|
||||||
apply_rating(5)
|
apply_rating(5)
|
||||||
end, "Rate group 5")
|
end, string.format(_("rate group %d"), 5))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
@@ -33,11 +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")
|
||||||
|
du.deprecated("contrib/rename-tags.lua","darktable release 4.0")
|
||||||
|
|
||||||
|
local gettext = darktable.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = _("rename tags"),
|
||||||
|
purpose = _("rename an existing tag"),
|
||||||
|
author = "Sebastian Witt (se.witt@gmx.net)",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
|
local rt = {}
|
||||||
|
rt.module_installed = false
|
||||||
|
rt.event_registered = false
|
||||||
|
|
||||||
-- GUI entries
|
-- 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 = ''
|
||||||
@@ -48,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
|
||||||
|
|
||||||
@@ -62,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
|
||||||
@@ -90,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
|
||||||
|
|
||||||
@@ -99,27 +126,63 @@ local function rename_tags()
|
|||||||
rename_reset()
|
rename_reset()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not rt.module_installed then
|
||||||
|
darktable.register_lib ("rename_tags", _("rename tag"), true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rt.rename_widget, nil, nil)
|
||||||
|
rt.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function destroy()
|
||||||
|
darktable.gui.libs["rename_tags"].visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restart()
|
||||||
|
darktable.gui.libs["rename_tags"].visible = true
|
||||||
|
end
|
||||||
|
|
||||||
-- GUI
|
-- 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
|
||||||
}
|
}
|
||||||
|
|
||||||
local rename_widget = darktable.new_widget ("box") {
|
rt.rename_widget = darktable.new_widget ("box") {
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
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
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not rt.event_registered then
|
||||||
|
darktable.register_event(
|
||||||
|
"rename_tags", "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
rt.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
darktable.register_lib ("rename_tags", "rename tag", true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rename_widget, nil, nil)
|
|
||||||
|
|
||||||
|
|||||||
236
contrib/rename_images.lua
Normal file
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,38 +20,51 @@ 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 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
|
||||||
|
|
||||||
local function select_untagged_images()
|
local function select_untagged_images(event, images)
|
||||||
job = dt.gui.create_job(_("select untagged images"), true, stop_job)
|
job = dt.gui.create_job(_("select untagged images"), true, stop_job)
|
||||||
|
|
||||||
local selection = {}
|
local selection = {}
|
||||||
|
|
||||||
for key,image in ipairs(dt.collection) do
|
for key,image in ipairs(images) do
|
||||||
if(job.valid) then
|
if(job.valid) then
|
||||||
job.percent = (key-1)/#dt.collection
|
job.percent = (key - 1)/#images
|
||||||
local tags = dt.tags.get_tags(image)
|
local tags = dt.tags.get_tags(image)
|
||||||
local hasTags = false
|
local hasTags = false
|
||||||
for _,tag in ipairs(tags) do
|
for _,tag in ipairs(tags) do
|
||||||
if not string.match(tag.name,"darktable|") then
|
if not string.match(tag.name, "darktable|") then
|
||||||
hasTags = true
|
hasTags = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if hasTags == false then
|
if hasTags == false then
|
||||||
table.insert(selection,image)
|
table.insert(selection, image)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
@@ -59,7 +72,19 @@ local function select_untagged_images()
|
|||||||
end
|
end
|
||||||
|
|
||||||
job.valid = false
|
job.valid = false
|
||||||
dt.gui.selection(selection)
|
-- return table of images to set the selection to
|
||||||
|
return selection
|
||||||
end
|
end
|
||||||
|
|
||||||
dt.gui.libs.select.register_selection(_("select untagged"),select_untagged_images,_("select all images containing no tags or only tags added by darktable"))
|
local function destroy()
|
||||||
|
dt.gui.libs.select.destroy_selection("select_untagged")
|
||||||
|
end
|
||||||
|
|
||||||
|
dt.gui.libs.select.register_selection(
|
||||||
|
"select_untagged", _("select untagged"),
|
||||||
|
select_untagged_images,
|
||||||
|
_("select all images containing no tags or only tags added by darktable"))
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|||||||
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,18 +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"
|
||||||
require "official/yield"
|
local gettext = dt.gettext.gettext
|
||||||
local gettext = dt.gettext
|
|
||||||
|
|
||||||
du.check_min_api_version("2.0.2", "slideshowMusic")
|
du.check_min_api_version("7.0.0", "slideshowMusic")
|
||||||
|
|
||||||
-- 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
|
||||||
|
|
||||||
@@ -46,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
|
||||||
|
|
||||||
@@ -62,19 +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",playSlideshowMusic)
|
dt.register_event("slideshow_music", "view-changed",
|
||||||
|
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,9 +76,30 @@ 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 "/"
|
||||||
@@ -87,18 +108,16 @@ local PATH_SEGMENT_REGEX = "(" .. PATH_SEPARATOR .. "?)([^" .. PATH_SEPARATOR ..
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Helper functions: BEGIN
|
-- Helper functions: BEGIN
|
||||||
|
|
||||||
|
local th = {}
|
||||||
|
th.module_installed = false
|
||||||
|
th.event_registered = false
|
||||||
|
|
||||||
local function pathExists(path)
|
local function pathExists(path)
|
||||||
local success, err, errno = os.rename(path, path)
|
local success, err, errno = os.rename(path, path)
|
||||||
if not success then
|
if not success then
|
||||||
@@ -122,13 +141,23 @@ local function createDirectory(path)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function install_module()
|
||||||
|
if not th.module_installed then
|
||||||
|
darktable.register_lib(LIB_ID,
|
||||||
|
_("transfer hierarchy"), true, true, {
|
||||||
|
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
|
||||||
|
}, th.transfer_widget, nil, nil)
|
||||||
|
th.module_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Helper functions: END
|
-- Helper functions: 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
|
||||||
@@ -241,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
|
||||||
|
|
||||||
@@ -264,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
|
||||||
@@ -287,12 +316,19 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local transfer_widget = darktable.new_widget("box") {
|
th.transfer_widget = darktable.new_widget("box") {
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
darktable.new_widget("button") {
|
darktable.new_widget("button") {
|
||||||
label = _("calculate"),
|
label = _("calculate"),
|
||||||
@@ -315,7 +351,7 @@ local 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,12 +383,26 @@ darktable.preferences.register(
|
|||||||
|
|
||||||
-- Preferences: END
|
-- Preferences: END
|
||||||
|
|
||||||
|
if darktable.gui.current_view().id == "lighttable" then
|
||||||
|
install_module()
|
||||||
|
else
|
||||||
|
if not th.event_registered then
|
||||||
|
darktable.register_event(
|
||||||
|
LIB_ID, "view-changed",
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then
|
||||||
|
install_module()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
th.event_registered = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart
|
||||||
|
script_data.destroy_method = "hide"
|
||||||
|
script_data.show = restart
|
||||||
|
|
||||||
|
return script_data
|
||||||
|
|
||||||
darktable.register_lib(LIB_ID,
|
|
||||||
"transfer hierarchy", true, true, {
|
|
||||||
[darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 }
|
|
||||||
}, transfer_widget, nil, nil)
|
|
||||||
|
|||||||
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
|
||||||
|
|
||||||
@@ -96,7 +110,7 @@ local resolutions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local framerates = {"15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60"}
|
local framerates = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60", "120", "240", "300"}
|
||||||
|
|
||||||
local formats = {
|
local formats = {
|
||||||
["AVI"] = {
|
["AVI"] = {
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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,25 +33,87 @@ 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")
|
||||||
|
|
||||||
|
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
|
||||||
|
local gettext = dt.gettext.gettext
|
||||||
|
|
||||||
|
local function _(msgid)
|
||||||
|
return gettext(msgid)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return data structure for script_manager
|
||||||
|
|
||||||
|
local script_data = {}
|
||||||
|
|
||||||
|
script_data.metadata = {
|
||||||
|
name = ("module example"),
|
||||||
|
purpose = _("example of how to create a lighttable module"),
|
||||||
|
author = "Tobias Jakobs",
|
||||||
|
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleExample"
|
||||||
|
}
|
||||||
|
|
||||||
|
script_data.destroy = nil -- function to destory the script
|
||||||
|
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil
|
||||||
|
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
|
||||||
|
script_data.show = nil -- only required for libs since the destroy_method only hides them
|
||||||
|
|
||||||
-- translation
|
-- translation
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/index.html#darktable_gettext
|
-- declare a local namespace and a couple of variables we'll need to install the module
|
||||||
local gettext = dt.gettext
|
local mE = {}
|
||||||
|
mE.widgets = {}
|
||||||
|
mE.event_registered = false -- keep track of whether we've added an event callback or not
|
||||||
|
mE.module_installed = false -- keep track of whether the module is module_installed
|
||||||
|
|
||||||
gettext.bindtextdomain("moduleExample", dt.configuration.config_dir .. "/lua/locale/")
|
--[[ We have to create the module in one of two ways depending on which view darktable starts
|
||||||
|
in. In orker to not repeat code, we wrap the darktable.register_lib in a local function.
|
||||||
|
]]
|
||||||
|
|
||||||
local function _(msgid)
|
local function install_module()
|
||||||
return gettext.dgettext("moduleExample", msgid)
|
if not mE.module_installed then
|
||||||
|
-- https://www.darktable.org/lua-api/index.html#darktable_register_lib
|
||||||
|
dt.register_lib(
|
||||||
|
"exampleModule", -- Module name
|
||||||
|
_("example module"), -- name
|
||||||
|
true, -- expandable
|
||||||
|
false, -- resetable
|
||||||
|
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
||||||
|
-- https://www.darktable.org/lua-api/types_lua_box.html
|
||||||
|
dt.new_widget("box") -- widget
|
||||||
|
{
|
||||||
|
orientation = "vertical",
|
||||||
|
dt.new_widget("button")
|
||||||
|
{
|
||||||
|
label = _("my ") .. "button",
|
||||||
|
clicked_callback = function (_)
|
||||||
|
dt.print(_("button clicked"))
|
||||||
|
end
|
||||||
|
},
|
||||||
|
table.unpack(mE.widgets),
|
||||||
|
},
|
||||||
|
nil,-- view_enter
|
||||||
|
nil -- view_leave
|
||||||
|
)
|
||||||
|
mE.module_installed = true
|
||||||
|
end
|
||||||
end
|
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")
|
||||||
@@ -60,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"){}
|
||||||
@@ -82,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
|
||||||
@@ -90,35 +152,44 @@ local slider = dt.new_widget("slider")
|
|||||||
value = 52 -- The current value of the slider
|
value = 52 -- The current value of the slider
|
||||||
}
|
}
|
||||||
|
|
||||||
-- https://www.darktable.org/lua-api/index.html#darktable_register_lib
|
-- pack the widgets in a table for loading in the module
|
||||||
dt.register_lib(
|
|
||||||
"exampleModule", -- Module name
|
|
||||||
"exampleModule", -- name
|
|
||||||
true, -- expandable
|
|
||||||
false, -- resetable
|
|
||||||
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
|
||||||
-- https://www.darktable.org/lua-api/types_lua_box.html
|
|
||||||
dt.new_widget("box") -- widget
|
|
||||||
{
|
|
||||||
orientation = "vertical",
|
|
||||||
dt.new_widget("button")
|
|
||||||
{
|
|
||||||
label = _("MyButton"),
|
|
||||||
clicked_callback = function (_)
|
|
||||||
dt.print(_("Button clicked"))
|
|
||||||
end
|
|
||||||
},
|
|
||||||
check_button,
|
|
||||||
combobox,
|
|
||||||
entry,
|
|
||||||
file_chooser_button,
|
|
||||||
label,
|
|
||||||
separator,
|
|
||||||
slider
|
|
||||||
},
|
|
||||||
nil,-- view_enter
|
|
||||||
nil -- view_leave
|
|
||||||
)
|
|
||||||
|
|
||||||
|
table.insert(mE.widgets, check_button)
|
||||||
|
table.insert(mE.widgets, combobox)
|
||||||
|
table.insert(mE.widgets, entry)
|
||||||
|
table.insert(mE.widgets, file_chooser_button)
|
||||||
|
table.insert(mE.widgets, label)
|
||||||
|
table.insert(mE.widgets, separator)
|
||||||
|
table.insert(mE.widgets, slider)
|
||||||
|
|
||||||
|
-- ... and tell dt about it all
|
||||||
|
|
||||||
|
|
||||||
|
if dt.gui.current_view().id == "lighttable" then -- make sure we are in lighttable view
|
||||||
|
install_module() -- register the lib
|
||||||
|
else
|
||||||
|
if not mE.event_registered then -- if we are not in lighttable view then register an event to signal when we might be
|
||||||
|
-- https://www.darktable.org/lua-api/index.html#darktable_register_event
|
||||||
|
dt.register_event(
|
||||||
|
"mdouleExample", "view-changed", -- we want to be informed when the view changes
|
||||||
|
function(event, old_view, new_view)
|
||||||
|
if new_view.name == "lighttable" and old_view.name == "darkroom" then -- if the view changes from darkroom to lighttable
|
||||||
|
install_module() -- register the lib
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
mE.event_registered = true -- keep track of whether we have an event handler installed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set the destroy routine so that script_manager can call it when
|
||||||
|
-- it's time to destroy the script and then return the data to
|
||||||
|
-- script_manager
|
||||||
|
script_data.destroy = destroy
|
||||||
|
script_data.restart = restart -- only required for lib modules until we figure out how to destroy them
|
||||||
|
script_data.destroy_method = "hide" -- tell script_manager that we are hiding the lib so it knows to use the restart function
|
||||||
|
script_data.show = restart -- if the script was "off" when darktable exited, the module is hidden, so force it to show on start
|
||||||
|
|
||||||
|
return script_data
|
||||||
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
|
-- 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
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -70,13 +70,10 @@ local dtsys = require "lib/dtutils.system" -- system utilities
|
|||||||
translations, inserting this lays the groundwork for anyone who wants to translate the strings.
|
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
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
@@ -86,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
|
||||||
@@ -142,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
|
||||||
@@ -155,11 +182,12 @@ local function extract_embedded_jpeg(images)
|
|||||||
in the user's path. When it finds the executable, it returns the command to run it.
|
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.
|
||||||
@@ -167,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)
|
||||||
@@ -187,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
|
||||||
@@ -204,41 +245,62 @@ end
|
|||||||
to see if the executable is there.
|
to see if the executable is there.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local executable = "ufraw-batch"
|
if dt.configuration.running_os ~= "linux" then
|
||||||
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
|
local executable = "ufraw-batch"
|
||||||
title = _("Select ufraw-batch[.exe] executable"),
|
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
|
||||||
value = df.get_executable_path_preference(executable),
|
title = string.format(_("select %s executable"), "ufraw-batch[.exe]"),
|
||||||
is_directory = false,
|
value = df.get_executable_path_preference(executable),
|
||||||
changed_callback = function(self)
|
is_directory = false,
|
||||||
if df.check_if_bin_exists(self.value) then
|
changed_callback = function(self)
|
||||||
df.set_executable_path_preference(executable, self.value)
|
if df.check_if_bin_exists(self.value) then
|
||||||
|
df.set_executable_path_preference(executable, self.value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
}
|
||||||
}
|
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
|
)
|
||||||
)
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Add a button to the selected images module in lighttable
|
Add a button to the selected images module in lighttable
|
||||||
]]
|
]]
|
||||||
|
|
||||||
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
|
||||||
185
lib/dtutils.lua
185
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
|
||||||
@@ -283,4 +320,148 @@ function dtutils.check_os(operating_systems)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["find_image_by_id"] = {
|
||||||
|
Name = [[find_image_by_id]],
|
||||||
|
Synopsis = [[look up an image by ID in the database]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
local img = du.find_image_by_id(imgid)
|
||||||
|
id - int - the ID to look up
|
||||||
|
]],
|
||||||
|
Description = [[find_image_by_id looks up an image by ID in the database.]],
|
||||||
|
Return_Value = [[result - dt_lua_image_t - image with the given ID if found, nil if not]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.find_image_by_id(imgid)
|
||||||
|
if #dt.database == 0 or imgid > dt.database[#dt.database].id then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if dt.configuration.api_version_string >= "6.2.0" then
|
||||||
|
return dt.database.get_image(imgid)
|
||||||
|
else
|
||||||
|
local min = 1
|
||||||
|
local max = #dt.database
|
||||||
|
while (max-min)//2 > 0 do
|
||||||
|
local mid = min + (max-min)//2
|
||||||
|
local midID = dt.database[mid].id
|
||||||
|
if imgid == midID then
|
||||||
|
return dt.database[mid]
|
||||||
|
elseif imgid < midID then
|
||||||
|
max = mid-1
|
||||||
|
else
|
||||||
|
min = mid+1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dt.database[min].id == imgid then
|
||||||
|
return dt.database[min]
|
||||||
|
elseif dt.database[max].id == imgid then
|
||||||
|
return dt.database[max]
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["deprecated"] = {
|
||||||
|
Name = [[deprecated]],
|
||||||
|
Synopsis = [[print deprecation warning]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
du.deprecated(script_name, removal_string)
|
||||||
|
script_name - name of the script being deprecated
|
||||||
|
removal_string - a string explaining when the script will be removed]],
|
||||||
|
Description = [[deprecated prints an error message saying the script is deprecated and when it will be removed]],
|
||||||
|
Return_Value = [[]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[local du = require "lib/dtutils"
|
||||||
|
du.deprecated("contrib/rename-tags.lua", "darktable release 4.0")]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.deprecated(script_name, removal_string)
|
||||||
|
dt.print_toast("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
|
||||||
|
dt.print_error("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["gen_uuid"] = {
|
||||||
|
Name = [[gen_uuid]],
|
||||||
|
Synopsis = [[generate a UUID string]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
uuid = du.gen_uuid(case)
|
||||||
|
case - "upper" or "lower" to specify the case of the UUID string]],
|
||||||
|
Description = [[gen_uuid prints an error message saying the script is gen_uuid and when it will be removed]],
|
||||||
|
Return_Value = [[uuid - string - a hexidecimal string representing the UUID in the requested case]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[https://gist.github.com/jrus/3197011]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.gen_uuid(case)
|
||||||
|
local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||||||
|
|
||||||
|
-- seed with os.time in seconds and add an extra degree of random for multiple calls in the same second
|
||||||
|
math.randomseed(os.time(), math.random(0, 65536))
|
||||||
|
|
||||||
|
local uuid = string.gsub(template, '[xy]', function (c)
|
||||||
|
local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
|
||||||
|
return string.format('%x', v)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
if case and case == "upper" then
|
||||||
|
uuid = string.upper(uuid)
|
||||||
|
end
|
||||||
|
|
||||||
|
return uuid
|
||||||
|
end
|
||||||
|
|
||||||
|
dtutils.libdoc.functions["compare_api_versions"] = {
|
||||||
|
Name = [[compare_api_versions]],
|
||||||
|
Synopsis = [[compare two API version strings]],
|
||||||
|
Usage = [[local du = require "lib/dtutils"
|
||||||
|
|
||||||
|
local result = du.compare_api_versions(version1, version2)
|
||||||
|
version1 - string - the first version string to compare (example: "5.0.0")
|
||||||
|
version2 - string - the second version string to compare (example: "5.1.0")]],
|
||||||
|
Description = [[compare_api_versions compares two version strings and returns 1 if version1 is greater,
|
||||||
|
-1 if version2 is greater, and 0 if they are equal.]],
|
||||||
|
Return_Value = [[result - 1 if version1 is greater, -1 if version2 is greater, 0 if they are equal.]],
|
||||||
|
Limitations = [[]],
|
||||||
|
Example = [[compare_api_versions("5.0.0", "5.1.0") returns -1]],
|
||||||
|
See_Also = [[]],
|
||||||
|
Reference = [[]],
|
||||||
|
License = [[]],
|
||||||
|
Copyright = [[]],
|
||||||
|
}
|
||||||
|
|
||||||
|
function dtutils.compare_api_versions(version1, version2)
|
||||||
|
local v1 = {}
|
||||||
|
for num in version1:gmatch("%d+") do table.insert(v1, tonumber(num)) end
|
||||||
|
local v2 = {}
|
||||||
|
for num in version2:gmatch("%d+") do table.insert(v2, tonumber(num)) end
|
||||||
|
|
||||||
|
for i = 1, math.max(#v1, #v2) do
|
||||||
|
local num1 = v1[i] or 0
|
||||||
|
local num2 = v2[i] or 0
|
||||||
|
if num1 > num2 then
|
||||||
|
return 1
|
||||||
|
elseif num1 < num2 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
return dtutils
|
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
|
||||||
@@ -329,6 +326,7 @@ local function _old_check_if_bin_exists(bin) -- only run on windows if preferen
|
|||||||
local path = nil
|
local path = nil
|
||||||
|
|
||||||
if string.match(bin, "\\") then
|
if string.match(bin, "\\") then
|
||||||
|
|
||||||
path = bin
|
path = bin
|
||||||
else
|
else
|
||||||
path = dtutils_file.get_executable_path_preference(bin)
|
path = dtutils_file.get_executable_path_preference(bin)
|
||||||
@@ -393,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
|
||||||
|
|
||||||
|
|
||||||
@@ -540,7 +445,7 @@ function dtutils_file.check_if_file_exists(filepath)
|
|||||||
local p = io.popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')")
|
local p = io.popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')")
|
||||||
local ans = p:read("*all")
|
local ans = p:read("*all")
|
||||||
p:close()
|
p:close()
|
||||||
if string.match(ans, "yes") then
|
if string.match(ans, "yes") then
|
||||||
result = true
|
result = true
|
||||||
end
|
end
|
||||||
-- result = os.execute('if exist "'..filepath..'" (cmd /c exit 0) else (cmd /c exit 1)')
|
-- result = os.execute('if exist "'..filepath..'" (cmd /c exit 0) else (cmd /c exit 1)')
|
||||||
@@ -614,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
|
||||||
@@ -667,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
|
||||||
@@ -690,8 +595,8 @@ dtutils_file.libdoc.functions["filename_increment"] = {
|
|||||||
|
|
||||||
local result = df.filename_increment(filepath)
|
local result = df.filename_increment(filepath)
|
||||||
filepath - string - filename to increment]],
|
filepath - string - filename to increment]],
|
||||||
Description = [[filename_increment solves the problem of filename confllict by adding an
|
Description = [[filename_increment solves the problem of filename confllict by adding an
|
||||||
increment to the filename. If the supplied filename has no increment then
|
increment to the filename. If the supplied filename has no increment then
|
||||||
"01" is added to the basename. If the filename already has an increment, then
|
"01" is added to the basename. If the filename already has an increment, then
|
||||||
1 is added to it and the filename returned.]],
|
1 is added to it and the filename returned.]],
|
||||||
Return_Value = [[result - string - the incremented filename]],
|
Return_Value = [[result - string - the incremented filename]],
|
||||||
@@ -743,7 +648,7 @@ dtutils_file.libdoc.functions["create_unique_filename"] = {
|
|||||||
filepath - string - the path and filename requested]],
|
filepath - string - the path and filename requested]],
|
||||||
Description = [[create_unique_filename takes a requested filepath and checks to see if
|
Description = [[create_unique_filename takes a requested filepath and checks to see if
|
||||||
it exists. If if doesn't then it's returned intact. If it already exists, then a two
|
it exists. If if doesn't then it's returned intact. If it already exists, then a two
|
||||||
digit increment is added to the filename and it is tested again. The increment keeps
|
digit increment is added to the filename and it is tested again. The increment keeps
|
||||||
increasing until either a unique filename is found or there have been 100 attempts.]],
|
increasing until either a unique filename is found or there have been 100 attempts.]],
|
||||||
Return_Value = [[result - string - the incremented filename]],
|
Return_Value = [[result - string - the incremented filename]],
|
||||||
Limitations = [[create_unique_filename will only attempt 100 increments.]],
|
Limitations = [[create_unique_filename will only attempt 100 increments.]],
|
||||||
@@ -822,7 +727,7 @@ dtutils_file.libdoc.functions["executable_path_widget"] = {
|
|||||||
local widget = df.executable_path_widget(executables)
|
local widget = df.executable_path_widget(executables)
|
||||||
executables - table - a table of strings that are executable names]],
|
executables - table - a table of strings that are executable names]],
|
||||||
Description = [[executable_path_widget takes a table of executable names
|
Description = [[executable_path_widget takes a table of executable names
|
||||||
and builds a set of file selector widgets to get the path to the executable.
|
and builds a set of file selector widgets to get the path to the executable.
|
||||||
The resulting widgets are wrapped in a box widget and returned.]],
|
The resulting widgets are wrapped in a box widget and returned.]],
|
||||||
Return_Value = [[widget - widget - a widget containing a file selector widget for
|
Return_Value = [[widget - widget - a widget containing a file selector widget for
|
||||||
each executable.]],
|
each executable.]],
|
||||||
@@ -837,10 +742,10 @@ dtutils_file.libdoc.functions["executable_path_widget"] = {
|
|||||||
function dtutils_file.executable_path_widget(executables)
|
function dtutils_file.executable_path_widget(executables)
|
||||||
local box_widgets = {}
|
local box_widgets = {}
|
||||||
table.insert(box_widgets, dt.new_widget("section_label"){label = "select executable(s)"})
|
table.insert(box_widgets, dt.new_widget("section_label"){label = "select executable(s)"})
|
||||||
for _, executable in pairs(executables) do
|
for _, executable in pairs(executables) do
|
||||||
table.insert(box_widgets, dt.new_widget("label"){label = "select " .. executable .. " executable"})
|
table.insert(box_widgets, dt.new_widget("label"){label = "select " .. executable .. " executable"})
|
||||||
local path = dtutils_file.get_executable_path_preference(executable)
|
local path = dtutils_file.get_executable_path_preference(executable)
|
||||||
if not path then
|
if not path then
|
||||||
path = ""
|
path = ""
|
||||||
end
|
end
|
||||||
table.insert(box_widgets, dt.new_widget("file_chooser_button"){
|
table.insert(box_widgets, dt.new_widget("file_chooser_button"){
|
||||||
@@ -870,7 +775,7 @@ dtutils_file.libdoc.functions["sanitize_filename"] = {
|
|||||||
local sanitized_filename = df.sanitize_filename(filename)
|
local sanitized_filename = df.sanitize_filename(filename)
|
||||||
filename - string - a filepath and filename]],
|
filename - string - a filepath and filename]],
|
||||||
Description = [[sanitize_file places quotes around the filename in an
|
Description = [[sanitize_file places quotes around the filename in an
|
||||||
operating system specific manner. The result is safe to pass as
|
operating system specific manner. The result is safe to pass as
|
||||||
an argument to the operating system.]],
|
an argument to the operating system.]],
|
||||||
Return_Value = [[sanitized_filename - string - quoted filename]],
|
Return_Value = [[sanitized_filename - string - quoted filename]],
|
||||||
Limitations = [[]],
|
Limitations = [[]],
|
||||||
@@ -892,7 +797,7 @@ dtutils_file.libdoc.functions["mkdir"] = {
|
|||||||
|
|
||||||
df.mkdir(path)
|
df.mkdir(path)
|
||||||
path - string - a directory path]],
|
path - string - a directory path]],
|
||||||
Description = [[mkdir creates directories if not already exists. It
|
Description = [[mkdir creates directories if not already exists. It
|
||||||
create whole parents subtree if needed
|
create whole parents subtree if needed
|
||||||
]],
|
]],
|
||||||
Return_Value = [[path - string - a directory path]],
|
Return_Value = [[path - string - a directory path]],
|
||||||
@@ -904,10 +809,10 @@ dtutils_file.libdoc.functions["mkdir"] = {
|
|||||||
Copyright = [[]],
|
Copyright = [[]],
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -932,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"] = {
|
||||||
@@ -953,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
|
||||||
@@ -983,5 +885,6 @@ if dt.configuration.running_os == "windows" then
|
|||||||
false)
|
false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return dtutils_file
|
return dtutils_file
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -51,54 +51,63 @@ local dt_print_error = dt.print_error
|
|||||||
local dt_print_log = dt.print_log
|
local dt_print_log = dt.print_log
|
||||||
local dt_print = dt.print
|
local dt_print = dt.print
|
||||||
|
|
||||||
|
|
||||||
-- set the default log levels
|
-- set the default log levels
|
||||||
|
|
||||||
dtutils_log.debug = {
|
dtutils_log.debug = {
|
||||||
label = "DEBUG:",
|
label = "DEBUG: ",
|
||||||
enabled = false,
|
enabled = false,
|
||||||
engine = dt_print_log,
|
engine = dt_print_log,
|
||||||
|
caller_info = 3,
|
||||||
level = 1,
|
level = 1,
|
||||||
}
|
}
|
||||||
dtutils_log.info = {
|
dtutils_log.info = {
|
||||||
label = "INFO:",
|
label = "INFO: ",
|
||||||
enable = false,
|
enable = false,
|
||||||
engine = dt_print_log,
|
engine = dt_print_log,
|
||||||
|
caller_info = 1,
|
||||||
level = 2,
|
level = 2,
|
||||||
}
|
}
|
||||||
dtutils_log.warn = {
|
dtutils_log.warn = {
|
||||||
label = "WARN:",
|
label = "WARN: ",
|
||||||
enabled = false,
|
enabled = false,
|
||||||
engine = dt_print_log,
|
engine = dt_print_log,
|
||||||
|
caller_info = 2,
|
||||||
level = 3,
|
level = 3,
|
||||||
}
|
}
|
||||||
dtutils_log.error = {
|
dtutils_log.error = {
|
||||||
label = "ERROR:",
|
label = "ERROR: ",
|
||||||
enabled = true,
|
enabled = true,
|
||||||
engine = dt_print_error,
|
engine = dt_print_log,
|
||||||
|
caller_info = 3,
|
||||||
level = 4,
|
level = 4,
|
||||||
}
|
}
|
||||||
dtutils_log.success = {
|
dtutils_log.success = {
|
||||||
label = "SUCCESS:",
|
label = "SUCCESS: ",
|
||||||
enabled = true,
|
enabled = true,
|
||||||
engine = dt_print_log,
|
engine = dt_print_log,
|
||||||
|
caller_info = 2,
|
||||||
level = 5,
|
level = 5,
|
||||||
}
|
}
|
||||||
dtutils_log.screen = {
|
dtutils_log.screen = {
|
||||||
label = "",
|
label = "",
|
||||||
enabled = true,
|
enabled = true,
|
||||||
engine = dt_print,
|
engine = dt_print,
|
||||||
|
caller_info = 0,
|
||||||
level = 9,
|
level = 9,
|
||||||
}
|
}
|
||||||
dtutils_log.always = {
|
dtutils_log.always = {
|
||||||
label = "",
|
label = "",
|
||||||
enabled = true,
|
enabled = true,
|
||||||
engine = dt_print_log,
|
engine = dt_print_log,
|
||||||
|
caller_info = 3,
|
||||||
level = 9,
|
level = 9,
|
||||||
}
|
}
|
||||||
dtutils_log.critical = {
|
dtutils_log.critical = {
|
||||||
label = "CRITICAL:",
|
label = "CRITICAL: ",
|
||||||
enabled = true,
|
enabled = true,
|
||||||
engine = print,
|
engine = print,
|
||||||
|
caller_info = 3,
|
||||||
level = 9,
|
level = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +129,7 @@ result = log.caller(level)
|
|||||||
Copyright = [[]],
|
Copyright = [[]],
|
||||||
}
|
}
|
||||||
|
|
||||||
function dtutils_log.caller(level)
|
function dtutils_log.caller(level, info)
|
||||||
local name = debug.getinfo(level).name
|
local name = debug.getinfo(level).name
|
||||||
local lineno = nil
|
local lineno = nil
|
||||||
local source = nil
|
local source = nil
|
||||||
@@ -131,7 +140,16 @@ function dtutils_log.caller(level)
|
|||||||
-- we just need the filename, so grab it from the string
|
-- we just need the filename, so grab it from the string
|
||||||
-- Thanks, Tobias Jakobs :-)
|
-- Thanks, Tobias Jakobs :-)
|
||||||
source = string.match(source, "@.-([^\\/]-%.?[^%.\\/]*)$")
|
source = string.match(source, "@.-([^\\/]-%.?[^%.\\/]*)$")
|
||||||
return name .. ": " .. source .. ": " .. lineno .. ":"
|
|
||||||
|
if info == 0 then
|
||||||
|
return ""
|
||||||
|
elseif info == 1 then
|
||||||
|
return source .. ": "
|
||||||
|
elseif info == 2 then
|
||||||
|
return source .. ": " .. name .. ": "
|
||||||
|
else
|
||||||
|
return source .. ": " .. name .. ": " .. lineno .. ":"
|
||||||
|
end
|
||||||
else
|
else
|
||||||
return "callback:"
|
return "callback:"
|
||||||
end
|
end
|
||||||
@@ -181,8 +199,8 @@ 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) .. " "
|
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 .. " "
|
||||||
end
|
end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
local dtutils_system = {}
|
local dtutils_system = {}
|
||||||
|
|
||||||
local dt = require "darktable"
|
local dt = require "darktable"
|
||||||
require "official/yield" -- necessary for dt.control.execute()
|
local ds = require "lib/dtutils.string"
|
||||||
|
|
||||||
dtutils_system.libdoc = {
|
dtutils_system.libdoc = {
|
||||||
Name = [[dtutils.system]],
|
Name = [[dtutils.system]],
|
||||||
@@ -86,8 +86,15 @@ function dtutils_system.windows_command(command)
|
|||||||
local file = io.open(fname, "w")
|
local file = io.open(fname, "w")
|
||||||
if file then
|
if file then
|
||||||
dt.print_log("opened file")
|
dt.print_log("opened file")
|
||||||
command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell
|
file:write("@echo off\n")
|
||||||
file:write(command)
|
file:write('for /f "tokens=2 delims=:." %%x in (\'chcp\') do set cp=%%x\n')
|
||||||
|
file:write("chcp 65001>nul\n") -- change the encoding of the terminal to handle non-english characters in path
|
||||||
|
file:write("\n")
|
||||||
|
file:write(command .. "\n")
|
||||||
|
file:write("set COMMAND_RC=%ERRORLEVEL%")
|
||||||
|
file:write("\n")
|
||||||
|
file:write("chcp %cp%>nul\n")
|
||||||
|
file:write("exit /b %COMMAND_RC%") -- otherwise, when the real command fails, script will still return "good" error code of chcp
|
||||||
file:close()
|
file:close()
|
||||||
|
|
||||||
result = dt.control.execute(fname)
|
result = dt.control.execute(fname)
|
||||||
@@ -119,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
|
||||||
@@ -129,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
|
||||||
|
|||||||
@@ -1,39 +1,78 @@
|
|||||||
# OpenInExplorer Darktable Plug-In.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) 2020 Volker Lenhardt
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
# Volker Lenhardt <volker.lenhardt@unitybox.de>, 2020.
|
# Volker Lenhardt <volker.lenhardt@unitybox.de>, 2020.
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: OpenInExplorer\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-04-19 14:43+0200\n"
|
"POT-Creation-Date: 2020-06-11 19:07+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Volker Lenhardt <volker.lenhardt@unitybox.de>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: OpenInExplorer.lua:64
|
#: OpenInExplorer.lua:71
|
||||||
msgid ""
|
msgid ""
|
||||||
"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time"
|
"OpenInExplorer plug-in only supports Linux and macOS, and Windows at this time"
|
||||||
msgstr "OpenInExplorer-Plug-in kann nur auf macOS, Linux oder Windows laufen"
|
msgstr "OpenInExplorer-Plug-in unterstützt zur Zeit nur Linux, macOS oder Windows"
|
||||||
|
|
||||||
#: OpenInExplorer.lua:79
|
#: OpenInExplorer.lua:87
|
||||||
|
msgid ""
|
||||||
|
"No links directory selected.\n"
|
||||||
|
"Please check the dt preferences (lua options)"
|
||||||
|
msgstr "Kein Verknüpfungs-Verzeichnis ausgewählt.\nBitte die dt-Voreinstellungen überprüfen (Lua-Optionen)"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:91
|
||||||
|
#, lua-format
|
||||||
|
msgid ""
|
||||||
|
"Links directory '%s' not found.\n"
|
||||||
|
"Please check the dt preferences (lua options)"
|
||||||
|
msgstr "Das Verknüpfungs-Verzeichnis '%s' konnte nicht gefunden werden.\nBitte die dt-Voreinstellungen überprüfen (Lua-Optionen)"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:148
|
||||||
|
msgid "Failed to create links. Missing rights?"
|
||||||
|
msgstr "Die Verknüpfungen konnten nicht erstellt werden. Fehlende Rechte?"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:164
|
||||||
msgid "Please select an image"
|
msgid "Please select an image"
|
||||||
msgstr "Es wurde kein Bild ausgewählt"
|
msgstr "Es wurde kein Bild ausgewählt"
|
||||||
|
|
||||||
#: OpenInExplorer.lua:98
|
#: OpenInExplorer.lua:170
|
||||||
msgid "Please select fewer images (max 15)"
|
msgid "Please select fewer images (max. 15)"
|
||||||
msgstr "Bitte nicht mehr als 15 Bilder auswählen"
|
msgstr "Bitte nicht mehr als 15 Bilder auswählen"
|
||||||
|
|
||||||
#: OpenInExplorer.lua:105
|
#: OpenInExplorer.lua:186
|
||||||
msgid "show in file explorer"
|
msgid "show in file explorer"
|
||||||
msgstr "im Dateimanager anzeigen"
|
msgstr "im Dateimanager anzeigen"
|
||||||
|
|
||||||
#: OpenInExplorer.lua:107
|
#: OpenInExplorer.lua:188
|
||||||
msgid "Opens the file manager at the selected image's location"
|
msgid "Open the file manager at the selected image's location"
|
||||||
msgstr "Öffnet den Dateimanager an der Position des ausgewählten Bildes"
|
msgstr "Öffnet den Dateimanager an der Position des ausgewählten Bildes"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:193
|
||||||
|
msgid "OpenInExplorer: linked files directory"
|
||||||
|
msgstr "OpenInExplorer: Verknüpfungs-Verzeichnis"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:194
|
||||||
|
msgid ""
|
||||||
|
"Directory to store the links to the file names. Requires restart to take "
|
||||||
|
"effect"
|
||||||
|
msgstr "Verzeichnis für die Verknüpfungen zu den Dateinamen. Erfordert dt-Neustart"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:197
|
||||||
|
msgid "Select directory"
|
||||||
|
msgstr "Verzeichnis auswählen"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:203
|
||||||
|
msgid "OpenInExplorer: use links"
|
||||||
|
msgstr "OpenInExplorer: Verknüpfungen nutzen"
|
||||||
|
|
||||||
|
#: OpenInExplorer.lua:204
|
||||||
|
msgid "Use links instead of multiple windows. Requires restart to take effect"
|
||||||
|
msgstr "Verknüpfungen statt einzelner Fenster nutzen. Erfordert dt-Neustart"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
73
locale/de_DE/LC_MESSAGES/photils.po
Normal file
73
locale/de_DE/LC_MESSAGES/photils.po
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Lua photils\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-05-12 13:12+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
msgid "get tags"
|
||||||
|
msgstr "hole stichwörter"
|
||||||
|
|
||||||
|
msgid "requires a restart to be applied"
|
||||||
|
msgstr "erfordert einen Neustart um angewendet zu werden"
|
||||||
|
|
||||||
|
msgid "min confidence value"
|
||||||
|
msgstr "min vertrauenswert"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The suggested tags were not generated\n"
|
||||||
|
" for the currently selected image!"
|
||||||
|
msgstr ""
|
||||||
|
"Die vorgeschlagenen Stichwörter wurden für das\n"
|
||||||
|
"aktuell ausgewählte Bild nicht generiert!"
|
||||||
|
|
||||||
|
#, lua-format
|
||||||
|
msgid " page %s of %s "
|
||||||
|
msgstr " seite %s von %s "
|
||||||
|
|
||||||
|
msgid "Tags successfully attached to image"
|
||||||
|
msgstr "Stichwörter erfolgreich an Bild angefügt"
|
||||||
|
|
||||||
|
#, lua-format
|
||||||
|
msgid "Error writing to `%s`"
|
||||||
|
msgstr "Fehler beim Schreiben in `%s`"
|
||||||
|
|
||||||
|
#, lua-format
|
||||||
|
msgid "%s failed, see terminal output for details"
|
||||||
|
msgstr "%s fehlgeschlagen, siehe Terminal-Ausgabe für Details"
|
||||||
|
|
||||||
|
#, lua-format
|
||||||
|
msgid "%s found %d tags for your image"
|
||||||
|
msgstr "%s gefunden %d-Stichwörter für Ihr Bild"
|
||||||
|
|
||||||
|
msgid "No image selected."
|
||||||
|
msgstr "Kein Bild ausgewählt."
|
||||||
|
|
||||||
|
msgid "This plugin can only handle a single image."
|
||||||
|
msgstr "Dieses Plugin kann nur ein einziges Bild verarbeiten."
|
||||||
|
|
||||||
|
#, lua-format
|
||||||
|
msgid "attach %d tags"
|
||||||
|
msgstr "%d stichwörter anhängen"
|
||||||
|
|
||||||
|
msgid "photils-cli not found"
|
||||||
|
msgstr "photils-cli nicht gefunden"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Select an image, click \"Get Tags\" and get \n"
|
||||||
|
"suggestions for tags."
|
||||||
|
msgstr ""
|
||||||
|
"Wählen Sie ein Bild aus, drücken Sie \"Hole Stichwörter\"\n"
|
||||||
|
" und erhalten Sie Vorschläge für Stichwörter."
|
||||||
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"
|
||||||
@@ -7,21 +7,29 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2016-09-07 22:25-0400\n"
|
"POT-Creation-Date: 2020-12-27 17:55+0100\n"
|
||||||
"PO-Revision-Date: 2016-10-03 14:55+0200\n"
|
"PO-Revision-Date: 2020-12-27 17:55+0100\n"
|
||||||
"Last-Translator: Pascal Obry <pascal@obry.net>\n"
|
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 1.8.9\n"
|
"X-Generator: Poedit 2.3\n"
|
||||||
"Language: fr_FR\n"
|
|
||||||
"X-Poedit-SourceCharset: UTF-8\n"
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
"X-Poedit-KeywordsList: gettext;dgettext:2;dcgettext:2ngettext:1,2;"
|
"X-Poedit-KeywordsList: _;gettext;dgettext:2;dcgettext:2ngettext:1,2;"
|
||||||
"dngettext:2,3\n"
|
"dngettext:2,3\n"
|
||||||
"X-Poedit-Basepath: .\n"
|
"X-Poedit-Basepath: ../../../contrib\n"
|
||||||
"Language-Team: \n"
|
"X-Poedit-SearchPath-0: clear_GPS.lua\n"
|
||||||
|
|
||||||
#: clear_GPS.lua:62
|
#: clear_GPS.lua:67
|
||||||
msgid "clear GPS data"
|
msgid "clear GPS data"
|
||||||
msgstr "effacer données GPS"
|
msgstr "effacer données GPS"
|
||||||
|
|
||||||
|
#: clear_GPS.lua:69
|
||||||
|
msgid "Clear GPS data from selected images"
|
||||||
|
msgstr "Effacer les données GPS des images sélectionnées"
|
||||||
|
|
||||||
|
#: clear_GPS.lua:75
|
||||||
|
msgid "Clear GPS data"
|
||||||
|
msgstr "Effacer les données GPS"
|
||||||
|
|||||||
78
locale/fr_FR/LC_MESSAGES/copy_attach_detach_tags.po
Normal file
78
locale/fr_FR/LC_MESSAGES/copy_attach_detach_tags.po
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# French messages for copy_attach_detach_tags.lua
|
||||||
|
# Christophe Agathon <christophe.agathon@gmail.com>, 2021
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: 2021-01-10 19:39+0100\n"
|
||||||
|
"PO-Revision-Date: 2021-01-10 19:43+0100\n"
|
||||||
|
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
"X-Poedit-Basepath: ../../../contrib\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-KeywordsList: _\n"
|
||||||
|
"X-Poedit-SearchPath-0: copy_attach_detach_tags.lua\n"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:93
|
||||||
|
msgid "Image tags copied ..."
|
||||||
|
msgstr "Mots-clés de l'image copiés ..."
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:115
|
||||||
|
msgid "No tag to attach, please copy tags first."
|
||||||
|
msgstr "Pas de mot-clé à attacher, veuillez d'abord copier les mots-clés."
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:139
|
||||||
|
msgid "Tags attached ..."
|
||||||
|
msgstr "Mots-clés attachés ..."
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:155
|
||||||
|
msgid "Tags removed from image(s)."
|
||||||
|
msgstr "Mots-clés supprimés des images."
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:161
|
||||||
|
msgid "Tags replaced"
|
||||||
|
msgstr "Mots-clés remplacés"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:166
|
||||||
|
msgid "tagging addon"
|
||||||
|
msgstr "mots-clés extra"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:189
|
||||||
|
msgid "tag clipboard"
|
||||||
|
msgstr "presse-papier des mots-clés"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:197
|
||||||
|
msgid "multi copy tags"
|
||||||
|
msgstr "copier mots-clés"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:198 copy_attach_detach_tags.lua:252
|
||||||
|
msgid "copy tags from selected image(s)"
|
||||||
|
msgstr "copier les mots-clés des images sélectionnnées"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:201 copy_attach_detach_tags.lua:257
|
||||||
|
msgid "paste tags to selected image(s)"
|
||||||
|
msgstr "coller les mots clés dans les images sélectionnées"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:202
|
||||||
|
msgid "paste tags"
|
||||||
|
msgstr "coller mots-clés"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:210
|
||||||
|
msgid "replace tags"
|
||||||
|
msgstr "remplacer mots-clés"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:211 copy_attach_detach_tags.lua:267
|
||||||
|
msgid "replace tags from selected image(s)"
|
||||||
|
msgstr "remplacer les mots-clés des images sélectionnées"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:214
|
||||||
|
msgid "remove all tags"
|
||||||
|
msgstr "supprimer tous mots-clés"
|
||||||
|
|
||||||
|
#: copy_attach_detach_tags.lua:215 copy_attach_detach_tags.lua:262
|
||||||
|
msgid "remove tags from selected image(s)"
|
||||||
|
msgstr "supprimer les mots-clés des images sélectionnées"
|
||||||
35
locale/fr_FR/LC_MESSAGES/copy_paste_metadata.po
Normal file
35
locale/fr_FR/LC_MESSAGES/copy_paste_metadata.po
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# French messages for copy_paste_metadata.lua
|
||||||
|
# Christophe Agathon <christophe.agathon@gmail.com>, 2020
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: copy_paste_metadata\n"
|
||||||
|
"POT-Creation-Date: 2020-12-26 12:49+0100\n"
|
||||||
|
"PO-Revision-Date: 2020-12-26 12:52+0100\n"
|
||||||
|
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
"X-Poedit-Basepath: ../../../official\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
"X-Poedit-KeywordsList: _\n"
|
||||||
|
"X-Poedit-SearchPath-0: copy_paste_metadata.lua\n"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:115
|
||||||
|
msgid "copy metadata"
|
||||||
|
msgstr "copier les métadonnées"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:117
|
||||||
|
msgid "copy metadata of the first selected image"
|
||||||
|
msgstr "copier les métadonnées de la première image sélectionnée"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:121
|
||||||
|
msgid "paste metadata"
|
||||||
|
msgstr "coller les métadonnées"
|
||||||
|
|
||||||
|
#: copy_paste_metadata.lua:123
|
||||||
|
msgid "paste metadata to the selected images"
|
||||||
|
msgstr "coller les métadonnées vers les images sélectionnées"
|
||||||
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é."
|
||||||
188
locale/fr_FR/LC_MESSAGES/script_manager.po
Normal file
188
locale/fr_FR/LC_MESSAGES/script_manager.po
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# French messages for script_manager.lua
|
||||||
|
# Christophe Agathon <christophe.agathon@gmail.com>, 2020
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: 2020-12-28 10:22+0100\n"
|
||||||
|
"PO-Revision-Date: 2020-12-28 10:24+0100\n"
|
||||||
|
"Last-Translator: Christophe Agathon <christophe.agathon@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
"X-Poedit-Basepath: ../../../tools\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Poedit-KeywordsList: _\n"
|
||||||
|
"X-Poedit-SearchPath-0: script_manager.lua\n"
|
||||||
|
|
||||||
|
#: script_manager.lua:190
|
||||||
|
msgid "Cant read from "
|
||||||
|
msgstr "Ne peut pas lire "
|
||||||
|
|
||||||
|
#: script_manager.lua:204
|
||||||
|
msgid "Loaded "
|
||||||
|
msgstr "Script chargé : "
|
||||||
|
|
||||||
|
#: script_manager.lua:207
|
||||||
|
msgid " failed to load"
|
||||||
|
msgstr " non chargé"
|
||||||
|
|
||||||
|
#: script_manager.lua:223
|
||||||
|
msgid " will not start when darktable is restarted"
|
||||||
|
msgstr " ne démarrera pas lorsque darktable sera redémarré"
|
||||||
|
|
||||||
|
#: script_manager.lua:281 script_manager.lua:345
|
||||||
|
msgid "find command is "
|
||||||
|
msgstr "la commande find est "
|
||||||
|
|
||||||
|
#: script_manager.lua:321
|
||||||
|
msgid "lua scripts successfully updated"
|
||||||
|
msgstr "mise à jour des scripts lua réussie"
|
||||||
|
|
||||||
|
#: script_manager.lua:381
|
||||||
|
msgid "category "
|
||||||
|
msgstr "la catégorie "
|
||||||
|
|
||||||
|
#: script_manager.lua:381
|
||||||
|
msgid " is already in use. Please specify a different category name."
|
||||||
|
msgstr " est déjà utilisée. Indiquez un autre nom de catégorie SVP."
|
||||||
|
|
||||||
|
#: script_manager.lua:410
|
||||||
|
msgid "scripts successfully installed into category "
|
||||||
|
msgstr "les scripts ont été installés dans la catégorie "
|
||||||
|
|
||||||
|
#: script_manager.lua:426
|
||||||
|
msgid "No scripts found to install"
|
||||||
|
msgstr "Nous n'avons pas trouvé de script à installer"
|
||||||
|
|
||||||
|
#: script_manager.lua:430
|
||||||
|
msgid "failed to download scripts"
|
||||||
|
msgstr "impossible de télécharger les scripts"
|
||||||
|
|
||||||
|
# Peut-être que « activé » serait mieux, mais ce n'est pas la traduction de « started »
|
||||||
|
#: script_manager.lua:458
|
||||||
|
msgid " started"
|
||||||
|
msgstr " démarré"
|
||||||
|
|
||||||
|
# Peut-être que « désactivé » serait mieux, mais ce n'est pas la traduction de « stopped »
|
||||||
|
#: script_manager.lua:460
|
||||||
|
msgid " stopped"
|
||||||
|
msgstr " arrêté"
|
||||||
|
|
||||||
|
#: script_manager.lua:545
|
||||||
|
msgid "Page "
|
||||||
|
msgstr "Page "
|
||||||
|
|
||||||
|
#: script_manager.lua:545
|
||||||
|
msgid " of "
|
||||||
|
msgstr " sur "
|
||||||
|
|
||||||
|
#: script_manager.lua:674
|
||||||
|
msgid "scripts to update"
|
||||||
|
msgstr "scripts à mettre à jour"
|
||||||
|
|
||||||
|
#: script_manager.lua:675
|
||||||
|
msgid "select the scripts installation to update"
|
||||||
|
msgstr "sélectionnez les scripts à mettre à jour"
|
||||||
|
|
||||||
|
#: script_manager.lua:684 script_manager.lua:742
|
||||||
|
msgid "update scripts"
|
||||||
|
msgstr "mettre à jour les scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:685
|
||||||
|
msgid "update the lua scripts from the repository"
|
||||||
|
msgstr "metre à jour les scripts depuis le dépôt"
|
||||||
|
|
||||||
|
#: script_manager.lua:696
|
||||||
|
msgid "enter the URL of the git repository containing the scripts you wish to add"
|
||||||
|
msgstr "indiquez l'URL du dépôt git contenant les scripts que vous souhaitez ajouter"
|
||||||
|
|
||||||
|
#: script_manager.lua:701
|
||||||
|
msgid "name of new category"
|
||||||
|
msgstr "nom de la nouvelle catégorie"
|
||||||
|
|
||||||
|
#: script_manager.lua:702
|
||||||
|
msgid "enter a category name for the additional scripts"
|
||||||
|
msgstr "indiquez un nom de catégorie pour les scripts supplémentaires"
|
||||||
|
|
||||||
|
#: script_manager.lua:707
|
||||||
|
msgid "URL to download additional scripts from"
|
||||||
|
msgstr "URL d'où seront téléchargés les scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:709
|
||||||
|
msgid "new category to place scripts in"
|
||||||
|
msgstr "nouvelle catégorie où seront placés les scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:712
|
||||||
|
msgid "install additional scripts"
|
||||||
|
msgstr "installer les scripts supplémentaires"
|
||||||
|
|
||||||
|
#: script_manager.lua:720
|
||||||
|
msgid "Enable \"Disable Scripts\" button"
|
||||||
|
msgstr "Activer le bouton « Désactiver les Scripts »"
|
||||||
|
|
||||||
|
#: script_manager.lua:730
|
||||||
|
msgid "Disable Scripts"
|
||||||
|
msgstr "Désactiver les scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:736
|
||||||
|
msgid "lua scripts will not run the next time darktable is started"
|
||||||
|
msgstr "les scripts lua ne seront pas lancés au prochain démarrage de darktable"
|
||||||
|
|
||||||
|
#: script_manager.lua:745
|
||||||
|
msgid "add more scripts"
|
||||||
|
msgstr "ajouter des scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:747
|
||||||
|
msgid "disable scripts"
|
||||||
|
msgstr "désactiver les scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:755
|
||||||
|
msgid "category"
|
||||||
|
msgstr "catégorie"
|
||||||
|
|
||||||
|
#: script_manager.lua:756
|
||||||
|
msgid "select the script category"
|
||||||
|
msgstr "sélectionner la catégorie"
|
||||||
|
|
||||||
|
#: script_manager.lua:780
|
||||||
|
msgid "Page:"
|
||||||
|
msgstr "Page :"
|
||||||
|
|
||||||
|
#: script_manager.lua:808
|
||||||
|
msgid "Scripts"
|
||||||
|
msgstr "Scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:817
|
||||||
|
msgid "scripts per page"
|
||||||
|
msgstr "scripts par page"
|
||||||
|
|
||||||
|
#: script_manager.lua:818
|
||||||
|
msgid "select number of start/stop buttons to display"
|
||||||
|
msgstr "sélectionner le nombre de boutons marche/arrêt à afficher"
|
||||||
|
|
||||||
|
#: script_manager.lua:829
|
||||||
|
msgid "change number of buttons"
|
||||||
|
msgstr "changer le nombre de boutons"
|
||||||
|
|
||||||
|
#: script_manager.lua:837
|
||||||
|
msgid "Configuration"
|
||||||
|
msgstr "Configuration"
|
||||||
|
|
||||||
|
#: script_manager.lua:858
|
||||||
|
msgid "action"
|
||||||
|
msgstr "action"
|
||||||
|
|
||||||
|
#: script_manager.lua:864
|
||||||
|
msgid "install/update scripts"
|
||||||
|
msgstr "installer/mettre à jour des scripts"
|
||||||
|
|
||||||
|
#: script_manager.lua:864
|
||||||
|
msgid "configure"
|
||||||
|
msgstr "configurer"
|
||||||
|
|
||||||
|
#: script_manager.lua:864
|
||||||
|
msgid "start/stop scripts"
|
||||||
|
msgstr "démarrer/arrêter des scripts"
|
||||||
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"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user