365 Commits

Author SHA1 Message Date
wpferguson
8afd6ccee4 Merge pull request #202 from wpferguson/fix_libraries
fix libraries
2019-12-16 22:45:59 -05:00
Bill Ferguson
e427294ec1 fix library problems found during unit testing 2019-12-16 22:42:32 -05:00
wpferguson
18283ccbc6 Merge pull request #201 from darktable-org/revert-199-library_fixes
Revert "Library fixes"
2019-12-16 22:31:40 -05:00
wpferguson
b7857c8462 Revert "Library fixes" 2019-12-16 22:30:50 -05:00
wpferguson
2d231806d2 Merge pull request #199 from wpferguson/library_fixes
Library fixes
2019-12-16 21:46:14 -05:00
wpferguson
36b58333ca Merge pull request #200 from wpferguson/README_update
Update README.md
2019-12-16 21:40:26 -05:00
Bill Ferguson
fc77af598d Another update 2019-12-16 20:39:10 -05:00
Bill Ferguson
602295ae89 Updated contributed scripts 2019-12-16 20:27:20 -05:00
Bill Ferguson
1c295a88ac Fixed bugs found during unit testing with testing framework 2019-12-11 22:55:44 -05:00
wpferguson
232535faf9 Merge pull request #197 from wpferguson/update_face_recognition
Update face recognition
2019-12-09 20:19:24 -05:00
Bill Ferguson
10b5dee40f Added windows compatibility 2019-12-09 20:08:11 -05:00
Bill Ferguson
be48459092 Converted face_recognition to a module and updated it to use newer library functions. Added tolerance adjustment to settings to fine tune the face detection. 2019-12-08 20:18:07 -05:00
supertobi
cb6199c1a8 Merge pull request #100 from GHswitt/master
Storage plugin for face_recognition
2019-12-07 21:34:38 +01:00
supertobi
c9eb087ade Update README.md
The ' is not not needed in the windows cmd
2019-12-04 13:01:58 +01:00
supertobi
87e223a854 Update README.md
Added: If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least once, because the directory is created at the first start of darktable.
2019-12-04 12:52:44 +01:00
wpferguson
7c7118dfad Merge pull request #193 from simon04/patch-1
README: list various third-party projects
2019-11-10 00:33:39 -05:00
Simon Legner
fdcd6c6dfe README: list various third-party projects 2019-11-04 21:42:15 +01:00
Bill Ferguson
205cbcf05d Checkpoint save 2019-07-03 12:41:58 -04:00
wpferguson
c9ccbb2750 Merge pull request #189 from wpferguson/remove_plugin_manager_symlink
Removed symlink for plugin_manager development
2019-06-19 11:25:32 -04:00
Bill Ferguson
eacfc4b623 Removed symlink for plugin_manager development 2019-06-19 11:23:11 -04:00
supertobi
d3dafd4ce1 Merge pull request #179 from BzKevin/BzKevin-enfuseAdvanced-refactor
Refactored enfuseAdvanced
2019-05-07 15:56:06 +02:00
supertobi
8004b96c41 Merge branch 'master' into BzKevin-enfuseAdvanced-refactor 2019-05-07 15:54:12 +02:00
supertobi
7cc58d9fac Merge pull request #164 from BzKevin/BzKevin-HDRMerge
Add HDRMerge
2019-05-07 13:00:40 +02:00
BzKevin
f82ae6c091 Change 'Create Unique Filename' to default 2019-05-06 21:35:41 -04:00
wpferguson
6ab029e692 Merge pull request #178 from BzKevin/BzKevin-AutoGrouper
Add AutoGrouper
2019-05-06 15:38:10 -04:00
BzKevin
2c3536afc9 Changes requestd via PR
1) mod changed to MOD
2) autogroup changed to autogrouper
3) prints "please select at least 2 images" if user attempts to run with fewer than 2 images selected
4) change visible module name to "auto group" (lower case)
5) changed soft max on slider to 60s
         - I also increase the hard max up to a full day, I saw no reason not to
2019-05-06 10:49:30 -04:00
supertobi
0fe95e91d5 Merge pull request #185 from darktable-org/file.lua
file.lua copy for windows
2019-04-16 11:00:19 +02:00
supertobi
cadd98dc59 dtutils_file.file_copy: use the result 2019-04-16 10:58:32 +02:00
wpferguson
92f99c3780 Merge pull request #184 from darktable-org/dt_utils_system
On Windows we don't need anything to open a file
2019-04-16 00:03:12 -04:00
supertobi
f1d397f908 OpenInExplorer
Changed from nautilus to org.freedesktop.FileManager1, so it works with all file manager that support this dbus API.
2019-04-15 23:09:01 +02:00
supertobi
eeaa6b478b Merge pull request #171 from darktable-org/supertobi-patch-1
make kml_export multi OS
2019-04-15 21:35:03 +02:00
supertobi
53d6b54de8 Merge branch 'master' into supertobi-patch-1 2019-04-15 21:34:44 +02:00
supertobi
ffd6abbe95 Fixed bugs after Linux testing 2019-04-15 21:28:48 +02:00
supertobi
f08421a48c Update kml_export.lua
Fixed Windows problem
2019-04-15 15:55:22 +02:00
supertobi
a383d1ebf8 Update kml_export.lua
Works with Windows
2019-04-15 15:48:31 +02:00
supertobi
d355095e16 file.lua copy for windows 2019-04-15 15:27:53 +02:00
supertobi
a81d992552 On Windows we don't need anything to open a file
On Windows we don't need any command to open a file. 
And "start" has problems with spaces in the filename, even if we put quoter around them:
https://stackoverflow.com/questions/13691827/opening-file-with-spaces-in-windows-via-command-prompt
2019-04-15 14:27:17 +02:00
wpferguson
eb13518b96 Merge pull request #183 from wpferguson/executable_manager_fix
executable manager windows string spllit bug
2019-03-28 20:17:57 -04:00
Bill Ferguson
bf6e180291 Fixed bug that caused the lua executale path to not split correctly on windows. Preferences are stored internally with forward slashes, not O/S specific path separators. 2019-03-28 20:13:08 -04:00
BzKevin
fcad7a5733 Makte Translatable + others
Made text translatable except for line 499 which kept giving me problems
   Making L499 translatable would throw the following error   lua:499: attempt to call a nil value (upvalue '_')
   Troubleshooting steps taken and results:
        1) Changed the translation function from _() to translate() - this worked, no errors but ugly
        2) Reverted the changes from 1, then changed all "for _, var in table do" to be "for ignore, var in table do" - this did not help

Moved a chunk of code due to anomalous behavior, I suspect previous code was attempting to change the value in the stack widget before it was fully created. Adding in a print statement before trying to change the stack.active would cause things to work fine. Moving it behind the register call also helped.
2019-03-22 12:01:56 -04:00
BzKevin
7ffdf1631c Make text translatable 2019-03-22 09:05:11 -04:00
BzKevin
a5ad5e67f3 Remove executable preferences
The executable choosers have been moved to a box which is now element 4 in the GUI stack. Once the user configures these and presses update they are confirmed good or bad and the GUI changed to normal view if all works out.
2019-03-12 13:04:21 -04:00
BzKevin
4b3b1f7786 Fix bug when updating exe preference
Move the preference.write() call inside the for loop
2019-03-12 12:42:37 -04:00
BzKevin
844e89a652 Use file chooser to set executable path preference
previous commit failed to actually utilize the newly created file chooser to set the path preference
2019-03-12 12:25:37 -04:00
BzKevin
d88af9ffd9 Minor tweaks to last commit 2019-03-12 11:20:04 -04:00
BzKevin
7a3a9ba559 Removed preferences for exe
The GUI is now made of a stack which has two boxes in it. One box contains all the normal GUI elements, the other contains a file chooser button and a normal button to call an update routine. This box is only displayed if an issue is found. I decided to track the install status via a preference boolean, rather than calling df_check_if_bin_exists at startup every time. I do this because on my windows PC I was starting to get slow startups with too many scripts installed and many many flashing CMD windows at startup as well, which was a nuisance.
2019-03-12 11:19:22 -04:00
BzKevin
5d3910da49 Tab to Space 2019-03-05 09:14:28 -05:00
BzKevin
f740d34f1f Tab to Space 2019-03-05 09:12:54 -05:00
BzKevin
58ca70c511 Tab to Space 2019-03-05 09:11:28 -05:00
BzKevin
ee59540648 Refactored
I was having issue wit this script periodically. The original version of this was real a hack-job of Holger Klemms original. This new version utilizes tables in a manner that I hope will make the code easier to follow and understand, but also allows for certain functions to be re-used throughout (and potentially in different scripts as well). In general:

Each program (align image stack, enfuse, exiftool) how has a table that contains information about that program, this includes a sub-table that contains argument data for the program.
There is a GUI table which contains all gui elements. Those elements are split into sub-tables within GUI based on what aspect of the scrpt behavior they relate to (AIS, ENF, Target File, Presets, etc...)
These two structures (and the similarities between them) have allows me to create loops which populate the arguments from the GUI and vice-versa, this is all done through an intermediary preference, the "active" preference. 
Whenever a change to a GUI element occurs the associated member of hte active preference is updated. then when it becomes time to build an argument string for a program command the active preference is read from and the value contained within is used. This works fine for all GUI elements that have a "changed" or "clicked" callback, as they can remain up-to-date real time. Sliders and Entry boxes do not have this, so at a certain point withing the script the GUI values are read directly for these elements and written to the associated active preference field.

As you will see about 50% of this code is simply building all the GUI elements and making sure that the initialization value for that element is valid (via the InRange() function). The actual heart of the program and it's functions really only takes up about 300 lines (starting at 180 and going through 476). That is broken down into multiple fairly concise functions. Hopefully this makes maintenance, feature enhancement, and troubleshooting much easier in the future.

I also made a few UI tweaks. This script use to create such a massive list of GUI elements that it was a nuisance when actually using it. They are now grouped into three containers for AIS options, Enfuse options, and target options. Only one of these options containers is displayed at a time. Additionally, I removed all the registered preferences I had before, so now I only register 3 options to enable user to point towards the program binaries.
2019-03-04 13:13:49 -05:00
BzKevin
9e97b8f5a1 Added comments, made the gap inclusive
Added comments throughout
Change the gap comparison to be inclusive ( from '<' to '<=')
2019-03-04 12:47:52 -05:00
BzKevin
95968c3d95 Add AutoGrouper
Adds AutoGrouper script which allows the user to select multiple images (or an entire collection) and automatically group them based on timestamp. The separation between groups is user defined via a slider.
2019-03-04 12:20:55 -05:00
BzKevin
85c242ff0e Comments added, minor updates
I have added comments throughout, mainly to the functions
I improved the performance of the CleanSpaces() function
Improved communication to user via dt.print()
2019-03-04 09:45:05 -05:00
supertobi
18783e7ea5 kml_export fixed typo 2019-03-04 15:12:04 +01:00
BzKevin
7bc4846c0a Refactored
After experiencing some issues after upgrading to dt2.6 I decided it would be worthwhile to refactor the code for more reliable performance. Specifically the script would crash dt when attempting to apply a style on import and sometimes just fail to run. I have refactored this in a manor that should make it more logical for future users to add/maintain as applicable. 

Refactored script has only been tested on Windows 10 w/ dt2.6. Hopefullt all cross-platform aspects have been maintained.
2019-03-03 15:48:08 -05:00
supertobi
49c4406bb4 Update video_mencoder.lua
fix linebrakes
2019-03-03 17:41:04 +01:00
supertobi
fedf2229a7 Merge pull request #176 from darktable-org/supertobi-patch-OpenInExplorer
OpenInExplorer
2019-03-01 23:32:53 +01:00
supertobi
2e5e2e9f0d OpenInExplorer added Nautilus support 2019-03-01 23:30:58 +01:00
supertobi
fa25ec4a26 OpenInExplorer Tap to Space 2019-03-01 23:27:26 +01:00
supertobi
0ef9f5fc6c kml_export df.mkdir(df.sanitize_filename())
df.mkdir(df.sanitize_filename(xportDirectory..PS..imageFoldername))
2019-03-01 14:22:35 +01:00
supertobi
3df9040cfb Merge pull request #175 from BzKevin/patch-2
gettext incorrectly pointed to "gimp"
2019-02-28 22:21:36 +01:00
BzKevin
bd3a74954c gettext incorrectly pointed to "gimp" 2019-02-28 15:08:57 -05:00
supertobi
69b9a1fa5c Merge pull request #173 from darktable-org/supertobi-patch-video_ffmpeg
video_ffmpeg add df.sanitize_filename()
2019-02-28 13:23:30 +01:00
supertobi
287d9e94cf video_ffmpeg
I forgot the argument
2019-02-28 13:22:10 +01:00
supertobi
24862a26f2 viedeo_ffmpeg df.sanitize_filename()
viedeo_ffmpeg df.sanitize_filename() for mkdir and rmdir
2019-02-28 11:41:56 +01:00
wpferguson
a319428489 Merge pull request #174 from wpferguson/fix_mkdir_rmdir_sanitize
make rmdir and mkdir path handling consistent with other dtutils.file routines
2019-02-27 13:31:47 -05:00
Bill Ferguson
11881b3b73 removed sanitize_filename wrapper from path argument 2019-02-27 13:29:29 -05:00
supertobi
5a6ee174af kml_export sanitize_filename()
dsys.launch_default_app(df.sanitize_filename(path))
2019-02-27 10:05:59 +01:00
supertobi
9183beeed4 video_ffmpeg add df.sanitize_filename()
I'm not 100% sure, but I think we need the df.sanitize_filename() here.
2019-02-27 09:57:36 +01:00
wpferguson
e5537fdff0 Merge pull request #162 from BzKevin/BzKevin-CollectHelper
Add CollectHelper
2019-02-26 17:02:13 -05:00
BzKevin
9e0490d371 Add gettext, check for color label
Add translate-ability. Also adds a check that the selected image has an active color label and displays a message if it does not
2019-02-26 14:30:11 -05:00
wpferguson
2b2a09e36d Merge pull request #172 from wpferguson/fix_open_default_app
fix lib/dtutils/system/open_default_app()
2019-02-26 12:06:21 -05:00
Bill Ferguson
5ff36fe2ec Added a space after the commands and removed quotes in the command invocation. 2019-02-26 12:03:04 -05:00
supertobi
9440085169 kml_export: use more utility functions
kml_export: use more utility functions as suggested by @wpferguson
2019-02-26 14:02:16 +01:00
supertobi
5c24077a30 kml_export
the command "convert" is deprecated and replaced by "magick"
2019-02-25 17:06:34 +01:00
supertobi
68a8eabbbb kml_export added preference for path to convert
kml_export added preference for path to convert as windows programs are not by default in the PATH
2019-02-25 16:31:22 +01:00
supertobi
a038333cce make kml_export multi OS
replaced mkdir, xdg-open, xdg-user-dir
2019-02-25 10:37:16 +01:00
supertobi
a5304fe74c First step to make kml_export.lua windows friendly
Replaced "/" with PS like in other scripts
2019-02-25 09:48:50 +01:00
wpferguson
aaf564b35c Merge pull request #168 from wpferguson/compatibility_updates
Compatibility updates
2019-02-24 17:28:56 -05:00
Bill Ferguson
de087f743f Fixed compatibility issues. Added new version check to scripts. Renamed lib/dtutils/file/rm() to lib/dtutils/file/rmdir since removing a file in windows with rmdir /q/s results in an error. Fixed os.tmpname() issued in enfuse. Under windows os.tmpname() doesn't put the file in a temporary directory, so fixed it to use darktable.configuration.tmp_dir. Cleaned up documentation in lib/dtutils/system.lua. 2019-02-24 15:19:37 -05:00
supertobi
21a8e441d2 Merge pull request #167 from wpferguson/deprecate_video_mencoder
deprecate video_mencoder
2019-02-22 09:42:06 +01:00
Bill Ferguson
32fc4556b1 Merge remote-tracking branch 'upstream/master' 2019-02-21 23:51:20 -05:00
wpferguson
238856ed89 Merge pull request #153 from cigno5/master
enfuse: convert raws to tiff automatically
2019-02-21 23:50:40 -05:00
Bill Ferguson
8f6a236eb6 Updated files 2019-02-21 23:49:52 -05:00
wpferguson
d9ce826b9d Merge pull request #157 from theres/dm_timelapse
Timelapse plugin based on ffmpeg
2019-02-21 11:57:03 -05:00
Bill Ferguson
76cc1d47f6 Added messages to video_mencoder.lua to notify users of it's deprecation and pending removal. 2019-02-21 11:53:56 -05:00
wpferguson
a3d0983d33 Merge pull request #166 from wpferguson/check_min_api_version
check_min_api_version
2019-02-20 11:50:36 -05:00
Bill Ferguson
245a7b59c4 Incorporated supertobi's comment about spacing. 2019-02-20 11:48:37 -05:00
Bill Ferguson
8b714edb67 Changed check_min_api_version() to print error messages and throw an error if the minimum api is not satisfied. Updated the scripts to reflect the changed check_min_api_version(). 2019-02-20 00:22:05 -05:00
Dominik Markiewicz
2a3d532881 video ffmpeg: fix default macos video location; replace missing path separator by const 2019-02-19 11:36:42 +01:00
Dominik Markiewicz
5555ee278e file lib: mkdir - handle existing directory case on Windows 2019-02-19 11:35:02 +01:00
Bill Ferguson
451c7fb956 Added check_min_api_function to dtutils.lua to check the minimum version of the lua API required for a script to run. This is intended to replace the API check done with darktable.configuration.check_version(), which requires changing each time the API version changes. check_min_api_version should work for all API version changes unless backward compatibility is broken. All the scripts using darktable.configuration.check_version() were changed to use check_min_api_version(). 2019-02-18 19:46:11 -05:00
wpferguson
391b3b9e4f Merge pull request #165 from BzKevin/patch-1
Update OpenInExplorer
2019-02-16 20:28:34 -05:00
BzKevin
130a8edcef Cancel script if not windows
added return to cancel installation of the script if not windows, after displaying a message to user
2019-02-15 20:23:07 -05:00
BzKevin
5fd3a56a31 Remove extraneous widgets in preferences
I now understand how preferences work better and have removed the extraneous widgets which were doing nothing
2019-02-15 14:24:51 -05:00
BzKevin
b682284a2a Update with 'local' variable designation
Updated variable definitions with 'local' designation. Doing so slightly broke the script so I modified the function calls a tad to pass variables in and out as needed for "CollectOnAll" function to work.
2019-02-15 14:08:04 -05:00
BzKevin
d1676ac2e0 Update OpenInExplorer
Added:
1) Check if no image selected or more than 15 images selected
2) Popup error message if not a windows OS detected
3) Fixed a typo
2019-02-15 13:40:03 -05:00
BzKevin
2cb3397d75 Update HDRMerge
Now only checks for binary proper install once the functionality of the program is actually utilized. This avoids to many external command calls at dt startup just to check if certain things are installed.
2019-02-15 13:22:49 -05:00
supertobi
936f4fe254 Merge pull request #163 from BzKevin/BzKevin-OpenInExplorer
Add OpenInExplorer
2019-02-14 09:45:59 +01:00
BzKevin
f611213662 Update OpenInExplorer
instead of being it's own lib this is now added to the selected image's module and the button text has been updated to match dt's style
2019-02-13 13:46:22 -05:00
BzKevin
ff0352cfe6 Updated version of Collect Helper
Reworded the button text to better match with the rest of darktable's buttons
2019-02-13 13:42:54 -05:00
BzKevin
4d6dfea3a5 Add CollectHelper 2019-02-13 12:23:34 -05:00
BzKevin
be386e1172 Add OpenInExplorer 2019-02-13 12:22:42 -05:00
BzKevin
2a2fbd18cd Add HDRMerge 2019-02-13 12:21:57 -05:00
theres
56159e91ce video_ffmpg: fix executable path widget for ffmpeg; try to get default location for files
- logic for craeting vidget for specifing ffmpeg location was invalid
- now default export location should be set to default system
Video/Movie directory
2019-02-04 08:41:54 +01:00
theres
3675f4d7cd video ffmpeg - post review fixes
- rename OS_PATH_SEPARATOR -> PS and use in all needed places
- change function name from `open_file` to `launch_default_app`
- move `mkdir`, `rm` and `launch_default_app` helper functions to libs
- add executable_path_widget to export module if needed, not to the
preferences
- add show_status
- remove support_format
- change function name `format` to `format_string`, add comments
- replace apostrophes by quote marks
2019-01-27 22:43:47 +01:00
theres
3a129d3c4f Timelapse plugin based on ffmpeg 2019-01-21 01:21:56 +01:00
wpferguson
cc4e7197ba Merge pull request #156 from wpferguson/readme_update
Update README.md
2019-01-14 23:56:00 -05:00
Bill Ferguson
fc5677e7b5 Added enfusedAdvanced and gen_i18n_mo descriptions 2019-01-14 23:53:26 -05:00
wpferguson
19542c1640 Merge pull request #148 from wpferguson/translation_tool
Translation tool
2019-01-14 23:33:52 -05:00
Bill Ferguson
68b5acc154 Updated comments to reflect name change 2019-01-12 22:27:06 -05:00
Bill Ferguson
5c2c9e2a8f Changed name to gen_18n_mo to further clarify what the program does. 2019-01-12 22:21:48 -05:00
wpferguson
0376e11544 Merge pull request #130 from BzKevin/master
Add enfuseAdvanced
2019-01-12 22:05:12 -05:00
supertobi
6d910ffb60 Merge pull request #154 from Hofer-Julian/patch-1
Typo
2019-01-11 15:17:25 +01:00
luca
32f6310947 Used DT internal conversion (in place of darktable-cli os call)
Now a TIFF exporter is instanced at the beginning and used for all
selected raws

No system call to darktable-cli anymore
2019-01-08 21:15:18 +01:00
luca
f464a52ea4 dt.print made properly with string.format
All print call have been replaced with string.format (when needed) and
call to dt.gettext, in a future opening to translation
2019-01-08 20:46:32 +01:00
Hofer-Julian
e3d755fb9a Typo 2019-01-07 13:10:06 +01:00
luca
e05e05d866 enfuse: convert raws to tiff automatically
it uses darktable-cli (if installed) to convert to a temporary file the
raw files that enfuse is not able to process.
2019-01-06 21:42:49 +01:00
wpferguson
b7ff1cd579 Merge pull request #151 from wpferguson/fix_script_manager_activate_script
Fix script manager script activation bug
2018-11-21 21:31:34 -05:00
Bill Ferguson
0550f4e28a Fixed bug where the wrong script could be activated, because the script to activate was found by string matching. Replaced string match with value of category selector and script name to avoid any ambiguity. 2018-11-21 21:24:28 -05:00
wpferguson
3456dc90d8 Merge pull request #150 from wpferguson/hugin_windows_fix
Hugin windows fix
2018-11-19 16:28:27 -05:00
Bill Ferguson
8e006cf853 Changed windows path quoting to double quotes instead of single. Changed dt.control.execute to dtsys.external_command to handle the windows quoted arguments. 2018-11-19 15:56:24 -05:00
supertobi
0e36af38d8 Merge pull request #146 from simon04/i18n-update
Update German translation of hugin and kml_export
2018-11-07 09:52:36 +01:00
supertobi
e10f3e2285 Merge pull request #149 from schwerdf/master
'LabelsToTags' is standalone.
2018-11-03 10:48:26 +01:00
August Schwerdfeger
31f1614679 'LabelsToTags' is standalone. 2018-11-02 20:47:09 -05:00
Bill Ferguson
80e7eca064 Added tool to compile source translation files and place the results in the correct locale directory 2018-09-26 23:59:42 -04:00
BzKevin
adb85e349a Update enfuseAdvanced.lua
Fixing accidental "invalid escape" at line 133
2018-09-18 11:54:24 -04:00
BzKevin
52a241c1ec Delete enfuse_pro_2
enfuse_pro_2 is now enfuseAdvanced
2018-09-18 10:55:23 -04:00
BzKevin
d092016729 Add enfuseAdvanced
enfuseAdvanced replaces what was previously known as enfuse_pro_2. This incorporates changes requested by wpferguson on previous versions.
2018-09-18 10:54:16 -04:00
Simon Legner
2d01f801bc Update German translation of hugin and kml_export 2018-09-18 10:25:34 +02:00
wpferguson
6e15373ce9 Merge pull request #145 from wpferguson/rename_tags_speed_up
rename-tags speed up
2018-09-13 10:55:04 -04:00
Bill Ferguson
e95ed5ad2b Found a way to access only the images tagged with the old tag instead of searching through all the images in the database. This results in a huge decrease in execution speed, depending on the size of the database. 2018-09-12 16:41:42 -04:00
wpferguson
dc9324f322 Merge pull request #144 from wpferguson/script_manager_dash_bug
script manager dash bug
2018-08-26 00:44:03 -04:00
Bill Ferguson
e7adf8cfb2 string.match on a filename with a - resulted in a failed match. Added a check for the dash and escaped it. 2018-08-26 00:39:58 -04:00
wpferguson
5f7c49ca85 Merge pull request #143 from wpferguson/script_manager_win_bug
fixed buy on windows where preferences were not getting saved
2018-08-23 13:10:58 -04:00
Bill Ferguson
f724335b91 fixed buy on windows where preferences wee not getting saved 2018-08-23 12:50:50 -04:00
wpferguson
3a5dfbc842 Merge pull request #135 from wpferguson/image_stack
Image Stacking
2018-08-23 11:45:16 -04:00
Bill Ferguson
380a1b66e6 Added path separator constant and used it where necessary. Added comment to attribute copy 2018-08-22 19:33:11 -04:00
wpferguson
c6cccf2d93 Merge pull request #141 from wpferguson/revert_script_manager_and
reverted change from nested if statements to a multiple and statement
2018-08-20 23:50:36 -04:00
Bill Ferguson
0858a19138 reverted change from nested if statements to a multiple and statement because it didn't work 2018-08-20 23:47:30 -04:00
wpferguson
f42199aec0 Merge pull request #140 from wpferguson/script_manager_typo
fixed typo in preferences
2018-08-20 23:28:04 -04:00
Bill Ferguson
8d746427a6 fixed typo in preferences 2018-08-20 23:24:30 -04:00
wpferguson
3c9b2b90d4 Merge pull request #139 from wpferguson/script_manager
darktable script management interface
2018-08-20 21:29:32 -04:00
Bill Ferguson
abc959a814 enabled link to lua-scripts version 2018-08-20 21:28:54 -04:00
wpferguson
c71c289547 Merge pull request #138 from wpferguson/executable_manager
Manage external executables used by the lua scripts
2018-08-20 21:22:47 -04:00
Bill Ferguson
02bb942abd Cleaned up the code and incorporated Tobias Jakob's comments 2018-08-20 21:12:07 -04:00
Bill Ferguson
a37c33ec36 Fixed enable preference not saved 2018-08-19 12:30:45 -04:00
Bill Ferguson
0e4926426f Added error feedback when script doesn't load 2018-08-19 01:11:54 -04:00
Bill Ferguson
a309db8739 Created tools/script_manager.lua to provide an interface from darktable for managing the lua scripts. Updated README.md to reflect the added script. 2018-08-18 15:04:43 -04:00
Bill Ferguson
1a998581ef Created tools/executable_manager.lua for managing external executables used by the lua scripts. Updated the README to reflect the new script. 2018-08-17 19:30:43 -04:00
wpferguson
f49b951331 Merge pull request #137 from wpferguson/enfuse_version_aware
Alter enfuse arguments based on version
2018-08-15 11:25:05 -04:00
Bill Ferguson
26e16ffeda Added a version check to modify the arguments based on the version of enfuse. Enfuse 4.2 no longer accepts the exposure-mu argument. The closest function is exposure-optimum. For versions of enfuse less than 4.2 exposure-mu is used. For versions of enfuse greater than 4.2, exposure-optimum is used. 2018-08-15 00:30:18 -04:00
Bill Ferguson
9e4a72e098 Creeated image_stack.lua based on Pat David's blog entry, https://patdavid.net/2013/05/noise-removal-in-photos-with-median_6.html. Updated README.md to reflect the new script. 2018-08-08 22:41:25 -04:00
wpferguson
a7d053648a Merge pull request #134 from wpferguson/multi_os_example
Multiple OS Example
2018-08-08 21:24:23 -04:00
Bill Ferguson
61a55f71f5 Created a cross platform script that calls an external executable. It also performs a service that some users might find useful, extracting the embedded jpeg image from a raw file, then importing and grouping it with the raw. Updated README.md to reflect the new file 2018-08-07 22:08:09 -04:00
wpferguson
9979d4abda Merge pull request #132 from wpferguson/windows_work_arounds
Cross platform external command capability
2018-08-07 11:02:04 -04:00
Bill Ferguson
5a2af47eda Removed unneeded require for yield, since it now in lib/dtutils.system 2018-08-07 11:00:16 -04:00
wpferguson
eab6b99155 Merge pull request #133 from sjjh/patch-1
corrected "dt" to "darktable" in line 63 of cr2hdr
2018-08-03 22:28:31 -04:00
sjjh
4acba4d48a corrected "dt" to "darktable" in line 63 of cr2hdr
object "darktable" is defined in line 35; usage of "dt" was probably a typo or left over from a refactoring.
2018-08-04 01:43:38 +02:00
Bill Ferguson
8b5a43e435 Added require statement for official/yield.lua so that darktable.control.execute() would have all of it's requirements met without the user having to remember to add it somewhere else 2018-07-12 13:23:33 -04:00
Bill Ferguson
26fce03d0f Removed leftover unused code 2018-07-11 11:49:01 -04:00
Bill Ferguson
1a22bfbcef Added system library to handle running external commands on different operating systems in a transparent manner. Added file and path quoting that is cross platform compatible. Changed gimp.lua to use the new utilities. 2018-07-11 11:32:48 -04:00
BzKevin
050783fc8b enfuse_pro_2 modified per wpferguson
1) Moved check_if_bin installed to run only once, when script first loaded (rather than every time it is called)
2) Changed executable path preferences per Bill's suggestions 
   dt.preferences.register("executable_paths", "align_image_stack",
   dt.preferences.register("executable_paths", "enfuse",
   dt.preferences.register("executable_paths", "exiftool",
3) Replaced custom block of code with df.create_unique_filename
4) Quoted paths in run commands for linux and MacOS (I still experience failures if I quote anything in the run command aside from the .exe path on windows)
5) Changed LUA API compatability check for 5.0.0 only

Tested on Windows and MacOS
2018-07-07 11:40:01 -04:00
wpferguson
d96d8fb4fc Merge pull request #131 from wpferguson/macos_command_fixes
MacOS Command Fixes
2018-07-07 11:08:23 -04:00
Bill Ferguson
4b32c34ebd Removed 'open -W -a' for mac commands from check_if_bin_exists since it only appears to be needed for GUIs. Added 'open -W -a' for macs using GIMP in gimp.lua 2018-07-06 23:04:44 -04:00
BzKevin
927bf12243 Add enfuse_pro_2
Enhanced version of Holger Klemm's enfuse_pro that adds features and X-platform compatibility.
2018-07-01 19:54:15 -04:00
BzKevin
a867791165 Delete enfuse_pro_2.lua 2018-07-01 19:52:47 -04:00
BzKevin
67232094c9 Delete OpenInPS.lua 2018-07-01 19:51:39 -04:00
BzKevin
b5e79abf08 Delete HDRMerge.lua 2018-07-01 19:51:26 -04:00
BzKevin
84c0fd7414 Add 3 new Pulg-Ins
HDRMerge: Add easy access to HDRMerge from within dt
enfuse_pro_2: Updated version of Holger Klemm's popular enfuse plug-in. Adds some features and cross-platform compatability
OpenInPS: add ability to open or export selected file with Photoshop
2018-06-30 14:58:50 -04:00
supertobi
850a7d8676 Merge pull request #126 from jbvoelker/gpx_tracks
gpx_export.lua: Create a separate <trk> for each source folder
2018-06-24 22:37:12 +02:00
Jannis_V
ded24e8037 gpx_export.lua: Create a separate <trk> for each source folder 2018-06-24 18:47:33 +02:00
Tobias Ellinghaus
71fbf5705c Use print_log in import_filter_manager 2018-05-23 10:23:48 +02:00
Tobias Ellinghaus
87b78717f3 Fix formatting of README.md 2018-05-23 10:22:37 +02:00
wpferguson
1cd9a34761 Merge pull request #123 from wpferguson/README.md_snap_update
Add snap information to README.md
2018-05-22 12:13:25 -04:00
Bill Ferguson
b9072cccba Updated README.md to reflect change to snap packaging enabling lua and included instructions on how to use the scripts with the snap 2018-05-22 12:11:55 -04:00
Tobias Ellinghaus
18fb3a68fd Re-fix README.md 2018-05-21 11:23:19 +02:00
wpferguson
c0b89e51d7 Merge pull request #122 from wpferguson/update_README.md_for_snaps
Updated README.md to include snap, flatpak, and appimage information
2018-05-20 15:10:32 -04:00
wpferguson
f2ac2595f9 Merge branch 'master' into update_README.md_for_snaps 2018-05-20 15:09:35 -04:00
Bill Ferguson
b241007d4f Updated README.md to include snap, flatpak, and appimage information 2018-05-20 15:02:37 -04:00
Tobias Ellinghaus
2b1af40bd1 Some fixes for the README 2018-05-20 16:11:02 +02:00
wpferguson
66f85a1f35 Merge pull request #121 from wpferguson/update_README.md
Update README.md
2018-05-19 23:17:15 -04:00
Bill Ferguson
22633d6130 Updated README.md to better present what the lua-scripts are and how to use them. Provided tables of scripts, whether they are standalone, and cross platform compatibility. Provided instructions for install on Linux, MacOS, and Windows as well as instructions for updating the scripts. 2018-05-18 22:28:33 -04:00
wpferguson
8ed4d838ee Merge pull request #120 from wpferguson/hugin_space_problem
hugin fails on images with spaces in the filenames
2018-05-16 11:15:37 -04:00
wpferguson
9175916771 Merge pull request #119 from wpferguson/fix_print_log_plaeceholder
Replace placeholder for dt.print_log with dt.print_log
2018-05-15 11:38:56 -04:00
Bill Ferguson
7acf2b9cac Fixed bug where log_level() always returned log.success instead of the correct log level 2018-05-15 01:22:09 -04:00
Bill Ferguson
965d8791d8 single quoted filename arguments to the hugin commands for the supplied image files. Single quoted any filenames derived from the supplied image files in case they contained spaces. Added logging at various levels so that the next time I have to work on this I can figure it out quicker. Normal logging level is log.error which only prints error information. Changing log level to log.info provides much more insight about what is occurring. Fixed gui not returning generated file to film strip. 2018-05-15 01:05:34 -04:00
Bill Ferguson
f961e6aa6b Replaced dt_print_log = dt.print_error with dt.print_log now that the function exists 2018-05-14 20:30:09 -04:00
wpferguson
b8182cfde5 Merge pull request #117 from marco44/fix_autostyle
Fix autostyle
2018-05-09 14:48:55 -04:00
marco44
78f0e367dc Change logging 2018-05-09 17:54:26 +02:00
marco44
aca0224263 Fix autostyle, checkIfBinExists doesn't seem to exist in latest releases anymore 2018-05-05 16:37:27 +02:00
supertobi
c74636e2c0 Fixed typo in README 2018-04-09 16:54:12 +02:00
supertobi
f113bc5b8a Added links to the README
Added links to the documentation.
2018-04-09 13:23:11 +02:00
supertobi
befae8b191 Improved README.md
I've added some information anarcat suggested on pixls.us:
https://discuss.pixls.us/t/don-t-forget-the-lua-scripts/1598/8
2018-04-09 11:59:52 +02:00
supertobi
1767e8f074 Merge pull request #111 from achienbsi/hugin_plugin_improvements
hugin plugin improvements
2018-03-19 22:32:19 +01:00
Andy Chien
44081b772f fixed version requirement due to updates to darktable.configuration.running_os 2018-03-19 06:51:30 -07:00
supertobi
3051eddd2e Merge pull request #115 from wpferguson/dtutils_file_fix
check_if_[bin|file]_exist fixes
2018-03-17 10:24:24 +01:00
Bill Ferguson
5266aa7547 Used a less aggressive quoting strategy that works with check_if_bin_exists, check_if_file_exists, and io.popen(). 2018-03-16 12:16:45 -04:00
supertobi
7e57f61947 Merge pull request #114 from wpferguson/check_if_bin_exists_fixes
Fix macOS returned path  and add wine awareness to check_if_bin_exists()
2018-03-11 16:49:53 +01:00
Bill Ferguson
1c587d3726 Changed wine test to check for .exe"$ or .EXE"$ because of quoting string paths 2018-03-10 13:45:04 -05:00
Bill Ferguson
b4ea97ce37 Fixed accidently wiped out check for macos 2018-03-10 13:38:17 -05:00
Bill Ferguson
83c1a5789a Changed wine check to operating system isn't windows 2018-03-10 13:07:19 -05:00
Bill Ferguson
841026661a Removed debugging statement 2018-03-10 12:56:18 -05:00
Bill Ferguson
692441e1d5 Added wine awareness for unix, linux, and macos. Added open -a -W to the macos path so that the path returned by the finder will work correctly. 2018-03-10 12:52:22 -05:00
Andy Chien
46ba555a20 * updated to work with new dtutils_file
* added checkbox to allow user to launch hugin in gui mode
* changed the logic so hugin will write temp files to tmp directory and then move the final result to the directory of original source files. this improves speed when original source files are on network mapped drive
* added some additional error messages
2018-03-05 09:48:30 -08:00
supertobi
de18432a7d Merge pull request #112 from wpferguson/os_path_fix
Windows and macOS external executable compatibility
2018-03-05 14:13:29 +01:00
supertobi
497b6cb59c Merge pull request #113 from wpferguson/check_os
Check operating system compatability
2018-03-04 22:27:08 +01:00
Bill Ferguson
c073ecf0dd Added check_os to lib/dtutils.lua so that scripts can check compatibility with the current operating system 2018-03-03 20:22:47 -05:00
Bill Ferguson
2ea4db8e38 Added windows and macOS external executable compatibility. Added functions set_executable_path_preference(), get_executable_path_preference(), and executable_path_widget() to lib/dtutils/file.lua to get, set and retrieve the location of executables on windows and macOS systems. Modified check_if_bin_exists to use the path preferences so executables can be found on windows and macOS systems as well as linux. Modified contrib/gimp.lua to use the new functions, thus making it usable on windows and macOS too. 2018-03-02 14:07:29 -05:00
Andy Chien
fe942100a4 hugin plugin improvements
* added preference requiring user to specify the paths for hugin, hugin_executor and pto_gen needed for this plguin
* added preference to allow user to choose to launch hugin in GUI mode
* minor fix to the way files are handled; due to some versions of hugin_executor not writing out to the path specified in --prefix, assume we are working in the tmp directory and then move the file afterwards
2018-02-26 07:29:41 -08:00
supertobi
79eb1428b0 Merge pull request #108 from achienbsi/file_fixes_for_windows
fixed a couple dtutils_file functions for windows platform
2018-02-26 15:57:11 +01:00
Andy Chien
467365dcbb platform dependent implementation of check_if_bin_exists and check_if_file_exists 2018-02-23 18:14:24 -08:00
supertobi
33a323fe85 Merge pull request #109 from achienbsi/hugin_improvements
improved a few aspects of the hugin script
2018-02-23 09:41:32 +01:00
Andy Chien
4ad9e77a7e improved a few aspects of the hugin script:
* handle stiching all from hugin_executor without the gui
* automatically create the panorama file name based on first selected file plus _pano
* automatically detects if output file exists, if so use an unique name
* delete temporarily exported file
2018-02-22 16:07:28 -08:00
Andy Chien
5d57347730 fixed a couple dtutils_file functions for windows platform 2018-02-22 16:01:54 -08:00
Tobias Ellinghaus
54572de8d9 Fix LabelsToTags for Lua 5.3
Fixes #106
2018-01-23 11:28:45 +01:00
supertobi
0e57a0804a Merge pull request #105 from elman22/master
Updated KML/KMZ export script
2018-01-20 14:27:04 +01:00
Tobias Ellinghaus
95077f639b New script to delete unused tags on startup 2018-01-17 10:42:44 +01:00
Tobias Ellinghaus
493dcc7ef7 Don't delete tags while iterating 2018-01-17 10:41:55 +01:00
Elman
f86b16735b Formatted code to follow project settings 2018-01-15 22:54:22 +01:00
Elman
0c1de20a1a Code formatting 2018-01-15 17:56:02 +01:00
Elman
e9d0f706ed Correct handling of duplicates 2018-01-15 17:41:26 +01:00
Elman
d297718358 Path is now sorted by exif_datetime_taken 2018-01-15 17:40:50 +01:00
Elman
b406b36cda Added all Slovak and Czech accents 2018-01-15 17:31:19 +01:00
supertobi
570732a9b2 Merge pull request #104 from schwerdf/master
New script: "Labels to Tags"
2018-01-08 11:33:25 +01:00
August Schwerdfeger
92b5507e0e Added 'Labels to Tags' script. 2018-01-07 00:37:41 -06:00
supertobi
73897b8ee7 Merge pull request #103 from jbvoelker/gpx-export
GPX export script
2018-01-03 22:27:46 +01:00
Jannis Völker
2d9cdc5a24 GPX export script which generates a GPX track from all selected images containing geo information 2018-01-03 20:51:35 +01:00
supertobi
234e5bc5fc Merge pull request #102 from wpferguson/api_5_0_0
Updated remaining scripts to api 5.0.0
2018-01-03 10:08:22 +01:00
Bill Ferguson
664496b430 Updated to api 5.0.0 and tested. include_all.lua changed to load official/yield and ignore the lib and tools directories. 2018-01-02 19:51:14 -05:00
supertobi
67d92bd070 Update selection_to_pdf.lua
added {5,0,0} to the check_version
2018-01-02 11:37:10 +01:00
supertobi
d6e53cc005 Update save_selection.lua
added {5,0,0} to the check_version
2018-01-02 11:35:57 +01:00
supertobi
2c77712ab4 Update image_path_in_ui.lua
added {5,0,0} to the check_version
2018-01-02 11:34:41 +01:00
supertobi
10c1eff1b6 Update generate_image_txt.lua
added {5,0,0} to the check_version
2018-01-02 11:33:40 +01:00
supertobi
4c26465dde Update enfuse.lua
added {5,0,0} to the check_version
2018-01-02 11:30:08 +01:00
supertobi
d29e5f49f4 Update delete_long_tags.lua
added {5,0,0} to the check_version
2018-01-02 11:28:44 +01:00
supertobi
0191558d26 Update copy_paste_metadata.lua
added {5,0,0} to the check_version
2018-01-02 11:24:42 +01:00
supertobi
d3692bcc09 check_for_updates.lua: added {5,0,0} to the check_version 2018-01-02 11:22:43 +01:00
supertobi
befa83bee8 added {5,0,0} to the check_version 2018-01-02 11:10:56 +01:00
supertobi
b4347e5701 added {5,0,0} to the check_version 2018-01-02 11:10:31 +01:00
supertobi
c4ed6c2b97 added {5,0,0} to the check_version 2018-01-02 11:09:42 +01:00
supertobi
6b5f650aac added {5,0,0} to the check_version 2018-01-02 11:08:49 +01:00
supertobi
63b38c1f53 added {5,0,0} to the check_version 2018-01-02 11:08:07 +01:00
supertobi
33b6f0d72e added {5,0,0} to the check_version 2018-01-02 11:07:20 +01:00
supertobi
a6f8070f65 Delete LICENSE 2017-12-12 16:36:38 +01:00
supertobi
29aaaf55cf Merge pull request #101 from darktable-org/add-license-1
Create LICENSE
2017-12-12 15:19:01 +01:00
supertobi
b47da137c1 Create LICENSE 2017-12-12 15:18:40 +01:00
GHswitt
8e6552ee9c Added ignore tag and unknown tag, verbose output 2017-12-11 17:43:53 +01:00
GHswitt
024e016df2 Storage plugin for face_recognition 2017-12-11 14:27:36 +01:00
supertobi
97dd8a1257 Merge pull request #80 from GHswitt/master
Add new GUI entry in lighttable for renaming tags
2017-11-15 16:11:33 +01:00
supertobi
83fc339c4b Merge pull request #99 from Sitwon/master
Support for importing the in-camera ratings from Fujifilm cameras
2017-10-14 21:20:16 +02:00
Ben Mendis
693c535428 Improving with feedback from @supertobi 2017-10-14 11:24:14 -04:00
supertobi
d74f2eba92 Create printExamples.lua 2017-10-11 11:29:48 +02:00
supertobi
d0ec941009 check if exiftool exists in autostyle.lua 2017-10-11 09:32:29 +02:00
Ben Mendis
178528508b Support for importing the in-camera ratings from Fujifilm cameras 2017-10-10 21:10:00 -04:00
supertobi
20a31b39e0 Added section_label moduleExample.lua 2017-10-10 20:22:39 +02:00
supertobi
fc252d5e7e New example for the running_os command 2017-10-10 17:27:09 +02:00
supertobi
4090266989 Removed not needed code in yield.lua 2017-10-10 17:22:24 +02:00
supertobi
19b7d29483 added {5,0,0} to the check_version 2017-09-26 15:21:20 +02:00
supertobi
af4e3884bb check result from io.popen("xdg-user-dir VIDEOS") 2017-09-26 15:02:50 +02:00
supertobi
d3742bb410 added {5,0,0} to the check_version 2017-09-26 14:55:12 +02:00
supertobi
75cb1a5fdd added {5,0,0} to the check_version 2017-09-26 14:55:07 +02:00
supertobi
8436c878e8 added {5,0,0} to the check_version 2017-09-26 14:53:54 +02:00
supertobi
63d3a34f46 added {5,0,0} to the check_version 2017-09-26 10:55:23 +02:00
supertobi
6cda97c335 check result from io.popen("xdg-user-dir DESKTOP") 2017-09-26 10:39:36 +02:00
supertobi
a40d8b8bc5 added {5,0,0} to the check_version 2017-09-26 10:34:16 +02:00
supertobi
15a492cfa3 added {5,0,0} to the check_version 2017-09-26 10:27:39 +02:00
supertobi
445e882579 check result from io.popen("xdg-user-dir DESKTOP") 2017-09-26 10:24:22 +02:00
supertobi
d4b155d0c2 Merge pull request #98 from jbvoelker/select-untagged
Add french and german .mo files for select_untagged.lua
2017-09-25 15:52:10 +02:00
Jannis_V
65f4960302 Add french and german .mo files for select_untagged.lua 2017-09-25 15:45:16 +02:00
supertobi
dc9e8d406d Update README.md
Added Windows path and the new path to hello_world.lua
2017-09-25 14:19:24 +02:00
supertobi
4d76006c2d Merge pull request #97 from jbvoelker/select-untagged
Selection of untagged images
2017-09-25 13:42:26 +02:00
Jannis Völker
731a6d0410 German and french translation for the select_untagged script 2017-09-14 16:00:48 +02:00
Jannis Völker
e4542aa451 Remove require 2017-09-14 11:52:05 +02:00
Jannis Völker
ecc4a29be4 Select all untagged images in the collection. Pictures containg only automatic tags added by darktable are also considered untagged 2017-09-13 17:26:47 +02:00
supertobi
0f0b3d6df5 Merge pull request #96 from chrisaga/patch-1
use dt.gettext after defining dt to avoid fatal error
2017-09-02 23:15:38 +02:00
chrisaga
a826c1725b use dt.gettext after defining dt to avoid fatal error 2017-09-02 14:30:52 +02:00
supertobi
4e3b461fd3 Merge pull request #95 from elman22/master
Fixed quotation marks typos
2017-08-08 09:24:07 +02:00
elman
d8a9dfafb2 Fixed quotation marks typos 2017-08-07 22:49:45 +02:00
supertobi
ede621bea7 Update geoJSON_export.lua
Removed wrong comment.
2017-07-25 15:49:50 +02:00
supertobi
2f7da8356f Update geoToolbox.lua
Label changed: select non-geo images
2017-04-29 20:46:38 +02:00
supertobi
d555cb2c94 Update geoJSON_export.lua
cleanup
2017-04-29 09:03:43 +02:00
supertobi
750d70bfff Update geoJSON_export.lua
check_version 5
2017-04-29 08:06:15 +02:00
supertobi
dcc205ec4f Update kml_export.lua
check_version 5
2017-04-29 08:05:19 +02:00
supertobi
356756286e Update gimp.lua
use "lib/dtutils.file"
2017-04-27 22:19:00 +02:00
supertobi
79511d9b42 Update geoJSON_export.lua
use "lib/dtutils.file"
2017-04-27 22:12:34 +02:00
supertobi
bff2df018b Update enfuse.lua
use "lib/dtutils.file"
2017-04-27 22:07:23 +02:00
supertobi
2562a218a0 Update kml_export.lua
use "lib/dtutils.file"
2017-04-27 22:04:15 +02:00
supertobi
d46b7e4aaf Update slideshowMusic.lua
use "lib/dtutils.file"
2017-04-27 21:55:19 +02:00
supertobi
dee7ee1dde Update hugin.lua
use "lib/dtutils.file"
2017-04-27 21:51:29 +02:00
supertobi
01fc339da5 Update video_mencoder.lua
use "lib/dtutils.file" and gettext
2017-04-27 21:46:30 +02:00
supertobi
ef46cd9b45 Update geoToolbox.lua
reverse geocodeing start
2017-04-27 20:20:29 +02:00
supertobi
ac3ccb287b Merge pull request #92 from khampf/passport-guide
ISO 19794-5/ICAO 9303 clipping guide as contrib/passport_guide.lua
2017-04-23 08:11:31 +02:00
khampf
f52292e8d1 Translatable by using gettext 2017-04-22 23:47:30 +03:00
khampf
693c9858c2 variables renamed for clarity (local functions tested without success) 2017-04-22 23:24:43 +03:00
khampf
c8d5e9d9d2 Longer version of GPL text 2017-04-22 22:06:55 +03:00
khampf
50668817db added API 5 to version check 2017-04-21 21:43:02 +03:00
khampf
91758b61c3 ISO 19794-5/ICAO 9303 clipping guide as contrib/passport_guide.lua 2017-04-21 13:51:40 +03:00
supertobi
39ae92a0fa Update geoToolbox.lua
fix open with gnome-maps
2017-04-18 22:31:49 +02:00
supertobi
95c203793c Merge pull request #91 from TurboGit/master
Ensure that a dot is used for decimal numbers.
2017-04-16 18:11:54 +02:00
Pascal Obry
c7ac974ab9 Ensure that a dot is used for decimal numbers.
This unbreak the slide show for acrobat reader.
2017-04-15 15:35:09 +02:00
supertobi
51931d2b01 Merge pull request #90 from TurboGit/master
general translation reorg.
2017-04-11 08:28:47 +02:00
Pascal Obry
d271f6d46d Add french translation for pdf_slideshow plugin. 2017-04-10 16:59:46 +02:00
Pascal Obry
efcd48f1d9 Remove trailing whitespaces. 2017-04-10 16:59:35 +02:00
Pascal Obry
76daa583b3 Move all translation into the main locale directory.
This is for consistency reason as the lib/dtutils/file.lua use this
directory. Also all translation into contrib where not usable as
not checked into this directory which was expected to be just lua.

All in all using locale directory will avoid clutter the main
directory with all possible languages and fixes the current scripts.
2017-04-10 16:56:59 +02:00
supertobi
3425a85fd0 Merge pull request #89 from TurboGit/master
Better renaming of the images to keep files in temp dir.
2017-04-09 22:10:49 +02:00
Pascal Obry
9a6c1510fd Another change to get better output.
This changeset gives better output for portrait and landscape pictures.
Now in both cases the image is properly center on the screen.
2017-04-09 18:46:21 +02:00
Pascal Obry
44c391cb5a Better renaming of the images to keep files in temp dir. 2017-04-09 11:07:29 +02:00
supertobi
6085be7589 Merge pull request #88 from TurboGit/master
Initial version of PDF Slideshow.
2017-04-09 10:35:28 +02:00
Pascal Obry
7d9bf8c729 Check for pdflatex tool.
If not present, exit.
2017-04-09 10:18:02 +02:00
Pascal Obry
1d72457dc1 Put author before title as this is what is rendered. 2017-04-09 10:17:39 +02:00
Pascal Obry
0e32d2b1b4 Rename jpeg files to use for the slideshow.
Some accents and weird characters could confuse pdflatex.
2017-04-09 10:17:00 +02:00
Pascal Obry
75165e8838 Ensure UTF-8 text from title or creator field are properly rendered. 2017-04-09 10:16:38 +02:00
Pascal Obry
d9536f1294 Initial version of PDF Slideshow.
A simple PDF slideshow generator. Each image is on a single slide and
a title bar at the bottom is added with the slide show general title,
the image author and title if defined in the metadata.

This script requires pdflatex.
2017-04-08 16:47:34 +02:00
Tobias Ellinghaus
44e4c3ddc5 enfuse: Fix typo in script description 2017-03-07 18:14:58 +01:00
supertobi
f67755f3b5 Update hugin.lua
changed comment
2017-03-07 13:19:04 +01:00
supertobi
68d768cbd3 Merge pull request #86 from TurboGit/master
minor tweak of the quicktag module
2017-02-28 17:00:49 +01:00
Pascal Obry
0be268c01f Remove trailing whitespaces. 2017-02-28 16:38:50 +01:00
Pascal Obry
1831db530d Make the label smaller to fit on screen.
As we are on the quicktag module it seems quite obvious that we are
handling quicktag. So let's just use "tag" instead of "quicktag", this
is a bit smaller and it saves spaces on the widget. This ensure that
the whole module is visible in most cases (panel size, font size...).
2017-02-28 16:36:32 +01:00
supertobi
8a32bf21d0 Merge pull request #72 from chrik5/quicktag
first version of a quicktag modul
2017-02-20 22:05:48 +01:00
chrik5
81c5bb4d52 [quicktag] update copyright 2017-02-20 18:00:59 +01:00
chrik5
93796bf4a8 [quicktag] fix German translation and remove print output 2017-02-20 17:57:53 +01:00
chrik5
4c34113f78 [quicktag] use string.format in messages for better translation 2017-02-20 15:42:48 +01:00
chrik5
e4b189a06e [quicktag] update TODO list 2017-02-20 12:11:52 +01:00
chrik5
0ebc885eb1 [quicktag] update German translation 2017-02-20 12:11:06 +01:00
chrik5
b4658ddf76 [quicktag] abbrevate tag button labels to a configurable length 2017-02-20 12:00:45 +01:00
chrik5
b38e2964ca [quicktag] add German translation 2017-02-17 16:22:13 +01:00
chrik5
5021e512b9 [quicktag] make script tranlateable 2017-02-17 16:21:23 +01:00
chrik5
6d927540c8 [quicktag] remove dead code 2017-02-17 13:40:39 +01:00
chrik5
6168767766 remove accidently added *.mo file 2017-02-17 13:36:49 +01:00
chrik5
5af200a29d [quicktag] don't allow to set empty tags 2017-02-17 10:32:06 +01:00
chrik5
8b333c1ab5 [quicktag] update and fix description 2017-02-17 10:25:06 +01:00
chrik5
d1f34ec136 [quicktag] fix and update some comments 2017-02-16 16:32:50 +01:00
chrik5
c887a0d2b5 [quicktag] remove tag list from preferences dialog, because the tags can be set from the modul now 2017-02-16 16:13:55 +01:00
Jérémy Rosen
1168ba4557 Merge remote-tracking branch 'origin/pr/82'
# Conflicts:
#	contrib/calcDistance.lua
#	contrib/geo_uri.lua
#	contrib/gps_select.lua
2017-01-24 23:22:04 +01:00
chrik5
e389420f07 quicktag - make API 5.0.0 compatible 2017-01-24 21:02:53 +01:00
chrik5
0590beb876 quicktag - add tags if they not exist in the taglist of darktable 2017-01-24 20:56:07 +01:00
supertobi
d30c0d0213 Merge pull request #83 from chrik5/API5.0.0
This scripts work with API version 5.0.0 & small fix for hugin
2017-01-24 13:52:03 +01:00
chrik5
eb774d37c6 hugin - fix error message hugin failed... 2017-01-24 12:43:30 +01:00
chrik5
cade6d5e0b hugin add API version 5.0.0 2017-01-24 12:36:42 +01:00
chrik5
19b71c5782 gimp add API version to 5.0.0 2017-01-24 12:23:30 +01:00
chrik5
5f802a8179 copy_attach_detach_tags add API version to 5.0.0 2017-01-24 12:22:37 +01:00
supertobi
99e1141446 dt.database -> dt.collection
select only images in the collection, not in the database
2017-01-12 13:27:29 +01:00
supertobi
3bd76b4fb5 added a progress bar
added a progress bar to elect_without_gps() and select_with_gps()
2017-01-12 13:16:59 +01:00
supertobi
3fb4ef34f4 past -> paste 2017-01-12 12:58:17 +01:00
supertobi
f7c2abdcd7 dt.gui.action_images -> dt.gui.selection() 2017-01-12 12:56:41 +01:00
supertobi
cc6006e226 removed "the" from "Past the GPS data" 2017-01-12 12:32:42 +01:00
supertobi
2816c2bcf5 changed label to lower case 2017-01-12 12:30:45 +01:00
supertobi
abc8061cd7 Update geoToolbox.lua
gettext translations
2017-01-09 14:00:14 +01:00
supertobi
a30c4d6be5 Update geoToolbox.lua
changed camel case to "_"
2017-01-09 13:53:23 +01:00
supertobi
0bd5b6ba5d Delete calcDistance.lua
replaced by geoToolbox.lua
2017-01-08 21:05:23 +01:00
supertobi
4ea3a500e8 Delete geo_uri.lua
replaced by geoToolbox.lua
2017-01-08 21:05:04 +01:00
supertobi
56925c1eed Delete gps_select.lua
replaced by geoToolbox.lua
2017-01-08 21:04:49 +01:00
supertobi
71e7269927 geo toolbox
this script replaces:
calcDistance.lua
geo_uri.lua 
gps_select.lua
2017-01-08 20:54:12 +01:00
Boucman
6babff1be1 Merge pull request #81 from chrik5/tagging
copy_attach_detach_tags: UI clean up. module reset empties clipboard and taglist.
2017-01-07 15:19:06 +01:00
chrik5
fec000086f copy_attach_detach_tags: add modul reset to USAGE section 2016-12-28 15:05:57 +01:00
chrik5
bddf4cedb4 copy_attach_detach_tags: UI clean up. Added module reset which empties clipboard and copied taglist. 2016-12-28 14:58:44 +01:00
chrik5
91c62e208b quicktag: rework of modul layout and behaviour
* long tags are stil a problem
* maybe two colums of tag buttons saves high, if someone wants to have many tags
* option to create non existing tags will be nice I guess
2016-12-28 12:32:12 +01:00
chrik5
1e0424a83f quicktag module: place entry field and buttons side-by-side, button labels are now the quicktags 2016-12-27 15:28:43 +01:00
GHswitt
c74d29e020 Add new GUI entry in lighttable for renaming tags
Rename tag: A new tag is added to all images containing old tag. After that, the old tag is removed from all images (and removed from tags). The GUI entry has two entries: Old tag, New tag and a Go button.
2016-12-14 01:08:10 +01:00
supertobi
eadfff9cd0 Merge pull request #78 from wpferguson/dt-2.2-compatibility
Darktable 2.2 compatibility
2016-11-08 19:07:41 +01:00
Bill Ferguson
93e30a8542 Verified API 4.0.0 compatibility and made the necessary changes 2016-11-07 22:53:52 -05:00
chrik5
ae8727ad96 Mark module quicktag as add API 3 compatible 2016-10-09 14:44:44 +02:00
chrik5
de823f0430 quicktag module make tag entries dynamic 2016-10-06 11:02:00 +02:00
chrik5
c6ced0b1ea first version of a quicktag modul 2016-10-05 16:33:13 +02:00
84 changed files with 8826 additions and 1184 deletions

209
README.md
View File

@@ -1,10 +1,209 @@
lua-scripts
===========
# Lua scripts
The Lua scripts in this repository are meant to be used together with darktable. Either copy them individually to `~/.config/darktable/lua` (you might have to create that folder) or just copy/symlink the whole repository there. That allows to update all your scripts with a simple call to `git pull`.
## Description
To enable one of the scripts you have to add a line like `require "official/hello_world"` to your `~/.config/darktable/luarc` file which will enable the example script in `official/hello_world.lua` (note the lack of the `.lua` suffix).
darktable can be customized and extended using the Lua programming language. This repository contains the collected
efforts of the darktable developers, maintainers, contributors and community. The following sections list the scripts
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.
### Official Scripts
These scripts are written primarily by the darktable developers and maintained by the authors and/or repository maintainers. They are located in the official/ directory.
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
check_for_updates|Yes|LMW|Check for updates to darktable
copy_paste_metadata|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images
delete_long_tags|Yes|LMW|Delete all tags longer than a specified length
delete_unused_tags|Yes|LMW|Delete tags that have no associated images
enfuse|No|L|Exposure blend several images (HDR)
generate_image_txt|No|L|Generate txt sidecar files to be overlaid on zoomed images
image_path_in_ui|Yes|LMW|Plugin to display selected image path
import_filter_manager|Yes|LMW|Manager for import filters
import_filters|No|LMW|Two import filters for use with import_filter_manager
save_selection|Yes|LMW|Provide save and restore from multiple selection buffers
selection_to_pdf|No|L|Generate a PDF file from the selected images
### Contributed Scripts
These scripts are contributed by users. They are meant to have an "owner", i.e. the author, who maintains them. Over time the community has helped maintain these scripts, as well as the authors. They are located in the contrib/ directory.
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
AutoGrouper|Yes|LMW|Group images together by time
autostyle|Yes|LMW|Automatically apply styles on import
clear_GPS|Yes|LMW|Reset GPS information for selected images
CollectHelper|Yes|LMW|Add buttons to selected images module to manipulate the collection
copy_attach_detach_tags|Yes|LMW|Copy and paste tags from/to images
cr2hdr|Yes|L|Process image created with Magic Lantern Dual ISO
enfuseAdvanced|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images
face_recognition|No|LM|Identify and tag images using facial recognition
fujifilm_ratings|No|LM|Support importing Fujifilm ratings
geoJSON_export|No|L|Create a geo JSON script with thumbnails for use in ...
geoToolbox|No|LMW|A toolbox of geo functions
gimp|No|LMW|Open an image in GIMP for editing and return the result
gpx_export|No|LMW|Export a GPX track file from selected images GPS data
HDRMerge|No|LMW|Combind the selected images into an HDR DNG and return the result
hugin|No|LMW|Combine selected images into a panorama and return the result
image_stack|No|LMW|Combine a stack of images to remove noise or transient objects
kml_export|No|L|Export photos with a KML file for usage in Google Earth
LabelsToTags|Yes|LMW|Apply tags based on color labels and ratings
OpenInExplorer|No|LMW|Open the selected images in the system file manager
passport_guide|Yes|LMW|Add passport cropping guide to darkroom crop tool
pdf_slideshow|No|LM|Export images to a PDF slideshow
quicktag|Yes|LMW|Create shortcuts for quickly applying tags
rate_group|Yes|LMW|Apply or remove a star rating from grouped images
rename-tags|Yes|LMW|Change a tag name
select_untagged|Yes|LMW|Enable selection of untagged images
slideshowMusic|No|L|Play music during a slideshow
video_ffmpeg|No|LMW|Export video from darktable
video_mencoder|No|L|Export video from darktable
### Example Scripts
These scripts provide examples of how to use specific portions of the API. They run, but are meant for demonstration purposes only. They are located in the examples/ directory.
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
api_version|Yes|LMW|Print the current API version
gettextExample|Yes|LM|How to use translation
hello_world|Yes|LMW|Prints hello world when darktable starts
moduleExample|Yes|LMW|How to create a lighttable module
multi_os|No|LMW|How to create a cross platform script that calls an external executable
preferenceExamples|Yes|LMW|How to use preferences in a script
printExamples|Yes|LMW|How to use various print functions from a script
running_os|Yes|LMW|Print out the running operating system
### Tools
Tool scripts perform functions relating to the repository, such as generating documentation. They are located in the tools/ directory.
Name|Standalone|OS |Purpose
----|:--------:|:---:|-------
executable_manager|Yes|LMW|Manage the external executables used by the lua scripts
gen_i18n_mo|No|LMW|Generate compiled translation files (.mo) from source files (.po)
get_lib_manpages|No|LM|Retrieve the library documentation and output it in man page and PDF format
get_libdoc|No|LMW|Retrieve the library documentation and output it as text
script_manager|No|LMW|Manage (install, update, enable, disable) the lua scripts
### Related third-party projects
The following third-party projects are listed for information only. Think of this collection as an `awesome-darktable-lua-scripts` list. Use at your own risk!
* [trougnouf/dtMediaWiki](https://github.com/trougnouf/dtMediaWiki) – Wikimedia Commons export
* [wpferguson/extra-dt-lua-scripts](https://github.com/wpferguson/extra-dt-lua-scripts)
* [xxv/darktable-git-annex](https://github.com/xxv/darktable-git-annex) – git-annex integration
* [theres/dt_backup](https://github.com/theres/dt_backup) – automatic backup on exit
* [Sitwon/dt_fujifilm_ratings](https://github.com/Sitwon/dt_fujifilm_ratings) – Fujifilm Ratings import
* [progo/leica-q-autocrop](https://github.com/progo/leica-q-autocrop) – crop in 35 mm or 50 mm equivalent
* [BzKevin/OpenInPS-Darktable-PlugIn](https://github.com/BzKevin/OpenInPS-Darktable-PlugIn) – open in Adobe Photoshop
* [johnnyrun/darktable_lua_gimp](https://github.com/johnnyrun/darktable_lua_gimp) – GIMP export
* [arru/darktable-scripts](https://github.com/arru/darktable-scripts)
* [nbremond77/darktable](https://github.com/nbremond77/darktable/tree/master/scripts)
## Download and Install
The recommended method of installation is using git to clone the repository. This ensures that all dependencies on other scripts
are met as well as providing an easy update path. Single scripts listed as standalone may be downloaded and installed by themselves.
### 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.
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
cd ~/snap/darktable/current
git clone https://github.com/darktable-org/lua-scripts.git lua
### flatpak and appimage packages
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.
### Linux and MacOS
Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and:
cd ~/.config/darktable/
git clone https://github.com/darktable-org/lua-scripts.git lua
### Windows
Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run:
Open a command prompt.
cd %LOCALAPPDATA%\darktable
git clone https://github.com/darktable-org/lua-scripts.git lua
If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least once, because the directory is created at the first start of darktable.
## Enabling
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"`.
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\
or `echo 'require "contrib/hugin"' >> ~/.config/darktable/luarc` to add an entry for hugin.
On windows from a command prompt:
`echo require "contrib/gimp" > %LOCALAPPDATA%\darktable\luarc` to create the file with a gimp entry\
or `echo require "contrib/hugin" >> %LOCALAPPDATA%\darktable\luarc` to add an entry for hugin.
## Disabling
To disable a script open the luarc file in your text editor and insert `--` at the start of the line containing the script you wish to disable, then save the file.
## Updating
To update the script repository, open a terminal or command prompt and do the following:
### Snap
cd ~/snap/darktable/current/lua
git pull
### Linux and MacOS
cd ~/.config/darktable/lua/
git pull
### Windows
cd %LOCALAPPDATA%\darktable\lua
git pull
## Documentation
Each script includes its own documentation and usage in its header, please refer to them.
In order to have your own scripts added here they have to be under a free license (GPL2+ will definitely work, others can be discussed). Scripts in the `official/` subfolder are maintained by the darktable community, those under `contrib/` are meant to have an "owner" who maintains them.
Lua-script libraries documentation may be generated using the tools in the tools/ directory.
More information about the scripting with Lua can be found in the darktable user manual:
https://www.darktable.org/usermanual/en/lua_chapter.html
The darktable Lua API documentation is here:
https://www.darktable.org/lua-api/
## Troubleshooting
Running darktable with Lua debugging enabled provides more information about what is occurring within the scripts.
### Snap
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
Open a terminal and start darktable with the command `darktable -d lua`. This provides debugging information to give you insight into what is happening.
### Windows
Open the Git Bash Shell. Start darktable with the command `/c/Program\ Files/darktable/bin/darktable -d lua`. This provides debugging information to give you insight into what is happening.
## Contributing
In order to have your own scripts added here they have to be under a free license (GPL2+ will definitely work, others can be discussed).

158
contrib/AutoGrouper.lua Normal file
View File

@@ -0,0 +1,158 @@
--[[AutoGrouper plugin for darktable
copyright (c) 2019 Kevin Ertel
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/>.
]]
--[[About this Plugin
This plugin adds the module "Auto Group" to darktable's lighttable view
----REQUIRED SOFTWARE----
None
----USAGE----
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.
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
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
in exif data will be ignored.
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
]]
local dt = require "darktable"
local MOD = 'autogrouper'
local gettext = dt.gettext
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("AutoGrouper",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("AutoGrouper", msgid)
end
local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive)
if test >= low and test <= high then
return true
else
return false
end
end
local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second
first_time = first.exif_datetime_taken
if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end
first_time = tonumber(string.gsub(first_time, '[^0-9]*',''))
second_time = second.exif_datetime_taken
if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end
second_time = tonumber(string.gsub(second_time, '[^0-9]*',''))
return first_time < second_time
end
local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations
local cleaned = string.gsub(str, '[^%d]',':')
cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss
local year = string.sub(cleaned,1,4)
local month = string.sub(cleaned,6,7)
local day = string.sub(cleaned,9,10)
local hour = string.sub(cleaned,12,13)
local min = string.sub(cleaned,15,16)
local sec = string.sub(cleaned,18,19)
return {year = year, month = month, day = day, hour = hour, min = min, sec = sec}
end
local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image
local curr_time = SeperateTime(curr_image.exif_datetime_taken)
local prev_time = SeperateTime(prev_image.exif_datetime_taken)
return os.time(curr_time)-os.time(prev_time)
end
local function main(on_collection)
local images = {}
if on_collection then
local col_images = dt.collection
for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection
table.insert(images,i,image)
end
else
images = dt.gui.selection()
end
dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value)
if #images < 2 then
dt.print('please select at least 2 images')
return
end
table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp
for i, image in ipairs(images) do
if i == 1 then
prev_image = image
elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group
local curr_image = image
if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then
images[i]:group_with(images[i-1])
end
prev_image = curr_image
end
end
end
-- GUI --
GUI = {
gap = {},
selected = {},
collection = {}
}
temp = dt.preferences.read(MOD, 'active_gap', 'integer')
if not InRange(temp, 1, 86400) then temp = 3 end
GUI.gap = dt.new_widget('slider'){
label = _('group gap [sec.]'),
tooltip = _('minimum gap, in seconds, between groups'),
soft_min = 1,
soft_max = 60,
hard_min = 1,
hard_max = 86400,
step = 1,
digits = 0,
value = temp,
reset_callback = function(self)
self.value = 3
end
}
GUI.selected = dt.new_widget("button"){
label = _('auto group: selected'),
tooltip =_('auto group selected images'),
clicked_callback = function() main(false) end
}
GUI.collection = dt.new_widget("button"){
label = _('auto group: collection'),
tooltip =_('auto group the entire collection'),
clicked_callback = function() main(true) end
}
dt.register_lib(
'AutoGroup_Lib', -- Module name
_('auto group'), -- name
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers
dt.new_widget("box"){
orientation = "vertical",
GUI.gap,
GUI.selected,
GUI.collection
}
)

221
contrib/CollectHelper.lua Normal file
View File

@@ -0,0 +1,221 @@
--[[Collect Helper plugin for darktable
copyright (c) 2019 Kevin Ertel
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/>.
]]
--[[About this plugin
This plugin adds the button(s) to the "Selected Images" module:
1) Return to Previous Collection
2) Collect on image's Folder
3) Collect on image's Color Label(s)
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.
Button Behavior:
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
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
----REQUIRED SOFTWARE----
NA
----USAGE----
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.
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.
In the "Selected Images" module click on "Collect on this Image"
----KNOWN ISSUES----
]]
local dt = require "darktable"
local gettext = dt.gettext
local previous = nil
local all_active = false
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("CollectHelper",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("CollectHelper", msgid)
end
-- FUNCTION --
local function CheckSingleImage(selection)
if #selection ~= 1 then
dt.print(_("Please select a single image"))
return true
end
return false
end
local function CheckHasColorLabel(selection)
local ret = false
for _,image in pairs(selection) do
if image.red then ret = true end
if image.blue then ret = true end
if image.green then ret = true end
if image.yellow then ret = true end
if image.purple then ret = true end
end
return ret
end
local function PreviousCollection()
if previous ~= nil then
previous = dt.gui.libs.collect.filter(previous)
end
end
local function CollectOnFolder(all_rules, all_active)
local images = dt.gui.selection()
if CheckSingleImage(images) then
return
end
local rules = {}
local rule = dt.gui.libs.collect.new_rule()
rule.mode = "DT_LIB_COLLECT_MODE_AND"
rule.data = images[1].path
rule.item = "DT_COLLECTION_PROP_FOLDERS"
table.insert(rules, rule)
if all_active then
for _,active_rule in pairs(rules) do
table.insert(all_rules, active_rule)
end
return all_rules
else
previous = dt.gui.libs.collect.filter(rules)
end
end
local function CollectOnColors(all_rules, all_active)
local images = dt.gui.selection()
if CheckSingleImage(images) then
return
end
if not CheckHasColorLabel(images) then
dt.print(_('select an image with an active color label'))
return
end
for _,image in pairs(images) do
local rules = {}
if image.red then
local red_rule = dt.gui.libs.collect.new_rule()
red_rule.mode = "DT_LIB_COLLECT_MODE_AND"
red_rule.data = "red"
red_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
table.insert(rules, red_rule)
end
if image.blue then
local blue_rule = dt.gui.libs.collect.new_rule()
blue_rule.mode = "DT_LIB_COLLECT_MODE_AND"
blue_rule.data = "blue"
blue_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
table.insert(rules, blue_rule)
end
if image.green then
local green_rule = dt.gui.libs.collect.new_rule()
green_rule.mode = "DT_LIB_COLLECT_MODE_AND"
green_rule.data = "green"
green_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
table.insert(rules, green_rule)
end
if image.yellow then
local yellow_rule = dt.gui.libs.collect.new_rule()
yellow_rule.mode = "DT_LIB_COLLECT_MODE_AND"
yellow_rule.data = "yellow"
yellow_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
table.insert(rules, yellow_rule)
end
if image.purple then
local purple_rule = dt.gui.libs.collect.new_rule()
purple_rule.mode = "DT_LIB_COLLECT_MODE_AND"
purple_rule.data = "purple"
purple_rule.item = "DT_COLLECTION_PROP_COLORLABEL"
table.insert(rules, purple_rule)
end
if all_active then
for _,active_rule in pairs(rules) do
table.insert(all_rules, active_rule)
end
return all_rules
else
previous = dt.gui.libs.collect.filter(rules)
end
end
end
local function CollectOnAll_AND()
local images = dt.gui.selection()
if CheckSingleImage(images) then
return
end
local rules = {}
if dt.preferences.read('module_CollectHelper','folder','bool') then
rules = CollectOnFolder(rules, true)
end
if dt.preferences.read('module_CollectHelper','colors','bool') then
rules = CollectOnColors(rules, true)
end
previous = dt.gui.libs.collect.filter(rules)
end
-- GUI --
dt.gui.libs.image.register_action(
_("collect: previous"),
function() PreviousCollection() end,
_("Sets the Collect parameters to be the previously active parameters")
)
if dt.preferences.read('module_CollectHelper','folder','bool') then
dt.gui.libs.image.register_action(
_("collect: folder"),
function() CollectOnFolder(_ , false) end,
_("Sets the Collect parameters to be the selected images's folder")
)
end
if dt.preferences.read('module_CollectHelper','colors','bool') then
dt.gui.libs.image.register_action(
_("collect: color label(s)"),
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(
_("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
_('Will create a collect parameter set that utelizes 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
)

424
contrib/HDRMerge.lua Normal file
View File

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

234
contrib/LabelsToTags.lua Normal file
View File

@@ -0,0 +1,234 @@
--[[
LABELS TO TAGS
Allows the mass-application of tags using color labels and ratings
as a guide.
AUTHOR
August Schwerdfeger (august@schwerdfeger.name)
INSTALLATION
* Copy this file into $CONFIGDIR/lua/, where CONFIGDIR
is your darktable configuration directory
* Add the following line in the file $CONFIGDIR/luarc:
require "LabelsToTags"
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None.
USAGE
In your 'luarc' file or elsewhere, use the function
'register_tag_mapping', defined in this module, to specify
one or more tag mappings for use by the module.
Any mappings so registered will be selectable, according
to their given names, in the module's "mapping" combo box.
A mapping takes the form of a table mapping patterns to
lists of tags. A pattern consists of 6 characters, of which
the first five represent color labels and the last the rating.
Each color label character may be '+', '-', or '*',
indicating that for this pattern to match, the corresponding
color label, respectively, must be on, must be off, or can be
either. Similarly, the rating character may be a numeral
between 0 and 5, "R" for rejected, or "*" for "any value."
An example call to 'register_tag_mapping' is provided in a
comment at the end of this file.
When the "Start" button is pressed, the module will
iterate over each selected image and check the state of
that image's color labels and rating against each pattern
defined in the selected mapping. For each pattern that
matches, the corresponding tags will be added to the
image. Any such tag not already existing in the database
will be created.
LICENSE
LGPLv2+
]]
local darktable = require("darktable")
local du = require "lib/dtutils"
du.check_min_api_version("3.0.0", "LabelsToTags")
-- Lua 5.3 no longer has "unpack" but "table.unpack"
unpack = unpack or table.unpack
local LIB_ID = "LabelsToTags"
-- Helper functions: BEGIN
local function keySet(t)
local rv = {}
for k,_ in pairs(t) do
table.insert(rv,k)
end
table.sort(rv)
return(rv)
end
local function generateLabelHash(img)
local hash = ""
hash = hash .. (img.red and "+" or "-")
hash = hash .. (img.yellow and "+" or "-")
hash = hash .. (img.green and "+" or "-")
hash = hash .. (img.blue and "+" or "-")
hash = hash .. (img.purple and "+" or "-")
hash = hash .. (img.rating == -1 and "R" or tostring(img.rating))
return(hash)
end
local function hashMatch(hash,pattern)
if #(hash) ~= #(pattern) then return(false) end
for i = 0,#hash do
if string.sub(hash,i,i) ~= string.sub(pattern,i,i) and
string.sub(pattern,i,i) ~= "*" then
return(false)
end
end
return(true)
end
-- Helper functions: END
local initialAvailableMappings = {
["Colors"] = { ["+*****"] = { "Red" },
["*+****"] = { "Yellow" },
["**+***"] = { "Green" },
["***+**"] = { "Blue" },
["****+*"] = { "Purple" } },
["Single colors"] = { ["+----*"] = { "Red", "Only red" },
["-+---*"] = { "Yellow", "Only yellow" },
["--+--*"] = { "Green", "Only green" },
["---+-*"] = { "Blue", "Only blue" },
["----+*"] = { "Purple", "Only purple" } },
["Ratings"] = { ["*****0"] = { "No stars", "Not rejected" },
["*****1"] = { "One star", "Not rejected" },
["*****2"] = { "Two stars", "Not rejected" },
["*****3"] = { "Three stars", "Not rejected" },
["*****4"] = { "Four stars", "Not rejected" },
["*****5"] = { "Five stars", "Not rejected" },
["*****R"] = { "Rejected" } }
}
local availableMappings = {}
local function getAvailableMappings()
if availableMappings == nil or next(availableMappings) == nil then
return(initialAvailableMappings)
else
return(availableMappings)
end
end
local function getComboboxTooltip()
if availableMappings == nil or next(availableMappings) == nil then
return("No registered mappings -- using defaults")
else
return("Select a label-to-tag mapping")
end
end
local mappingComboBox = darktable.new_widget("combobox"){
label = "mapping",
value = 1,
tooltip = getComboboxTooltip(),
reset_callback = function(selfC)
if selfC == nil then
return
end
i = 1
for _,m in pairs(keySet(getAvailableMappings())) do
selfC[i] = m
i = i+1
end
n = #selfC
for j = i,n do
selfC[i] = nil
end
selfC.value = 1
selfC.tooltip = getComboboxTooltip()
end,
unpack(keySet(getAvailableMappings()))
}
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)
job.percent = 0.0
local pctIncrement = 1.0 / #(darktable.gui.action_images)
local availableMappings = getAvailableMappings()
local memoizedTags = {}
for _,img in ipairs(darktable.gui.action_images) do
local tagsToApply = {}
local hash = generateLabelHash(img)
for k,v in pairs(availableMappings[mappingComboBox.value]) do
if hashMatch(hash,k) then
for _,tag in ipairs(v) do
tagsToApply[tag] = true
end
end
end
for k,_ in pairs(tagsToApply) do
if memoizedTags[k] == nil then
memoizedTags[k] = darktable.tags.create(k)
end
darktable.tags.attach(memoizedTags[k],img)
end
job.percent = job.percent + pctIncrement
end
job.valid = false
end
local my_widget = darktable.new_widget("box") {
orientation = "vertical",
mappingComboBox,
darktable.new_widget("button") {
label = "start",
tooltip = "Tag all selected images",
clicked_callback = doTagging
}
}
local PATTERN_PATTERN = "^[+*-][+*-][+*-][+*-][+*-][0-5R*]$"
darktable.register_tag_mapping = function(name, mapping)
if availableMappings[name] ~= nil then
darktable.print_error("Tag mapping '" .. name .. "' already registered")
return
end
for pattern,tags in pairs(mapping) do
if string.match(pattern,PATTERN_PATTERN) == nil then
darktable.print_error("In tag mapping '" .. name .. "': Pattern '" .. pattern .. "' invalid")
return
end
for _,tag in ipairs(tags) do
if type(tag) ~= "string" then
darktable.print_error("In tag mapping '" .. name .. "': All tag mappings must be lists of strings")
return
end
end
end
availableMappings[name] = mapping
mappingComboBox.reset_callback(mappingComboBox)
end
--[[
darktable.register_tag_mapping("Example",
{ ["+----*"] = { "Red", "Only red" },
["-+---*"] = { "Yellow", "Only yellow" },
["--+--*"] = { "Green", "Only green" },
["---+-*"] = { "Blue", "Only blue" },
["****+*"] = { "Purple" },
["----+*"] = { "Only purple" },
["*****1"] = { "One star" },
["*****R"] = { "Rejected" } })
]]
darktable.register_lib(LIB_ID,"labels to tags",true,true,{
[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20},
},my_widget,nil,nil)

113
contrib/OpenInExplorer.lua Normal file
View File

@@ -0,0 +1,113 @@
--[[
OpenInExplorer plugin for darktable
copyright (c) 2018 Kevin Ertel
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/>.
]]
--[[About this plugin
This plugin adds the module "OpenInExplorer" to darktable's lighttable view
----REQUIRED SOFTWARE----
Microsoft Windows or Linux with installed Nautilus
----USAGE----
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.
2) Require this file in your luarc file, as with any other dt plug-in
Select the photo(s) you wish to find in explorer and press "Go to Folder".
A file explorer window will be opened for each selected file at the file's location; the file will be highlighted.
----KNOWN ISSUES----
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dsys = require "lib/dtutils.system"
--Check API version
du.check_min_api_version("5.0.0", "OpenInExplorer")
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
--Detect OS and modify accordingly--
local proper_install = false
if dt.configuration.running_os ~= "macos" then
proper_install = true
else
dt.print_error('OpenInExplorer plug-in only supports Windows and Linux at this time')
return
end
-- FUNCTIONS --
local function open_in_explorer() --Open in Explorer
local images = dt.gui.selection()
local curr_image = ""
if #images == 0 then
dt.print('please select an image')
elseif #images <= 15 then
for _,image in pairs(images) do
curr_image = image.path..PS..image.filename
local run_cmd = "explorer.exe /select, "..curr_image
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
resp = dsys.external_command(run_cmd)
end
else
dt.print('please select fewer images (max 15)')
end
end
local function open_in_nautilus() --Open in Nautilus
local images = dt.gui.selection()
local curr_image = ""
if #images == 0 then
dt.print('please select an image')
elseif #images <= 15 then
for _,image in pairs(images) do
curr_image = image.path..PS..image.filename
local run_cmd = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass 1 ]] .. df.sanitize_filename("file://"..curr_image) .. [[ ""]]
dt.print_log("OpenInExplorer run_cmd = "..run_cmd)
resp = dsys.external_command(run_cmd)
end
else
dt.print('please select fewer images (max 15)')
end
end
local function open_in_filemanager() --Open
--Inits--
if not proper_install then
return
end
if (dt.configuration.running_os == "windows") then
open_in_explorer()
elseif (dt.configuration.running_os == "linux") then
open_in_nautilus()
end
end
-- GUI --
if proper_install then
dt.gui.libs.image.register_action(
"show in file explorer",
function() open_in_filemanager() end,
"Opens File Explorer at the selected image's location"
)
end

View File

@@ -28,6 +28,8 @@ USAGE
> exiftool -AutoDynamicRange XE021351.RAF
Auto Dynamic Range : 200%
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* exiftool
LICENSE
GPLv2
@@ -35,13 +37,14 @@ GPLv2
]]
local darktable = require "darktable"
local du = require "lib/dtutils"
local filelib = require "lib/dtutils.file"
-- Forward declare the functions
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
darktable.configuration.check_version(...,{2,0,2},{2,1,0},{3,0,0})
du.check_min_api_version("2.0.2", "autostyle")
-- Receive the event triggered
function autostyle_apply_one_image_event(event,image)
@@ -56,17 +59,22 @@ function autostyle_apply_one_image (image)
-- check they all exist (correct syntax)
if (not tag) then
darktable.print("EXIF TAG not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
return
return 0
end
if (not value) then
darktable.print("value to match not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
return
return 0
end
if (not style_name) then
darktable.print("style name not found in " .. darktable.preferences.read("autostyle","exif_tag","string"))
return
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
@@ -77,40 +85,50 @@ function autostyle_apply_one_image (image)
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 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
return
darktable.print("Couldn't get attribute " .. auto_dr_attr .. " from exiftool's output")
return 0
end
if auto_dr_attr==value then
-- darktable.print("Image " .. image.filename .. ": autostyle automatically applied " .. darktable.preferences.read("autostyle","exif_tag","string") )
darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. darktable.preferences.read("autostyle","exif_tag","string") )
darktable.styles.apply(style,image)
-- else
-- darktable.print("Image " .. image.filename .. ": autostyle not applied, exif tag " .. darktable.preferences.read("autostyle","exif_tag","string") .. " not matched: " .. auto_dr_attr)
return 1
else
darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. darktable.preferences.read("autostyle","exif_tag","string") .. " not matched: " .. auto_dr_attr)
return 0
end
end
function autostyle_apply( shortcut)
local images = darktable.gui.action_images
local images_processed =0
local images_submitted =0
for _,image in pairs(images) do
autostyle_apply_one_image(image)
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 cmd="exiftool -" .. attr .. " '" .. path .. "'";
local exifresult=get_stdout(cmd)
local attribute=string.match(exifresult,": (.*)")
if (attribute == nil) then
darktable.print( "Could not find the attribute " .. attr .. " using the command: <" .. cmd .. ">")
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
@@ -118,7 +136,7 @@ end
function get_stdout(cmd)
-- Open the command, for reading
local fd = assert(io.popen(cmd, 'r'))
dt.control.read(fd)
darktable.control.read(fd)
-- slurp the whole file
local data = assert(fd:read('*a'))

View File

@@ -1,106 +0,0 @@
--[[
This file is part of darktable,
Copyright 2014-2016 by Tobias Jakobs.
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/>.
]]
--[[
darktable calc distance script
This script calcs the distance between two images using the GPS data in the metadata
USAGE
* require this script from your main lua file
* register a shortcut
]]
local dt = require "darktable"
local gettext = dt.gettext
dt.configuration.check_version(...,{3,0,0})
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("calcDistance",dt.configuration.config_dir.."/lua/")
local function _(msgid)
return gettext.dgettext("calcDistance", msgid)
end
local function calcDistance()
local sel_images = dt.gui.selection()
local lat1 = 0;
local lon1 = 0;
local lat2 = 0;
local lon2 = 0;
local ele1 = 0;
local ele2 = 0;
local i = 0;
local sel_images = dt.gui.selection()
for _,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
i = i + 1;
if (i == 1) then
lat1 = image.latitude;
lon1 = image.longitude;
ele1 = image.elevation;
end
lat2 = image.latitude;
lon2 = image.longitude;
ele2 = image.elevation;
end
end
-- I used code from here:
-- http://stackoverflow.com/questions/27928/how-do-i-calculate-distance-between-two-latitude-longitude-points
local earthRadius = 6371; -- Radius of the earth in km
local dLat = math.rad(lat2-lat1); -- deg2rad below
local dLon = math.rad(lon2-lon1);
local a =
math.sin(dLat/2) * math.sin(dLat/2) +
math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) *
math.sin(dLon/2) * math.sin(dLon/2)
;
local angle = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
local distance = earthRadius * angle; -- Distance in km
-- Add the elevation to the calculation
local elevation = 0;
elevation = math.abs(ele1 - ele2) / 1000; --in km
distance = math.sqrt(math.pow(elevation,2) + math.pow(distance,2) );
local distanceUnit
if (distance < 1) then
distance = distance * 1000
distanceUnit = "m"
else
distanceUnit = "km"
end
dt.print(string.format("Distance: %.2f %s", distance, distanceUnit))
end
-- Register
dt.register_event("shortcut",calcDistance,_("Calculate the distance from latitude and longitude in km"))

View File

@@ -37,16 +37,17 @@
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext
-- not a number
local NaN = 0/0
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("3.0.0", "clear_GPS")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("clear_GPS",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("clear_GPS",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("clear_GPS", msgid)

View File

@@ -33,15 +33,19 @@ USAGE
darktable internal tags starting with 'darktable|' will not be touched
* <your shortcut3> removes all expect darktable internal tags from selected images
* <your shortcut4> replaces all tags expect darktable internals
* A module reset will empty the clipboard
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local debug = require "darktable.debug"
local gettext = dt.gettext
dt.configuration.check_version(...,{3,0,0},{4,0,0})
du.check_min_api_version("3.0.0", "copy_attach_detach_tags")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("copy_attach_detach_tags", msgid)
@@ -72,7 +76,7 @@ local function mcopy_tags()
--remove duplicate and 'darktable|' tags create final image_tags
for _,k in ipairs(tag_list_tmp) do
if not string.match(tostring(k), 'darktable|') then
if not hash[k] then
image_tags[#image_tags+1] = k
@@ -83,36 +87,36 @@ local function mcopy_tags()
dt.print(_('Image tags copied ...'))
--create UI tag list
--create UI tag list
local taglist = ""
for _,tag in ipairs(image_tags) do
if taglist == "" then
taglist = tostring(tag)
else
taglist = taglist.."\n"..tostring(tag)
end
end
end
end
taglist_label.label = taglist
taglist_label.label = taglist
return(image_tags)
end
-- attach copied tags to all selected images
local function attach_tags()
if next(image_tags) == nil then
dt.print(_('No tags to attached, please copy tags first.'))
return true
end
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
local present_image_tags = {}
present_image_tags = dt.tags.get_tags(image)
for _,ct in ipairs(image_tags) do
-- check if image has tags and attach
if next(present_image_tags) == nil then
@@ -136,7 +140,7 @@ local function detach_tags()
for _,image in ipairs(sel_images) do
local present_image_tags = {}
present_image_tags = dt.tags.get_tags(image)
for _,present_tag in ipairs(present_image_tags) do
if not string.match(tostring(present_tag), 'darktable|') then
dt.tags.detach(present_tag,image)
@@ -156,43 +160,58 @@ end
taglist_label.reset_callback = mcopy_tags
-- create buttons and elements
local mc_button = dt.new_widget("button") {
label = _('multi copy tags'),
clicked_callback = mcopy_tags}
local p_button = dt.new_widget("button") {
label = _('paste tags'),
clicked_callback = attach_tags}
local rep_button = dt.new_widget("button") {
label = _('replace tags'),
clicked_callback = replace_tags}
local rem_button = dt.new_widget("button") {
label = _('remove all tags'),
clicked_callback = detach_tags}
local taglabel = dt.new_widget("label") {
label = _('tag clipboard'),
selectable = false,
ellipsize = "middle",
halign = "start"}
local box1 = dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("button") {
label = _('multi copy tags'),
clicked_callback = mcopy_tags},
dt.new_widget("button") {
label = _('paste tags'),
clicked_callback = attach_tags}
}
local box2 = dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("button") {
label = _('replace tags'),
clicked_callback = replace_tags},
dt.new_widget("button") {
label = _('remove all tags'),
clicked_callback = detach_tags}
}
local sep = dt.new_widget("separator"){}
-- pack elements into widget table for a nicer layout
local widget_table = {}
widget_table[1] = box1
widget_table[#widget_table+1] = box2
widget_table[#widget_table+1] = sep
widget_table[#widget_table+1] = taglabel
widget_table[#widget_table+1] = taglist_label
-- create modul
dt.register_lib("tagging_addon","Tagging addon",true,false,{
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",
mc_button,
p_button,
rep_button,
rem_button,
taglabel,
dt.new_widget("separator"){},
taglist_label
},
-- orientation = "vertical",
reset_callback = function()
taglist_label.label = ""
image_tags = {}
end,
table.unpack(widget_table),
},
nil,
nil
)

View File

@@ -33,9 +33,10 @@ USAGE
]]
local darktable = require "darktable"
local du = require "lib/dtutils"
-- Tested with darktable 2.0.1
darktable.configuration.check_version(...,{2,0,0},{3,0,0})
du.check_min_api_version("2.0.0", "cr2hdr")
local queue = {}
local processed_files = {}
@@ -60,7 +61,7 @@ end
local function convert_image(image)
if string.sub(image.filename, -3) == "CR2" then
local filename = image.path .. "/" .. image.filename
local result = dt.control.execute( "cr2hdr " .. filename)
local result = darktable.control.execute( "cr2hdr " .. filename)
local out_filename = string.gsub(filename, ".CR2", ".DNG")
local file = io.open(out_filename)
if file then

1127
contrib/enfuseAdvanced.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
--[[
Face recognition for darktable
Copyright (c) 2017 Sebastian Witt
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/>.
]]
--[[
face_recognition
Add a new storage option to send images to face_recognition.
Images are exported to darktable tmp dir first.
A directory with known faces must exist, the image name are the
tag names which will be used.
Multiple images for one face can exist, add a number to it, the
number will be removed from the tag, for example:
People|IknowYou1.jpg
People|IknowYou2.jpg
People|Another.jpg
People|Youtoo.jpg
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* https://github.com/ageitgey/face_recognition
* https://github.com/darktable-org/lua-scripts/tree/master/lib
USAGE
* require this file from your main luarc config file.
This plugin will add a new storage option and calls face_recognition after export.
]]
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
-- constants
local MODULE = "face_recognition"
local PS = dt.configuration.running_os == "windows" and '\\' or '/'
local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt"
-- namespace
local fc = {}
-- ensure we meet the minimum api
du.check_min_api_version("5.0.0", "face_recognition")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("face_recognition", dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("face_recognition", msgid)
end
-- preferences
if not dt.preferences.read(MODULE, "initialized", "bool") then
dt.preferences.write(MODULE, "unknown_tag", "string", "unknown_person")
dt.preferences.write(MODULE, "ignore_tags", "string", "")
dt.preferences.write(MODULE, "tolerance", "float", 0.6)
dt.preferences.write(MODULE, "num_cores", "integer", 0)
dt.preferences.write(MODULE, "known_image_path", "directory", dt.configuration.config_dir .. "/face_recognition")
dt.preferences.write(MODULE, "export_format", "integer", 1)
dt.preferences.write(MODULE, "max_width", "integer", 1000)
dt.preferences.write(MODULE, "max_height", "integer", 1000)
dt.preferences.write(MODULE, "initialized", "bool", true)
end
local function build_image_table(images)
local image_table = {}
local file_extension = ""
local tmp_dir = dt.configuration.tmp_dir .. PS
local ff = fc.export_format.value
local cnt = 0
-- check for plugin-data and direct_edit and build image table accordingly
if string.match(ff, "JPEG") then
file_extension = ".jpg"
elseif string.match(ff, "PNG") then
file_extension = ".png"
elseif string.match(ff, "TIFF") then
file_extension = ".tif"
end
for _,img in ipairs(images) do
image_table[img] = tmp_dir .. df.get_basename(img.filename) .. file_extension
cnt = cnt + 1
end
return image_table, cnt
end
local function stop_job(job)
job.valid = false
end
local function do_export(img_tbl)
local exporter = nil
local upsize = false
local upscale = false
local ff = fc.export_format.value
local height = dt.preferences.read(MODULE, "max_height", "integer")
local width = dt.preferences.read(MODULE, "max_width", "integer")
local images = 0
for k,v in pairs(img_tbl) do
images = images + 1
end
-- get the export format parameters
if string.match(ff, "JPEG") then
exporter = dt.new_format("jpeg")
exporter.quality = 80
elseif string.match(ff, "PNG") then
exporter = dt.new_format("png")
exporter.bpp = 8
elseif string.match(ff, "TIFF") then
exporter = dt.new_format("tiff")
exporter.bpp = 8
end
exporter.max_height = height
exporter.max_width = width
-- export the images
local job = dt.gui.create_job(_("export images"), true, stop_job)
local exp_cnt = 0
local percent_step = 1.0 / images
job.percent = 0.0
for img,export in pairs(img_tbl) do
exp_cnt = exp_cnt + 1
dt.print(string.format(_("Exporting image %i of %i images"), exp_cnt, images))
exporter:write_image(img, export, upsize)
job.percent = job.percent + percent_step
end
job.valid = false
-- return success, or not
return true
end
local function save_preferences()
dt.preferences.write(MODULE, "unknown_tag", "string", fc.unknown_tag.text)
dt.preferences.write(MODULE, "ignore_tags", "string", fc.ignore_tags.text)
dt.preferences.write(MODULE, "max_width", "integer", tonumber(fc.width.text))
dt.preferences.write(MODULE, "max_height", "integer", tonumber(fc.height.text))
dt.preferences.write(MODULE, "num_cores", "integer", fc.num_cores.value)
local val = fc.tolerance.value
val = string.gsub(tostring(val), ",", ".")
dt.preferences.write(MODULE, "tolerance", "float", tonumber(val))
end
-- Check if image has ignored tag attached
local function ignoreByTag (image, ignoreTags)
local tags = image:get_tags ()
local ignoreImage = false
-- For each image tag
for _,t in ipairs (tags) do
-- Check if it contains a ignore tag
for _,it in ipairs (ignoreTags) do
if string.find (t.name, it, 1, true) then
-- The image has ignored tag attached
ignoreImage = true
dt.print_log ("Face recognition: Ignored tag: " .. it .. " found in " .. image.id .. ":" .. t.name)
end
end
end
return ignoreImage
end
local function cleanup(img_list)
for _, img in ipairs(img_list) do
os.remove(img)
end
os.remove(OUTPUT)
end
local function face_recognition ()
local bin_path = df.check_if_bin_exists("face_recognition")
if not bin_path then
dt.print(_("Face recognition not found"))
return
end
save_preferences()
-- Get preferences
local knownPath = dt.preferences.read(MODULE, "known_image_path", "directory")
local nrCores = dt.preferences.read(MODULE, "num_cores", "integer")
local ignoreTagString = dt.preferences.read(MODULE, "ignore_tags", "string")
local unknownTag = dt.preferences.read(MODULE, "unknown_tag", "string")
-- face_recognition uses -1 for all cores, we use 0 in preferences
if nrCores < 1 then
nrCores = -1
end
-- Split ignore tags (if any)
ignoreTags = {}
for tag in string.gmatch(ignoreTagString, '([^,]+)') do
table.insert (ignoreTags, tag)
dt.print_log ("Face recognition: Ignore tag: " .. tag)
end
-- list of exported images
local image_table, cnt = build_image_table(dt.gui.action_images)
if cnt > 0 then
local success = do_export(image_table)
if success then
-- do the face recognition
local img_list = {}
for img,v in pairs(image_table) do
table.insert (img_list, v)
end
-- Get path of exported images
local path = df.get_path (img_list[1])
dt.print_log ("Face recognition: Path to unknown images: " .. path)
local tolerance = dt.preferences.read(MODULE, "tolerance", "float")
local command = bin_path .. " --cpus " .. nrCores .. " --tolerance " .. tolerance .. " " .. knownPath .. " " .. path .. " > " .. OUTPUT
dt.print_log("Face recognition: Running command: " .. command)
dt.print(_("Starting face recognition..."))
dtsys.external_command(command)
-- Open output file
local f = io.open(OUTPUT, "rb")
if not f then
dt.print(_("Face recognition failed"))
else
dt.print(_("Face recognition finished"))
f:close ()
end
-- Read output
dt.print(_("processing results..."))
local result = {}
for line in io.lines(OUTPUT) do
local file, tag = string.match (line, "(.*),(.*)$")
tag = string.gsub (tag, "%d*$", "")
dt.print_log ("File:"..file .." Tag:".. tag)
if result[file] ~= nil then
table.insert (result[file], tag)
else
result[file] = {tag}
end
end
-- Attach tags
for file,tags in pairs(result) do
-- Find image in table
for img,file2 in pairs(image_table) do
if file == file2 then
for _,t in ipairs (tags) do
-- Check if image is ignored
if ignoreByTag (img, ignoreTags) then
dt.print_log("Face recognition: Ignoring image with ID " .. img.id)
else
-- Check of unrecognized unknown_person
if t == "unknown_person" then
t = unknownTag
end
dt.print_log ("ImgId:" .. img.id .. " Tag:".. t)
-- Create tag if it does not exists
local tag = dt.tags.create (t)
img:attach_tag (tag)
end
end
end
end
end
dt.print(_("face recognition complete"))
cleanup(img_list)
else
dt.print(_("image export failed"))
return
end
else
dt.print(_("no images selected"))
return
end
end
-- build the interface
fc.unknown_tag = dt.new_widget("entry"){
text = dt.preferences.read(MODULE, "unknown_tag", "string"),
tooltip = _("tag to be used for unknown person"),
editable = true,
}
fc.ignore_tags = dt.new_widget("entry"){
text = dt.preferences.read(MODULE, "ignore_tags", "string"),
tooltip = _("tags of images to ignore"),
editable = true,
}
fc.tolerance = dt.new_widget("slider"){
label = _("tolerance"),
tooltip = ("detection tolerance - 0.6 default - lower if too many faces detected"),
soft_min = 0.0,
hard_min = 0.0,
soft_max = 1.0,
soft_min = 1.0,
step = 0.1,
digits = 1,
value = 0.0,
}
fc.num_cores = dt.new_widget("slider"){
label = _("processor cores"),
tooltip = _("number of processor cores to use, 0 for all"),
soft_min = 0,
soft_max = 16,
hard_min = 0,
hard_max = 64,
step = 1,
digits = 0,
value = dt.preferences.read(MODULE, "num_cores", "integer"),
}
fc.known_image_path = dt.new_widget("file_chooser_button"){
title = _("known image directory"),
tooltip = _("face data directory"),
value = dt.preferences.read(MODULE, "known_image_path", "directory"),
is_directory = true,
changed_callback = function(this)
dt.preferences.write(MODULE, "known_image_path", "directory", this.value)
end
}
fc.export_format = dt.new_widget("combobox"){
label = _("export image format"),
tooltip = _("format for exported images"),
selected = dt.preferences.read(MODULE, "export_format", "integer"),
changed_callback = function(this)
dt.preferences.write(MODULE, "export_format", "integer", this.selected)
end,
"JPEG", "PNG", "TIFF",
}
fc.width = dt.new_widget("entry"){
text = tostring(dt.preferences.read(MODULE, "max_width", "integer")),
tooltip = _("maximum exported image width"),
editable = true,
}
fc.height = dt.new_widget("entry"){
text = tostring(dt.preferences.read(MODULE, "max_height", "integer")),
tooltip = _("maximum exported image height"),
editable = true,
}
fc.execute = dt.new_widget("button"){
label = "detect faces",
clicked_callback = function(this)
face_recognition()
end
}
local widgets = {
dt.new_widget("label"){ label = _("unknown person tag")},
fc.unknown_tag,
dt.new_widget("label"){ label = _("togs of images to ignore")},
fc.ignore_tags,
dt.new_widget("label"){ label = _("face data directory")},
fc.known_image_path,
}
if dt.configuration.running_os == "windows" or dt.configuration.running_os == "macos" then
table.insert(widgets, df.executable_path_widget({"face_recognition"}))
end
table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")})
table.insert(widgets, fc.tolerance)
table.insert(widgets, fc.num_cores)
table.insert(widgets, fc.export_format)
table.insert(widgets, dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("label"){ label = _("width ")},
fc.width,
})
table.insert(widgets, dt.new_widget("box"){
orientation = "horizontal",
dt.new_widget("label"){ label = _("height ")},
fc.height,
})
table.insert(widgets, fc.execute)
fc.widget = dt.new_widget("box"){
orientation = vertical,
table.unpack(widgets)
}
--fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float")
-- Register
--dt.register_storage("module_face_recognition", _("Face recognition"), show_status, face_recognition)
dt.register_lib(
"face_recognition", -- Module name
_("face recognition"), -- Visible name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers
fc.widget,
nil,-- view_enter
nil -- view_leave
)
fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float")
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -0,0 +1,71 @@
--[[ fujifilm_ratings-0.1
Support for importing Fujifilm in-camera ratings in darktable.
Copyright (C) 2017 Ben Mendis <ben.mendis@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 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.
Dependencies:
- exiftool (https://www.sno.phy.queensu.ca/~phil/exiftool/)
--]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local gettext = dt.gettext
du.check_min_api_version("4.0.0", "fujifilm_ratings")
gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("fujifilm_ratings", msgid)
end
local function detect_rating(event, image)
if not df.check_if_bin_exists("exiftool") then
dt.print_error(_("exiftool not found"))
return
end
local RAF_filename = tostring(image)
local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG")
local command = "exiftool -Rating " .. JPEG_filename
dt.print_error(command)
local output = io.popen(command)
local jpeg_result = output:read("*all")
output:close()
if string.len(jpeg_result) > 0 then
jpeg_result = string.gsub(jpeg_result, "^Rating.*(%d)", "%1")
image.rating = tonumber(jpeg_result)
dt.print_error(_("Using JPEG Rating: ") .. tostring(jpeg_result))
return
end
command = "exiftool -Rating " .. RAF_filename
dt.print_error(command)
output = io.popen(command)
local raf_result = output:read("*all")
output:close()
if string.len(raf_result) > 0 then
raf_result = string.gsub(raf_result, "^Rating.*(%d)", "%1")
image.rating = tonumber(raf_result)
dt.print_error(_("Using RAF Rating: ") .. tostring(raf_result))
end
end
dt.register_event("post-import-image", detect_rating)
print(_("fujifilm_ratings loaded."))

View File

@@ -31,15 +31,17 @@ USAGE
* require this script from your main Lua file
]]
local dt = require "darktable"
require "official/yield"
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("3.0.0", "geoJSON_export")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("geoJSON_export", msgid)
@@ -52,7 +54,7 @@ local function spairs(_table, order) -- Code copied from http://stackoverflow.co
for _key in pairs(_table) do keys[#keys + 1] = _key end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(_table, a, b) end)
else
@@ -73,55 +75,45 @@ local function show_status(storage, image, format, filename, number, total, high
dt.print(string.format(_("Export Image %i/%i"), number, total))
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (not result) then
dt.print_error(bin.." not found")
ret = false
end
ret = true
return ret
end
local function create_geoJSON_file(storage, image_table, extra_data)
if not checkIfBinExists("mkdir") then
if not df.check_if_bin_exists("mkdir") then
dt.print_error(_("mkdir not found"))
return
end
if not checkIfBinExists("convert") then
if not df.check_if_bin_exists("convert") then
dt.print_error(_("convert not found"))
return
end
if not checkIfBinExists("xdg-open") then
if not df.check_if_bin_exists("xdg-open") then
dt.print_error(_("xdg-open not found"))
return
end
if not checkIfBinExists("xdg-user-dir") then
if not df.check_if_bin_exists("xdg-user-dir") then
dt.print_error(_("xdg-user-dir not found"))
return
end
dt.print_error("Will try to export geoJSON file now")
local xportDirectory = dt.preferences.read("geoJSON_export","ExportDirectory","string")
local exportDirectory = dt.preferences.read("geoJSON_export","ExportDirectory","string")
-- Creates dir if not exsists
local imageFoldername = "files/"
local mkdirCommand = "mkdir -p "..exportDirectory.."/"..imageFoldername
dt.control.execute( mkdirCommand)
dt.control.execute( mkdirCommand)
-- Create the thumbnails
for image,exported_image in pairs(image_table) 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
) then
local path, filename, filetype = string.match(image, "(.-)([^\\/]-%.?([^%.\\/]*))$")
filename = string.upper(string.gsub(filename,"%.", "_"))
local path, filename, filetype = string.match(exported_image, "(.-)([^\\/]-%.?([^%.\\/]*))$")
filename = string.upper(string.gsub(filename,"%.%w*", ""))
-- convert -size 92x92 filename.jpg -resize 92x92 +profile "*" thumbnail.jpg
-- In this example, '-size 120x120' gives a hint to the JPEG decoder that the image is going to be downscaled to
-- 120x120, allowing it to run faster by avoiding returning full-resolution images to GraphicsMagick for the
-- subsequent resizing operation. The '-resize 120x120' specifies the desired dimensions of the output image. It
-- In this example, '-size 120x120' gives a hint to the JPEG decoder that the image is going to be downscaled to
-- 120x120, allowing it to run faster by avoiding returning full-resolution images to GraphicsMagick for the
-- subsequent resizing operation. The '-resize 120x120' specifies the desired dimensions of the output image. It
-- will be scaled so its largest dimension is 120 pixels. The '+profile "*"' removes any ICM, EXIF, IPTC, or other
-- profiles that might be present in the input and aren't needed in the thumbnail.
@@ -131,8 +123,8 @@ local function create_geoJSON_file(storage, image_table, extra_data)
dt.control.execute( concertCommand)
end
-- delete the original image to not get into the kmz file
os.remove(image)
-- delete the original image
os.remove(exported_image)
local pattern = "[/]?([^/]+)$"
filmName = string.match(image.film.path, pattern)
@@ -143,17 +135,30 @@ local function create_geoJSON_file(storage, image_table, extra_data)
-- Create the geoJSON file
local geoJSON_file = [[
{
{
"type": "FeatureCollection",
"features": [
]]
for image,exported_image in pairs(image_table) do
filename = string.upper(string.gsub(image.filename,"%.", "_"))
if ((image.longitude and image.latitude) and
for image,exported_image in pairs(image_table) do
--filename = string.upper(string.gsub(image.filename,"%.", "_"))
-- Extract filename, e.g DSC9784.ARW -> DSC9784
filename = string.upper(string.gsub(image.filename,"%.%w*", ""))
-- Extract extension from exported image (user can choose JPG or PNG), e.g DSC9784.JPG -> .JPG
extension = string.match(exported_image,"%.%w*$")
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local image_title, image_description
if (image.title and image.title ~= "") then
image_title = image.title
else
image_title = image.filename
end
image_description = image.description
geoJSON_file = geoJSON_file..
[[ {
"type": "Feature",
@@ -164,8 +169,8 @@ local function create_geoJSON_file(storage, image_table, extra_data)
geoJSON_file = geoJSON_file..
[[ },
"properties": {
"title": "]]..image.title..[[",
"description": "]]..image.description..[[",
"title": "]]..image_title..[[",
"description": "]]..image_description..[[",
"image": "]]..imageFoldername..filename..[[.jpg",
"icon": {
"iconUrl": "]]..imageFoldername.."thumb_"..filename..[[.jpg",
@@ -179,7 +184,7 @@ local function create_geoJSON_file(storage, image_table, extra_data)
,]]
end
end
geoJSON_file = geoJSON_file:sub(0,geoJSON_file:len()-1)
geoJSON_file = geoJSON_file..
[[
@@ -196,7 +201,7 @@ local function create_geoJSON_file(storage, image_table, extra_data)
<html>
<head>
<meta charset=utf-8 />
<title>2014-05-31 Rieselfelder</title>
<title>]]..filmName..[[</title>
<script src='https://api.tiles.mapbox.com/mapbox.js/v2.2.4/mapbox.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.2.4/mapbox.css' rel='stylesheet' />
<style>
@@ -213,7 +218,7 @@ local function create_geoJSON_file(storage, image_table, extra_data)
.setView([0,0], 5);
var myLayer = L.mapbox.featureLayer()
.loadURL(']]..exportgeoJSONFilename..[[')
.loadURL(']]..exportgeoJSONFilename..[[')
.addTo(mapTooltipsJS);
// Set a custom icon on each marker based on feature properties.
@@ -260,7 +265,7 @@ local function create_geoJSON_file(storage, image_table, extra_data)
var polyline = L.polyline(line, polyline_options).addTo(mapTooltipsJS);
});
</script>
</body>
</html>
@@ -277,11 +282,11 @@ local function create_geoJSON_file(storage, image_table, extra_data)
dt.print("geoJSON file created in "..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
local geoJSONFileOpenCommand
geoJSONFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportgeoJSONFilename.."\""
dt.control.execute( geoJSONFileOpenCommand)
dt.control.execute( geoJSONFileOpenCommand)
end
@@ -310,6 +315,9 @@ dt.preferences.register("geoJSON_export",
local handle = io.popen("xdg-user-dir DESKTOP")
local result = handle:read()
handle:close()
if (result == nil) then
result = ""
end
dt.preferences.register("geoJSON_export",
"ExportDirectory",
"directory",

688
contrib/geoToolbox.lua Normal file
View File

@@ -0,0 +1,688 @@
--[[
This file is part of darktable,
copyright (c) 2016 Tobias Jakobs
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/>.
]]
--[[
USAGE
* require this script from your main lua file
To do this add this line to the file .config/darktable/luarc:
require "geoToolbox"
* it creates a new geoToolbox lighttable module
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
du.check_min_api_version("3.0.0", "geoToolbox")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("geoToolbox", msgid)
end
-- <GUI>
local labelDistance = dt.new_widget("label")
labelDistance.label = _("Distance:")
local label_copy_gps_lat = dt.new_widget("check_button")
{
label = _("latitude:"),
value = true
}
local label_copy_gps_lon = dt.new_widget("check_button")
{
label = _("longitude:"),
value = true
}
local label_copy_gps_ele = dt.new_widget("check_button")
{
label = _("elevation:"),
value = true
}
-- </GUI>
local function select_with_gps()
local selection = {}
job = dt.gui.create_job(_("GPS selection"), true, stop_selection)
for key,image in ipairs(dt.collection) do
if job.valid then
job.percent = (key-1)/#dt.collection
if (image.longitude and image.latitude) then
table.insert(selection,image)
end
else
break
end
end
dt.gui.selection(selection)
job.valid = false
end
local function select_without_gps()
local selection = {}
job = dt.gui.create_job(_("GPS selection"), true, stop_selection)
for key,image in ipairs(dt.collection) do
if job.valid then
job.percent = (key-1)/#dt.collection
if (not image.longitude and not image.latitude) then
table.insert(selection,image)
end
else
break
end
end
dt.gui.selection(selection)
job.valid = false
end
local function stop_selection(job)
job.valid = false
end
-- This is used for older images in the DB
function isnan(x) return x ~= x end
-- Function from:
-- https://forums.coronalabs.com/topic/29019-convert-string-to-date/
-- This looks much scarier than it really is. The goal is to turn that string
-- into a Unix timestamp (number of seconds since Jan 1, 1970, the standard
-- used by most systems). So we use the string.match() method to fetch the
-- various date and time parts into their own variables: xyear, xmonth,etc.
function make_time_stamp(dateString)
local convertedTimestamp
--dt.print_error(dateString)
if (dateString) then
local pattern = "(%d+)%:(%d+)%:(%d+) (%d+):(%d+):(%d+)"
local xyear, xmonth, xday, xhour, xminute, xseconds = dateString:match(pattern)
--dt.print_error(xyear)
convertedTimestamp = os.time({
year = xyear,
month = xmonth,
day = xday,
hour = xhour,
min = xminute,
sec = xseconds})
else
convertedTimestamp = 0
end
return convertedTimestamp
end
local first_have_data = false
local first_latitude = ''
local first_longitude = ''
local first_elevation = ''
local first_image_date = 0
local function get_first_coordinate()
local sel_images = dt.gui.action_images
first_latitude = ''
first_longitude = ''
first_elevation = ''
first_image_date = 0
for _,image in ipairs(sel_images) do
if not image then
first_have_data = false
else
image_date = image.exif_datetime_taken
first_have_data = true
if (image.latitude) then
first_latitude = image.latitude
end
if (image.longitude) then
first_longitude = image.longitude
end
if (image.elevation) then
first_elevation = image.elevation
end
if (image.exif_datetime_taken) then
first_image_date = make_time_stamp(image.exif_datetime_taken)
--dt.print_error(image.exif_datetime_taken)
--dt.print_error(first_image_date)
end
end
return
end
end
local second_have_data = false
local second_latitude = ''
local second_longitude = ''
local second_elevation = ''
local second_image_date = 0
local function get_second_coordinate()
local sel_images = dt.gui.action_images
second_latitude = ''
second_longitude = ''
second_elevation = ''
second_image_date = 0
for _,image in ipairs(sel_images) do
if not image then
second_have_data = false
else
image_date = image.exif_datetime_taken
second_have_data = true
if (image.latitude) then
second_latitude = image.latitude
end
if (image.longitude) then
second_longitude = image.longitude
end
if (image.elevation) then
second_elevation = image.elevation
end
if (image.exif_datetime_taken) then
second_image_date = make_time_stamp(image.exif_datetime_taken)
end
end
return
end
end
local calc_in_between_slider = dt.new_widget("slider")
{
label = "Position between",
soft_min = 0, -- The soft minimum value for the slider, the slider can't go beyond this point
soft_max = 100, -- The soft maximum value for the slider, the slider can't go beyond this point
hard_min = -100, -- The hard minimum value for the slider, the user can't manually enter a value beyond this point
hard_max = 200, -- The hard maximum value for the slider, the user can't manually enter a value beyond this point
value = 50 -- The current value of the slider
}
--ToDo: this needs more love
local function calc_in_between()
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
if image then
image_date = make_time_stamp(image.exif_datetime_taken)
if (first_have_data and second_have_data) then
local start_new = 0
local end_new = second_image_date - first_image_date
local image_date_new = image_date - first_image_date
local percent_in_between
if (end_new == 0) then
percent_in_between = 1
else
percent_in_between = image_date_new/end_new
end
calc_in_between_slider.value = percent_in_between * 100
dt.print_error(percent_in_between)
local in_between_latitude = first_latitude + (second_latitude - first_latitude) * percent_in_between
local in_between_longitude = first_longitude + (second_longitude - first_longitude) * percent_in_between
if (first_elevation and second_elevation) then
local in_between_elevation = first_elevation + (second_elevation - first_elevation) * percent_in_between
dt.print_error(first_image_date)
dt.print_error(second_image_date)
dt.print_error(image_date)
dt.print_error(first_elevation)
dt.print_error(second_elevation)
dt.print_error(in_between_elevation)
end
end
end
end
end
local copy_gps_have_data = false
local copy_gps_latitude = ''
local copy_gps_longitude = ''
local copy_gps_elevation = ''
local function copy_gps()
local sel_images = dt.gui.action_images
copy_gps_latitude = ''
copy_gps_longitude = ''
copy_gps_elevation = ''
for _,image in ipairs(sel_images) do
if not image then
copy_gps_have_data = false
else
copy_gps_have_data = true
if (image.latitude and label_copy_gps_lat.value) then
copy_gps_latitude = image.latitude
end
if (image.longitude and label_copy_gps_lon.value) then
copy_gps_longitude = image.longitude
end
if (image.elevation and label_copy_gps_ele.value) then
copy_gps_elevation = image.elevation
end
end
label_copy_gps_lat.label = _("latitude: ") .. copy_gps_latitude
label_copy_gps_lon.label = _("longitude: ") ..copy_gps_longitude
label_copy_gps_ele.label = _("elevation: ") .. copy_gps_elevation
return
end
end
local function paste_gps(image)
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
if (label_copy_gps_lat.value) then
image.latitude = copy_gps_latitude
end
if (label_copy_gps_lon.value) then
image.longitude = copy_gps_longitude
end
if (label_copy_gps_ele.value) then
image.elevation = copy_gps_elevation
end
end
end
local function open_location_in_gnome_maps()
if not df.check_if_bin_exists("gnome-maps") then
dt.print_error(_("gnome-maps not found"))
return
end
local sel_images = dt.gui.action_images
local lat1 = 0;
local lon1 = 0;
local i = 0;
-- Use the first image with geo information
for _,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
lat1 = string.gsub(image.latitude, ",", ".");
lon1 = string.gsub(image.longitude, ",", ".");
break
end
end
local startCommand
startCommand = "gnome-maps \"geo:" .. lat1 .. "," .. lon1 .."\""
dt.print_error(startCommand)
dt.control.execute(startCommand)
end
-- Trim Funktion from: http://lua-users.org/wiki/StringTrim
local function trim12(s)
local from = s:match"^%s*()"
return from > #s and "" or s:match(".*%S", from)
end
local function reverse_geocode()
if not df.check_if_bin_exists("curl") then
dt.print_error(_("curl not found"))
return
end
if not df.check_if_bin_exists("jq") then
dt.print_error(_("jq not found"))
return
end
local sel_images = dt.gui.selection() --action_images
local lat1 = 0;
local lon1 = 0;
local i = 0;
-- Use the first image with geo information
for _,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
lat1 = string.gsub(image.latitude, ",", ".");
lon1 = string.gsub(image.longitude, ",", ".");
break
end
end
local startCommand
local tokan = dt.preferences.read("geoToolbox","mapBoxKey","string")
local types = "country";
local types = "region";
local types = "place";
local types = "poi";
-- MapBox documentation
-- https://www.mapbox.com/api-documentation/#retrieve-places-near-a-location
-- curl -s, --silent Silent mode (don't output anything)
-- jq could be replaced with a Lua JSON parser
startCommand = string.format("curl --silent \"https://api.mapbox.com/geocoding/v5/mapbox.places/%s,%s.json?types=%s&access_token=%s\" | jq '.features | .[0] | '.text''", lon1, lat1, types, tokan)
local handle = io.popen(startCommand)
local result = trim12(handle:read("*a"))
handle:close()
-- Errorhandling would be nice
--dt.print_error("startCommand: "..startCommand)
--dt.print_error("result: '"..result.."'")
if (result ~= "null") then
dt.print(string.sub(result, 2, string.len(result)-2))
end
end
-- I used code from here:
-- http://stackoverflow.com/questions/27928/how-do-i-calculate-distance-between-two-latitude-longitude-points
local function get_distance(lat1, lon1, ele1, lat2, lon2, ele2)
local earthRadius = 6371; -- Radius of the earth in km
local dLat = math.rad(lat2-lat1); -- deg2rad below
local dLon = math.rad(lon2-lon1);
local a =
math.sin(dLat/2) * math.sin(dLat/2) +
math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) *
math.sin(dLon/2) * math.sin(dLon/2)
;
local angle = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
local distance = earthRadius * angle; -- Distance in km
-- Add the elevation to the calculation
local elevation = 0
elevation = math.abs(ele1 - ele2) / 1000; --in km
if (elevation > 0) then
distance = math.sqrt(math.pow(elevation,2) + math.pow(distance,2) )
end
return distance
end
local function calc_distance()
local lat1 = 0;
local lon1 = 0;
local lat2 = 0;
local lon2 = 0;
local ele1 = 0;
local ele2 = 0;
local i = 0;
local sel_images = dt.gui.selection()
for _,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
i = i + 1;
if (i == 1) then
lat1 = image.latitude
lon1 = image.longitude
if (image.elevation) then
ele1 = image.elevation
else
ele1 = 0
end
end
lat2 = image.latitude
lon2 = image.longitude
if (image.elevation) then
ele2 = image.elevation
else
ele2 = 0;
end
end
end
local distance = get_distance(lat1, lon1, ele1, lat2, lon2, ele2)
if (distance < 1) then
distance = distance * 1000
distanceUnit = _("m")
else
distanceUnit = _("km")
end
return string.format(_("Distance: %.2f %s"), distance, distanceUnit)
end
local function print_calc_distance()
dt.print(calc_distance())
end
local function toolbox_calc_distance()
labelDistance.label = calc_distance()
end
local altitude_file_chooser_button = dt.new_widget("file_chooser_button")
{
title = _("export altitude CSV"), -- The title of the window when choosing a file
value = "", -- The currently selected file
is_directory = true -- True if the file chooser button only allows directories to be selecte
}
local altitude_filename = dt.new_widget("entry")
{
text = "altitude.csv",
placeholder = "altitude.csv",
editable = true,
tooltip = _("Name of the exported file"),
reset_callback = function(self) self.text = "text" end
}
local function altitude_profile()
dt.print(_("Start export"))
local sel_images = dt.gui.action_images
local lat1 = 0;
local lon1 = 0;
local lat2 = 0;
local lon2 = 0;
local ele1 = 0;
local ele2 = 0;
local i = 0;
local csv_file = '';
csv_file = "km;m".."\n";
local distance = 0;
local distanceFromStart = 0;
local elevation = 0;
local elevationAdd = 0;
local sel_images = dt.gui.action_images
for _,image in ipairs(sel_images) do
if ((not isnan(image.longitude) and not isnan(image.latitude) and not isnan(image.elevation) and image.elevation) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
i = i + 1;
if (i == 1) then
lat1 = image.latitude
lon1 = image.longitude
ele1 = image.elevation
else
lat1 = lat2
lon1 = lon2
ele1 = ele2
end
lat2 = image.latitude
lon2 = image.longitude
ele2 = image.elevation
if (i == 1) then
distanceFromStart = 0
else
local distance = get_distance(lat1, lon1, ele1, lat2, lon2, ele2)
distanceFromStart = distanceFromStart + distance;
end
csv_file = csv_file .. distanceFromStart..";"..image.elevation.."\n";
end
end --for
local exportDirectory = altitude_file_chooser_button.value
local exportFilename = altitude_filename.text
if (exportFilename == '') then
exportFilename = altitude_filename.placeholder
end
file = io.open(exportDirectory.."/"..exportFilename, "w")
file:write(csv_file)
file:close()
dt.print(_("File created in ")..exportDirectory)
end
local separator = dt.new_widget("separator"){}
local separator2 = dt.new_widget("separator"){}
local separator3 = dt.new_widget("separator"){}
local separator4 = dt.new_widget("separator"){}
local separator5 = dt.new_widget("separator"){}
dt.register_lib(
"geoToolbox", -- Module name
"geo toolbox", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
dt.new_widget("box")
{
orientation = "vertical",
dt.new_widget("button")
{
label = _("select geo images"),
tooltip = _("Select all images with GPS information"),
clicked_callback = select_with_gps
},
dt.new_widget("button")
{
label = _("select non-geo images"),
tooltip = _("Select all images without GPS information"),
clicked_callback = select_without_gps
},
separator,--------------------------------------------------------
dt.new_widget("button")
{
label = _("copy GPS data"),
tooltip = _("Copy GPS data"),
clicked_callback = copy_gps
},
label_copy_gps_lat,
label_copy_gps_lon,
label_copy_gps_ele,
dt.new_widget("button")
{
label = _("paste GPS data"),
tooltip = _("Paste GPS data"),
clicked_callback = paste_gps
},
separator2,--------------------------------------------------------
--ToDo: This need a better UI
--[[
dt.new_widget("button")
{
label = "get 1st coordinate",
tooltip = "Select first image and click this button",
clicked_callback = get_first_coordinate
},
dt.new_widget("button")
{
label = "get 2nd coordinate",
tooltip = "Select second image and click this button",
clicked_callback = get_second_coordinate
},
dt.new_widget("button")
{
label = "calc in between",
tooltip = "Select third image and click this button",
clicked_callback = calc_in_between
},
calc_in_between_slider,
separator3,--------------------------------------------------------
]]
dt.new_widget("button")
{
label = _("open in Gnome Maps"),
tooltip = _("Open location in Gnome Maps"),
clicked_callback = open_location_in_gnome_maps
},
separator4,--------------------------------------------------------
dt.new_widget("button")
{
label = _("reverse geocode"),
tooltip = _("This just shows the name of the location, but doesn't add it as tag"),
clicked_callback = reverse_geocode
},
separator5,--------------------------------------------------------
dt.new_widget("label"){label = _("altitude CSV export")},
altitude_file_chooser_button,
altitude_filename,
dt.new_widget("button")
{
label = _("export altitude CSV file"),
tooltip = _("Create an altitude profile using the GPS data in the metadata"),
clicked_callback = altitude_profile
},
labelDistance
},
nil,-- view_enter
nil -- view_leave
)
-- Preferences
dt.preferences.register("geoToolbox",
"mapBoxKey",
"string",
_("geoToolbox export: MapBox Key"),
_("https://www.mapbox.com/studio/account/tokens"),
'' )
-- Register
dt.register_event("shortcut", print_calc_distance, _("Calculate the distance from latitude and longitude in km"))
dt.register_event("mouse-over-image-changed", toolbox_calc_distance)
dt.register_event("shortcut", select_with_gps, _("Select all images with GPS information"))
dt.register_event("shortcut", select_without_gps, _("Select all images without GPS information"))
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: hl Lua;

View File

@@ -1,89 +0,0 @@
--[[
This file is part of darktable,
Copyright 2016 by Tobias Jakobs.
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/>.
]]
--[[
darktable script to open a geo uri in gnome-maps
ADDITIANAL SOFTWARE NEEDED FOR THIS SCRIPT
* gnome-maps >= 3.20
USAGE
* require this script from your main lua file
* register a shortcut
]]
local dt = require "darktable"
require "official/yield"
local gettext = dt.gettext
dt.configuration.check_version(...,{3,0,0})
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("geo_uri",dt.configuration.config_dir.."/lua/")
local function _(msgid)
return gettext.dgettext("geo_uri", msgid)
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (not result) then
dt.print_error(bin.." not found")
ret = false
end
ret = true
return ret
end
local function openLocationInGnomeMaps()
if not checkIfBinExists("gnome-maps") then
dt.print_error(_("gnome-maps not found"))
return
end
local sel_images = dt.gui.selection()
local lat1 = 0;
local lon1 = 0;
local i = 0;
-- Use the first image with geo information
for _,image in ipairs(sel_images) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
lat1 = image.latitude;
lon1 = image.longitude;
break
end
local startCommand
startCommand = "gnome-maps \"geo:" .. lat1 .. "," .. lon1 .."\""
dt.print_error(startCommand)
if dt.control.execute( startCommand) then
dt.print(_("Command failed ..."))
end
end
end
-- Register
dt.register_event("shortcut", openLocationInGnomeMaps, _("Open Location in Gnome Maps"))

View File

@@ -4,7 +4,7 @@
Copyright (C) 2016 Bill Ferguson <wpferguson@gmail.com>.
Portions are lifted from hugin.lua and thus are
Portions are lifted from hugin.lua and thus are
Copyright (c) 2014 Wolfgang Goetz
Copyright (c) 2015 Christian Kanzian
@@ -29,9 +29,9 @@
This script provides another storage (export target) for darktable. Selected
images are exported in the specified format to temporary storage. GIMP is launched
and opens the files. After editing, the exported images are overwritten to save the
and opens the files. After editing, the exported images are overwritten to save the
changes. When GIMP exits, the exported files are moved into the current collection
and imported into the database. The imported files then show up grouped with the
and imported into the database. The imported files then show up grouped with the
originally selected images.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
@@ -50,8 +50,8 @@
CAVEATS
* Developed and tested on Ubuntu 14.04 LTS with darktable 2.0.3 and GIMP 2.9.3 (development version with
> 8 bit color)
* There is no provision for dealing with the xcf files generated by GIMP, since darktable doesn't deal with
them. You may want to save the xcf file if you intend on doing further edits to the image or need to save
* There is no provision for dealing with the xcf files generated by GIMP, since darktable doesn't deal with
them. You may want to save the xcf file if you intend on doing further edits to the image or need to save
the layers used. Where you save them is up to you.
BUGS, COMMENTS, SUGGESTIONS
@@ -64,245 +64,105 @@
]]
local dt = require "darktable"
require "official/yield"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext
local gimp_widget = nil
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("5.0.0", "gimp")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/")
local function split_filepath(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, "(.-)(([^\\/]-)%.?([^%.\\/]*))$")
return result
end
local function get_path(str)
local parts = split_filepath(str)
return parts["path"]
end
local function get_filename(str)
local parts = split_filepath(str)
return parts["filename"]
end
local function get_basename(str)
local parts = split_filepath(str)
return parts["basename"]
end
local function get_filetype(str)
local parts = split_filepath(str)
return parts["filetype"]
end
gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("gimp", msgid)
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (result) then
dt.print_error("true checkIfBinExists: "..bin)
ret = true
else
dt.print_error(bin.." not found")
ret = false
end
return ret
end
-- Thanks Tobias Jakobs for the idea and the correction
function checkIfFileExists(filepath)
local file = io.open(filepath,"r")
local ret
if file ~= nil then
io.close(file)
dt.print_error("true checkIfFileExists: "..filepath)
ret = true
else
dt.print_error(filepath.." not found")
ret = false
end
return ret
end
local function filename_increment(filepath)
-- break up the filepath into parts
local path = get_path(filepath)
local basename = get_basename(filepath)
local filetype = get_filetype(filepath)
-- check to see if we've incremented before
local increment = string.match(basename, "_(%d-)$")
if increment then
-- we do 2 digit increments so make sure we didn't grab part of the filename
if string.len(increment) > 2 then
-- we got the filename so set the increment to 01
increment = "01"
else
increment = string.format("%02d", tonumber(increment) + 1)
basename = string.gsub(basename, "_(%d-)$", "")
end
else
increment = "01"
end
local incremented_filepath = path .. basename .. "_" .. increment .. "." .. filetype
dt.print_error("original file was " .. filepath)
dt.print_error("incremented file is " .. incremented_filepath)
return incremented_filepath
end
local function groupIfNotMember(img, new_img)
local function group_if_not_member(img, new_img)
local image_table = img:get_group_members()
local is_member = false
for _,image in ipairs(image_table) do
dt.print_error(image.filename .. " is a member")
dt.print_log(image.filename .. " is a member")
if image.filename == new_img.filename then
is_member = true
dt.print_error("Already in group")
dt.print_log("Already in group")
end
end
if not is_member then
dt.print_error("group leader is "..img.group_leader.filename)
dt.print_log("group leader is "..img.group_leader.filename)
new_img:group_with(img.group_leader)
dt.print_error("Added to group")
dt.print_log("Added to group")
end
end
local function sanitize_filename(filepath)
local path = get_path(filepath)
local basename = get_basename(filepath)
local filetype = get_filetype(filepath)
local sanitized = string.gsub(basename, " ", "\\ ")
return path .. sanitized .. "." .. filetype
end
local function show_status(storage, image, format, filename,
number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
end
local function fileCopy(fromFile, toFile)
local result = nil
-- if cp exists, use it
if checkIfBinExists("cp") then
result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'")
end
-- if cp was not present, or if cp failed, then a pure lua solution
if not result then
local fileIn, err = io.open(fromFile, 'rb')
if fileIn then
local fileOut, errr = io.open(toFile, 'w')
if fileOut then
local content = fileIn:read(4096)
while content do
fileOut:write(content)
content = fileIn:read(4096)
end
result = true
fileIn:close()
fileOut:close()
else
dt.print_error("fileCopy Error: " .. errr)
end
else
dt.print_error("fileCopy Error: " .. err)
end
end
return result
end
local function fileMove(fromFile, toFile)
local success = os.rename(fromFile, toFile)
if not success then
-- an error occurred, so let's try using the operating system function
if checkIfBinExists("mv") then
success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'")
end
-- if the mv didn't exist or succeed, then...
if not success then
-- pure lua solution
success = fileCopy(fromFile, toFile)
if success then
os.remove(fromFile)
else
dt.print_error("fileMove Error: Unable to move " .. fromFile .. " to " .. toFile .. ". Leaving " .. fromFile .. " in place.")
dt.print(string.format(_("Unable to move edited file into collection. Leaving it as %s"), fromFile))
end
end
end
return success -- nil on error, some value if success
end
local function gimp_edit(storage, image_table, extra_data) --finalize
if not checkIfBinExists("gimp") then
local gimp_executable = df.check_if_bin_exists("gimp")
if not gimp_executable then
dt.print_error(_("GIMP not found"))
return
end
-- list of exported images
if dt.configuration.running_os == "macos" then
gimp_executable = "open -W -a " .. gimp_executable
end
-- list of exported images
local img_list
-- reset and create image list
img_list = ""
for _,exp_img in pairs(image_table) do
exp_img = sanitize_filename(exp_img)
exp_img = df.sanitize_filename(exp_img)
img_list = img_list ..exp_img.. " "
end
dt.print(_("Launching GIMP..."))
local gimpStartCommand
gimpStartCommand = "gimp "..img_list
dt.print_error(gimpStartCommand)
gimpStartCommand = gimp_executable .. " " .. img_list
dt.control.execute( gimpStartCommand)
dt.print_log(gimpStartCommand)
dtsys.external_command(gimpStartCommand)
-- for each of the image, exported image pairs
-- move the exported image into the directory with the original
-- 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
local myimage_name = image.path .. "/" .. get_filename(exported_image)
local myimage_name = image.path .. "/" .. df.get_filename(exported_image)
while checkIfFileExists(myimage_name) do
myimage_name = filename_increment(myimage_name)
while df.check_if_file_exists(myimage_name) do
myimage_name = df.filename_increment(myimage_name)
-- limit to 99 more exports of the original export
if string.match(get_basename(myimage_name), "_(d-)$") == "99" then
break
if string.match(df.get_basename(myimage_name), "_(d-)$") == "99" then
break
end
end
dt.print_error("moving " .. exported_image .. " to " .. myimage_name)
local result = fileMove(exported_image, myimage_name)
dt.print_log("moving " .. exported_image .. " to " .. myimage_name)
local result = df.file_move(exported_image, myimage_name)
if result then
dt.print_error("importing file")
dt.print_log("importing file")
local myimage = dt.database.import(myimage_name)
groupIfNotMember(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
dt.print_error("attaching tag")
dt.print_log("attaching tag")
dt.tags.attach(tag,myimage)
end
end
@@ -312,8 +172,13 @@ local function gimp_edit(storage, image_table, extra_data) --finalize
end
-- Register
dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit)
local executables = {"gimp"}
if dt.configuration.running_os ~= "linux" then
gimp_widget = df.executable_path_widget(executables)
end
dt.register_storage("module_gimp", _("Edit with GIMP"), show_status, gimp_edit, nil, nil, gimp_widget)
--

View File

@@ -1,60 +0,0 @@
--[[
This file is part of darktable,
Copyright 2014 by Tobias Jakobs.
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/>.
]]
--[[
darktable select images with or without GPS informations
USAGE
* require this script from your main lua file
* register a shortcuts
]]
local dt = require "darktable"
local gettext = dt.gettext
dt.configuration.check_version(...,{2,0,1},{3,0,0})
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gps_select",dt.configuration.config_dir.."/lua/")
local function _(msgid)
return gettext.dgettext("gps_select", msgid)
end
table = require "table"
local function selectWithGPS()
local selection = {}
for _,image in ipairs(dt.database) do
if (image.longitude and image.latitude) then
table.insert(selection,image)
end
end
dt.gui.selection(selection)
end
local function selectWithoutGPS()
local selection = {}
for _,image in ipairs(dt.database) do
if (not image.longitude and not image.latitude) then
table.insert(selection,image)
end
end
dt.gui.selection(selection)
end
dt.register_event("shortcut", selectWithGPS, _("Select all images with GPS information"))
dt.register_event("shortcut", selectWithoutGPS, _("Select all images without GPS information"))

157
contrib/gpx_export.lua Normal file
View File

@@ -0,0 +1,157 @@
--[[
This file is part of darktable,
copyright (c) 2017 Jannis_V
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/>.
]]
--[[
Simple darktable GPX generator script
This script generates a GPX track from all images having GPS latitude
and longitude information.
For each source folder, a separate <trk> is generated in the gpx file.
]]
local dt = require "darktable"
local df = require "lib/dtutils.file"
local dl = require "lib/dtutils"
local gettext = dt.gettext
dl.check_min_api_version("3.0.0", "gpx-export")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gpx_export",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("gpx_export", msgid)
end
local path_entry = dt.new_widget("entry")
{
text = dt.preferences.read("gpx_exporter", "gpxExportPath", "string"),
editable=true,
reset_callback = function(self)
self.text = "~/darktable.gpx"
dt.preferences.write("gpx_exporter", "gpxExportPath", "string", self.text)
end,
tooltip = _("gpx file path"),
}
local function stop_job(job)
job.valid = false
end
local function create_gpx_file()
dt.preferences.write("gpx_exporter", "gpxExportPath", "string", path_entry.text)
path = path_entry.text:gsub("^~", os.getenv("HOME")) -- Expand ~ to home
path = path:gsub("//", "/")
dt.print(_("exporting gpx file..."))
job = dt.gui.create_job(_("gpx export"), true, stop_job)
local sel_images = dt.gui.action_images
local segments = {}
for key,image in dl.spairs(sel_images, function(t, a, b) return t[b].path > t[a].path end) do
print(image.path)
if (job.valid) then
job.percent = (key - 1) / #sel_images
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Just in case
) then
if (segments[image.path] == nil) then
segments[image.path] = {}
end
if (image.exif_datetime_taken == "") then
dt.print(image.path.."/"..image.filename.._(" does not have date information and won't be processed"))
print(image.path.."/"..image.filename.._(" does not have date information and won't be processed")) -- Also print to terminal
else
segments[image.path][image.filename] = image
end
end
else
break
end
end
local gpx_file = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
gpx_file = gpx_file.."<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"Darktable GPX Exporter\"\n"
gpx_file = gpx_file.."version=\"1.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
gpx_file = gpx_file.."xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
for key, folder in dl.spairs(segments) do
gpx_file = gpx_file.."\t<trk>\n"
gpx_file = gpx_file.."\t\t<name>"..key.."</name>\n";
gpx_file = gpx_file.."\t\t<trkseg>\n";
for key2, image in dl.spairs(folder, function(t, a, b) return t[b].exif_datetime_taken > t[a].exif_datetime_taken end) do
date_format = "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)"
my_year, my_month, my_day, my_hour, my_min, my_sec = image.exif_datetime_taken:match(date_format)
local my_timestamp = os.time({year=my_year, month=my_month, day=my_day, hour=my_hour, min=my_min, sec=my_sec})
gpx_file = gpx_file.."\t\t\t<trkpt lat=\""..string.gsub(tostring(image.latitude), ",", ".").."\" lon=\""..string.gsub(tostring(image.longitude), ",", ".").."\">\n"
gpx_file = gpx_file.."\t\t\t\t<time>"..os.date("!%Y-%m-%dT%H:%M:%SZ", my_timestamp).."</time>\n"
gpx_file = gpx_file.."\t\t\t\t<name>"..image.path.."/"..image.filename.."</name>\n"
gpx_file = gpx_file.."\t\t\t</trkpt>\n"
end
gpx_file = gpx_file.."\t\t</trkseg>\n";
gpx_file = gpx_file.."\t</trk>\n";
end
job.valid = false
gpx_file = gpx_file.."</gpx>\n";
local file = io.open(path, "w")
if (file == nil) then
dt.print(_("invalid path: ")..path)
else
file:write(gpx_file)
file:close()
dt.print(_("gpx file created: ")..path)
end
end
dt.register_lib(
"gpx_exporter",
"gpx export",
true, -- expandable
true, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
dt.new_widget("box")
{
orientation = "vertical",
dt.new_widget("button")
{
label = _("export"),
tooltip = _("export gpx file"),
clicked_callback = create_gpx_file
},
dt.new_widget("box")
{
orientation = "horizontal",
dt.new_widget("label")
{
label = _("file:"),
},
path_entry
},
},
nil,-- view_enter
nil -- view_leave
)

View File

@@ -1,129 +1,230 @@
--[[
Hugin storage for darktable
Hugin storage for darktable
copyright (c) 2014 Wolfgang Goetz
copyright (c) 2015 Christian Kanzian
copyright (c) 2015 Tobias Jakobs
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/>.
]]
--[[
HUGIN
Add a new storage option to send images to hugin.
Images are exported to darktable tmp dir first.
Add a new storage option to send images to hugin.
Images are exported to darktable tmp dir first.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* hugin
USAGE
* require this file from your main luarc config file.
* require this file from your main luarc config file
* set the hugin tool paths (on some platforms)
* if hugin gui mode is used, save the final result in the tmp directory with the first file name and _pano as suffix for the image to be automatically imported to DT afterwards
This plugin will add a new storage option and calls hugin after export.
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local log = require "lib/dtutils.log"
local dtsys = require "lib/dtutils.system"
require "official/yield"
local gettext = dt.gettext
-- works with darktable API version 2.0.0 and 3.0.0
dt.configuration.check_version(...,{2,0,0},{3,0,0})
local namespace = 'module_hugin'
local user_pref_str = 'prefer_gui'
local user_prefer_gui = dt.preferences.read(namespace, user_pref_str, "bool")
log.msg(log.info, "user_prefer_gui set to ", user_prefer_gui)
local hugin_widget = nil
local exec_widget = nil
local executable_table = {"hugin", "hugin_executor", "pto_gen"}
-- get the proper path quoting quote
local PQ = dt.configuration.running_os == "windows" and '"' or "'"
-- works with darktable API version from 5.0.0 on
du.check_min_api_version("5.0.0", "hugin")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("hugin", msgid)
return gettext.dgettext("hugin", msgid)
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (result) then
dt.print_error("true checkIfBinExists: "..bin)
ret = true
else
dt.print_error(bin.." not found")
ret = false
end
return ret
local function user_preference_changed(widget)
user_prefer_gui = widget.value
dt.preferences.write(namespace, user_pref_str, "bool", user_prefer_gui)
end
local function show_status(storage, image, format, filename,
number, total, high_quality, extra_data)
dt.print("Export to Hugin "..tostring(number).."/"..tostring(total))
dt.print("exporting to hugin: "..tostring(number).."/"..tostring(total))
end
local function create_panorama(storage, image_table, extra_data) --finalize
if not checkIfBinExists("hugin") then
dt.print_error(_("hugin not found"))
return
end
-- Since Hugin 2015.0.0 hugin provides a command line tool to start the assistant
-- http://wiki.panotools.org/Hugin_executor
-- We need pto_gen to create pto file for hugin_executor
-- http://hugin.sourceforge.net/docs/manual/Pto_gen.html
local hugin_executor = false
if (checkIfBinExists("hugin_executor") and checkIfBinExists("pto_gen")) then
hugin_executor = true
-- save the current log level and set log level to
-- log.error for normal operations
-- log.info for more insight about what is happening
-- log.debug for even more information
local saved_log_level = log.log_level()
log.log_level(log.error)
local hugin = df.check_if_bin_exists("hugin")
log.msg(log.info, "hugin set to ", hugin)
local hugin_executor = df.check_if_bin_exists("hugin_executor")
log.msg(log.info, "hugin_executor set to ", hugin_executor)
local pto_gen = df.check_if_bin_exists("pto_gen")
log.msg(log.info, "pto_gen set to ", pto_gen)
local gui_available = false
if hugin then
gui_available = true
else
dt.print(_("hugin is not found, did you set the path?"))
log.msg(log.error, "hugin executable not found. Check if the executable is installed.")
log.msg(log.error, "If the executable is installed, check that the path is set correctly.")
return
end
-- list of exported images
local img_list
local cmd_line_available = false
if hugin_executor and pto_gen then
cmd_line_available = true
end
-- list of exported images
local img_list = ""
local img_set = {}
-- reset and create image list
img_list = ""
for _,v in pairs(image_table) do
img_list = img_list ..v.. " "
for k,v in pairs(image_table) do
log.msg(log.debug, "k is ", k, " and v is ", v)
img_list = img_list .. PQ .. v .. PQ .. ' ' -- surround the filename with single quotes to handle spaces
table.insert(img_set, k)
end
dt.print(_("Will try to stitch now"))
log.msg(log.info, "img_list is ", img_list)
local huginStartCommand
if (hugin_executor) then
huginStartCommand = "pto_gen "..img_list.." -o "..dt.configuration.tmp_dir.."/project.pto"
dt.print(_("Creating pto file"))
dt.control.execute( huginStartCommand)
-- use first file as basename for output file
table.sort(img_set, function(a,b) return a.filename<b.filename end);
local first_file
for _, k in ipairs(img_set) do
first_file = k
break
end
dt.print(_("Running Assistent"))
huginStartCommand = "hugin_executor --assistant "..dt.configuration.tmp_dir.."/project.pto"
dt.control.execute( huginStartCommand)
if first_file == nil then
dt.print("no file selected")
return
end
huginStartCommand = "hugin "..dt.configuration.tmp_dir.."/project.pto"
local pto_path = dt.configuration.tmp_dir..'/project.pto'
log.msg(log.info, "pto_path is ", pto_path)
local filepath = df.split_filepath(first_file.filename)
log.msg(log.info, "filepath is ", filepath)
local tmp_filename = filepath['basename'].."_pano.tif"
log.msg(log.info, "tmp_filename is ", tmp_filename)
-- some versions of hugin_executor don't seem to output the result
-- to the --prefix location, so assume we are working in the tmp
-- directory and then move the file to the original file location afterwards
local src_path = dt.configuration.tmp_dir.."/"..tmp_filename
log.msg(log.info, "src_path is ", src_path)
local dst_path = first_file.path..'/'..tmp_filename
log.msg(log.info, "dst_path is ", dst_path)
if df.check_if_file_exists(dst_path) then
dst_path = df.create_unique_filename(dst_path)
log.msg(log.info, "dst_path updated to ", dts_path)
end
dt.print(_("will try to stitch now"))
local huginStartCommand = nil
if (cmd_line_available and not user_prefer_gui) then
log.msg(log.info, "using the command line tools to create panorama")
huginStartCommand = pto_gen.." "..img_list.." -o "..pto_path
dt.print(_("creating pto file"))
log.msg(log.info, "pto creation command is ", huginStartCommand)
dtsys.external_command(huginStartCommand)
dt.print(_("running assistant"))
huginStartCommand = hugin_executor.." --assistant "..pto_path
dtsys.external_command(huginStartCommand)
huginStartCommand = hugin_executor..' --stitching --prefix=' .. "'" .. src_path .. "'" .. ' ' .. pto_path
log.msg(log.info, "command line huginStartCommand is ", huginStartCommand)
elseif gui_available then
if (user_prefer_gui) then
dt.print(_("launching hugin"))
else
dt.print(_("unable to find command line tools, launching hugin"))
end
-- the gui produces a differnt output filename than the command line
src_path = dt.configuration.tmp_dir .. "/" .. df.get_basename(img_set[1].filename) .. " - " .. df.get_basename(img_set[#img_set].filename) .. ".tif"
log.msg(log.info, "set src_path to ", src_path)
huginStartCommand = hugin..' '..img_list
log.msg(log.info, "gui huginStartCommand is ", huginStartCommand)
else
huginStartCommand = "hugin "..img_list
end
dt.print_error(huginStartCommand)
if dt.control.execute( huginStartCommand)
then
dt.print(_("Command hugin failed ..."))
dt.print(_("hugin isn't available."))
end
if not (huginStartCommand==nil) then
if not dtsys.external_command(huginStartCommand) then
dt.print(_("hugin failed ..."))
log.msg(log.error, huginStartCommand, " failed")
else
if df.check_if_file_exists(src_path) then
log.msg(log.debug, "found ", src_path, " importing to ", dst_path)
df.file_move(src_path, dst_path)
dt.print(_("importing file "..dst_path))
dt.database.import(dst_path)
end
end
log.log_level(saved_log_level)
end
-- cleanup the temp files
for k,v in pairs(image_table) do
os.remove(v)
end
end
-- Register
dt.register_storage("module_hugin", _("Hugin Panorama"), show_status, create_panorama)
if dt.configuration.running_os ~= "linux" then
exec_widget = df.executable_path_widget(executable_table)
end
hugin_widget = dt.new_widget("box") {
orientation = "vertical",
dt.new_widget("check_button")
{
label = _(" launch hugin gui"),
value = user_prefer_gui,
tooltip = _('launch hugin in gui mode'),
clicked_callback = user_preference_changed
},
exec_widget
}
dt.register_storage(namespace, _("hugin panorama"), show_status, create_panorama, nil, nil, hugin_widget)
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

587
contrib/image_stack.lua Normal file
View File

@@ -0,0 +1,587 @@
--[[
image_stack.lua - process a stack of images
Copyright (C) 2018 Bill Ferguson <wpferguson@gmail.com>.
Copyright (C) 2016,2017 Holger Klemm
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_stack - export a stack of images and process them, returning the result
This script provides another storage (export target) for darktable. Selected
images are exported in the specified format to temporary storage. The images are aligned
if the user requests it. When the images are ready, imagemagick is launched and uses
the selected evaluate-sequence operator to process the images. The output file is written
to a filename representing the imput files in the format specified by the user. The resulting
image is imported into the film roll. The source images can be tagged as part of the file
creation so that a user can later find the contributing images.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* align_image_stack - http://www.hugin.org
* imagemagick - http://www.imagemagick.org
USAGE
* require this script from your main lua file
* select the images to process with image_stack
* in the export dialog select "image stack" and select the format and bit depth for the
exported image
* Select whether the images need to be aligned.
* Select the stack operator
* Select the output format
* Select whether to tag the source images used to create the resulting file
* Specify executable locations if necessary
* Press "export"
* The resulting image will be imported
NOTES
Mean is a fairly quick operation. On my machine (i7-6800K, 16G) it takes a few seconds. Median, on the other hand
takes approximately 10x longer to complete. Processing 10 and 12 image stacks took over a minute. I didn't test all
the other functions, but the ones I did fell between Mean and Median performance wise.
BUGS, COMMENTS, SUGGESTIONS
* Send to Bill Ferguson, wpferguson@gmail.com
CHANGES
THANKS
* Thanks to Pat David and his blog entry on blending images, https://patdavid.net/2013/05/noise-removal-in-photos-with-median_6.html
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local gettext = dt.gettext
local job = nil
-- path separator constant
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- works with LUA API version 5.0.0
du.check_min_api_version("5.0.0", "image_stack")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("image_stack",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("image_stack", msgid)
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- GUI definitions
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local label_align_options= dt.new_widget("section_label"){
label = _('image align options')
}
local label_image_stack_options = dt.new_widget("section_label"){
label = _("image stack options")
}
local label_executable_locations = dt.new_widget("section_label"){
label = _("executable locations")
}
if not(dt.preferences.read("align_image_stack", "initialized", "bool")) then
dt.preferences.write("align_image_stack", "def_radial_distortion", "bool", false)
dt.preferences.write("align_image_stack", "def_optimize_field", "bool", false)
dt.preferences.write("align_image_stack", "def_optimize_image_center", "bool", true)
dt.preferences.write("align_image_stack", "def_auto_crop", "bool", true)
dt.preferences.write("align_image_stack", "def_distortion", "bool", true)
dt.preferences.write("align_image_stack", "def_grid_size", "integer", 5)
dt.preferences.write("align_image_stack", "def_control_points", "integer", 8)
dt.preferences.write("align_image_stack", "def_control_points_remove", "integer", 3)
dt.preferences.write("align_image_stack", "def_correlation", "integer", 9)
dt.preferences.write("align_image_stack", "initialized", "bool", true)
end
if not(dt.preferences.read("image_stack", "initialized", "bool")) then
dt.preferences.write("image_stack", "align_images", "bool", true)
dt.preferences.write("image_stack", "stack_function", "integer", 2)
dt.preferences.write("image_stack", "output_format", "integer", 6)
dt.preferences.write("image_stack", "tag_images", "bool", true)
dt.preferences.write("image_stack", "initialized", "bool", true)
end
local chkbtn_will_align = dt.new_widget("check_button"){
label = _('perform image alignment'),
value = dt.preferences.read("image_stack", "align_images", "bool"),
tooltip = _('align the image stack before processing')
}
local chkbtn_radial_distortion = dt.new_widget("check_button"){
label = _('optimize radial distortion for all images'),
value = dt.preferences.read("align_image_stack", "def_radial_distortion", "bool"),
tooltip = _('optimize radial distortion for all images, \nexcept for first'),
}
local chkbtn_optimize_field = dt.new_widget("check_button"){
label = _('optimize field of view for all images'),
value = dt.preferences.read("align_image_stack", "def_optimize_field", "bool"),
tooltip =_('optimize field of view for all images, except for first. \nUseful for aligning focus stacks (DFF) with slightly \ndifferent magnification.'),
}
local chkbtn_optimize_image_center = dt.new_widget("check_button"){
label = _('optimize image center shift for all images'),
value = dt.preferences.read("align_image_stack", "def_optimize_image_center", "bool"),
tooltip =_('optimize image center shift for all images, \nexcept for first.'),
}
local chkbtn_auto_crop = dt.new_widget("check_button"){
label = _('auto crop the image'),
value = dt.preferences.read("align_image_stack", "def_auto_crop", "bool"),
tooltip =_('auto crop the image to the area covered by all images.'),
}
local chkbtn_distortion = dt.new_widget("check_button"){
label = _('load distortion from lens database'),
value = dt.preferences.read("align_image_stack", "def_distortion", "bool"),
tooltip =_('try to load distortion information from lens database'),
}
local cmbx_grid_size = dt.new_widget("combobox"){
label = _('image grid size'),
tooltip =_('break image into a rectangular grid \nand attempt to find num control points in each section.\ndefault: (5x5)'),
value = dt.preferences.read("align_image_stack", "def_grid_size", "integer"), --5
"1", "2", "3","4","5","6","7","8","9",
reset_callback = function(self)
self.value = dt.preferences.read("align_image_stack", "def_grid_size", "integer")
end
}
local cmbx_control_points = dt.new_widget("combobox"){
label = _('control points/grid'),
tooltip =_('number of control points (per grid, see option -g) \nto create between adjacent images \ndefault: (8).'),
value = dt.preferences.read("align_image_stack", "def_control_points", "integer"), --8, "1", "2", "3","4","5","6","7","8","9",
"1", "2", "3","4","5","6","7","8","9",
reset_callback = function(self)
self.value = dt.preferences.read("align_image_stack", "def_control_points", "integer")
end
}
local cmbx_control_points_remove = dt.new_widget("combobox"){
label = _('remove control points with error'),
tooltip =_('remove all control points with an error higher \nthan num pixels \ndefault: (3)'),
value = dt.preferences.read("align_image_stack", "def_control_points_remove", "integer"), --3, "1", "2", "3","4","5","6","7","8","9",
"1", "2", "3","4","5","6","7","8","9",
reset_callback = function(self)
self.value = dt.preferences.read("align_image_stack", "def_control_points_remove", "integer")
end
}
local cmbx_correlation = dt.new_widget("combobox"){
label = _('correlation threshold for control points'),
tooltip =_('correlation threshold for identifying \ncontrol points \ndefault: (0.9).'),
value = dt.preferences.read("align_image_stack", "def_correlation", "integer"), --9, "0,1", "0,2", "0,3","0,4","0,5","0,6","0,7","0,8","0,9",
"0.1", "0.2", "0.3","0.4","0.5","0.6","0.7","0.8","0.9","1.0",
reset_callback = function(self)
self.value = dt.preferences.read("align_image_stack", "def_correlation", "integer")
end
}
local cmbx_stack_function = dt.new_widget("combobox"){
label = _("select stack function"),
tooltip = _("select function to be \napplied to image stack"),
value = dt.preferences.read("image_stack", "stack_function", "integer"), "Mean", "Median", "Abs", "Add", "And", "Divide", "Max", "Min",
"Or", "Subtract", "Sum", "Xor",
reset_callback = function(self)
self.value = dt.preferences.read("image_stack", "stack_function", "integer")
end
}
local cmbx_output_format = dt.new_widget("combobox"){
label = _("select output format"),
tooltip = _("choose the format for the resulting image"),
value = dt.preferences.read("image_stack", "output_format", "integer"), "EXR", "JPG", "PNG", "PNM", "PPM", "TIF",
reset_callback = function(self)
self.value = dt.preferences.read("image_stack", "output_format", "integer")
end
}
local chkbtn_tag_source_file = dt.new_widget("check_button"){
label = _("tag source images used?"),
value = dt.preferences.read("image_stack", "tag_images", "bool"),
tooltip = _("tag the source images used to create the output file?")
}
local image_stack_widget = dt.new_widget("box"){
orientation = "vertical",
label_align_options,
chkbtn_will_align,
chkbtn_radial_distortion,
chkbtn_optimize_field,
chkbtn_optimize_image_center,
chkbtn_auto_crop,
chkbtn_distortion,
cmbx_grid_size,
cmbx_control_points,
cmbx_control_points_remove,
cmbx_correlation,
dt.new_widget("separator"){},
dt.new_widget("separator"){},
label_image_stack_options,
cmbx_stack_function,
cmbx_output_format,
chkbtn_tag_source_file,
}
local executables = {"align_image_stack", "convert"}
if dt.configuration.running_os ~= "linux" then
image_stack_widget[#image_stack_widget + 1] = df.executable_path_widget(executables)
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- local functions
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
end
-- read the gui and populate the align_image_stack arguments
local function get_align_image_stack_arguments()
--Setup Align Image Stack Arguments--
local align_args = ""
if (chkbtn_radial_distortion.value) then align_args = align_args .. " -d" end
if (chkbtn_optimize_field.value) then align_args = align_args .. " -m" end
if (chkbtn_optimize_image_center.value) then align_args = align_args .. " -i" end
if (chkbtn_auto_crop.value) then align_args = align_args .. " -C" end
if (chkbtn_distortion.value) then align_args = align_args .. " --distortion" end
dt.preferences.write("align_image_stack", "def_radial_distortion", "bool", chkbtn_radial_distortion.value)
dt.preferences.write("align_image_stack", "def_optimize_field", "bool", chkbtn_optimize_field.value)
dt.preferences.write("align_image_stack", "def_optimize_image_center", "bool", chkbtn_optimize_image_center.value)
dt.preferences.write("align_image_stack", "def_auto_crop", "bool", chkbtn_auto_crop.value)
dt.preferences.write("align_image_stack", "def_distortion", "bool", chkbtn_distortion.value)
align_args = align_args.." -g "..cmbx_grid_size.value
align_args = align_args.." -c "..cmbx_control_points.value
align_args = align_args.." -t "..cmbx_control_points_remove.value
align_args = align_args.." --corr="..cmbx_correlation.value
dt.preferences.write("align_image_stack", "def_grid_size", "integer", cmbx_grid_size.selected)
dt.preferences.write("align_image_stack", "def_control_points", "integer", cmbx_control_points.selected)
dt.preferences.write("align_image_stack", "def_control_points_remove", "integer", cmbx_control_points_remove.selected)
dt.preferences.write("align_image_stack", "def_correlation", "integer", cmbx_correlation.selected)
if (dt.preferences.read("align_image_stack", "align_use_gpu", "bool")) then align_args = align_args .. " --gpu" end
align_args = align_args .. " -a " .. dt.configuration.tmp_dir .. "/aligned_ "
return align_args
end
-- extract, and sanitize, an image list from the supplied image table
local function extract_image_list(image_table)
local img_list = ""
local result = {}
for img,expimg in pairs(image_table) do
table.insert(result, expimg)
end
table.sort(result)
for _,exp_img in ipairs(result) do
img_list = img_list .. " " .. df.sanitize_filename(exp_img)
end
return img_list, #result
end
-- don't leave files laying around the operating system
local function cleanup(img_list)
dt.print_log("image list is " .. img_list)
files = du.split(img_list, " ")
for _,f in ipairs(files) do
f = string.gsub(f, '[\'\"]', "")
os.remove(f)
end
end
-- List files based on a search pattern. This is cross platform compatible
-- but the windows version is recursive in order to get ls type listings.
-- Normally this shouldn't be a problem, but if you use this code just beware.
-- If you want to do it non recursively, then remove the /s argument from dir
-- and grab the path component from the search string and prepend it to the files
-- found.
local function list_files(search_string)
local ls = "ls "
local files = {}
local dir_path = nil
local count = 1
if dt.configuration.running_os == "windows" then
ls = "dir /b/s "
search_string = string.gsub(search_string, "/", "\\\\")
end
local f = io.popen(ls .. search_string)
if f then
local found_file = f:read()
while found_file do
files[count] = found_file
count = count + 1
found_file = f:read()
end
f:close()
end
return files
end
-- create a filename from a multi image set. The image list is sorted, then
-- combined with first and last if more than 3 images or a - separated list
-- if three images or less.
local function make_output_filename(image_table)
local images = {}
local cnt = 1
local max_distinct_names = 3
local name_separator = "-"
local outputFileName = nil
local result = {}
for img,expimg in pairs(image_table) do
table.insert(result, expimg)
end
table.sort(result)
for _,img in pairs(result) do
images[cnt] = df.get_basename(img)
cnt = cnt + 1
end
cnt = cnt - 1
if cnt > 1 then
if cnt > max_distinct_names then
-- take the first and last
outputFileName = images[1] .. name_separator .. images[cnt]
else
-- join them
outputFileName = du.join(images, name_separator)
end
else
-- return the single name
outputFileName = images[cnt]
end
return outputFileName
end
-- get the path where the collection is stored
local function extract_collection_path(image_table)
local collection_path = nil
for i,_ in pairs(image_table) do
collection_path = i.path
break
end
return collection_path
end
-- copy an images database attributes to another image. This only
-- copies what the database knows, not the actual exif data in the
-- image itself.
local function copy_image_attributes(from, to, ...)
local args = {...}
if #args == 0 then
args[1] = "all"
end
if args[1] == "all" then
args[1] = "rating"
args[2] = "colors"
args[3] = "exif"
args[4] = "meta"
args[5] = "GPS"
end
for _,arg in ipairs(args) do
if arg == "rating" then
to.rating = from.rating
elseif arg == "colors" then
to.red = from.red
to.blue = from.blue
to.green = from.green
to.yellow = from.yellow
to.purple = from.purple
elseif arg == "exif" then
to.exif_maker = from.exif_maker
to.exif_model = from.exif_model
to.exif_lens = from.exif_lens
to.exif_aperture = from.exif_aperture
to.exif_exposure = from.exif_exposure
to.exif_focal_length = from.exif_focal_length
to.exif_iso = from.exif_iso
to.exif_datetime_taken = from.exif_datetime_taken
to.exif_focus_distance = from.exif_focus_distance
to.exif_crop = from.exif_crop
elseif arg == "GPS" then
to.elevation = from.elevation
to.longitude = from.longitude
to.latitude = from.latitude
elseif arg == "meta" then
to.publisher = from.publisher
to.title = from.title
to.creator = from.creator
to.rights = from.rights
to.description = from.description
else
dt.print_error(_("Unrecognized option to copy_image_attributes: " .. arg))
end
end
end
local function stop_job()
job.valid = false
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- main program
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local function image_stack(storage, image_table, extra_data)
local will_align = chkbtn_will_align.value
local img_list, image_count = extract_image_list(image_table)
local tmp_dir = dt.configuration.tmp_dir .. PS
local stack_function = cmbx_stack_function.value
local output_format = cmbx_output_format.value
local tag_source = chkbtn_tag_source_file.value
local tasks = 3
if will_align then
tasks = tasks + 1
end
if tag_source then
tasks = tasks + 1
end
local percent_step = 1 / tasks
-- update preferences
dt.preferences.write("image_stack", "align_images", "bool", will_align)
dt.preferences.write("image_stack", "stack_function", "integer", cmbx_stack_function.selected)
dt.preferences.write("image_stack", "output_format", "integer", cmbx_output_format.selected)
if image_count < 2 then
dt.print(_("ERROR: at least 2 images required for image stacking, exiting..."))
dt.print_error(_(image_count .. " image(s) selected, at least 2 required"))
return
end
job = dt.gui.create_job("image stack", true, stop_job)
job.percent = job.percent + percent_step
-- align images if requested
if will_align then
local align_image_stack_executable = df.check_if_bin_exists("align_image_stack")
if align_image_stack_executable then
local align_args = get_align_image_stack_arguments()
local align_image_stack_command = align_image_stack_executable .. align_args .. " " .. img_list
dt.print_log(align_image_stack_command)
dt.print(_("aligning images..."))
local result = dtsys.external_command(align_image_stack_command)
if result == 0 then
dt.print(_("images aligned"))
local files = list_files(tmp_dir .. "aligned_*")
cleanup(img_list)
img_list = extract_image_list(files)
job.percent = job.percent + percent_step
else
dt.print(_("ERROR: image alignment failed"))
dt.print_error(_("image alignment failed"))
cleanup(img_list)
return
end
else
dt.print(_("ERROR: align_image_stack not found"))
dt.print_error(_("align_image_stack not found"))
cleanup(img_list)
return
end
end
-- stack images
local output_filename = tmp_dir .. make_output_filename(image_table) .. "." .. string.lower(output_format)
local convert_arguments = img_list .. " -evaluate-sequence " .. stack_function .. " " .. df.sanitize_filename(output_filename)
local convert_executable = df.check_if_bin_exists("convert")
-- tif images exported from darktable have private tiff tags embedded in them and convert complains about them
local ignore_tif_tags = " -quiet -define tiff:ignore-tags=40965,42032,42033,42034,42036,18246,18249,36867,34864,34866 "
if convert_executable then
local convert_command = convert_executable .. ignore_tif_tags .. convert_arguments
dt.print_log(_("convert command is " .. convert_command))
dt.print(_("processing image stack"))
local result = dtsys.external_command(convert_command)
if result == 0 then
dt.print(_("image stack processed"))
cleanup(img_list)
job.percent = job.percent + percent_step
-- import image
dt.print(_("importing result"))
local film_roll_path = extract_collection_path(image_table)
local import_filename = df.create_unique_filename(film_roll_path .. PS .. df.get_filename(output_filename))
df.file_move(output_filename, import_filename)
imported_image = dt.database.import(import_filename)
local created_tag = dt.tags.create(_("Created with|image_stack"))
dt.tags.attach(created_tag, imported_image)
-- all the images are the same except for time, so just copy the attributes
-- from the first
for img,_ in pairs(image_table) do
copy_image_attributes(img, imported_image)
break
end
job.percent = job.percent + percent_step
-- tag images if requested
if tag_source then
dt.print(_("tagging source images"))
local source_tag = dt.tags.create(_("Source file|" .. imported_image.filename))
for img, _ in pairs(image_table) do
dt.tags.attach(source_tag, img)
end
job.percent = job.percent + percent_step
end
else
dt.print(_("ERROR: image stack processing failed"))
cleanup(img_list)
end
else
dt.print(_("ERROR: convert executable not found"))
dt.print_error(_("convert executable not found"))
cleanup(img_list)
end
job.valid = false
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- darktable integration
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
dt.preferences.register("align_image_stack", "align_use_gpu", -- name
"bool", -- type
_('align image stack: use GPU for remaping'), -- label
_('set the GPU remapping for image align'), -- tooltip
false)
dt.register_storage("module_image_stack", _("image stack"), show_status, image_stack, nil, nil, image_stack_widget)

View File

@@ -1,412 +1,373 @@
--[[
This file is part of darktable,
Copyright 2016 by Tobias Jakobs.
Copyright 2016 by Erik Augustin.
This file is part of darktable,
Copyright 2018 by Tobias Jakobs.
Copyright 2018 by Erik Augustin.
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 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.
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/>.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
]]
--[[
darktable KML export script
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* mkdir
* zip (only if you create KMZ files)
* convert (ImageMagick)
* xdg-open
* xdg-user-dir
* zip (at the moment Linux only and only if you create KMZ files)
* magick (ImageMagick)
* xdg-user-dir (Linux)
WARNING
This script is only tested with Linux
USAGE
* require script "official/yield" from your main Lua file in the first line
* require this script from your main Lua file
* when choosing file format, pick JPEG or PNG as Google Earth doesn't support other formats
]]
local dt = require "darktable"
require "official/yield"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local ds = require "lib/dtutils.string"
local dsys = require "lib/dtutils.system"
local gettext = dt.gettext
dt.configuration.check_version(...,{3,0,0})
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
du.check_min_api_version("5.0.0", kml_export)
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("kml_export",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("kml_export",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("kml_export", msgid)
end
-- Sort a table
local function spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
-- collect the keys
local keys = {}
for _key in pairs(_table) do keys[#keys + 1] = _key end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(_table, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], _table[keys[i]]
end
end
return gettext.dgettext("kml_export", msgid)
end
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(string.format(_("Export Image %i/%i"), number, total))
dt.print(string.format(_("Export Image %i/%i"), number, total))
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (not result) then
dt.print_error(bin.." not found")
ret = false
end
ret = true
return ret
end
-- Strip accents from a string
-- Copied from https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/
function string.stripAccents( str )
local tableAccents = {}
tableAccents["à"] = "a"
tableAccents["á"] = "a"
tableAccents["â"] = "a"
tableAccents["ã"] = "a"
tableAccents["ä"] = "a"
tableAccents["ç"] = "c"
tableAccents["è"] = "e"
tableAccents["é"] = "e"
tableAccents["ê"] = "e"
tableAccents["ë"] = "e"
tableAccents["ì"] = "i"
tableAccents["í"] = "i"
tableAccents["î"] = "i"
tableAccents["ï"] = "i"
tableAccents["ñ"] = "n"
tableAccents["ò"] = "o"
tableAccents["ó"] = "o"
tableAccents["ô"] = "o"
tableAccents["õ"] = "o"
tableAccents["ö"] = "o"
tableAccents["ù"] = "u"
tableAccents["ú"] = "u"
tableAccents["û"] = "u"
tableAccents["ü"] = "u"
tableAccents["ý"] = "y"
tableAccents["ÿ"] = "y"
tableAccents["À"] = "A"
tableAccents["Á"] = "A"
tableAccents["Â"] = "A"
tableAccents["Ã"] = "A"
tableAccents["Ä"] = "A"
tableAccents["Ç"] = "C"
tableAccents["È"] = "E"
tableAccents["É"] = "E"
tableAccents["Ê"] = "E"
tableAccents["Ë"] = "E"
tableAccents["Ì"] = "I"
tableAccents["Í"] = "I"
tableAccents["Î"] = "I"
tableAccents["Ï"] = "I"
tableAccents["Ñ"] = "N"
tableAccents["Ò"] = "O"
tableAccents["Ó"] = "O"
tableAccents["Ô"] = "O"
tableAccents["Õ"] = "O"
tableAccents["Ö"] = "O"
tableAccents["Ù"] = "U"
tableAccents["Ú"] = "U"
tableAccents["Û"] = "U"
tableAccents["Ü"] = "U"
tableAccents["Ý"] = "Y"
local normalizedString = ""
for strChar in string.gmatch(str, "([%z\1-\127\194-\244][\128-\191]*)") do
if tableAccents[strChar] ~= nil then
normalizedString = normalizedString..tableAccents[strChar]
else
normalizedString = normalizedString..strChar
-- Add duplicate index to filename
-- image.filename does not have index, exported_image has index
function addDuplicateIndex( index, filename )
if index > 0 then
filename = filename.."_"
if index < 10 then
filename = filename.."0"
end
filename = filename..index
end
return normalizedString
end
-- Escape XML characters
-- Keep &amp; first, otherwise it will double escape other characters
-- https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents
function string.escapeXmlCharacters( str )
str = string.gsub(str,"&", "&amp;")
str = string.gsub(str,"\"", "&quot;")
str = string.gsub(str,"'", "&apos;")
str = string.gsub(str,"<", "&lt;")
str = string.gsub(str,">", "&gt;")
return str
return filename
end
local function create_kml_file(storage, image_table, extra_data)
if not checkIfBinExists("mkdir") then
return
end
if not checkIfBinExists("convert") then
return
end
if not checkIfBinExists("xdg-open") then
return
end
if not checkIfBinExists("xdg-user-dir") then
return
end
local magickPath
if dt.configuration.running_os == "linux" then
magickPath = 'convert'
else
magickPath = dt.preferences.read("kml_export","magickPath","string")
end
dt.print_error("Will try to export KML file now")
if not df.check_if_bin_exists(magickPath) then
dt.print_error(_("magick not found"))
return
end
if dt.configuration.running_os == "linux" then
if not df.check_if_bin_exists("xdg-user-dir") then
dt.print_error(_("xdg-user-dir not found"))
return
end
end
dt.print_log("Will try to export KML file now")
local imageFoldername
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then
if not checkIfBinExists("zip") then
return
end
local imageFoldername
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true
and dt.configuration.running_os == "linux") then
if not df.check_if_bin_exists("zip") then
dt.print_error(_("zip not found"))
return
end
exportDirectory = dt.configuration.tmp_dir
imageFoldername = ""
else
exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string")
-- Creates dir if not exsists
imageFoldername = "files"..PS
df.mkdir(df.sanitize_filename(exportDirectory..PS..imageFoldername))
end
-- Create the thumbnails
for image,exported_image in pairs(image_table) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local path, filename, filetype = string.match(exported_image, "(.-)([^\\/]-%.?([^%.\\/]*))$")
filename = string.upper(string.gsub(filename,"%.%w*", ""))
-- magick -size 92x92 filename.jpg -resize 92x92 +profile "*" thumbnail.jpg
-- In this example, '-size 120x120' gives a hint to the JPEG decoder that the image is going to be downscaled to
-- 120x120, allowing it to run faster by avoiding returning full-resolution images to GraphicsMagick for the
-- subsequent resizing operation. The '-resize 120x120' specifies the desired dimensions of the output image. It
-- will be scaled so its largest dimension is 120 pixels. The '+profile "*"' removes any ICM, EXIF, IPTC, or other
-- profiles that might be present in the input and aren't needed in the thumbnail.
local convertToThumbCommand = ds.sanitize(magickPath) .. " -size 96x96 " .. exported_image .. " -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" " .. exportDirectory .. PS .. imageFoldername .. "thumb_" .. filename .. ".jpg"
if (exported_image ~= exportDirectory..PS..imageFoldername..filename.."."..filetype) then
df.file_copy(exported_image, exportDirectory..PS..imageFoldername..filename.."."..filetype)
end
dsys.external_command(convertToThumbCommand)
exportDirectory = dt.configuration.tmp_dir
imageFoldername = ""
else
exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string")
-- Creates dir if not exsists
imageFoldername = "files/"
local mkdirCommand = "mkdir -p "..exportDirectory.."/"..imageFoldername
dt.control.execute(mkdirCommand)
-- Remove exported image if it has no GPS data
os.remove(exported_image)
end
local pattern = "[/]?([^/]+)$"
filmName = string.match(image.film.path, pattern)
-- Create the thumbnails
for image,exported_image in pairs(image_table) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local path, filename, filetype = string.match(exported_image, "(.-)([^\\/]-%.?([^%.\\/]*))$")
filename = string.upper(string.gsub(filename,"%.%w*", ""))
-- convert -size 92x92 filename.jpg -resize 92x92 +profile "*" thumbnail.jpg
-- In this example, '-size 120x120' gives a hint to the JPEG decoder that the image is going to be downscaled to
-- 120x120, allowing it to run faster by avoiding returning full-resolution images to GraphicsMagick for the
-- subsequent resizing operation. The '-resize 120x120' specifies the desired dimensions of the output image. It
-- will be scaled so its largest dimension is 120 pixels. The '+profile "*"' removes any ICM, EXIF, IPTC, or other
-- profiles that might be present in the input and aren't needed in the thumbnail.
-- Strip accents from the filename, because GoogleEarth can't open them
-- https://github.com/darktable-org/lua-scripts/issues/54
filmName = ds.strip_accents(filmName)
local convertToThumbCommand = "convert -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory.."/"..imageFoldername.."thumb_"..filename..".jpg"
dt.control.execute(convertToThumbCommand)
else
-- Remove exported image if it has no GPS data
os.remove(exported_image)
end
-- Remove chars we don't like to have in filenames
filmName = string.gsub(filmName, [[\]], "")
filmName = string.gsub(filmName, [[/]], "")
filmName = string.gsub(filmName, [[:]], "")
filmName = string.gsub(filmName, [["]], "")
filmName = string.gsub(filmName, "<", "")
filmName = string.gsub(filmName, ">", "")
filmName = string.gsub(filmName, "|", "")
filmName = string.gsub(filmName, "*", "")
filmName = string.gsub(filmName, "?", "")
filmName = string.gsub(filmName,'[.]', "") -- At least Windwows has problems with the "." and the start command
end
local pattern = "[/]?([^/]+)$"
filmName = string.match(image.film.path, pattern)
-- Strip accents from the filename, because GoogleEarth can't open them
-- https://github.com/darktable-org/lua-scripts/issues/54
filmName = string.stripAccents(filmName)
end
exportKMLFilename = filmName..".kml"
exportKMZFilename = filmName..".kmz"
exportKMLFilename = filmName..".kml"
exportKMZFilename = filmName..".kmz"
-- Create the KML file
local kml_file = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
kml_file = kml_file.."<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
kml_file = kml_file.."<Document>\n"
-- Create the KML file
local kml_file = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
kml_file = kml_file.."<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
kml_file = kml_file.."<Document>\n"
--image_table = dt.gui.selection();
kml_file = kml_file.."<name>"..filmName.."</name>\n"
kml_file = kml_file.." <description>Exported from darktable</description>\n"
--image_table = dt.gui.selection();
kml_file = kml_file.."<name>"..filmName.."</name>\n"
kml_file = kml_file.." <description>Exported from darktable</description>\n"
for image,exported_image in pairs(image_table) do
for image,exported_image in pairs(image_table) do
-- Extract filename, e.g DSC9784.ARW -> DSC9784
filename = string.upper(string.gsub(image.filename,"%.%w*", ""))
-- Handle duplicates
filename = addDuplicateIndex( image.duplicate_index, filename )
-- Extract extension from exported image (user can choose JPG or PNG), e.g DSC9784.JPG -> .JPG
extension = string.match(exported_image,"%.%w*$")
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
kml_file = kml_file.." <Placemark>\n"
local image_title, image_description
if (image.title and image.title ~= "") then
image_title = string.escapeXmlCharacters(image.title)
else
image_title = image.filename
end
-- Characters should not be escaped in CDATA, but we are using HTML fragment, so we must escape them
image_description = string.escapeXmlCharacters(image.description)
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
kml_file = kml_file.." <Placemark>\n"
kml_file = kml_file.." <name>"..image_title.."</name>\n"
kml_file = kml_file.." <description>"..image_description.."</description>\n"
kml_file = kml_file.." <Style>\n"
kml_file = kml_file.." <IconStyle>\n"
kml_file = kml_file.." <Icon>\n"
kml_file = kml_file.." <href>"..imageFoldername.."thumb_"..filename..".jpg</href>\n"
kml_file = kml_file.." </Icon>\n"
kml_file = kml_file.." </IconStyle>\n"
kml_file = kml_file.." <BalloonStyle>\n"
kml_file = kml_file.." <text><![CDATA[<p><b>"..image_title.."</b></p><img src=\""..imageFoldername..filename..extension.."\"><p>"..image_description.."</p>]]></text>\n"
kml_file = kml_file.." <textColor>ff000000</textColor>\n"
kml_file = kml_file.." <displayMode>default</displayMode>\n"
kml_file = kml_file.." </BalloonStyle>\n"
kml_file = kml_file.." </Style>\n"
kml_file = kml_file.." <Point>\n"
kml_file = kml_file.." <extrude>1</extrude>\n"
kml_file = kml_file.." <coordinates>"..string.gsub(tostring(image.longitude),",", ".")..","..string.gsub(tostring(image.latitude),",", ".")..",0</coordinates>\n"
kml_file = kml_file.." <TimeStamp>\n"
kml_file = kml_file.." <when>"..string.gsub(image.exif_datetime_taken," ", "T").."Z".."</when>\n"
kml_file = kml_file.." </TimeStamp>\n"
kml_file = kml_file.." </Point>\n"
kml_file = kml_file.." </Placemark>\n"
end
end
-- Connects all images with an path
if ( dt.preferences.read("kml_export","CreatePath","bool") == true ) then
kml_file = kml_file.." <Placemark>\n"
kml_file = kml_file.." <name>Path</name>\n" -- ToDo: I think a better name would be nice
--kml_file = kml_file.." <description></description>\n"
kml_file = kml_file.." <Style>\n"
kml_file = kml_file.." <LineStyle>\n"
kml_file = kml_file.." <color>ff0000ff</color>\n"
kml_file = kml_file.." <width>5</width>\n"
kml_file = kml_file.." </LineStyle>\n"
kml_file = kml_file.." </Style>\n"
kml_file = kml_file.." <LineString>\n"
kml_file = kml_file.." <coordinates>\n"
for image,exported_image in spairs(image_table, function(t,a,b) return t[b] < t[a] end) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local altitude = 0;
if (image.elevation) then
altitude = image.elevation;
end
kml_file = kml_file.." "..string.gsub(tostring(image.longitude),",", ".")..","..string.gsub(tostring(image.latitude),",", ".")..",altitude\n"
end
local image_title, image_description
if (image.title and image.title ~= "") then
image_title = ds.escape_xml_characters(image.title)
else
image_title = filename..extension
end
kml_file = kml_file.." </coordinates>\n"
kml_file = kml_file.." </LineString>\n"
-- Characters should not be escaped in CDATA, but we are using HTML fragment, so we must escape them
image_description = ds.escape_xml_characters(image.description)
kml_file = kml_file.." <name>"..image_title.."</name>\n"
kml_file = kml_file.." <description>"..image_description.."</description>\n"
kml_file = kml_file.." <Style>\n"
kml_file = kml_file.." <IconStyle>\n"
kml_file = kml_file.." <Icon>\n"
kml_file = kml_file.." <href>"..imageFoldername.."thumb_"..filename..".jpg</href>\n"
kml_file = kml_file.." </Icon>\n"
kml_file = kml_file.." </IconStyle>\n"
kml_file = kml_file.." <BalloonStyle>\n"
kml_file = kml_file.." <text><![CDATA[<p><b>"..image_title.."</b></p><img src=\""..imageFoldername..filename..extension.."\"><p>"..image_description.."</p>]]></text>\n"
kml_file = kml_file.." <textColor>ff000000</textColor>\n"
kml_file = kml_file.." <displayMode>default</displayMode>\n"
kml_file = kml_file.." </BalloonStyle>\n"
kml_file = kml_file.." </Style>\n"
kml_file = kml_file.." <Point>\n"
kml_file = kml_file.." <extrude>1</extrude>\n"
kml_file = kml_file.." <coordinates>"..string.gsub(tostring(image.longitude),",", ".")..","..string.gsub(tostring(image.latitude),",", ".")..",0</coordinates>\n"
kml_file = kml_file.." <TimeStamp>\n"
kml_file = kml_file.." <when>"..string.gsub(image.exif_datetime_taken," ", "T").."Z".."</when>\n"
kml_file = kml_file.." </TimeStamp>\n"
kml_file = kml_file.." </Point>\n"
kml_file = kml_file.." </Placemark>\n"
end
end
kml_file = kml_file.."</Document>\n"
kml_file = kml_file.."</kml>"
-- Connects all images with an path
if ( dt.preferences.read("kml_export","CreatePath","bool") == true ) then
kml_file = kml_file.." <Placemark>\n"
kml_file = kml_file.." <name>Path</name>\n" -- ToDo: I think a better name would be nice
--kml_file = kml_file.." <description></description>\n"
local file = io.open(exportDirectory.."/"..exportKMLFilename, "w")
file:write(kml_file)
file:close()
kml_file = kml_file.." <Style>\n"
kml_file = kml_file.." <LineStyle>\n"
kml_file = kml_file.." <color>ff0000ff</color>\n"
kml_file = kml_file.." <width>5</width>\n"
kml_file = kml_file.." </LineStyle>\n"
kml_file = kml_file.." </Style>\n"
dt.print("KML file created in "..exportDirectory)
kml_file = kml_file.." <LineString>\n"
kml_file = kml_file.." <coordinates>\n"
-- Compress the files to create a KMZ file
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then
exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string")
local createKMZCommand = "zip --test --move --junk-paths "
createKMZCommand = createKMZCommand .."\""..exportDirectory.."/"..exportKMZFilename.."\" " -- KMZ filename
createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir.."/"..exportKMLFilename.."\" " -- KML file
for image,exported_image in pairs(image_table) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local filename = string.upper(string.gsub(image.filename,"%.%w*", ""))
createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir.."/"..imageFoldername.."thumb_"..filename..".jpg\" " -- thumbnails
createKMZCommand = createKMZCommand .."\""..exported_image.."\" " -- images
end
for image,exported_image in du.spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local altitude = 0;
if (image.elevation) then
altitude = image.elevation;
end
dt.control.execute(createKMZCommand)
kml_file = kml_file.." "..string.gsub(tostring(image.longitude),",", ".")..","..string.gsub(tostring(image.latitude),",", ".")..",altitude\n"
end
end
-- Open the file with the standard programm
if ( dt.preferences.read("kml_export","OpenKmlFile","bool") == true ) then
local kmlFileOpenCommand
kml_file = kml_file.." </coordinates>\n"
kml_file = kml_file.." </LineString>\n"
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then
kmlFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportKMZFilename.."\""
else
kmlFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportKMLFilename.."\""
end
dt.control.execute(kmlFileOpenCommand)
kml_file = kml_file.." </Placemark>\n"
end
kml_file = kml_file.."</Document>\n"
kml_file = kml_file.."</kml>"
local file = io.open(exportDirectory..PS..exportKMLFilename, "w")
file:write(kml_file)
file:close()
dt.print("KML file created in "..exportDirectory)
-- Compress the files to create a KMZ file
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true
and dt.configuration.running_os == "linux") then
exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string")
local createKMZCommand = "zip --test --move --junk-paths "
createKMZCommand = createKMZCommand .."\""..exportDirectory..PS..exportKMZFilename.."\" " -- KMZ filename
createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir..PS..exportKMLFilename.."\" " -- KML file
for image,exported_image in pairs(image_table) do
if ((image.longitude and image.latitude) and
(image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data
) then
local filename = string.upper(string.gsub(image.filename,"%.%w*", ""))
-- Handle duplicates
filename = addDuplicateIndex( image.duplicate_index, filename )
createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir..PS..imageFoldername.."thumb_"..filename..".jpg\" " -- thumbnails
createKMZCommand = createKMZCommand .."\""..exported_image.."\" " -- images
end
end
dt.control.execute(createKMZCommand)
end
-- Open the file with the standard programm
if ( dt.preferences.read("kml_export","OpenKmlFile","bool") == true ) then
local path
if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true
and dt.configuration.running_os == "linux") then
path = exportDirectory..PS..exportKMZFilename
else
path = exportDirectory..PS..exportKMLFilename
end
dsys.launch_default_app(df.sanitize_filename(path))
end
end
-- Preferences
dt.preferences.register("kml_export",
"OpenKmlFile",
"bool",
_("KML export: Open KML/KMZ file after export"),
_("Opens the KML file after the export with the standard program for KML files"),
false )
if dt.configuration.running_os == "windows" then
dt.preferences.register("kml_export",
"OpenKmlFile",
"bool",
_("KML export: Open KML file after export"),
_("Opens the KML file after the export with the standard program for KML files"),
false )
else
dt.preferences.register("kml_export",
"OpenKmlFile",
"bool",
_("KML export: Open KML/KMZ file after export"),
_("Opens the KML file after the export with the standard program for KML files"),
false )
end
local defaultDir = ''
if dt.configuration.running_os == "windows" then
defaultDir = os.getenv("USERPROFILE")
elseif dt.configuration.running_os == "macos" then
defaultDir = os.getenv("home")
else
local handle = io.popen("xdg-user-dir DESKTOP")
defaultDir = handle:read()
handle:close()
end
local handle = io.popen("xdg-user-dir DESKTOP")
local result = handle:read()
handle:close()
dt.preferences.register("kml_export",
"ExportDirectory",
"directory",
_("KML export: Export directory"),
_("A directory that will be used to export the KML/KMZ files"),
result )
dt.preferences.register("kml_export",
"CreatePath",
"bool",
_("KML export: Connect images with path"),
_("connect all images with a path"),
false )
"ExportDirectory",
"directory",
_("KML export: Export directory"),
_("A directory that will be used to export the KML/KMZ files"),
defaultDir )
if dt.configuration.running_os ~= "linux" then
dt.preferences.register("kml_export",
"magickPath", -- name
"file", -- type
_("KML export: ImageMagick binary Location"), -- label
_("Install location of magick[.exe]. Requires restart to take effect."), -- tooltip
"magick") -- default
end
dt.preferences.register("kml_export",
"CreateKMZ",
"bool",
_("KML export: Create KMZ file"),
_("Compress all imeges to one KMZ file"),
true )
"CreatePath",
"bool",
_("KML export: Connect images with path"),
_("connect all images with a path"),
false )
if dt.configuration.running_os == "linux" then
dt.preferences.register("kml_export",
"CreateKMZ",
"bool",
_("KML export: Create KMZ file"),
_("Compress all imeges to one KMZ file"),
true )
end
-- Register
dt.register_storage("kml_export", _("KML/KMZ Export"), nil, create_kml_file)
if dt.configuration.running_os == "windows" then
dt.register_storage("kml_export", _("KML Export"), nil, create_kml_file)
else
dt.register_storage("kml_export", _("KML/KMZ Export"), nil, create_kml_file)
end
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: hl Lua;

View File

@@ -0,0 +1,95 @@
--[[
Passport cropping guide for darktable
copyright (c) 2017 Kåre Hampf
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 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.
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"
* (optional) add the line:
"plugins/darkroom/clipping/extra_aspect_ratios/passport 36x47mm=47:36"
to $CONFIGDIR/darktablerc
USAGE
* when using the cropping tool, select "passport" as guide and if you added the line in yout rc
select "passport 36x47mm" as aspect
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext
du.check_min_api_version("2.0.0", "passport_guide")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("passport_guide",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("passport_guide", msgid)
end
dt.guides.register_guide("passport",
-- draw
function(cairo, x, y, width, height, zoom_scale)
local _width, _height
-- get the max 36x47 rectangle
local aspect_ratio = 47 / 36
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 / 36, _height / 47)
-- the outer rectangle
cairo:rectangle( 0, 0, 36, 47)
-- vertical bars
cairo:draw_line(16.5, 8, 16.5, 36)
cairo:draw_line(19.5, 8, 19.5, 36)
-- long horisontal bars
cairo:draw_line(6, 4, 30, 4)
cairo:draw_line(6, 40, 30, 40)
-- short horisontal bars
cairo:draw_line(9, 6, 27, 6)
cairo:draw_line(9, 38, 27, 38)
cairo:restore()
end,
-- gui
function()
return dt.new_widget("label"){label = _("ISO 19794-5/ICAO 9309 passport"), halign = "start"}
end
)
-- 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

248
contrib/pdf_slideshow.lua Normal file
View File

@@ -0,0 +1,248 @@
--[[
This file is part of darktable,
copyright (c) 2017 Pascal Obry
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/>.
]]
--[[
SELECTION_TO_PDF
Generates a PDF slideshow (via Latex) containing all selected images
one per slide.
ADDITIANAL SOFTWARE NEEDED FOR THIS SCRIPT
* a PDF-Viewer
* pdflatex (Latex)
USAGE
* require this file from your main lua config file:
This plugin will add a new exporter that will allow you to generate a pdf slideshow.
The interface will let you add:
- a global title for the slideshow (prefix in all slide label)
- a delay for the transition between each slide
Each slide will contain a single picture with a label at the bottom with the
format (all fields can be the empty string):
<global title> / <image creator> / <image title>
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("pdf_slideshow",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("pdf_slideshow", msgid)
end
if not df.check_if_bin_exists("pdflatex") then
dt.print_error(_("pdflatex not found"))
return
end
du.check_min_api_version("4.0.0", "pdf_slideshow")
dt.preferences.register
("pdf_slideshow","open with","string",
_("a pdf viewer"),
_("can be an absolute pathname or the tool may be in the PATH"),
"xdg-open")
local title_widget = dt.new_widget("entry") {
placeholder=_("slideshow title")
}
local delay_widget = dt.new_widget("slider")
{
label = _("transition delay (s)"),
soft_min = 1, -- The soft minimum value for the slider, the slider can't go beyond this point
soft_max = 20, -- The soft maximum value for the slider, the slider can't go beyond this point
hard_min = 1, -- The hard minimum value for the slider, the user can't manually enter a value beyond this point
hard_max = 60, -- The hard maximum value for the slider, the user can't manually enter a value beyond this point
value = 5, -- The current value of the slider
step = 1,
digits = 0
}
local check_button_title = dt.new_widget("check_button")
{
label = _('include image title'),
value = true,
tooltip = _('whether to include the image title (if defined) into the slide')
}
local check_button_author = dt.new_widget("check_button")
{
label = _('include image author'),
value = true,
tooltip = _('whether to include the image author (if defined) into the slide')
}
local widget = dt.new_widget("box") {
orientation=horizontal,
dt.new_widget("label"){label = _("slideshow title")},
title_widget,
dt.new_widget("label"){label = ""},
delay_widget,
check_button_author,
check_button_title
}
local ending = [[
\end{document}
]]
-- only jpeg files are supported by latex
local function support_format(storage, format)
fmt = string.lower(format.name)
if string.match(fmt,"jpeg") ~= nil and string.match(fmt,"2000") == nil then
return true
else
return false
end
end
local filename =dt.configuration.tmp_dir.."/pdfout.tex"
local function my_write(latexfile,arg)
local res,errmsg = latexfile:write(arg)
if not res then
error(errmsg)
end
end
local function img_filename(file,i)
local dir=string.gsub(file, "(.*)(%/.*%..*)", "%1")
local ext=string.gsub(file, "(.*)(%..*)", "%2")
local lfile = dir.."/slide"..i..ext
return lfile
end
local function slide(latexfile,i,image,file)
local title = ""
if title_widget.text ~= "" then
title = title_widget.text
end
if check_button_author.value == true and image.creator ~= "" then
if title ~= "" then
title = title.." / "
end
title = title..image.creator
end
if check_button_title.value == true and image.title ~= "" then
if title ~= "" then
title = title.." / "
end
title = title..image.title
end
-- make sure there is no non-printable characters in the title
title = string.gsub(title, "\n", "")
title = string.gsub(title, "\r", "")
title = string.gsub(title, "\t", "")
local lfile = img_filename(file,i)
os.rename(file, lfile)
my_write(latexfile,"\\begin{figure}\n")
my_write(latexfile,"\\begin{measuredfigure}\n")
my_write(latexfile,"\\includegraphics[height=0.92\\textheight]{"..lfile.."}\n")
my_write(latexfile,"\\end{measuredfigure}\n")
my_write(latexfile,"\\caption{{\\color{white}"..title.."}}\n")
my_write(latexfile,"\\end{figure}\n")
end
dt.register_storage("pdf_slideshow",_("pdf slideshow"),
nil,
function(storage,image_table)
local preamble = [[
\documentclass[a4paper,12pt,landscape]{beamer}
\usetheme{default}
\usepackage[utf8]{inputenc}
\usepackage{graphicx}
\usepackage{threeparttable}
\usepackage[space]{grffile} % needed to support filename with spaces
\pagestyle{empty}
\parindent0pt
\usepackage{geometry}
\usepackage{color}
\pagecolor{black!100}
\color{white}
\geometry{a4paper,landscape,left=2mm,right=2mm, top=2mm, bottom=2mm}
\mode<presentation>
\transduration{]]..string.gsub(delay_widget.value, ",", ".")..[[}
\setbeamertemplate{footline}{}
\setbeamertemplate{headline}{}
\setbeamertemplate{frametitle}{}
\setbeamertemplate{navigation symbols}{}
\setbeamercolor{background canvas}{fg=white,bg=black!100}
\setbeamercolor{normal text}{fg=white}
\hypersetup{pdfstartpage=1,pdfpagemode=FullScreen}
\setbeamertemplate{caption}{\insertcaption}
\begin{document}
]]
local errmsg
local latexfile
latexfile,errmsg=io.open(filename,"w")
if not latexfile then
error(errmsg)
end
my_write(latexfile,preamble)
local i = 1
for img,file in pairs(image_table) do
slide(latexfile,i,img,file)
my_write(latexfile,"\n\\bigskip\n")
i = i+1
end
my_write(latexfile,ending)
latexfile:close()
-- convert to PDF
local dir=string.gsub(filename, "(.*/)(.*)", "%1")
local locfile=string.gsub(filename, "(.*/)(.*)", "%2")
local command = "pdflatex -halt-on-error -output-directory "..dir.." "..locfile
local result = dt.control.execute(command)
if result ~= 0 then
dt.print(_("problem running pdflatex")) -- this one is probably usefull to the user
error(_("problem running ")..command)
end
-- open the PDF
local pdffile=string.gsub(filename, ".tex", ".pdf")
command = dt.preferences.read("pdf_slideshow","open with","string")
command = command.." "..pdffile
local result = dt.control.execute(command)
if result ~= 0 then
dt.print(_("problem running pdf viewer")) -- this one is probably usefull to the user
error(_("problem running ")..command)
end
-- finally do some clean-up
local i = 1
for img,file in pairs(image_table) do
local lfile = img_filename(file,i)
os.remove(lfile)
i = i+1
end
end,
support_format,
nil,
widget)
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

252
contrib/quicktag.lua Normal file
View File

@@ -0,0 +1,252 @@
--[[
This file is part of darktable,
Copyright (C) 2017 by Christian Kanzian
Thanks to
Copyright (C) 2016 Bill Ferguson
Copyright (C) 2017 Tobias Jakobs
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/>.
]]
--[[
QUICKTAG
For faster attaching your favorite tags, the script adds shortcuts and
the module "quicktag" in lighttable mode with a changeable number of buttons.
To each button a tag can be assigned. If the tags do not exist in your database,
they are added to the database once they get the first time attached to an image.
The number of buttons/shortcuts can be changed in the lua preferences.
Changes in the number require a restart of darktable.
INSTALATION
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory
* add the following line in the file $CONFIGDIR/luarc require "quicktag"
USAGE
* set the number of quicktags between 2 and 10 in the preferences dialog and restart darktable
* if wanted set the shortcuts in the preferences dialog
* to add or change a quicktag, first select the old tag with the combobox "old quicktag",
enter a new tag in the "new quicktag" filed and press "set quicktag"
* use a shortcut or button to attach the tag to selected images
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local debug = require "darktable.debug"
local gettext = dt.gettext
du.check_min_api_version("3.0.0", "quicktag")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("quicktag", msgid)
end
-- maximum length of button labels
dt.preferences.register("quickTag",
"labellength",
"integer", -- type
_("max. length of button labels"), -- label
_("may range from 15 to 60 - needs a restart"), -- tooltip
25, -- default
15, -- min
60)
local max_label_length = dt.preferences.read("quickTag", "labellength", "integer")
-- register number of quicktags
dt.preferences.register("quickTag",
"quickTagnumber",
"integer", -- type
_("number of quicktag fields"), -- label
_("may range from 2 to 20 - needs a restart"), -- tooltip
5, -- default
2, -- min
20)
local qnr = dt.preferences.read("quickTag", "quickTagnumber", "integer")
-- get quicktags from the preferences
local quicktag_table = {}
local function read_pref_tags()
for j=1,qnr do
quicktag_table[j] = dt.preferences.read("quickTag", "quicktag"..j, "string")
end
end
local quicktag_label = {}
local function abbrevate_tags(t)
for i=1,qnr do
local taglength = string.len(t[i])
if taglength > max_label_length then
local max_2 = math.floor(max_label_length/2)
quicktag_label[i] = string.sub(t[i],0,max_2).."..."..string.sub(t[i],taglength - max_2)
else
quicktag_label[i] = t[i]
end
-- print(quicktag_label[i])
end
end
-- quicktag function to attach tags
local function tagattach(tag,qtagnr)
if tag == "" then
dt.print(string.format(_("quicktag %i is empty, please set a tag"), qtagnr))
return true
end
local tagnr = dt.tags.find(tag)
--create tag if it does not exist
if tagnr == nil then
dt.tags.create(tag)
tagnr = dt.tags.find(tag)
end
local sel_images = dt.gui.action_images
if next(sel_images) == nil then
dt.print(_("no images selected"))
return true
end
local counter = 0
for _,image in ipairs(sel_images) do
dt.tags.attach(tagnr,image)
counter = counter+1
end
dt.print(string.format(_("tag \"%s\" attached to %i image(s)"), tag, counter))
end
-- create quicktag module elements
-- read tags from preferences for initialization
read_pref_tags()
abbrevate_tags(quicktag_table)
local button = {}
-- function to create buttons with tags as labels
for j=1,qnr do
button[#button+1] = dt.new_widget("button") {
label = j..": "..quicktag_table[j],
clicked_callback = function() tagattach(tostring(quicktag_table[j]),j) end}
end
local old_quicktag = dt.new_widget("combobox"){
label = _("old tag"),
tooltip = _("select the quicktag to replace")
}
local function update_quicktag_list()
-- abrevate long tags for the labels
abbrevate_tags(quicktag_table)
for j=1,qnr do
button[j].label = j..": "..quicktag_label[j]
old_quicktag[j] = j..": "..quicktag_table[j]
end
end
update_quicktag_list()
local new_quicktag = dt.new_widget("entry"){
text = "",
placeholder = _("new tag"),
is_password = true,
editable = true,
tooltip = _("enter your tag here")
}
local set_quicktag_button = dt.new_widget("button") {
label = _("set tag"),
clicked_callback = function()
local old_tag = quicktag_table[old_quicktag.selected]
if new_quicktag.text == "" then
dt.print(_("new quicktag is empty!"))
else
quicktag_table[old_quicktag.selected] = new_quicktag.text
dt.preferences.write("quickTag", "quicktag"..old_quicktag.selected, "string", new_quicktag.text)
dt.print(string.format(_("quicktag \"%s\" replaced by \"%s\""), old_tag, new_quicktag.text))
update_quicktag_list()
new_quicktag.text = ""
end
end,
}
local new_qt_widget = dt.new_widget ("box") {
orientation = "horizontal",
dt.new_widget("label") { label = _("new tag") },
new_quicktag,
set_quicktag_button
}
-- back UI elements in a table
-- thanks to wpferguson for the hint
local widget_table = {}
for i=1,qnr do
widget_table[#widget_table + 1] = button[i]
end
widget_table[#widget_table + 1] = dt.new_widget("separator"){}
widget_table[#widget_table + 1] = old_quicktag
widget_table[#widget_table + 1] = new_qt_widget
--create module
dt.register_lib(
"quicktag", -- Module name
"quicktag", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}},
dt.new_widget("box"){
orientation = "vertical",
table.unpack(widget_table),
},
nil,-- view_enter
nil -- view_leave
)
-- create shortcuts
for i=1,qnr do
dt.register_event("shortcut",
function(event, shortcut) tagattach(tostring(quicktag_table[i])) end,
string.format(_("quicktag %i"),i))
end
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;

View File

@@ -39,6 +39,10 @@
]]
local dt = require "darktable"
local du = require "lib/dtutils"
-- added version check
du.check_min_api_version("3.0.0", "rate_group")
local function apply_rating(rating)
local images = dt.gui.action_images
@@ -81,4 +85,4 @@ end, "Rate group 4")
dt.register_event("shortcut",function(event, shortcut)
apply_rating(5)
end, "Rate group 5")
end, "Rate group 5")

125
contrib/rename-tags.lua Normal file
View File

@@ -0,0 +1,125 @@
--[[
Copyright (c) 2016 Sebastian Witt <se.witt@gmx.net>
Copyright (c) 2018 Bill Ferguson <wpferguson@gmail.com>
]]
--[[
Rename tags
AUTHOR
Sebastian Witt (se.witt@gmx.net)
INSTALLATION
* copy this file in $CONFIGDIR/lua/ where CONFIGDIR
is your darktable configuration directory
* add the following line in the file $CONFIGDIR/luarc
require "rename-tags"
USAGE
* In lighttable there is a new entry: 'rename tag'
* Enter old tag (this one gets deleted!)
* Enter new tag name
LICENSE
GPLv2
Changes
* 20180912 - Did an RTFM and read a bug (12277, thanks Christian G) that showed
a way to get the images containing the old tag one by one instead of searching
the entire database. Changed rename_tags() function to use this method.
]]
local darktable = require "darktable"
local du = require "lib/dtutils"
local debug = require "darktable.debug"
-- check API version
du.check_min_api_version("3.0.0", "rename-tags")
-- GUI entries
local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" }
local new_tag = darktable.new_widget("entry") { tooltip = "Enter new tag" }
local function rename_reset()
old_tag.text = ''
new_tag.text = ''
end
-- This function does the renaming
local function rename_tags()
-- If entries are empty, return
if old_tag.text == '' then
darktable.print ("Old tag can't be empty")
return
end
if new_tag.text == '' then
darktable.print ("New tag can't be empty")
return
end
local count = 0
-- Check if old tag exists
local ot = darktable.tags.find (old_tag.text)
if not ot then
darktable.print ("Old tag does not exist")
return
end
-- Show job
local job = darktable.gui.create_job ("Renaming tag", true)
old_tag.editable = false
new_tag.editable = false
-- Create if it does not exists
local nt = darktable.tags.create (new_tag.text)
-- Get number of images for old tag
local dbcount = #ot
-- loop through the images containing the old tag, and attach the new tag
for i=1, #ot do
-- Update progress bar
job.percent = i / dbcount
ot[i]:attach_tag(nt)
count = count + 1
end
-- Delete old tag, this removes it from all images
darktable.tags.delete (ot)
job.valid = false
darktable.print ("Renamed tags for " .. count .. " images")
old_tag.editable = true
new_tag.editable = true
-- reset the gui fields
rename_reset()
end
-- GUI
local old_widget = darktable.new_widget ("box") {
orientation = "horizontal",
darktable.new_widget("label") { label = "Old tag" },
old_tag
}
local new_widget = darktable.new_widget ("box") {
orientation = "horizontal",
darktable.new_widget("label") { label = "New tag" },
new_tag
}
local rename_widget = darktable.new_widget ("box") {
orientation = "vertical",
reset_callback = rename_reset,
old_widget,
new_widget,
darktable.new_widget("button") { label = "Go", clicked_callback = rename_tags }
}
darktable.register_lib ("rename_tags", "rename tag", true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rename_widget, nil, nil)

View File

@@ -0,0 +1,65 @@
--[[
This file is part of darktable,
copyright (c) 2017 Jannis_V
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 untagged images (darktable|* tags are ignored)
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local gettext = dt.gettext
du.check_min_api_version("3.0.0", "select_untagged")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("select_untagged",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("select_untagged", msgid)
end
local function stop_job(job)
job.valid = false
end
local function select_untagged_images()
job = dt.gui.create_job(_("select untagged images"), true, stop_job)
local selection = {}
for key,image in ipairs(dt.collection) do
if(job.valid) then
job.percent = (key-1)/#dt.collection
local tags = dt.tags.get_tags(image)
local hasTags = false
for _,tag in ipairs(tags) do
if not string.match(tag.name,"darktable|") then
hasTags = true
end
end
if hasTags == false then
table.insert(selection,image)
end
else
break
end
end
job.valid = false
dt.gui.selection(selection)
end
dt.gui.libs.select.register_selection(_("select untagged"),select_untagged_images,_("select all images containing no tags or only tags added by darktable"))

View File

@@ -23,40 +23,30 @@ You need rhythmbox-client installed to use this script
USAGE
* require this script from your main lua file
]]
local dt = require "darktable"
require "official/yield"
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
dt.configuration.check_version(...,{2,0,2},{3,0,0})
du.check_min_api_version("2.0.2", "slideshowMusic")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("slideshowMusic", msgid)
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (not result) then
dt.print_error(bin.." not found")
ret = false
end
ret = true
return ret
end
local function playSlideshowMusic(_, old_view, new_view)
local filename, playMusic
filename = dt.preferences.read("slideshowMusic","SlideshowMusic","string")
playMusic = dt.preferences.read("slideshowMusic","PlaySlideshowMusic","bool")
if not checkIfBinExists("rhythmbox-client") then
if not df.check_if_bin_exists("rhythmbox-client") then
dt.print_error(_("rhythmbox-client not found"))
return
end
@@ -65,14 +55,14 @@ local function playSlideshowMusic(_, old_view, new_view)
if (new_view.id == "slideshow") then
playCommand = 'rhythmbox-client --play-uri="'..filename..'"'
--dt.print_error(playCommand)
dt.control.execute( playCommand)
dt.control.execute( playCommand)
else
if (old_view and old_view.id == "slideshow") then
stopCommand = "rhythmbox-client --pause"
--dt.print_error(stopCommand)
dt.control.execute( stopCommand)
dt.control.execute( stopCommand)
end
end
end
@@ -80,7 +70,7 @@ end
-- Preferences
dt.preferences.register("slideshowMusic", "SlideshowMusic", "file", _("Slideshow background music file"), "", "")
dt.preferences.register("slideshowMusic",
dt.preferences.register("slideshowMusic",
"PlaySlideshowMusic",
"bool",
_("Play slideshow background music"),

460
contrib/video_ffmpeg.lua Normal file
View File

@@ -0,0 +1,460 @@
--[[Timelapse video plugin based on ffmpeg for darktable 2.4.X and 2.6.X
copyright (c) 2018, 2019 Dominik Markiewicz
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/>.
]]
--[[About this plugin
This plugin will add the new export module "video ffmpeg".
----REQUIRED SOFTWARE----
ffmpeg
----USAGE----
1. Go to Lighttable
2. Select images you want to use as a video ffmpeg frames
3. In image export module select "video ffmpeg"
4. Configure you video settings
5. Export
----WARNING----
This script has been tested under Linux only
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dsys = require "lib/dtutils.system"
local gettext = dt.gettext
du.check_min_api_version("5.0.0")
local MODULE_NAME = "video_ffmpeg"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS)
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
end
---- DECLARATIONS
local resolutions = {
["QVGA"] = {
["label"] = "QVGA 320x240 (4:3)",
["w"] = 320,
["h"] = 240
},
["HVGA"] = {
["label"] = "HVGA 480x320 (3:2)",
["w"] = 480,
["h"] = 320
},
["VGA"] = {
["label"] = "VGA 640x480 (4:3)",
["w"] = 640,
["h"] = 480
},
["HDTV 720p"] = {
["label"] = "HDTV 720p 1280x720 (16:9)",
["w"] = 1280,
["h"] = 720
},
["HDTV 1080p"] = {
["label"] = "HDTV 1080p 1920x1080 (16:9)",
["w"] = 1920,
["h"] = 1080
},
["Cinema TV"] = {
["label"] = "Cinema TV 2560x1080 (21:9)",
["w"] = 2560,
["h"] = 1080
},
["2K"] = {
["label"] = "2K 2048x1152 (16:9)",
["w"] = 2048,
["h"] = 1152
},
["4K"] = {
["label"] = "4K 4096x2304 (16:9)",
["w"] = 4096,
["h"] = 2304
}
}
local framerates = {"15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60"}
local formats = {
["AVI"] = {
["extension"] = "avi",
["codecs"] = {"mpeg4", "h263p", "h264", "mpeg2video", "hevc", "vp9"}
},
["Matroska"] = {
["extension"] = "mkv",
["codecs"] = {"h264", "h263p", "mpeg4", "mpeg2video", "hevc", "vp9"}
},
["WebM"] = {
["extension"] = "webm",
["codecs"] = {"vp9", "h263p", "h264", "mpeg4", "mpeg2video", "hevc"}
},
["MP4"] = {
["extension"] = "mp4",
["codecs"] = {"h264", "h263p", "mpeg4", "mpeg2video"}
},
["QuickTime"] = {
["extension"] = "mov",
["codecs"] = {"h264", "h263p", "mpeg4", "mpeg2video"}
}
}
local res_list = {}
for i, v in pairs(resolutions) do
table.insert(res_list, v["label"])
end
table.sort(res_list)
-- inital fulfill list of all available formats
local format_list = {}
for i,v in pairs(formats) do
table.insert(format_list, i)
end
table.sort(format_list)
-- initial fulfill list of all available codecs for the first (default if nothig saved yet) format
-- if format was changed and stored in preferences, this list will be replaced by one matching selected format
local codec_list = formats[format_list[1]]["codecs"]
table.sort(codec_list)
local function extract_resolution(description)
for _, v in pairs(resolutions) do
if v["label"] == description then
return v["w"].."x"..v["h"]
end
end
return "100x100"
end
---- GENERIC UTILS
local function replace_combobox_elements(combobox, new_items, to_select)
if to_select == nil then
to_select = combobox.value
end
to_select_idx = 1
for i , name in ipairs(new_items) do
if name == to_select then
to_select_idx = i
break
end
end
local old_elements_count = #combobox
for i, name in ipairs(new_items) do
combobox[i] = name
end
if old_elements_count > #new_items then
for j = old_elements_count, #new_items + 1, -1 do
combobox[j] = nil
end
end
combobox.value = to_select_idx
end
--[[
This function allow to format string by substitute tokens like `{label}` by value provided in `symbols` table
ex:
```lua
format_string("I like {bananas} and {potatos}. {bananas}!", {["bananas"]: "darktable", ["potatos"]: "lua"})
-> "I like darktable and lua. darktable!"
```
I you want to preserve given currly braces, you need to escape it by backslash like so:
```lua
format_string("it preserve \\{label\\} but substitute {label}", {["label"]: "this"})
-> "It preserve {label} but substitute "
```
--]]
local function format_string(label, symbols)
local es1, es2 = "\u{ffe0}", "\u{ffe1}" -- for simplicity, just some strange utf characters
local result = label:gsub("\\{", es1):gsub("\\}", es2)
for s,v in pairs(symbols) do
result = result:gsub("{"..s.."}", v)
end
return result:gsub(es1, "{"):gsub(es2, "}")
end
----- COMPONENTS
local function combobox_pref_read(name, all_values)
local value = dt.preferences.read(MODULE_NAME, name, "string")
for i,v in pairs(all_values) do
if v == value then return i end
end
return 1
end
local function combobox_pref_write(name)
local writer = function(widget)
dt.preferences.write(MODULE_NAME, name, "string", widget.value)
end
return writer
end
local function string_pref_read(name, default)
local value = dt.preferences.read(MODULE_NAME, name, "string")
if value ~= nil and value ~= "" then return value end
return default
end
local function string_pref_write(name, widget_attribute)
widget_attribute = widget_attribute or "value"
local writer = function(widget)
dt.preferences.write(MODULE_NAME, name, "string", widget[widget_attribute])
end
return writer
end
local framerates_selector = dt.new_widget("combobox"){
label = _("framerate"),
tooltip = _("select framerate of output video"),
value = combobox_pref_read("framerate", framerates),
changed_callback = combobox_pref_write("framerate"),
table.unpack(framerates)
}
local res_selector = dt.new_widget("combobox"){
label = _("resolution"),
tooltip = _("select resolution of output video"),
value = combobox_pref_read("resolution", res_list),
changed_callback = combobox_pref_write("resolution"),
table.unpack(res_list)
}
local codec_selector = dt.new_widget("combobox"){
label = _("codec"),
tooltip = _("select codec"),
value = combobox_pref_read("codec", codec_list),
changed_callback = combobox_pref_write("codec"),
table.unpack(codec_list)
}
local format_selector = dt.new_widget("combobox"){
label = _("format container"),
tooltip = _("select format of output video"),
value = combobox_pref_read("format", format_list),
changed_callback = function(widget)
combobox_pref_write("format")(widget)
codec_list = formats[widget.value]["codecs"]
table.sort(codec_list)
replace_combobox_elements(codec_selector, codec_list)
end,
table.unpack(format_list)
}
local destination_label = dt.new_widget("section_label"){
label = _("output file destination"),
tooltip = _("settings of output file destination and name")
}
local defaultVideoDir = ''
if dt.configuration.running_os == "windows" then
defaultVideoDir = os.getenv("USERPROFILE")..PS .."videos"
elseif dt.configuration.running_os == "macos" then
defaultVideoDir = os.getenv("home")..PS.."Videos"
else
local handle = io.popen("xdg-user-dir VIDEOS")
defaultVideoDir = handle:read()
handle:close()
end
local output_directory_chooser = dt.new_widget("file_chooser_button"){
title = _("Select export path"),
is_directory = true,
tooltip =_("select the target directory for the timelapse. \nthe filename is created automatically."),
value = string_pref_read("export_path", defaultVideoDir),
changed_callback = string_pref_write("export_path")
}
local auto_output_directory_btn = dt.new_widget("check_button") {
label = "",
tooltip = _("if selected, output video will be placed in the same directory as first of selected images"),
value = not dt.preferences.read(MODULE_NAME, "not_auto_output_directory", "bool"), -- reverse, for true as default
clicked_callback = function (widget)
dt.preferences.write(MODULE_NAME, "not_auto_output_directory", "bool", not widget.value)
output_directory_chooser.sensitive = not output_directory_chooser.sensitive
end
}
local destination_box = dt.new_widget("box") {
orientation = "horizontal",
auto_output_directory_btn,
output_directory_chooser
}
local override_output_cb = dt.new_widget("check_button"){
label = _("override output file on conflict"),
tooltip = _("if checked, in case of file name conflict, the file will be overwritten"),
value = dt.preferences.read(MODULE_NAME, "override_output", "bool"),
clicked_callback = function (widget)
dt.preferences.write(MODULE_NAME, "override_output", "bool", widget.value)
end
}
local filename_entry = dt.new_widget("entry"){
tooltip = _("enter output file name without extension.\n\n"..
"You can use some placeholders:\n"..
"- {time} - time in format HH-mm-ss\n"..
"- {date} - date in foramt YYYY-mm-dd\n"..
"- {first_file} - name of first input file\n"..
"- {last_file} - name of last last_file"
),
text = string_pref_read("filename_entry","timelapse_{date}_{time}"),
-- changed_callback = string_pref_write("filename_entry", "text") -- Not imlemented yet
}
local output_box = dt.new_widget("box"){
orientation="vertical",
destination_label,
override_output_cb,
destination_box,
filename_entry,
}
local open_after_export_cb = dt.new_widget("check_button"){
label = _(" open after export"),
tooltip = _("open video file after successful export"),
value = dt.preferences.read(MODULE_NAME, "open_after_export", "bool"),
clicked_callback = function (widget)
dt.preferences.write(MODULE_NAME, "open_after_export", "bool", widget.value)
end
}
local widgets_list = {}
if not df.check_if_bin_exists("ffmpeg") then
table.insert(widgets_list, df.executable_path_widget({"ffmpeg"}))
end
table.insert(widgets_list, res_selector)
table.insert(widgets_list, framerates_selector)
table.insert(widgets_list, format_selector)
table.insert(widgets_list, codec_selector)
table.insert(widgets_list, output_box)
table.insert(widgets_list, open_after_export_cb)
local module_widget = dt.new_widget("box") {
orientation = "vertical",
table.unpack(widgets_list)
}
---- EXPORT & REGISTRATION
local function show_status(enf_storage, image, format, filename, number, total, high_quality, extra_data)
dt.print(_("export ")..tostring(number).." / "..tostring(total))
end
local function init_export(storage, img_format, images, high_quality, extra_data)
-- store filename preference here cause there is no changed_callback on entry yet
string_pref_write("filename_entry", "text")(filename_entry)
extra_data["images"] = images -- needed, to preserve images order
extra_data["tmp_dir"] = dt.configuration.tmp_dir..PS..MODULE_NAME.."_"..os.time()
extra_data["fps"] = framerates_selector.value
extra_data["res"] = extract_resolution(res_selector.value)
extra_data["codec"] = codec_selector.value
extra_data["img_ext"] = "."..img_format.extension
local override_output = override_output_cb.value
local output_directory = auto_output_directory_btn.value and images[1].path or output_directory_chooser.value
local filename_mappings = {
date = os.date("%Y-%m-%d"),
time = os.date("%H-%M-%S"),
first_file = images[1].filename,
last_file = images[#images].filename
}
local output_extension = "."..formats[format_selector.value]["extension"]
local filename = format_string(filename_entry.text, filename_mappings)
local path = output_directory..PS..filename..output_extension
if not override_output then
path = df.create_unique_filename(path)
end
extra_data["output_file"] = path
extra_data["open_after_export"] = open_after_export_cb.value
end
local function export(extra_data)
local ffmpeg_path = df.check_if_bin_exists("ffmpeg")
if not ffmpeg_path then
dt.print_error("ffmpeg not found")
dt.print("ERROR - ffmpeg not found")
return
end
local dir = extra_data["tmp_dir"]
local fps = extra_data["fps"]
local res = extra_data["res"]
local codec = extra_data["codec"]
local img_ext = extra_data["img_ext"]
local output_file = extra_data["output_file"]
local dir_create_result = df.mkdir(df.sanitize_filename(df.get_path(output_file)))
if dir_create_result ~= 0 then return dir_create_result end
local cmd = ffmpeg_path.." -y -r "..fps.." -i "..dir..PS.."%d"..img_ext.." -s:v "..res.." -c:v "..codec.." -crf 18 -preset veryslow "..df.sanitize_filename(output_file)
return dsys.external_command(cmd), output_file
end
local function finalize_export(storage, images_table, extra_data)
local tmp_dir = extra_data["tmp_dir"]
dt.print(_("prepare merge process"))
local result = df.mkdir(df.sanitize_filename(tmp_dir))
if result ~= 0 then dt.print(_("ERROR: cannot create temp directory")) end
local images = extra_data["images"]
-- rename all images to consecutive numbers
for i, file in pairs(images) do
local filename = images_table[file]
dt.print_error(filename, file.filename)
df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"])
end
dt.print("Start video building...")
local result, path = export(extra_data)
if result ~= 0 then
dt.print(_("ERROR: cannot build image, see console for more info"))
else
dt.print(_("SUCCESS"))
if extra_data["open_after_export"] then
dsys.launch_default_app(df.sanitize_filename(path))
end
end
df.rmdir(df.sanitize_filename(tmp_dir))
end
dt.register_storage(
"module_video_ffmpeg",
_(MODULE_NAME),
show_status,
finalize_export,
nil,
init_export,
module_widget
)

View File

@@ -31,38 +31,39 @@ USAGE
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
require "official/yield"
local gettext = dt.gettext
dt.configuration.check_version(...,{2,0,1})
du.check_min_api_version("2.0.1", "video_mencoder")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("video_mencoder",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("video_mencoder", msgid)
end
local function show_status(storage, image, format, filename, number, total, high_quality, extra_data)
dt.print("Export Image "..tostring(number).."/"..tostring(total))
end
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (result) then
--dt.print_error("true checkIfBinExists: "..bin)
ret = true
else
dt.print_error(bin.." not found")
ret = false
end
return ret
end
local function create_video_mencoder(storage, image_table, extra_data)
if not checkIfBinExists("mencoder") then
dt.print("video_mencoder is deprecated. Please use video_ffmpeg instead. Please see the log for more details.")
dt.print_log("The mencoder executable is no longer distributed as part of most distributions,")
dt.print_log("therefore video_mencoder is deprecated. video_ffmpeg should be used in it's place. video_mencoder")
dt.print_log("will be removed when darktable 2.8 is released.")
if not df.check_if_bin_exists("mencoder") then
dt.print_error(_("mencoder not found"))
return
end
if not checkIfBinExists("xdg-open") then
if not df.check_if_bin_exists("xdg-open") then
dt.print_error(_("xdg-open not found"))
return
end
if not checkIfBinExists("xdg-user-dir") then
if not df.check_if_bin_exists("xdg-user-dir") then
dt.print_error(_("xdg-user-dir not found"))
return
end
@@ -111,6 +112,9 @@ dt.preferences.register("video_mencoder", "OpenVideo", "bool", "Video exort (MEn
local handle = io.popen("xdg-user-dir VIDEOS")
local result = handle:read()
handle:close()
if (result == nil) then
result = ""
end
dt.preferences.register("video_mencoder", "ExportDirectory", "directory", "Video exort (MEncoder): Video export directory","A directory that will be used to export a Video",result)
-- Get the MEncoder codec list with: mencoder -ovc help

View File

@@ -53,7 +53,10 @@ LUA ERROR Hallo Welt!
]]
local dt = require "darktable"
dt.configuration.check_version(...,{3,0,0})
local du = require "lib/dtutils"
--check API version
du.check_min_api_version("3.0.0", "gettextExample")
-- Not translated Text
dt.print_error("Hello World!")
@@ -64,7 +67,7 @@ dt.print_error(gettext.gettext("image"))
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("gettextExample",dt.configuration.config_dir.."/lua/")
gettext.bindtextdomain("gettextExample",dt.configuration.config_dir.."/lua/locale/")
-- Translate a string using the specified textdomain
dt.print_error(gettext.dgettext("gettextExample", 'Hello World!'))

View File

@@ -27,7 +27,9 @@ USAGE
]]
local dt = require "darktable"
dt.configuration.check_version(...,{2,0,0},{3,0,0})
local du = require "lib/dtutils"
du.check_min_api_version("2.0.0", "hello_world")
dt.print("hello, world")

View File

@@ -31,8 +31,9 @@ https://www.darktable.org/lua-api/index.html.php#darktable_new_widget
]]
local dt = require "darktable"
local du = require "lib/dtutils"
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("3.0.0", "moduleExample")
-- add a new lib
local check_button = dt.new_widget("check_button"){label = "MyCheck_button", value = true}
@@ -70,33 +71,69 @@ local slider = dt.new_widget("slider")
value = 52 -- The current value of the slider
}
dt.register_lib(
"exampleModule", -- Module name
"exampleModule", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
dt.new_widget("box") -- widget
if (dt.configuration.api_version_major >= 6) then
local section_label = dt.new_widget("section_label")
{
orientation = "vertical",
dt.new_widget("button")
label = "MySectionLabel"
}
dt.register_lib(
"exampleModule", -- Module name
"exampleModule", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
dt.new_widget("box") -- widget
{
label = "MyButton",
clicked_callback = function (_)
dt.print("Button clicked")
end
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,
section_label
},
check_button,
combobox,
entry,
file_chooser_button,
label,
separator,
slider
},
nil,-- view_enter
nil -- view_leave
)
nil,-- view_enter
nil -- view_leave
)
else
dt.register_lib(
"exampleModule", -- Module name
"exampleModule", -- name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
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
)
end
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua
-- kate: hl Lua;

244
examples/multi_os.lua Normal file
View File

@@ -0,0 +1,244 @@
--[[
This file is part of darktable,
copyright (c) 2017,2018 Bill Ferguson <wpferguson@gmail.com>
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/>.
]]
--[[
multi_os - an example script that runs on linux, MacOS, and Windows.
multi_os is an example of how to write a script that will run on different
operating systems. It uses the lua-scripts libraries to lessen the amount
of code that needs to be written, as well as gaining access to tested
cross-platform routines. This script also performs a function that some
may find useful. It creates a button in the lighttable selected images module
that extracts the embedded jpeg image from a raw file, then imports it and groups
it with the raw file. A keyboard shortcut is also created. A key combination can
be assigned to the shortcut in the lua preferences and then the action can be invoked
by hovering over the image and pressing the key combination.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* ufraw-batch - https://ufraw.sourceforge.net
MacOS - install with homebrew
USAGE
* require this script from your main lua file
* start darktable, open prefreences, go to lua options
and update the executable location if you are running
Windows or MacOS, then restart darktable.
* select an image or images
* click the button to extract the jpeg
BUGS, COMMENTS, SUGGESTIONS
* Send to Bill Ferguson, wpferguson@gmail.com
CHANGES
]]
--[[
require "darktable" provides the interface to the darktable lua functions used to
interact with darktable
]]
local dt = require "darktable"
--[[
require "lib/..." provides access to functions that have been pulled from various
scripts and consolidated into libraries. Using the libraries eliminates having to
recode common functions in every script.
]]
local du = require "lib/dtutils" -- utilities
local df = require "lib/dtutils.file" -- file utilities
local dtsys = require "lib/dtutils.system" -- system utilities
--[[
darktable is an international program, and it's user interface has been translated into
many languages. The lua API provides gettext which is a function that looks for and replaces
strings with the translated equivalents based on locale. Even if you don't provide the
translations, inserting this lays the groundwork for anyone who wants to translate the strings.
]]
local gettext = dt.gettext
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("multi_os",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("multi_os", msgid)
end
--[[
Check that the current api version is greater than or equal to the specified minimum. If it's not
then du.check_min_api_version will print an error to the log and return false. If the minimum api is
not met, then just refuse to load and return. Optionally, you could print an error message to the
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")
--[[
copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image
to the extracted jpeg image. Even though the subroutine could act on the variables in the main program
directly, passing them as arguments and using them in the subroutine is much safer.
]]
local function copy_image_attributes(from, to, ...)
local args = {...}
if #args == 0 then
args[1] = "all"
end
if args[1] == "all" then
args[1] = "rating"
args[2] = "colors"
args[3] = "exif"
args[4] = "meta"
args[5] = "GPS"
end
for _,arg in ipairs(args) do
if arg == "rating" then
to.rating = from.rating
elseif arg == "colors" then
to.red = from.red
to.blue = from.blue
to.green = from.green
to.yellow = from.yellow
to.purple = from.purple
elseif arg == "exif" then
to.exif_maker = from.exif_maker
to.exif_model = from.exif_model
to.exif_lens = from.exif_lens
to.exif_aperture = from.exif_aperture
to.exif_exposure = from.exif_exposure
to.exif_focal_length = from.exif_focal_length
to.exif_iso = from.exif_iso
to.exif_datetime_taken = from.exif_datetime_taken
to.exif_focus_distance = from.exif_focus_distance
to.exif_crop = from.exif_crop
elseif arg == "GPS" then
to.elevation = from.elevation
to.longitude = from.longitude
to.latitude = from.latitude
elseif arg == "meta" then
to.publisher = from.publisher
to.title = from.title
to.creator = from.creator
to.rights = from.rights
to.description = from.description
else
dt.print_error("Unrecognized option to copy_image_attributes: " .. arg)
end
end
end
--[[
The main function called from the button or the shortcut. It takes one or more raw files, passed
in a table and extracts the embedded jpeg images
]]
local function extract_embedded_jpeg(images)
--[[
check if the executable exists, since we can't do anything without it. check_if_bin_exists()
checks to see if there is a saved executable location. If not, it checks for the executable
in the user's path. When it finds the executable, it returns the command to run it.
]]
local ufraw_executable = df.check_if_bin_exists("ufraw-batch")
if ufraw_executable then
for _, image in ipairs(images) do
if image.is_raw then
local img_file = du.join({image.path, image.filename}, "/")
--[[
dtsys.external_command() is operating system aware and formats the command as necessary.
df.sanitize_filename() is used to quote the image filepath to protect from spaces. It is also
operating system aware and uses the corresponding quotes.
]]
if dtsys.external_command(ufraw_executable .. " --silent --embedded-image " .. df.sanitize_filename(img_file)) then
local jpg_img_file = df.chop_filetype(img_file) .. ".embedded.jpg"
dt.print_log("jpg_img_file set to ", jpg_img_file) -- print a debugging message
local myimage = dt.database.import(jpg_img_file)
myimage:group_with(image.group_leader)
copy_image_attributes(image, myimage, "all")
--[[
copy all of the tags except the darktable tags
]]
for _,tag in pairs(dt.tags.get_tags(image)) do
if not (string.sub(tag.name,1,9) == "darktable") then
dt.print_log("attaching tag")
dt.tags.attach(tag,myimage)
end
end
end
else
dt.print_error(image.filename .. " is not a raw file. No image can be extracted") -- print debugging error message
dt.print(image.filename .. " is not a raw file. No image can be extracted") -- print the error to the screen
end
end
else
dt.print_error("ufraw-batch not found. Exiting...") -- print debugging error message
dt.print("ufraw-batch not found. Exiting...") -- print the error to the screen
end
end
--[[
Windows and MacOS don't place executables in the user's path so their location needs to be specified
so that the script can find them. An exception to this is packages installed on MacOS with homebrew. Those
executables are put in /usr/local/bin. These are saved as executable path preferences. check_if_bin_exists()
looks for the executable path preference for the executable. If it doesn't find one, then the path is checked
to see if the executable is there.
]]
local executable = "ufraw-batch"
local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){
title = _("Select ufraw-batch[.exe] executable"),
value = df.get_executable_path_preference(executable),
is_directory = false,
changed_callback = function(self)
if df.check_if_bin_exists(self.value) then
df.set_executable_path_preference(executable, self.value)
end
end
}
dt.preferences.register("executable_paths", "ufraw-batch", -- name
"file", -- type
_('multi_os: ufraw-batch location'), -- label
_('Installed location of ufraw-batch. Requires restart to take effect.'), -- tooltip
"ufraw-batch", -- default
ufraw_batch_path_widget
)
--[[
Add a button to the selected images module in lighttable
]]
dt.gui.libs.image.register_action(
_("extract embedded jpeg"),
function(event, images) extract_embedded_jpeg(images) end,
"extract embedded jpeg"
)
--[[
Add a shortcut
]]
dt.register_event(
"shortcut",
function(event, shortcut) extract_embedded_jpeg(dt.gui.action_images) end,
"extract embedded jpeg"
)

View File

@@ -22,7 +22,9 @@ USAGE
* require this script from your main lua file
]]
local dt = require "darktable"
dt.configuration.check_version(...,{2,0,1},{3,0,0})
local du = require "lib/dtutils"
du.check_min_api_version("2.0.1", "preferenceExamples")
dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference.
"preferenceExamplesString", -- name

View File

@@ -0,0 +1,42 @@
--[[
This file is part of darktable,
copyright (c) 2017 Tobias Jakobs
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/>.
]]
--[[
Print Examples
prints "hello world when DT starts
USAGE
* require this file from your main lua config file:
]]
local dt = require "darktable"
local du = require "lib/dtutils"
du.check_min_api_version("5.0.0", "printExamples")
-- Will print a string to the darktable control log (the long
-- overlayed window that appears over the main panel).
dt.print("print")
-- This function will print its parameter if the Lua logdomain is
-- activated. Start darktable with the "-d lua" command line option
-- to enable the Lua logdomain.
dt.print_error("print error")
-- This function will print its parameter if the Lua logdomain is
-- activated. Start darktable with the "-d lua" command line option
-- to enable the Lua logdomain.
dt.print_log("print log")
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

36
examples/running_os.lua Normal file
View File

@@ -0,0 +1,36 @@
--[[
This file is part of darktable,
copyright (c) 2017 Tobias Jakobs
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/>.
]]
--[[
USAGE
* require this script from your main lua file
To do this add this line to the file .config/darktable/luarc:
require "running_os"
prints the operating system
]]
local dt = require "darktable"
local du = require "lib/dtutils"
du.check_min_api_version("5.0.0", "running_os")
dt.print("You are running: "..dt.configuration.running_os)
--
-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua

View File

@@ -1,6 +1,7 @@
--[[
This file is part of darktable,
copyright (c) 2014 Jérémy Rosen
copyright (c) 2018 Bill Ferguson
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,14 +34,20 @@ Note that you need to restart DT for your changes to enabled scripts to take eff
]]
local dt = require "darktable"
local io = require "io"
dt.configuration.check_version(...,{3,0,0})
local output = io.popen("cd "..dt.configuration.config_dir.."/lua ;find . -name \\*.lua -print")
-- must be loaded for scripts using darktable.control_execute to work
require "official/yield"
dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0})
-- find all scripts, but skip the lib and tools directories
local output = io.popen("cd "..dt.configuration.config_dir.."/lua ;find . -name lib -prune -o -name tools -prune -o -name \\*.lua -print")
local my_name={...}
my_name = my_name[1]
for line in output:lines() do
local req_name = line:sub(3,-5)
if req_name ~= my_name then
if req_name ~= my_name and not string.match(req_name, "yield") then
dt.preferences.register(my_name,req_name,"bool","enable "..req_name,
"Should the script "..req_name.." be enabled at next startup",false)

View File

@@ -32,7 +32,42 @@ local dt = require "darktable"
local log = require "lib/dtutils.log"
dt.configuration.check_version(...,{3,0,0})
dtutils.libdoc.functions["check_min_api_version"] = {
Name = [[check_min_api_version]],
Synopsis = [[check the minimum required api version against the current api version]],
Usage = [[local du = require "lib/dtutils"
local result = du.check_min_api_version(min_api, script_name)
min_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_min_api_version compares the minimum api required for the appllication to
run against the current api version. The minimum api version is typically the api version that
was current when the application was created. If the minimum 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.
This function is intended to replace darktable.configuration.check_version(). The application code
won't have to be updated each time the api changes because this only checks the minimum version required.]],
Return_Value = [[result - true if the minimum 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_min_api_version("5.0.0") does nothing if the api is greater or equal to 5.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_min_api_version(min_api, script_name)
local current_api = dt.configuration.api_version_string
if min_api > current_api then
dt.print_error("This application is written for lua api version " .. min_api .. " or later.")
dt.print_error("The current lua api version is " .. current_api)
dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. min_api .. " or later required.")
error("Minimum API " .. min_api .. " not met for " .. script_name .. ".", 0)
end
end
dtutils.libdoc.functions["split"] = {
Name = [[split]],
@@ -215,4 +250,37 @@ function dtutils.spairs(_table, order) -- Code copied from http://stackoverflow.
end
end
dtutils.libdoc.functions["check_os"] = {
Name = [[check_os]],
Synopsis = [[check that the operating system is supported]],
Usage = [[local du = require "lib/dtutils"
local result = du.check_os(operating_systems)
operating_systems - a table of operating system names such as {"windows","linux","macos","unix"}]],
Description = [[check_os checks a supplied table of operating systems against the operating system the
script is running on and returns true if the OS is in the list, otherwise false]],
Return_Value = [[result - boolean - true if the operating system is supported, false if not.]],
Limitations = [[]],
Example = [[local du = require "lib/dtutils"
if du.check_os({"windows"}) then
-- run the script
else
dt.print("Script <script name> only runs on windows")
return
end]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils.check_os(operating_systems)
for _,os in pairs(operating_systems) do
if dt.configuration.running_os == os then
return true
end
end
return false
end
return dtutils

View File

@@ -1,5 +1,8 @@
local dtutils_file = {}
local dt = require "darktable"
local du = require "lib/dtutils"
local ds = require "lib/dtutils.string"
local dsys = require "lib/dtutils.system"
local log = require "lib/dtutils.log"
@@ -23,7 +26,7 @@ dtutils_file.libdoc = {
local gettext = dt.gettext
dt.configuration.check_version(...,{3,0,0})
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/")
@@ -34,14 +37,18 @@ end
dtutils_file.libdoc.functions["check_if_bin_exists"] = {
Name = [[check_if_bin_exists]],
Synopsis = [[check if an executable is in the path]],
Synopsis = [[check if an executable exists]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.check_if_bin_exists(bin)
bin - string - the binary to check for]],
Description = [[check_if_bin_exists checks to see if the specified binary executable is
in the path.]],
Return_Value = [[result - boolean - true if the executable was found, false if not]],
Description = [[check_if_bin_exists checks to see if the specified binary exists.
check_if_bin_exists first checks to see if a preference for the binary has been
registered and uses that if found. The presence of the file is verified, then
quoted and returned. If no preference is specified and the operating system is
linux then the which command is used to check for a binary in the path. If found
that path is returned. If no binary is found, false is returned.]],
Return_Value = [[result - string - the sanitized path of the binary, false if not found]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
@@ -51,9 +58,30 @@ dtutils_file.libdoc.functions["check_if_bin_exists"] = {
}
function dtutils_file.check_if_bin_exists(bin)
local result = os.execute("which " .. bin)
if not result then
result = false
local result = false
local path = nil
if string.match(bin, "/") or string.match(bin, "\\") then
path = bin
else
path = dtutils_file.get_executable_path_preference(bin)
end
if string.len(path) > 0 then
if dtutils_file.check_if_file_exists(path) then
if (string.match(path, ".exe$") or string.match(path, ".EXE$")) and dt.configuration.running_os ~= "windows" then
result = dtutils_file.sanitize_filename("wine " .. path)
else
result = dtutils_file.sanitize_filename(path)
end
end
elseif dt.configuration.running_os == "linux" then
local p = io.popen("which " .. bin)
local output = p:read("*a")
p:close()
if string.len(output) > 0 then
result = dtutils_file.sanitize_filename(output:sub(1,-2))
end
end
return result
end
@@ -79,9 +107,14 @@ dtutils_file.libdoc.functions["split_filepath"] = {
function dtutils_file.split_filepath(str)
-- strip out single quotes from quoted pathnames
str = string.gsub(str, "'", "")
str = string.gsub(str, '"', '')
local result = {}
-- Thank you Tobias Jakobs for the awesome regular expression, which I tweaked a little
result["path"], result["filename"], result["basename"], result["filetype"] = string.match(str, "(.-)(([^\\/]-)%.?([^%.\\/]*))$")
if result["basename"] == "" and result["filetype"]:len() > 1 then
result["basename"] = result["filetype"]
result["filetype"] = ""
end
return result
end
@@ -194,10 +227,34 @@ dtutils_file.libdoc.functions["check_if_file_exists"] = {
}
function dtutils_file.check_if_file_exists(filepath)
local result = os.execute("test -e " .. filepath)
if not result then
result = false
local result = false
if (dt.configuration.running_os == 'windows') then
filepath = string.gsub(filepath, '[\\/]+', '\\')
local p = io.popen("if exist " .. filepath .. " (echo 'yes') else (echo 'no')")
local ans = p:read("*all")
p:close()
if string.match(ans, "yes") then
result = true
end
-- result = os.execute('if exist "'..filepath..'" (cmd /c exit 0) else (cmd /c exit 1)')
-- if not result then
-- result = false
-- end
elseif (dt.configuration.running_os == "linux") then
result = os.execute('test -e ' .. dtutils_file.sanitize_filename(filepath))
if not result then
result = false
end
else
local file = io.open(filepath, "r")
if file then
result = true
file:close()
else
result = false
end
end
return result
end
@@ -220,7 +277,11 @@ dtutils_file.libdoc.functions["chop_filetype"] = {
function dtutils_file.chop_filetype(path)
local length = dtutils_file.get_filetype(path):len() + 2
return string.sub(path, 1, -length)
if length > 2 then
return string.sub(path, 1, -length)
else
return path
end
end
dtutils_file.libdoc.functions["file_copy"] = {
@@ -245,9 +306,12 @@ dtutils_file.libdoc.functions["file_copy"] = {
function dtutils_file.file_copy(fromFile, toFile)
local result = nil
-- if cp exists, use it
if dtutils_file.check_if_bin_exists("cp") then
if dt.configuration.running_os == "windows" then
result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"')
elseif dtutils_file.check_if_bin_exists("cp") then
result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'")
end
-- if cp was not present, or if cp failed, then a pure lua solution
if not result then
local fileIn, err = io.open(fromFile, 'rb')
@@ -324,7 +388,7 @@ dtutils_file.libdoc.functions["filename_increment"] = {
"01" is added to the basename. If the filename already has an increment, then
1 is added to it and the filename returned.]],
Return_Value = [[result - string - the incremented filename]],
Limitations = [[]],
Limitations = [[The filename will be incremented to 99]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
@@ -347,6 +411,9 @@ function dtutils_file.filename_increment(filepath)
if string.len(increment) > 2 then
-- we got the filename so set the increment to 01
increment = "01"
elseif increment == "99" then
dt.print_error("not incrementing, filename has already been incremented 99 times.")
return filepath
else
increment = string.format("%02d", tonumber(increment) + 1)
basename = string.gsub(basename, "_(%d-)$", "")
@@ -384,11 +451,182 @@ function dtutils_file.create_unique_filename(filepath)
while dtutils_file.check_if_file_exists(filepath) do
filepath = dtutils_file.filename_increment(filepath)
-- limit to 99 more exports of the original export
if string.match(dtfileutils.get_basename(filepath), "_(d-)$") == "99" then
break
local increment = string.match(dtutils_file.get_basename(filepath), "_(%d-)$")
if increment == "99" then
break
end
end
return filepath
end
dtutils_file.libdoc.functions["set_executable_path_preference"] = {
Name = [[set_executable_path_preference]],
Synopsis = [[set a preference for the path to an executable]],
Usage = [[local df = require "lib/dtutils.file"
df.set_executable_path_preference(executable, path)
executable - string - the name of the executable to set the path for
path - string - the path to the binary]],
Description = [[set_executable_path_preference takes an executable name and path to the
executable and registers the preference for later use.]],
Return_Value = [[]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.set_executable_path_preference(executable, path)
dt.preferences.write("executable_paths", executable, "string", path)
end
dtutils_file.libdoc.functions["get_executable_path_preference"] = {
Name = [[get_executable_path_preference]],
Synopsis = [[return the path to an executable from a preference]],
Usage = [[local df = require "lib/dtutils.file"
local result = df.get_executable_path_preference(executable)
executable - string - the name of the executable to get the path for]],
Description = [[get_executable_path_preference returns the path preference to
the requested executable.]],
Return_Value = [[result - string - path to the executable]],
Limitations = [[executable should be the basename of the executable without extensions]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.get_executable_path_preference(executable)
return dt.preferences.read("executable_paths", executable, "string")
end
dtutils_file.libdoc.functions["executable_path_widget"] = {
Name = [[executable_path_widget]],
Synopsis = [[create a widget to get executable path preferences]],
Usage = [[local df = require "lib/dtutils.file"
local widget = df.executable_path_widget(executables)
executables - table - a table of strings that are 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.
The resulting widgets are wrapped in a box widget and returned.]],
Return_Value = [[widget - widget - a widget containing a file selector widget for
each executable.]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.executable_path_widget(executables)
local box_widgets = {}
table.insert(box_widgets, dt.new_widget("section_label"){label = "select executable(s)"})
for _, executable in pairs(executables) do
table.insert(box_widgets, dt.new_widget("label"){label = "select " .. executable .. " executable"})
local path = dtutils_file.get_executable_path_preference(executable)
if not path then
path = ""
end
table.insert(box_widgets, dt.new_widget("file_chooser_button"){
title = "select " .. executable .. " executable",
value = path,
is_directory = false,
changed_callback = function(self)
if dtutils_file.check_if_bin_exists(self.value) then
dtutils_file.set_executable_path_preference(executable, self.value)
end
end}
)
end
local box = dt.new_widget("box"){
orientation = "vertical",
table.unpack(box_widgets)
}
return box
end
dtutils_file.libdoc.functions["sanitize_filename"] = {
Name = [[sanitize_filename]],
Synopsis = [[make a filename safe to pass as an argument]],
Usage = [[local df = require "lib/dtutils.file"
local sanitized_filename = df.sanitize_filename(filename)
filename - string - a filepath and filename]],
Description = [[sanitize_file places quotes around the filename in an
operating system specific manner. The result is safe to pass as
an argument to the operating system.]],
Return_Value = [[sanitized_filename - string - quoted filename]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.sanitize_filename(filename)
return ds.sanitize(filename)
end
dtutils_file.libdoc.functions["mkdir"] = {
Name = [[mkdir]],
Synopsis = [[create the directory(ies) if they do not already exists]],
Usage = [[local df = require "lib/dtutils.file"
df.mkdir(path)
path - string - a directory path]],
Description = [[mkdir creates directories if not already exists. It
create whole parents subtree if needed
]],
Return_Value = [[path - string - a directory path]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.mkdir(path)
if not dtutils_file.check_if_file_exists(path) then
local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p"
return dsys.external_command(mkdir_cmd.." "..path)
else
return 0
end
end
dtutils_file.libdoc.functions["rmdir"] = {
Name = [[rmdir]],
Synopsis = [[recursively remove a directory]],
Usage = [[local df = require "lib/dtutils.file"
df.rmdir(path)
path - string - a directory path]],
Description = [[rm allow to recursively remove directories]],
Return_Value = [[path - string - a directory path]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_file.rmdir(path)
local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r"
return dsys.external_command(rm_cmd.." "..path)
end
return dtutils_file

View File

@@ -48,8 +48,7 @@ dtutils_log.libdoc = {
local dt = require "darktable"
local dt_print_error = dt.print_error
-- work around until dt.print_log()
local dt_print_log = dt.print_error
local dt_print_log = dt.print_log
local dt_print = dt.print
-- set the default log levels
@@ -243,6 +242,7 @@ function dtutils_log.log_level(...)
for _,v in ipairs(levels) do
if dtutils_log[v].enabled == true then
log_level = dtutils_log[v]
break
end
end
end

View File

@@ -1,4 +1,7 @@
local dtutils_string = {}
local dt = require "darktable"
dtutils_string.libdoc = {
Name = [[dtutils.string]],
Synopsis = [[a library of string utilities for use in darktable lua scripts]],
@@ -164,10 +167,68 @@ dtutils_string.libdoc.functions["urlencode"] = {
function dtutils_string.urlencode(str)
if (str) then
str = string.gsub (str, "\n", "\r\n")
str = string.gsub (str, "([^%w ])", function () return string.format ("%%%02X", string.byte()) end)
str = string.gsub (str, "([^%w ])", function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
end
return str
end
dtutils_string.libdoc.functions["sanitize"] = {
Name = [[sanitize]],
Synopsis = [[surround a string in quotes making it safe to pass as an argument]],
Usage = [[local ds = require "lib/dtutils.string"
local result = ds.sanitize(str)
str - string - the string that needs to be made safe]],
Description = [[sanitize converts a string into a version suitable for
use passing as an argument in a system command.]],
Return_Value = [[result - string - a websafe string]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_string.sanitize(str)
local result = ""
local os_quote = dt.configuration.running_os == "windows" and '"' or "'"
if dtutils_string.is_not_sanitized(str) then
result = os_quote .. str .. os_quote
end
return result
end
dtutils_string.libdoc.functions["is_not_sanitized"] = {
Name = [[is_not_sanitized]],
Synopsis = [[Check if a string has been sanitized]],
Usage = [[local ds = require "lib/dtutils.string"
local result = ds.is_not_sanitized(str)
str - string - the string that needs to be made safe]],
Description = [[is_not_sanitized checks a string to see if it
has been made safe use passing as an argument in a system command.]],
Return_Value = [[result - boolean - true if the string is not sanitized otherwise false]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_string.is_not_sanitized(str)
local os_quote = dt.configuration.running_os == "windows" and '"' or "'"
if string.match(str, os_quote .. ".*" .. os_quote) then
return false
else
return true
end
end
return dtutils_string

133
lib/dtutils/system.lua Normal file
View File

@@ -0,0 +1,133 @@
local dtutils_system = {}
local dt = require "darktable"
require "official/yield" -- necessary for dt.control.execute()
dtutils_system.libdoc = {
Name = [[dtutils.system]],
Synopsis = [[a library of system utilities for use in darktable lua scripts]],
Usage = [[local ds = require "lib/dtutils.system"]],
Description = [[This library contains routines for interfacing to the operating system from
darktable lua scripts.]],
Return_Value = [[du - library - the darktable lua system library]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[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/>.]],
Copyright = [[Copyright (c) 2018 Bill Ferguson <wpferguson@gmail.com>]],
functions = {}
}
dtutils_system.libdoc.functions["external_command"] = {
Name = [[external_command]],
Synopsis = [[pass a command to the operating system for execution and return the result]],
Usage = [[local dsys = require "lib/dtutils.system"
local result = dsys.external_command(command)
command - string - a string containing the command and arguments to be passed to the operating system for execution.]],
Description = [[external_command passes a command to the operating system for execution and returns the results.]],
Return_Value = [[result - the return value signalling success or failure.]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_system.external_command(command)
local result = nil
if dt.configuration.running_os == "windows" then
result = dtutils_system.windows_command(command)
else
result = dt.control.execute(command)
end
return result
end
dtutils_system.libdoc.functions["windows_command"] = {
Name = [[windows_command]],
Synopsis = [[pass a command to the windows operating system for execution and return the result]],
Usage = [[local dsys = require "lib/dtutils.system"
local result = dsys.windows_command(command)
command - string - a string containing the command and arguments to be passed to the operating system for execution.]],
Description = [[The normal method of executing an operating system command is using dt.control.execute(), but that doesn't
work with Windows when more than one item in the command is quoted. In order to ensure command execution on Windows we
create a batch file in the temporary directory, put the command in it, execute the batch file, then return the result.]],
Return_Value = [[result - the return value signalling success or failure.]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_system.windows_command(command)
local result = 1
local fname = dt.configuration.tmp_dir .. "/run_command.bat"
local file = io.open(fname, "w")
if file then
dt.print_log("opened file")
command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell
file:write(command)
file:close()
result = dt.control.execute(fname)
dt.print_log("result from windows command was " .. result)
os.remove(fname)
else
dt.print_error("Windows command failed: unable to create batch file")
end
return result
end
dtutils_system.libdoc.functions["launch_default_app"] = {
Name = [[launch_default_app]],
Synopsis = [[open file in default application]],
Usage = [[local dsys = require "lib/dtutils.file"
result = dsys.launch_default_app(path)
path - string - a file path]],
Description = [[launch_default_app allows opening a file in the application that is assigned as default
for that filetype in the users's system]],
Return_Value = [[result - the return value signalling success or failure.]],
Limitations = [[]],
Example = [[]],
See_Also = [[]],
Reference = [[]],
License = [[]],
Copyright = [[]],
}
function dtutils_system.launch_default_app(path)
local open_cmd = "xdg-open "
if (dt.configuration.running_os == "windows") then
open_cmd = "" -- On Windows we don't need any command. (start e.g. has problems with spaces in the filename, even if we put quoter around them.) https://stackoverflow.com/questions/13691827/opening-file-with-spaces-in-windows-via-command-prompt
elseif (dt.configuration.running_os == "macos") then
open_cmd = "open "
end
return dtutils_system.external_command(open_cmd .. path)
end
return dtutils_system

View File

@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-01-31 22:59+0100\n"
"PO-Revision-Date: 2016-01-31 23:09+0100\n"
"PO-Revision-Date: 2018-09-18 10:22+0200\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: Tobias Jakobs <tobias.jakobs@gmail.com>\n"
"Last-Translator: Simon Legner <Simon.Legner@gmail.com>\n"
"Language-Team: \n"
"X-Generator: Poedit 1.8.4\n"
"X-Generator: Poedit 2.1.1\n"
#: hugin.lua:73
msgid "hugin not found"
@@ -27,7 +27,7 @@ msgstr "Versuche Bilder zusammenzufügen"
#: hugin.lua:102
msgid "Creating pto file"
msgstr "Erzeuge pto Datei"
msgstr "Erzeuge pto-Datei"
#: hugin.lua:105
msgid "Running Assistent"
@@ -39,4 +39,4 @@ msgstr "Aufruf von hugin fehlgeschlagen ..."
#: hugin.lua:124
msgid "Hugin Panorama"
msgstr "Hugin Panorama"
msgstr "Hugin-Panorama"

View File

@@ -2,45 +2,45 @@ msgid ""
msgstr ""
"Project-Id-Version: Lua gettextTest\n"
"POT-Creation-Date: 2016-01-22 15:33+0100\n"
"PO-Revision-Date: 2016-01-22 23:28+0100\n"
"Last-Translator: Tobias Jakobs <tobias.jakobs@gmail.com>\n"
"PO-Revision-Date: 2018-09-18 10:21+0200\n"
"Last-Translator: Simon Legner <Simon.Legner@gmail.com>\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"
"X-Generator: Poedit 1.8.4\n"
"X-Generator: Poedit 2.1.1\n"
"X-Poedit-Basepath: .\n"
"Plural-Forms: nplurals=2;t" plural=(n != 1);\n"
"Plural-Forms: nplurals=2;t\" plural=(n != 1);\n"
"X-Poedit-KeywordsList: gettext;dgettext:2;dcgettext:2;ngettext:1,2;dngettext:2,3\n"
"X-Poedit-SearchPath-0: .\n"
msgid "KML/KMZ Export"
msgstr "KML/KMZ Expor
msgstr "KML/KMZ-Export"
msgid "Export Image %i/%i"
msgstr "Exportiere Bild %i/%i"
msgid "KML export: Open KML/KMZ file after export"
msgstr "KML export: KML/KMZ-Datei nach dem exportieren öffnen"
msgstr "KML-Export KML/KMZ-Datei nach dem Export öffnen"
msgid "Opens the KML file after the export with the standard programm for KML files"
msgstr "Öffnet die KML-Datei mit dem Standardprogram für KML-Dateien"
msgid "KML export: Export directory"
msgstr "KML export: Export-Verzeichnis"
msgstr "KML-Export: Export-Verzeichnis"
msgid "A directory that will be used to export the KML/KMZ files"
msgstr "Verzeichnis in das die KML/KMZ-Dateien exportiert werden"
msgid "KML export: Connect images with path"
msgstr "KML export: Bilder mit einer Linie verbinden"
msgstr "KML-Export: Bilder mit einer Linie verbinden"
msgid "connect all images with a path"
msgstr "Verbinde alle Bilder mit einer Linie"
msgid "KML export: Create KMZ file"
msgstr "KML export: KMZ-Datei erstellen"
msgstr "KML-Export: KMZ-Datei erstellen"
msgid "Compress all imeges to one KMZ file"
msgstr "Alle Bilder in einer KMZ-Datei komprimieren"

View File

@@ -0,0 +1,84 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Christian Kanzian <christian.kanzian@gmx.at>, 2017.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-20 17:38+0100\n"
"PO-Revision-Date: 2017-02-20 17:50+0100\n"
"Last-Translator: Christian Kanzian <christian.kanzian@gmx.at>\n"
"Language-Team: German <kde-i18n-de@kde.org>\n"
"Language: de\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 1.5\n"
#: quicktag.lua:64
msgid "max. length of button labels"
msgstr "Max. Länge der Schaltflächen-Beschriftung"
#: quicktag.lua:65
msgid "may range from 15 to 60 - needs a restart"
msgstr "Bereich von 15 bis 60 möglich - Neustart erforderlich"
#: quicktag.lua:78
msgid "number of quicktag fields"
msgstr "Anzahl von quicktag Feldern"
#: quicktag.lua:79
msgid "may range from 2 to 20 - needs a restart"
msgstr "Bereich von 2 bis 20 möglich - Neustart erforderlich"
#: quicktag.lua:119
#, lua-format
msgid "quicktag %i is empty, please set a tag"
msgstr "quicktag %i ist leer - bitte Tag eingeben"
#: quicktag.lua:134
msgid "no images selected"
msgstr "keine Bilder ausgewählt"
#: quicktag.lua:144
#, lua-format
msgid "tag \"%s\" attached to %i image(s)"
msgstr "Tag \"%s\" an %i Bild(er) angehängt"
#: quicktag.lua:165
msgid "old quicktag"
msgstr "Alter quicktag"
#: quicktag.lua:166
msgid "select the quicktag to replace"
msgstr "Wähle den quicktag der ersetzt werden soll"
#: quicktag.lua:182 quicktag.lua:206
msgid "new quicktag"
msgstr "Neuer quicktag"
#: quicktag.lua:185
msgid "enter your tag here"
msgstr "Bitte Tag hier eingeben"
#: quicktag.lua:189
msgid "set quicktag"
msgstr "Setze Tag"
#: quicktag.lua:193
msgid "new quicktag is empty!"
msgstr "Neuer Tag ist leer!"
#: quicktag.lua:197
#, lua-format
msgid "quicktag \"%s\" replaced by \"%s\""
msgstr "quicktag \"%s\" ersetzt durch \"%s\" "
#: quicktag.lua:246
#, lua-format
msgid "quicktag %i"
msgstr "quicktag %i"

Binary file not shown.

View File

@@ -0,0 +1,27 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2017-09-14 12:18+0200\n"
"PO-Revision-Date: 2017-09-14 12:23+0200\n"
"Last-Translator: Jannis_V\n"
"Language-Team: \n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.7.1\n"
"X-Poedit-Basepath: ../../../contrib\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SearchPath-0: select_untagged.lua\n"
#: select_untagged.lua:38
msgid "select untagged images"
msgstr "Auswahl von Bildern ohne Tags"
#: select_untagged.lua:64
msgid "select untagged"
msgstr "Ohne Tags"
#: select_untagged.lua:64
msgid "select all images containing no tags or only tags added by darktable"
msgstr "Wähle alle Bilder der aktuellen Sammlung aus, die keine Tags oder nur automatisch durch darktable erzeugte Tags enthalten"

View File

@@ -0,0 +1,70 @@
# 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.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-04-10 09:30+0200\n"
"PO-Revision-Date: 2017-04-10 09:48+0200\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: Pascal Obry <pascal@obry.net>\n"
"Language-Team: \n"
"X-Generator: Poedit 1.8.11\n"
#: pdf_slideshow.lua:55
msgid "pdflatex not found"
msgstr "pdflatex non trouvé"
#: pdf_slideshow.lua:63
msgid "a pdf viewer"
msgstr "lecteur pdf"
#: pdf_slideshow.lua:64
msgid "can be an absolute pathname or the tool may be in the PATH"
msgstr "peut être un chemin absolu où l'outil peut être dans le chemin par défaut"
#: pdf_slideshow.lua:68 pdf_slideshow.lua:95
msgid "slideshow title"
msgstr "titre du diaporama"
#: pdf_slideshow.lua:72
msgid "transition delay (s)"
msgstr "temporisation (s) entre diapositives"
#: pdf_slideshow.lua:83
msgid "include image title"
msgstr "inclus le titre de l'image"
#: pdf_slideshow.lua:85
msgid "whether to include the image title (if defined) into the slide"
msgstr "indique si le titre de l'image (si défini) doit être inclus dans la diapositive"
#: pdf_slideshow.lua:89
msgid "include image author"
msgstr "inclus l'auteur de l'image"
#: pdf_slideshow.lua:91
msgid "whether to include the image author (if defined) into the slide"
msgstr "indique si l'auteur de l'image (si défini) doit être inclus dans la diapositive"
#: pdf_slideshow.lua:167
msgid "pdf slideshow"
msgstr "diaporama pdf"
#: pdf_slideshow.lua:220
msgid "problem running pdflatex"
msgstr "impossible d'exécuter pdflatex"
#: pdf_slideshow.lua:221 pdf_slideshow.lua:231
msgid "problem running "
msgstr "impossible d'exécuter "
#: pdf_slideshow.lua:230
msgid "problem running pdf viewer"
msgstr "impossible d'exécuter le lecteur pdf"

Binary file not shown.

View File

@@ -0,0 +1,27 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2017-09-14 15:42+0200\n"
"PO-Revision-Date: 2017-09-14 15:52+0200\n"
"Last-Translator: Jannis_V\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.7.1\n"
"X-Poedit-Basepath: ../../../contrib\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SearchPath-0: select_untagged.lua\n"
#: select_untagged.lua:38
msgid "select untagged images"
msgstr "sélection des images sans mots-clés"
#: select_untagged.lua:64
msgid "select untagged"
msgstr "sans mots-clés"
#: select_untagged.lua:64
msgid "select all images containing no tags or only tags added by darktable"
msgstr "sélection des images sans mots-clés ou seulement avec les mots-clés ajoutés par darktable"

View File

@@ -28,10 +28,11 @@ USAGE
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local https = require "ssl.https"
local cjson = require "cjson"
dt.configuration.check_version(...,{2,0,0},{3,0,0})
du.check_min_api_version("2.0.0", "check_for_updates")
-- compare two version strings of the form "major.minor.patch"
-- returns -1, 0, 1 if the first version is smaller, equal, greater than the second version,

View File

@@ -26,8 +26,9 @@ USAGE
]]
local dt = require "darktable"
local du = require "lib/dtutils"
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("3.0.0", "copy_paste_metadata")
-- set this to "false" if you don't want to overwrite metadata fields
-- (title, description, creator, publisher and rights) that are already set

View File

@@ -1,6 +1,6 @@
--[[
This file is part of darktable,
copyright (c) 2014 Tobias Ellinghaus
copyright (c) 2014--2018 Tobias Ellinghaus
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
]]
--[[
DELETE LONG TAGS
A simple script that will automatically delete all tag longer than a set length
A simple script that will automatically delete all tags longer than a set length
USAGE
* require this script from your main lua file
@@ -29,7 +29,9 @@ all tags longer than the given length will be automatically deleted at every res
]]
local dt = require "darktable"
dt.configuration.check_version(...,{2,0,0},{3,0,0})
local du = require "lib/dtutils"
du.check_min_api_version("2.0.0", "delete_long_tags")
dt.preferences.register("delete_long_tags", "length", "integer",
"maximum length of tags to keep",
@@ -38,10 +40,18 @@ dt.preferences.register("delete_long_tags", "length", "integer",
local max_length = dt.preferences.read("delete_long_tags", "length", "integer")
-- deleting while iterating the tags list seems to break the iterator!
local long_tags = {}
for _,t in ipairs(dt.tags) do
local len = #t.name
if len > max_length then
print("deleting tag `"..t.name.."' (length: "..len..")")
t:delete()
table.insert(long_tags, t.name)
end
end
for _,name in pairs(long_tags) do
tag = dt.tags.find(name)
tag:delete()
end

View File

@@ -0,0 +1,47 @@
--[[
This file is part of darktable,
copyright (c) 2018 Tobias Ellinghaus
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/>.
]]
--[[
DELETE UNUSED TAGS
A simple script that will automatically delete all tags that are not attached to any images
USAGE
* require this script from your main lua file
* restart darktable
all tags that are not used will be automatically deleted at every restart
]]
local dt = require "darktable"
local du = require "lib/dtutils"
du.check_min_api_version("5.0.0", "delete_unused_tags")
-- deleting while iterating the tags list seems to break the iterator!
local unused_tags = {}
for _, t in ipairs(dt.tags) do
if #t == 0 then
table.insert(unused_tags, t.name)
end
end
for _,name in pairs(unused_tags) do
print("deleting tag `" .. name .. "'")
tag = dt.tags.find(name)
tag:delete()
end

View File

@@ -17,7 +17,7 @@
]]
--[[
RUN ENFUSE ON THE SELECTED IMAGES
This script uses enfuse to merge the selected images into one HDR and imports the result.
This script uses enfuse to merge the selected images into one tonemapped image and imports the result.
It only works on ldr images (like, JPEG).
USAGE
@@ -33,46 +33,77 @@ TODO
]]
local dt = require "darktable"
require "official/yield"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
dt.configuration.check_version(...,{3,0,0})
local gettext = dt.gettext
-- thanks Tobias Jakobs for this function (taken from contrib/hugin.lua)
local function checkIfBinExists(bin)
local handle = io.popen("which "..bin)
local result = handle:read()
local ret
handle:close()
if (result) then
dt.print_error("true checkIfBinExists: "..bin)
ret = true
else
dt.print_error(bin.." not found")
ret = false
end
return ret
du.check_min_api_version("3.0.0", "enfuse")
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("enfuse",dt.configuration.config_dir..PS .. "lua" .. PS .. "locale" .. PS)
local function _(msgid)
return gettext.dgettext("enfuse", msgid)
end
-- add a new lib
-- is enfuse installed?
local enfuse_installed = checkIfBinExists("enfuse")
local enfuse_installed = df.check_if_bin_exists("enfuse")
-- instance of DT tiff exporter
local tiff_exporter = dt.new_format("tiff")
tiff_exporter.bpp = 16
tiff_exporter.max_height = 0
tiff_exporter.max_width = 0
-- check the version so that we can use the correct arguments
local version = nil
local p = io.popen(enfuse_installed .. " --version")
local f = p:read("all")
p:close()
version = string.match(f, "enfuse (%d.%d)")
dt.print_log("enfuse version is " .. version)
-- initialize exposure_mu value and depth setting in config to sane defaults (would be 0 otherwise)
if dt.preferences.read("enfuse", "depth", "integer") == 0 then
dt.preferences.write("enfuse", "depth", "integer", 2)
dt.preferences.write("enfuse", "exposure_mu", "float", 0.5)
dt.preferences.write("enfuse", "exposure_optimum", "float", 0.5)
end
-- set up some widgets, initialized from config
local exposure_mu = dt.new_widget("slider")
{
label = "exposure mu",
tooltip = "center also known as MEAN of Gaussian weighting function (0 <= MEAN <= 1); default: 0.5",
hard_min = 0,
hard_max = 1,
value = dt.preferences.read("enfuse", "exposure_mu", "float")
}
local exposure_mu = nil
if version < "4.2" then
exposure_mu = dt.new_widget("slider")
{
label = "exposure mu",
tooltip = "center also known as MEAN of Gaussian weighting function (0 <= MEAN <= 1); default: 0.5",
hard_min = 0,
hard_max = 1,
value = dt.preferences.read("enfuse", "exposure_mu", "float")
}
else
exposure_mu = dt.new_widget("slider")
{
label = "exposure optimum",
tooltip = "optimum exposure value, usually the maximum of the weighting function (0 <= OPTIMUM <=1); default 0.5",
hard_min = 0,
hard_max = 1,
value = dt.preferences.read("enfuse", "exposure_optimum", "float")
}
end
local depth = dt.new_widget("combobox")
{
@@ -83,6 +114,115 @@ local depth = dt.new_widget("combobox")
"8", "16", "32"
}
local enfuse_button = dt.new_widget("button")
{
label = enfuse_installed and "run enfuse" or "enfuse not installed",
clicked_callback = function ()
-- remember exposure_mu
-- TODO: find a way to save it whenever the value changes
local mu = exposure_mu.value
if version < "4.2" then
dt.preferences.write("enfuse", "exposure-mu", "float", mu)
else
dt.preferences.write("enfuse", "exposure-optimum", "float", mu)
end
-- create a temp response file
local response_file = os.tmpname()
if dt.configuration.running_os == "windows" then
response_file = dt.configuration.tmp_dir .. response_file -- windows os.tmpname() defaults to root directory
end
local f = io.open(response_file, "w")
if not f then
dt.print(string.format(_("Error writing to `%s`"), response_file))
os.remove(response_file)
return
end
-- add all filenames to the response file
local cnt = 0
local n_skipped = 0
local target_dir
for i_, i in ipairs(dt.gui.action_images) do
-- only use ldr files as enfuse can't open raws
if i.is_ldr then
cnt = cnt + 1
f:write(i.path..PS..i.filename.."\n")
target_dir = i.path
-- alternatively raws will be exported as tiff
elseif i.is_raw then
local tmp_exported = os.tmpname()..".tif"
if dt.configuration.running_os == "windows" then
tmp_exported = dt.configuration.tmp_dir .. tmp_exported -- windows os.tmpname() defaults to root directory
end
dt.print(string.format(_("Converting raw file '%s' to tiff..."), i.filename))
tiff_exporter:write_image(i, tmp_exported, false)
dt.print_log(string.format("Raw file '%s' converted to '%s'", i.filename, tmp_exported))
cnt = cnt + 1
f:write(tmp_exported.."\n")
target_dir = i.path
-- other images will be skipped
else
dt.print(string.format(_("Skipping %s..."), i.filename))
n_skipped = n_skipped + 1
end
end
f:close()
-- bail out if there is nothing to do
if cnt == 0 then
dt.print(_("No suitable images selected, nothing to do for enfuse"))
os.remove(response_file)
return
end
if n_skipped > 0 then
dt.print(string.format(_("%d image(s) skipped"), n_skipped))
end
-- call enfuse on the response file
-- TODO: find something nicer
local ugly_decimal_point_hack = string.gsub(string.format("%.04f", mu), ",", ".")
-- TODO: make filename unique
local output_image = target_dir.. PS .. "enfuse.tif"
local exposure_option = " --exposure-optimum "
if version < "4.2" then
exposure_option = " --exposure-mu "
end
local command = enfuse_installed.." --depth "..depth.value..exposure_option..ugly_decimal_point_hack
.." -o \""..output_image.."\" \"@"..response_file.."\""
if dtsys.external_command( command) > 0 then
dt.print(_("Enfuse failed, see terminal output for details"))
os.remove(response_file)
return
end
-- remove the response file
os.remove(response_file)
-- import resulting tiff
local image = dt.database.import(output_image)
-- tell the user that everything worked
dt.print(_("enfuse was successful, resulting image has been imported"))
-- normally printing to stdout is bad, but we allow enfuse to show its output, so adding one extra line is ok
print(string.format(_("enfuse: done, resulting image '%s' has been imported with id %d"), output_image, image.id))
end
}
local lib_widgets = {}
if not enfuse_installed then
table.insert(lib_widgets, df.executable_path_widget({"ffmpeg"}))
end
table.insert(lib_widgets, exposure_mu)
table.insert(lib_widgets, depth)
table.insert(lib_widgets, enfuse_button)
-- ... and tell dt about it all
dt.register_lib(
"enfuse", -- plugin name
@@ -94,79 +234,7 @@ dt.register_lib(
{
orientation = "vertical",
sensitive = enfuse_installed,
exposure_mu,
depth,
dt.new_widget("button")
{
label = enfuse_installed and "run enfuse" or "enfuse not installed",
clicked_callback = function (_)
-- remember exposure_mu
-- TODO: find a way to save it whenever the value changes
local mu = exposure_mu.value
dt.preferences.write("enfuse", "exposure_mu", "float", mu)
-- create a temp response file
local response_file = os.tmpname()
local f = io.open(response_file, "w")
if not f then
dt.print("error writing to `"..response_file.."'")
os.remove(response_file)
return
end
-- add all filenames to the response file
local cnt = 0
local n_skipped = 0
local target_dir
for _, i in ipairs(dt.gui.action_images) do
-- only use ldr files as enfuse can't open raws. alternatively we could export raws that we encounter
if i.is_ldr then
cnt = cnt + 1
f:write(i.path.."/"..i.filename.."\n")
target_dir = i.path
else
dt.print("skipping "..i.filename)
n_skipped = n_skipped + 1
end
end
f:close()
-- bail out if there is nothing to do
if cnt == 0 then
dt.print("no suitable images selected, nothing to do for enfuse")
os.remove(response_file)
return
end
if n_skipped == 1 then
dt.print(n_skipped.." image skipped")
elseif n_skipped > 1 then
dt.print(n_skipped.." images skipped")
end
-- call enfuse on the response file
-- TODO: find something nicer
local ugly_decimal_point_hack = string.gsub(string.format("%.04f", mu), ",", ".")
-- TODO: make filename unique
local output_image = target_dir.."/enfuse.tiff"
local command = "enfuse --depth "..depth.value.." --exposure-mu "..ugly_decimal_point_hack
.." -o \""..output_image.."\" \"@"..response_file.."\""
if dt.control.execute( command) > 0 then
dt.print("enfuse failed, see terminal output for details")
os.remove(response_file)
return
end
-- remove the response file
os.remove(response_file)
-- import resulting tiff
local image = dt.database.import(output_image)
-- tell the user that everything worked
dt.print("enfuse was successful, resulting image was imported")
-- normally printing to stdout is bad, but we allow enfuse to show its output, so adding one extra line is ok
print("enfuse: done, resulting image '"..output_image.."' was imported with id "..image.id)
end
}
table.unpack(lib_widgets)
},
nil,-- view_enter
nil -- view_leave

View File

@@ -35,10 +35,11 @@ USAGE
-- * make filenames with double quotes (") work
local dt = require "darktable"
local du = require "lib/dtutils"
require "darktable.debug"
require "official/yield"
dt.configuration.check_version(...,{2,1,0},{3,0,0})
du.check_min_api_version("2.1.0", "generate_image_txt")
dt.preferences.register("generate_image_txt",
"enabled",

View File

@@ -29,7 +29,9 @@ This plugin will add a widget at the bottom of the left column in lighttable mod
]]
local dt = require "darktable"
dt.configuration.check_version(...,{2,0,0},{3,0,0})
local du = require "lib/dtutils"
du.check_min_api_version("2.0.0", "image_path_in_ui")
local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle", halign = "start"}

View File

@@ -67,7 +67,7 @@ end)
-- add a new global function to register import filters
dt.register_import_filter = function(name, callback)
local active_filter = dt.preferences.read("import_filter_manager", "active_filter", "string")
dt.print_error("registering import filter `" .. name .. "'")
dt.print_log("registering import filter `" .. name .. "'")
import_filter_list[name] = callback
n_import_filters = n_import_filters + 1
filter_dropdown[n_import_filters] = name

View File

@@ -34,7 +34,9 @@ increase it if you need more temporary selection buffers
]]
local dt = require "darktable"
dt.configuration.check_version(...,{2,0,0},{3,0,0})
local du = require "lib/dtutils"
du.check_min_api_version("2.0.0", "save_selection")
local buffer_count = 5

View File

@@ -34,9 +34,10 @@ Plugin allows you to choose how many thumbnails you need per row
]]
local dt = require "darktable"
local du = require "lib/dtutils"
require "official/yield"
dt.configuration.check_version(...,{2,0,0},{3,0,0})
du.check_min_api_version("2.0.0", "selection_to_pdf")
dt.preferences.register
("selection_to_pdf","Open with","string",

View File

@@ -25,21 +25,6 @@ USAGE
local dt = require "darktable"
local yield_orig = coroutine.yield
-- ToDo: Test with dt master
--[[
if (dt.configuration.api_version_major >= 4) then
coroutine.yield = function(yield_type, command)
if (yield_type == "RUN_COMMAND") then
dt.control.execute(command)
elseif (yield_type == "FILE_READABLE") then
dt.control.read(command)
elseif (yield_type == "WAIT_MS") then
dt.control.sleep(command)
end
end
end
]]
if (dt.configuration.api_version_major < 4) then
dt.control = {}
dt.control.execute = function(command)

View File

@@ -0,0 +1,192 @@
--[[
This file is part of darktable,
copyright (c) 2018 Bill Ferguson
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/>.
]]
--[[
executable_manager.lua - a tool for managing external executables used by darktable lua scripts
executable_manager is a tool for managing the executable preferences stored in the darktablerc file.
On startup the darktablerc file is scanned and a widget is built for each executable path. The user
can select the executable from a drop down list and then modify the settings as desired.
Any changes made using executable_manager won't be saved in the darktablerc file until darktable exits, but
the preference is updated when the change is made so scripts will pick up the changes without restarting
darktable.
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
du.check_min_api_version("5.0.0", "executable_manager")
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
local gettext = dt.gettext
gettext.bindtextdomain("executable_manager",dt.configuration.config_dir.."/lua/locale/")
-- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - - - - - -
local function _(msg)
return gettext.dgettext("executable_manager", msg)
end
local function grep(file, pattern)
local result = {}
if dt.configuration.running_os == "windows" then
-- use find to get the matches
local command = "\\windows\\system32\\find.exe " .. "\"" .. pattern .. "\"" .. " " .. file
local f = io.popen(command)
local output = f:read("all")
f:close()
-- strip out the first line
result = du.split(output, "\n")
table.remove(result, 1)
else
-- use grep and just return the answers
local command = "grep " .. pattern .. " " .. file
local f = io.popen(command)
local output = f:read("all")
f:close()
result = du.split(output, "\n")
end
return result
end
local function update_combobox_choices(combobox, choice_table, selected)
local items = #combobox
local choices = #choice_table
for i, name in ipairs(choice_table) do
combobox[i] = name
end
if choices < items then
for j = items, choices + 1, -1 do
combobox[j] = nil
end
end
combobox.value = selected
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N P R O G R A M
-- - - - - - - - - - - - - - - - - - - - - - - - - - - -
local exec_man = {} -- our own namespace
local DARKTABLERC = dt.configuration.config_dir .. PS .. "darktablerc"
-- grep the executable_paths statements out of the darktablerc file
local matches = grep(DARKTABLERC, "executable_paths")
-- check if we have something to manage and exit if not
if #matches == 0 then
dt.print(_("No executable paths found, exiting..."))
return
end
-- build a table of the path preferences
local exec_table = {}
for _,pref in ipairs(matches) do
local parts = du.split(pref, "=")
local tmp = du.split(parts[1], "/") -- preferences are stored with forward slashes
table.insert(exec_table, tmp[#tmp])
end
local executable_path_widgets = {}
for i,exec in ipairs(exec_table) do
executable_path_widgets[exec] = dt.new_widget("file_chooser_button"){
title = _("select ") .. exec .. _(" executable"),
value = df.get_executable_path_preference(exec),
is_directory = false,
changed_callback = function(self)
if df.check_if_bin_exists(self.value) then
df.set_executable_path_preference(exec, self.value)
end
end
}
end
-- create a stack widget to hold the executable path widgets
exec_man.stack = dt.new_widget("stack"){}
-- create a combobox to for indexing into the stack of widgets
exec_man.selector = dt.new_widget("combobox"){
label = "executable",
tooltip = _("select executable to modify"),
value = 1, "placeholder",
changed_callback = function(self)
for i,exec in ipairs(exec_table) do
if self.value == exec then
exec_man.stack.active = i
end
end
end
}
-- loop through the tabke of path preferences and populate the widgets
for i,exec in ipairs(exec_table) do
exec_man.stack[i] = dt.new_widget("box"){
executable_path_widgets[exec],
dt.new_widget("button"){
label = "clear",
tooltip = _("Clear path for ") .. exec,
clicked_callback = function()
df.set_executable_path_preference(exec, "")
executable_path_widgets[exec].value = ""
end
}
}
df.executable_path_widget({exec})
end
update_combobox_choices(exec_man.selector, exec_table, 1)
-- register the lib
dt.register_lib(
"executable_manager", -- Module name
"executable manager", -- Visible name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 100}}, -- containers
dt.new_widget("box") -- widget
{
orientation = "vertical",
exec_man.selector,
exec_man.stack,
},
nil,-- view_enter
nil -- view_leave
)

99
tools/gen_i18n_mo.lua Normal file
View File

@@ -0,0 +1,99 @@
--[[
gen_18n_mo.lua - generate .mo files from .po files and put them in the correct place
Copyright (C) 2016,2018 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/>.
]]
--[[
gen_i18n_mo - generate translation files from the source and place them in the appropriate locale directory
gen_i18n_mo finds all the .po files scattered throughout the script tree, compiles them into
.mo files and places them in the correct locale directory for use by the gettext tools.
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local log = require "lib/dtutils.log"
local dtsys = require "lib/dtutils.system"
du.check_min_api_version("5.0.0", "gen_I18n_mo")
-- figure out the path separator
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" .. PS
local LOCALE_DIR = dt.configuration.config_dir .. PS .. "lua" .. PS .. "locale" .. PS
-- check if we have msgfmt
local msgfmt_executable = df.check_if_bin_exists("msgfmt")
if msgfmt_executable then
-- find the .po files
local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.po -print"
if dt.configuration.running_os == "windows" then
find_cmd = "dir /b/s " .. LUA_DIR .. "\\*.po"
end
local output = io.popen(find_cmd)
-- for each .po file....
for line in output:lines() do
local fname = df.get_filename(line)
-- get the language used... this depends on the file being named using
-- the convention /..../lang/LC_MESSAGES/file.po where lang is de_DE, fr_FR, etc.
local path_parts = du.split(line, PS)
local lang = path_parts[#path_parts - 2]
-- ensure there is a destination directory for them
local mkdir_cmd = "mkdir -p "
if dt.configuration.running_os == "windows" then
mkdir_cmd = "mkdir "
end
if not df.check_if_file_exists(LOCALE_DIR .. lang .. PS .. "LC_MESSAGES") then
log.msg(log.info, "Creating locale", lang)
os.execute(mkdir_cmd .. LOCALE_DIR .. lang .. PS .. "LC_MESSAGES")
end
-- generate the mo file
fname = string.gsub(fname, ".po$", ".mo")
log.msg(log.info, "Compiling translation to", fname)
local result = os.execute(msgfmt_executable .. " -o " .. LOCALE_DIR .. lang .. PS .. "LC_MESSAGES" .. PS .. fname .. " " .. line)
end
else
log.msg(log.screen, "ERROR: msgfmt executable not found. Please install or specifiy location in preferences.")
end
dt.preferences.register("executable_paths", "msgfmt", -- name
"file", -- type
'gen_i18n_mo: msgfmt location', -- label
'Install location of msgfmt. Requires restart to take effect.', -- tooltip
"msgfmt", -- default
dt.new_widget("file_chooser_button"){
title = "Select msgfmt[.exe] file",
value = "",
is_directory = false,
}
)

View File

@@ -10,7 +10,7 @@ local df = require "lib/dtutils.file"
local log = require "lib/dtutils.log"
local libname = nil
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("3.0.0", "get_lib_manpages")
local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations",
"Example", "See_Also", "Reference", "License", "Copyright"}

View File

@@ -5,8 +5,9 @@
]]
local dt = require "darktable"
local du = require "lib/dtutils"
dt.configuration.check_version(...,{3,0,0})
du.check_min_api_version("3.0.0", "get_libdoc")
local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations",
"Example", "See_Also", "Reference", "License", "Copyright"}

774
tools/script_manager.lua Normal file
View File

@@ -0,0 +1,774 @@
--[[
This file is part of darktable,
copyright (c) 2018 Bill Ferguson <wpferguson@gmail.com>
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/>.
]]
--[[
script_manager.lua - a tool for managing the darktable lua scripts
script_manager is designed to run as a standalone script so that it
may be used as a drop in luarc file in the user's $HOME/.config/darktable
($HOME/AppData/Local/darktable on windows) directory. It may also be
required from a luarc file.
On startup script_manager checks to see if there is an existing scripts directory.
If there is an existing lua scripts directory then it is read to see what scripts are present.
Scripts are sorted by "category" based on what subdirectory they are found in, thus with a lua
scripts directory that matched the current repository the categories would be contrib, examples,
offical, and tools. Each script has an Enable/Disable button to enable or disable the script.
A link is created to the user's Downloads directory on linux, unix and MacOS. Windows users must create the
link manually using mklink.exe. Additional "un-official" scripts may be downloaded
from other sources and placed in the users Downloads directory. These scripts all fall in a downloads category.
They also each have an Enable/Disable button.
]]
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
-- Tell gettext where to find the .mo file translating messages for a particular domain
gettext.bindtextdomain("script_manager",dt.configuration.config_dir.."/lua/locale/")
local function _(msgid)
return gettext.dgettext("script_manager", msgid)
end
collectgarbage("stop")
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
-- path separator
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
-- command separator
local CS = dt.configuration.running_os == "windows" and "&" or ";"
local LUA_DIR = dt.configuration.config_dir .. PS .. "lua"
local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git"
dt.print_log("LUA_DIR is " .. LUA_DIR)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local script_manager = {}
local sm = script_manager
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
local function prequire(script)
dt.print_log("Loading " .. script)
local status, lib = pcall(require, script)
if status then
dt.print_log("Loaded " .. script)
else
dt.print_error("Error loading " .. script)
dt.print_error(lib)
end
return status, lib
end
local function update_combobox_choices(combobox, choice_table, selected)
local items = #combobox
local choices = #choice_table
for i, name in ipairs(choice_table) do
combobox[i] = name
end
if choices < items then
for j = items, choices + 1, -1 do
combobox[j] = nil
end
end
combobox.value = selected
end
local function install_scripts()
local result = false
if df.check_if_file_exists(LUA_DIR) then
if df.check_if_file_exists(LUA_DIR .. ".orig") then
if dt.configuration.running_os == "windows" then
os.execute("rmdir /s " .. LUA_DIR .. ".orig")
else
os.execute("rm -rf " .. LUA_DIR .. ".orig")
end
end
os.rename(LUA_DIR, LUA_DIR .. ".orig")
end
local git = df.check_if_bin_exists("git")
if not git then
dt.print("ERROR: git not found. Install or specify the location of the git executable.")
return
end
local git_command = git .. " clone " .. sm.lua_repository .. " " .. LUA_DIR
dt.print_log("install git command is " .. git_command)
result = os.execute(git_command)
if not df.check_if_file_exists(LUA_DIR .. PS .. "downloads") then
os.execute("mkdir " .. LUA_DIR .. PS .. "downloads")
end
return result
end
local function update_scripts()
local result = false
local git = df.check_if_bin_exists("git")
if not git then
dt.print("ERROR: git not found. Install or specify the location of the git executable.")
return
end
local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " pull"
dt.print_log("update git command is " .. git_command)
if dt.configuration.running_os == "windows" then
result = dtsys.windows_command(git_command)
else
result = os.execute(git_command)
end
if dt.preferences.read("script_manager", "use_lua_scripts_version", "bool") and
dt.configuration.running_os == "windows" then
use_lua_scripts_version()
end
return result
end
local function add_script_data(script_file)
-- the script file supplied is category/filename.filetype
-- the following pattern splits the string into category, path, name, fileename, and filetype
-- for example contrib/gimp.lua becomes
-- category - contrib
-- path -
-- name - gimp.lua
-- filename - gimp
-- filetype - lua
-- Thanks Tobias Jakobs for the awesome regulary expression
local pattern = "(.-)/(.-)(([^\\/]-)%.?([^%.\\/]*))$"
if dt.configuration.running_os == "windows" then
-- change the path separator from / to \ for windows
pattern = "(.-)\\(.-)(([^\\]-)%.?([^%.\\]*))$"
end
dt.print_log("processing " .. script_file)
-- add the script data
local category,path,name,filename,filetype = string.match(script_file, pattern)
dt.print_log("category is " .. category)
dt.print_log("name is " .. name)
if #sm.script_categories == 0 or not string.match(du.join(sm.script_categories, " "), category) then
sm.script_categories[#sm.script_categories + 1] = category
sm.script_names[category] = {}
end
if name then
dt.print_log("category is " .. category)
dt.print_log("name is " .. name)
if not string.match(du.join(sm.script_names[category], " "), name) then
sm.script_names[category][#sm.script_names[category] + 1] = name
sm.script_paths[category .. "/" .. name] = category .. "/" .. path .. name
if category == "downloads" then
sm.have_downloads = true
end
end
end
end
local function scan_scripts()
local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.lua -print | sort"
if dt.configuration.running_os == "windows" then
find_cmd = "dir /b/s " .. LUA_DIR .. "\\*.lua | sort"
end
-- scan the scripts
local output = io.popen(find_cmd)
for line in output:lines() do
local l = string.gsub(line, LUA_DIR .. PS, "") -- strip the lua dir off
local script_file = l:sub(1,-5)
if not string.match(script_file, "script_manager") then -- let's not include ourself
if not string.match(script_file, "plugins") then -- skip plugins
if not string.match(script_file, "lib" .. PS) then -- let's not try and run libraries
if not string.match(script_file, "include_all") then -- skip include_all.lua
if not string.match(script_file, "yield") then -- special case, because everything needs this
add_script_data(script_file)
else
prequire(script_file) -- load yield.lua
end
end
end
end
end
end
-- work around because we can't dynamically add a new stack child. We create an empty child that will be
-- populated with downloads as they occur. If there are already downloads then this is just ignored
add_script_data("downloads" .. PS)
end
-- get the script documentation, with some assumptions
local function get_script_doc(script)
local description = nil
f = io.open(LUA_DIR .. PS .. script .. ".lua")
if f then
-- slurp the file
local content = f:read("*all")
f:close()
-- assume that the second block comment is the documentation
description = string.match(content, "%-%-%[%[.-%]%].-%-%-%[%[(.-)%]%]")
else
dt.print_error("Cant read from " .. script)
end
if description then
return description
else
return "No documentation available"
end
end
local function activate(script, scriptname)
dt.print_log("activating " .. scriptname)
local status, err = prequire(sm.script_paths[script])
if status then
dt.preferences.write("script_manager", script, "bool", true)
dt.print("Loaded " .. scriptname)
else
dt.print(scriptname .. " failed to load")
dt.print_error("Error loading " .. scriptname)
dt.print_error("Error message: " .. err)
end
return status
end
local function deactivate(script, scriptname)
-- presently the lua api doesn't support unloading gui elements therefore
-- we just mark then inactive for the next time darktable starts
-- deactivate it....
dt.preferences.write("script_manager", script, "bool", false)
dt.print_log("setting " .. scriptname .. " to not start")
dt.print(scriptname .. " will not be active when darktable is restarted")
end
local function create_enable_disable_button(btext, sname, req)
return dt.new_widget("button")
{
label = btext .. sname,
tooltip = get_script_doc(req),
clicked_callback = function (self)
-- split the label into action and target
local action, target = string.match(self.label, "(.+) (.+)")
-- load the script if it's not loaded
dt.print_log("Looking for target " .. target)
local scat = sm.category_selector.value
-- check, and fix, the filename for a - since it is a lua magic character in string match
dt.print_log("checking category " .. scat .. " for target " .. target)
local starget = du.join({scat, target}, "/")
dt.print_log("starget to activate is " .. starget)
dt.print_log("target to activate is " .. target)
if action == "Enable" then
local status = activate(starget, target)
if status then
self.label = "Disable " .. target
end
else
deactivate(starget, target)
self.label = "Enable " .. target
end
end
}
end
local function load_script_stack()
-- load the scripts
table.sort(sm.script_categories)
for _,cat in ipairs(sm.script_categories) do
local tmp = {}
table.sort(sm.script_names[cat])
if not sm.script_widgets[cat] then
for _,sname in ipairs(sm.script_names[cat]) do
local req = du.join({cat, sname}, "/")
local btext = "Enable "
if dt.preferences.read("script_manager", req, "bool") then
local status, err = prequire(sm.script_paths[req])
if status then
btext = "Disable "
else
dt.print_error("Error loading " .. sname)
dt.print_error("Error message: " .. err)
end
else
dt.preferences.write("script_manager", req, "bool", false)
end
tmp[#tmp + 1] = create_enable_disable_button(btext, sname, req)
end
sm.script_widgets[cat] = dt.new_widget("box")
{
orientation = "vertical",
table.unpack(tmp),
}
elseif #sm.script_widgets[cat] ~= #sm.script_names[cat] then
for index,sname in ipairs(sm.script_names[cat]) do
local req = du.join({cat, sname}, "/")
dt.print_error("script is " .. sname .. " and index is " .. index)
if sm.script_widgets[cat][index] then
sm.script_widgets[cat][index] = nil
end
sm.script_widgets[cat][index] = create_enable_disable_button("Enable ", sname, req)
end
end
end
if not sm.script_stack then
sm.script_stack = dt.new_widget("stack"){}
for i,cat in ipairs(sm.script_categories) do
sm.script_stack[i] = sm.script_widgets[cat]
end
sm.script_stack.active = 1
end
end
local function update_stack_choices(combobox, choice_table)
sm.have_downloads = true
local items = #combobox
local choices = #choice_table
if #sm.script_widgets["downloads"] == 0 then
choices = choices - 1
sm.have_downloads = false
end
cnt = 1
for i, name in ipairs(choice_table) do
if (name == "downloads" and sm.have_downloads) or name ~= "downloads" then
combobox[cnt] = name
cnt = cnt + 1
end
end
if choices < items then
for j = items, choices + 1, -1 do
combobox[j] = nil
end
end
combobox.value = 1
end
local function build_scripts_block()
-- build the whole script block
scan_scripts()
-- set up the stack for the choices
load_script_stack()
if not sm.category_selector then
-- set up the combobox for the categories
sm.category_selector = dt.new_widget("combobox"){
label = "Category",
tooltip = "Select the script category",
value = 1, "placeholder",
changed_callback = function(self)
local cnt = 1
for i,cat in ipairs(sm.script_categories) do
if cat == self.value then
sm.script_stack.active = i
end
end
end
}
end
update_stack_choices(sm.category_selector, sm.script_categories)
if not sm.scripts then
sm.scripts = dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label"){ label = "Scripts" },
sm.category_selector,
sm.script_stack,
}
end
end
local function insert_scripts_block()
table.insert(sm.main_menu_choices, "Enable/Disable Scripts")
update_combobox_choices(sm.main_menu, sm.main_menu_choices, 1)
sm.main_stack[#sm.main_stack + 1] = sm.scripts
end
local function use_lua_scripts_version()
if dt.configuration.running_os == "windows" then
-- copy tools\script_manger.lua to luarc
df.file_copy(LUA_DIR .. PS .. "tools\\script_manager.lua", dt.configuration.config_dir .. PS .. "luarc")
else
-- create a symbolic link from luarc to tools/script_manager.lua
if df.check_if_file_exists(dt.configuration.config_dir .. "/luarc") then
os.remove(dt.configuration.config_dir .. "/luarc")
end
os.execute("ln -s " .. LUA_DIR .. "/tools/script_manager.lua " .. dt.configuration.config_dir .. "/luarc")
end
end
local function link_downloads_directory()
if not df.check_if_file_exists("$HOME/Downloads") then
os.execute("mkdir $HOME/Downloads")
end
if df.check_if_file_exists(LUA_DIR .. "/downloads") then
os.remove(LUA_DIR .. "/downloads")
end
os.execute("ln -s " .. "$HOME/Downloads " .. LUA_DIR .. "/downloads")
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- M A I N P R O G R A M
-- - - - - - - - - - - - - - - - - - - - - - - -
-- api check
du.check_min_api_version("5.0.0")
-- set up tables to contain all the widgets and choices
sm.script_widgets = {}
sm.script_categories = {}
sm.script_names = {}
sm.script_paths = {}
sm.main_menu_choices = {}
sm.main_stack_items = {}
-- see if we've run this before
sm.initialized = dt.preferences.read("script_manager", "initialized", "bool")
if not sm.initialized then
-- write out preferences
dt.preferences.write("script_manager", "lua_repository", "string", LUA_SCRIPT_REPO)
dt.preferences.write("script_manager", "use_distro_version", "bool", false)
dt.preferences.write("script_manager", "link_downloads_directory", "bool", false)
dt.preferences.write("script_manager", "initialized", "bool", true)
end
sm.have_scripts = df.check_if_file_exists(LUA_DIR)
sm.git_managed = df.check_if_file_exists(LUA_DIR .. PS .. ".git")
if sm.have_scripts then
dt.print_log("found lua scripts directory")
else
dt.print_log("lua scripts directory not found")
end
if sm.git_managed then
dt.print_log("scripts managed by git")
else
dt.print_log("scripts not managed")
end
local git = df.check_if_bin_exists("git")
if git then
dt.print_log("git found at " .. git)
sm.need_git = false
else
dt.print_log("git not found")
sm.need_git = true
end
sm.repository = dt.new_widget("entry")
{
text = dt.preferences.read("script_manager", "lua_repository", "string"),
editable = true,
}
sm.need_install = false
local install_update_text = _("install")
if sm.have_scripts and sm.git_managed then
install_update_text = _("update")
else
sm.need_install = true
end
sm.install_update_button = dt.new_widget("button"){
label = install_update_text .. _(" scripts"),
clicked_callback = function(self)
sm.lua_repository = sm.repository.text
dt.preferences.write("script_manager", "lua_repository", "string", sm.repository.text)
if sm.need_install then
local result = install_scripts()
if result then
build_scripts_block()
insert_scripts_block()
sm.have_scripts = true
sm.git_managed = true
sm.need_install = false
self.label = _("update scripts")
dt.print(_("installed scripts from " .. sm.repository.text))
else
dt.print(_("Error installing scripts from " .. sm.repository.text))
end
else
local result = update_scripts()
if result then
--build_scripts_block()
dt.print(_("updated scripts from " .. sm.repository.text))
else
dt.print(_("Error updating scripts from " .. sm.repository.text))
end
end
end
}
if not sm.need_install then
sm.reinstall_button = dt.new_widget("button"){
label = "reinstall scripts",
clicked_callback = function(self)
sm.lua_repository = sm.repository.text
dt.preferences.write("script_manager", "lua_repository", "string", sm.repository.text)
local result = install_scripts()
if result then
build_scripts_block()
insert_scripts_block()
sm.have_scripts = true
sm.git_managed = true
sm.need_install = false
sm.install_update_button.label = "update scripts"
dt.print(_("reinstalled scripts from " .. sm.repository.text))
dt.print_log(_("scripts reinstalled"))
else
dt.print(_("ERROR: script reinstallation failed"))
dt.print_error(_("script reinstall failed"))
end
end
}
sm.install_update_widgets = {
sm.install_update_button,
sm.reinstall_button,
}
else
sm.install_update_widgets = {
sm.install_update_button,
}
end
sm.install_update_box = dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label"){ label = "Install/Update scripts" },
table.unpack(sm.install_update_widgets),
}
table.insert(sm.main_menu_choices, "Install/Update Scripts")
table.insert(sm.main_stack_items, sm.install_update_box)
-- configuration items
sm.repository_update = dt.new_widget("button"){
label = "update",
clicked_callback = function()
dt.preferences.write("script_manager", "lua_repository", "string", sm.repository.text)
sm.install_update_button.label = "install"
sm.install_update_button.sensitive = true
sm.reinstall_button.sensitive = false
sm.need_install = true
end
}
sm.repository_reset = dt.new_widget("button"){
label = "reset",
clicked_callback = function()
sm.repository.text = LUA_SCRIPT_REPO
dt.preferences.write("script_manager", "lua_repository", "string", LUA_SCRIPT_REPO)
sm.install_update_button.label = "install"
sm.install_update_button.sensitive = true
sm.reinstall_button.sensitive = false
sm.need_install = true
end
}
sm.update_reset = dt.new_widget("box"){
orientation = "horizontal",
sm.repository_update,
sm.repository_reset,
}
-- replace standalone version of script_manager with distributed version, i.e. tools/script_manager.lua
-- link on linux/MacOS, copy on windows
-- this option won't be active until the repository version of this script is accepted
sm.use_lua_scripts_version = dt.new_widget("check_button"){
label = "Use lua scripts distributed version",
tooltip = "Use the standalone version (false) or the distributed version (true)",
value = dt.preferences.read("script_manager", "use_distro_version", "bool"),
clicked_callback = function(self)
if dt.preferences.read("script_manager", "use_distro_version", "bool") == self.value then
-- do nothing
else
sm.apply_configuration.sensitive = true
end
end
}
-- link downloads to $HOME/downloads on linux and MacOS
sm.link_downloads_directory = dt.new_widget("check_button"){
label = "Link lua/downloads to $HOME/Downloads",
tooltip = "Linking the directories enables dropping a script in $HOME/downloads\nand having it recognized the next time darktable starts",
value = dt.preferences.read("script_manager", "link_downloads_directory", "bool"),
clicked_callback = function(self)
if dt.preferences.read("script_manager", "link_downloads_directory", "bool") == self.value then
-- do nothing
else
sm.apply_configuration.sensitive = true
end
end
}
sm.apply_configuration = dt.new_widget("button"){
label = "Apply",
sensitive = false,
clicked_callback = function(self)
dt.preferences.write("script_manager", "use_distro_version", "bool", sm.use_lua_scripts_version.value)
if sm.use_lua_scripts_version.value then
use_lua_scripts_version()
end
if dt.configuration.running_os ~= "windows" then
dt.preferences.write("script_manager", "link_downloads_directory", "bool", sm.link_downloads_directory.value)
if sm.link_downloads_directory.value then
link_downloads_directory()
end
end
end
}
-- get git location on windows
sm.git_location = df.executable_path_widget({"git"})
sm.configuration_widgets = {
sm.repository,
sm.update_reset,
dt.new_widget("separator"){},
dt.new_widget("separator"){},
}
if dt.configuration.running_os == "windows" or sm.need_git then
sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.git_location
end
if dt.configuration.running_os ~= "windows" then
sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.use_lua_scripts_version
sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.link_downloads_directory
end
sm.configuration_widgets[#sm.configuration_widgets + 1] = sm.apply_configuration
sm.config_box = dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("label") { label = "Configuration" },
table.unpack(sm.configuration_widgets),
}
table.insert(sm.main_menu_choices, "Configure")
table.insert(sm.main_stack_items, sm.config_box)
-- set up the outside stack for config, install/update, and download
-- make a stack for the choices
sm.main_stack = dt.new_widget("stack"){
table.unpack(sm.main_stack_items),
}
-- make a combobox for the selector
sm.main_menu = dt.new_widget("combobox"){
label = "Action",
tooltip = "Select the action you want to perform",
value = 1, "No actions available",
changed_callback = function(self)
for pos,str in ipairs(sm.main_menu_choices) do
if self.value == str then
sm.main_stack.active = pos
dt.preferences.write("script_manager", "sm_main_menu_value", "integer", pos)
end
end
end
}
if #sm.main_menu_choices > 0 then
update_combobox_choices(sm.main_menu, sm.main_menu_choices, 1)
end
sm.main_box = dt.new_widget("box"){
orientation = "vertical",
sm.main_menu,
sm.main_stack,
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- D A R K T A B L E I N T E G R A T I O N
-- - - - - - - - - - - - - - - - - - - - - - - -
-- register the module
dt.register_lib(
"script_manager", -- Module name
"script manager", -- Visible name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 100}}, -- containers
dt.new_widget("box") -- widget
{
orientation = "vertical",
sm.main_box,
},
nil,-- view_enter
nil -- view_leave
)
-- set up the scripts block if we have them otherwise we'll wait until we download them
if sm.have_scripts then
-- scan for scripts and populate the categories
build_scripts_block()
-- add the widgets to the lib
insert_scripts_block()
sm.main_menu.selected = 3
end
collectgarbage("restart")