darktable verfügt über eine vielseitige Skript-Schnittstelle zur Erweiterung der Funktionalität.
Lua kann verwendet werden, um Aktionen zu definieren, die Darktable ausführen wird, wenn ein bestimmtes Ereignis ausgelöst wird. Ein Beispiel wäre der Aufruf einer externen Anwendung während des Dateiexports, um zusätzliche Verarbeitungsschritte außerhalb von Darktable durchzuführen.
darktable verwendet Lua, ein unabhängiges Projekt, das 1993 gegründet wurde und eine leistungsstarke, schnelle, leichtgewichtige und einbindbare Skriptsprache bietet. Lua wird von vielen Open-Source-Anwendungen sowie in kommerziellen Programmen und für die Spieleprogrammierung verwendet.
darktable uses Lua version 5.3. Describing the principles and syntax of Lua is beyond the scope of this usermanual. For a detailed introduction see the Lua reference manual.
Beim Start von darktable werden automatisch zwei Lua-Skripte ausgeführt:
ein Skript namens luarc in $DARKTABLE/share/darktable
ein Skript mit dem Namen luarc im Konfigurationsverzeichnis des Benutzers
$DARKTABLE wird hier verwendet, um das Installationsverzeichnis von darktable abzubilden.
Dies ist das einzige Mal, dass darktable Lua-Skripte selbstständig ausführen kann. Das Skript kann Callbacks registrieren, um Aktionen auf verschiedenen Darktable-Ereignissen durchzuführen. Dieser Callback-Mechanismus ist der primäre Weg, um lua-Aktionen auszulösen.
Beginnen wir mit einem einfachen Beispiel. Wir werden etwas Code auf die Konsole drucken. Erstellen Sie eine Datei namens luarc im Konfigurationsverzeichnis von darktable (normalerweise ~/.config/darktable/) und fügen Sie die folgende Zeile hinzu:
print("Hallo Welt !")
Start darktable and you will see the sentence Hello World ! printed on the console. Nothing fancy but it's a start...
An dieser Stelle gibt es im Skript nichts Spezielles zu Darktable. Wir verwenden einfach die Standardfunktion print
, um einen String auszugeben. Das ist schön und gut, aber wir können es noch besser machen. Um auf die Darktable-API zuzugreifen,
müssen Sie zuerst require
und das zurückgegebene Objekt in einer Variablen speichern. Sobald dies geschehen ist, können Sie auf die Darktable-API als
Unterfelder des zurückgegebenen Objekts zugreifen. All dies ist im Lua API Referenzhandbuch von darktable dokumentiert (siehe
Abschnitt 9.2, „Lua API“).
local darktable = require "darktable" darktable.print_log("Hello World !")
Run the script ... and nothing happens. The function
darktable.print_log
is just like print but will only print the message if you have enabled lua traces with
-d lua on the command line. This is the recommended way to do traces
in a darktable lua script.
Dieses erste Beispiel zeigte uns die Grundlagen von Lua und erlaubte uns zu überprüfen, ob alles richtig funktioniert. Lass uns etwas komplizierteres machen. Versuchen wir, die Liste der Bilder auszudrucken, die mit einem roten Etikett versehen sind. Aber zunächst einmal, was ist ein Bild?
local darktable = require "darktable" local debug = require "darktable.debug" print(darktable.debug.dump(darktable.database[1]))
Das Ausführen des obigen Codes erzeugt eine Menge Output. Wir werden es uns gleich anschauen, aber zuerst schauen wir uns den Code selbst an.
Wir wissen, dass wir darktable
. Hier benötigen wir separat darktable.debug
, was ein optionaler Abschnitt der API ist, der Hilfsfunktionen zur Verfügung stellt, um Lua-Skripte zu debuggen.
darktable.database
ist eine von der API bereitgestellte Tabelle, die alle Bilder in der Datenbank enthält (aktuell sichtbar oder nicht, dupliziert
oder nicht...). Jeder Eintrag in der Datenbank ist ein Bildobjekt. Bildobjekte sind komplexe Objekte, die es Ihnen ermöglichen,
Ihr Bild auf verschiedene Weise zu manipulieren (alles ist im Abschnitt types_dt_lua_image_t
des API-Handbuchs dokumentiert). Um unsere Bilder anzuzeigen, verwenden wir darktable.debug.dump
, eine Funktion, die alles als Parameter nimmt und den Inhalt rekursiv ausgibt. Da es sich bei Bildern um komplexe Objekte
handelt, die indirekt auf andere komplexe Objekte verweisen, ist das Ergebnis enorm. Unten sehen Sie ein Beispiel für die
Ausgabe.
toplevel (userdata,dt_lua_image_t) : /images/100.JPG publisher (string) : "" path (string) : "/images" move (function) exif_aperture (number) : 2.7999999523163 rights (string) : "" make_group_leader (function) exif_crop (number) : 0 duplicate_index (number) : 0 is_raw (boolean) : false exif_iso (number) : 200 is_ldr (boolean) : true rating (number) : 1 description (string) : "" red (boolean) : false get_tags (function) duplicate (function) creator (string) : "" latitude (nil) blue (boolean) : false exif_datetime_taken (string) : "2014:04:27 14:10:27" exif_maker (string) : "Panasonic" drop_cache (function) title (string) : "" reset (function) create_style (function) apply_style (function) film (userdata,dt_lua_film_t) : /images 1 (userdata,dt_lua_image_t): .toplevel [......] exif_exposure (number) : 0.0062500000931323 exif_lens (string) : "" detach_tag (function): toplevel.film.2.detach_tag exif_focal_length (number) : 4.5 get_group_members (function): toplevel.film.2.get_group_members id (number) : 1 group_with (function): toplevel.film.2.group_with delete (function): toplevel.film.2.delete purple (boolean) : false is_hdr (boolean) : false exif_model (string) : "DMC-FZ200" green (boolean) : false yellow (boolean) : false longitude (nil) filename (string) : "100.JPG" width (number) : 945 attach_tag (function): toplevel.film.2.attach_tag exif_focus_distance (number) : 0 height (number) : 648 local_copy (boolean) : false copy (function): toplevel.film.2.copy group_leader (userdata,dt_lua_image_t): .toplevel
Wie wir sehen können, hat ein Bild eine große Anzahl von Feldern, die alle möglichen Informationen darüber liefern. Wir interessieren uns für das rote Etikett. Dieses Feld ist ein boolesches Feld, und die Dokumentation sagt uns, dass es geschrieben werden kann. Jetzt müssen wir nur noch alle Bilder mit diesem Feld finden und ausdrucken.
darktable = require "darktable" for _,v in ipairs(darktable.database) do if v.red then print(tostring(v)) end end
Dieser Code sollte an dieser Stelle recht einfach zu verstehen sein, aber er enthält ein paar interessante Aspekte über Lua, die es wert sind, hervorgehoben zu werden:
ipairs
ist eine Standard-Lua-Funktion, die durch alle numerischen Indizes einer Tabelle iteriert. Wir benutzen es hier, weil darktable.database
nicht-numerische Indizes hat, die Funktionen sind, um die Datenbank selbst zu manipulieren (z. B. Bilder hinzufügen oder
löschen).
Das Iterieren durch eine Tabelle gibt sowohl den Schlüssel als auch den verwendeten Wert zurück. Es ist in Lua üblich, eine
Variable mit dem Namen „_
“_
zu verwenden, um Werte zu speichern, die uns egal sind.
Beachten Sie, dass wir hier die Standard-Lua-Funktion tostring verwenden und nicht die darktable-spezifische darktable.debug.dump
. Die Standardfunktion gibt einen Namen f
Die vorherigen Skripte haben bisher dinge beim Start ausgeführt. Dies ist von begrenztem Nutzen und erlaubt es uns nicht, auf reale Benutzeraktionen zu reagieren. Um fortgeschrittenere Dinge zu tun, müssen wir eine Funktion registrieren, die bei einem bestimmten Ereignis aufgerufen wird. Das häufigste Ereignis, auf das man reagieren muss, ist eine Tastenkombination.
darktable = require "darktable" local function hello_shortcut(event, shortcut) darktable.print("Hello, I just received '"..event.. "' with parameter '"..shortcut.."'") end darktable.register_event("shortcut",hello_shortcut, "A shortcut that print its parameters")
Now start darktable, go to
preferences => shortcut => lua => A shortcut that prints its parameters
assign a shortcut and try it. You should have a nice message printed on the screen.
Let's look at the code in detail. We first define a function with two parameters. These
parameters are strings. The first one is the type of event that is triggered (
"shortcut"
) and the second one is what shortcut specifically (
"A shortcut that print its parameters"
). The function itself calls
darktable.print
that will print the message as an overlay in the main window.
Sobald diese Funktion definiert ist, registrieren wir sie als Shortcut-Callback. Dazu rufen wir darktable.register_event
auf, eine generische Funktion für alle Arten von Ereignissen. Wir sagen ihm, dass wir ein Shortcut-Ereignis registrieren,
dann geben wir den Callback zum Aufruf und zuletzt geben wir den String an, den wir verwenden, um die Verknüpfung im Einstellungsfenster
zu beschreiben.
Let's try a shortcut that is a little more interactive. This one will look at the images the user is currently interested in (selected or moused-over) and will increase their rating.
darktable = require "darktable" darktable.register_event("shortcut",function(event,shortcut) local images = darktable.gui.action_images for _,v in pairs(images) do v.rating = v.rating + 1 end end,"Increase the rating of an image")
An dieser Stelle sollte der größte Teil dieses Codes selbsterklärend sein. Nur ein paar Anmerkungen:
Anstatt eine Funktion zu deklarieren und zu referenzieren, wird diese direkt im Aufruf von darktable.register_event
deklariert. Dies ist genau äquivalent, aber etwas kompakter.
image.rating
ist ein Feld eines jeden Bildes, das seine Bewertung angibt (zwischen 0 und 5 Sternen, -1 bedeutet abgelehnt).
darktable.gui.action_images
ist eine Tabelle mit allen interessanten Bildern. darktable wirkt auf ausgewählte Bilder, wenn ein Bild ausgewählt ist, und
auf das Bild unter der Maus, wenn kein Bild ausgewählt ist. Diese Funktion erlaubt es, der UI-Logik von Darktable in lua zu
folgen.
Wenn Sie ein Bild auswählen und die Tastenkombination ein paar Mal drücken, funktioniert es zunächst korrekt, aber wenn Sie fünf Sterne erreicht haben, wird der folgende Fehler auf der Konsole angezeigt:
LUA ERROR : rating too high : 6 stack traceback: [C]: in ? [C]: in function '__newindex' ./configdir/luarc:10: in function <./configdir/luarc:7> LUA ERROR : rating too high : 6
Das ist die Art wie Lua Fehler meldet. Wir haben versucht, eine Bewertung von 6 auf ein Bild zu setzen, aber eine Bewertung kann nur bis zu 5 gehen. Es wäre trivial, einen Check hinzuzufügen, aber lassen Sie uns den komplizierten Weg gehen und stattdessen den Fehler abfangen.
darktable.register_event("shortcut",function(event,shortcut) local images = darktable.gui.action_images for _,v in pairs(images) do result,message = pcall(function() v.rating = v.rating + 1 end) if not result then darktable.print_error("could not increase rating of image ".. tostring(v).." : "..message) end end end,"Increase the rating of an image")
pcall
wird sein erstes Argument ausführen und jede von ihm ausgelöste Ausnahme abfangen. Wenn es keine Ausnahme gibt, wird true
plus jedes von der Funktion zurückgegebene Ergebnis zurückgegeben; wenn es eine Ausnahme gibt, wird false
und die Fehlermeldung der Ausnahme zurückgegeben. Wir testen diese Ergebnisse einfach und drucken sie auf die Konsole....
We have learned to use lua to adapt darktable to our particular workflow, now let's look at how to use lua to easily export images. darktable can easily export images to some online services but there are always more. If you are able to upload an image to a service via the command line then you can use lua to integrate it into darktable's user interface.
In diesem nächsten Beispiel verwenden wir lua, um über scp zu exportieren. Ein neuer Speicher erscheint in der Benutzeroberfläche von Darktable, der Bilder über den Kopiermechanismus von ssh an ein entferntes Ziel exportiert.
darktable = require "darktable" darktable.preferences.register("scp_export","export_path", "string","target SCP path", "Complete path to copy to. Can include user and hostname","") darktable.register_storage("scp_export","Export via scp", function( storage, image, format, filename, number, total, high_quality, extra_data) if not darktable.control.execute("scp "..filename.." ".. darktable.preferences.read("scp_export", "export_path","string")) then darktable.print_error("scp failed for "..tostring(image)) end end)
darktable.preferences.register
will add a new preference to darktable's preference menu.
scp_export
and
export_path
allows us to uniquely identify our preference. These fields are reused when we read the
value of the preference. The
string
field tells the lua engine that the preference is a string. It could also be an integer,
a filename or any of the types detailed in API manual relating to
types_lua_pref_type
. We then have the label for the preference in the preference menu, the tooltip when
hovering over the value and a default value.
darktable.register_storage
ist der Aufruf, der tatsächlich einen neuen Speicher registriert. Das erste Argument ist ein Name für die Speicherung, das
zweite ist das Label, das in der Benutzeroberfläche angezeigt wird, und das letzte ist eine Funktion, um jedes Bild aufzurufen.
Diese Funktion hat viele Parameter, aber filename
ist die einzige, die wir in diesem Beispiel verwenden. Es enthält den Namen einer temporären Datei, in die das Bild von der
Darktable-Engine exportiert wurde.
Dieser Code wird funktionieren, hat aber einige Einschränkungen. Dies ist letztlich ein einfaches Beispiel:
Wir verwenden Präferenzen, um den Zielpfad zu konfigurieren. Schöner ist, ein Element zur Exportoberfläche in darktable hinzuzufügen. Wie das geht, erfahren Sie im nächsten Abschnitt.
Wir prüfen nicht den zurückgegebenen Wert von scp. Dieser Befehl kann fehlschlagen, insbesondere wenn der Benutzer die Einstellung nicht korrekt gesetzt hat.
Dieses Skript kann keine Eingaben vom Benutzer lesen. Der Remote-Scp muss eine passwortlose Kopie verwenden. Scp kann nicht einfach mit einem Passwort versehen werden, sodass wir es so belassen werden.
Es wird keine Meldung angezeigt, wenn das Beispiel fertig ist, nur der Fortschrittsbalken unten links zeigt dem Benutzer an, dass die Arbeit erledigt ist.
We use
darktable.control.execute
to call an external program. The normal
os.execute
would block other lua codes from happening.
Unser vorheriges Beispiel war etwas eingeschränkt. Insbesondere die Verwendung einer Vorliebe für den Exportpfad war nicht sehr schön. Wir können das noch besser machen, indem wir der Benutzeroberfläche im Exportdialog Elemente hinzufügen.
UI-Elemente werden über die Funktion darktable_new_widget
erzeugt. Diese Funktion nimmt einen Widgettyp als Parameter und gibt ein neues Objekt zurück, das diesem Widget entspricht.
Sie können dann verschiedene Felder auf dieses Widget setzen, um seine Parameter zu setzen. Sie werden dieses Objekt dann
als Parameter für verschiedene Funktionen verwenden, die es zum Darktable-UI hinzufügen. Das folgende einfache Beispiel fügt
eine Lib in der Lighttable-Ansicht mit einem einfachen Label hinzu
local my_label = darktable.new_widget("label") my_label.label = "Hello, world !" dt.register_lib("test","test",false,{ [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER",20}, },my_label)
Es gibt einen netten syntaktischen Trick, um das Lesen und Schreiben von UI-Element-Code zu erleichtern. Sie können diese Objekte als Funktionen mit einer Tabelle von Schlüsselwerten als Argument aufrufen. Damit kann das folgende Beispiel funktionieren. Es erstellt ein Container-Widget mit zwei Sub-Widgets. Ein Label und ein Texteingabefeld.
local my_widget = darktable.new_widget("box"){ orientation = "horizontal", darktable.new_widget("label"){ label = "here => " }, darktable.new_widget("entry"){ tooltip = "please enter text here" } }
Da wir das jetzt wissen, verbessern wir unser Skript ein wenig.
darktable = require "darktable" local scp_path = darktable.new_widget("entry"){ tooltip ="Complete path to copy to. Can include user and hostname", text = "", reset_callback = function(self) self.text = "" end } darktable.register_storage("scp_export","Export via scp", function( storage, image, format, filename, number, total, high_quality, extra_data) if not darktable.control.execute("scp "..filename.." ".. scp_path.text ) then darktable.print_error("scp failed for "..tostring(image)) end end, nil, --finalize nil, --supported nil, --initialize darktable.new_widget("box") { orientation ="horizontal", darktable.new_widget("label"){label = "target SCP PATH "}, scp_path, })
Bisher war unser lua-Code in luarc. Das ist ein guter Weg, um Ihr Skript zu entwickeln, aber nicht sehr praktisch für den Vertrieb. Wir müssen daraus ein richtiges Luamodul machen. Dazu speichern wir den Code in einer separaten Datei (scp-storage.lua in unserem Fall):
--[[ SCP STORAGE a simple storage to export images via scp AUTHOR Jérémy Rosen (jeremy.rosen@enst-bretagne.fr) INSTALLATION * copy this file in $CONFIGDIR/lua/ where CONFIGDIR is your darktable configuration directory * add the following line in the file $CONFIGDIR/luarc require "scp-storage" USAGE * select "Export via SCP" in the storage selection menu * set the target directory * export your images LICENSE GPLv2 ]] darktable = require "darktable" darktable.configuration.check_version(...,{2,0,0}) local scp_path = darktable.new_widget("entry"){ tooltip ="Complete path to copy to. Can include user and hostname", text = "", reset_callback = function(self) self.text = "" end } darktable.register_storage("scp_export","Export via scp", function( storage, image, format, filename, number, total, high_quality, extra_data) if darktable.control.execute("scp "..filename.." ".. scp_path.text ) then darktable.print_error("scp failed for "..tostring(image)) end end, nil, --finalize nil, --supported nil, --initialize darktable.new_widget("box") { orientation ="horizontal", darktable.new_widget("label"){label = "target SCP PATH "}, scp_path, })
darktable will look for scripts (following the normal lua rules) in the standard
directories plus
$CONFIGDIR/lua/?.lua
. So our script can be called by simply adding
require "scp-storage"
in the luarc file. A couple of extra notes...
Die Funktion darktable.configuration.check_version
überprüft die Kompatibilität. Der ...
wird zum Skriptnamen und {2,0,0}
ist die API-Version, mit der Sie Ihr Skript getestet haben. Sie können mehrere API-Versionen hinzufügen, wenn Sie Ihr Skript
für mehrere Versionen von Darktable aktualisieren.
Stellen Sie sicher, dass alle Ihre Funktionen als local
deklariert sind, um den allgemeinen Namensraum nicht zu schädigen.
Make sure you do not leave debug prints in your code.
darktable.print_log
in particular allows you to leave debug prints in your final code without disturbing
the console.
Sie können jede Lizenz für Ihr Skript frei wählen, jedoch müssen Skripte, die auf der darktable Website veröffentlicht werden, der GPLv2 unterliegen.
Once you have filled all the fields, checked your code, you can upload it to our script page here.
Es ist möglich, einen lua-Befehl über die DBus-Schnittstelle an darktable zu senden. Die Methode org.darktable.service.Remote.Lua nimmt einen einzelnen String-Parameter, der als lua-Befehl interpretiert wird. Der Befehl wird im aktuellen lua-Kontext ausgeführt und sollte entweder nil oder einen String zurückgeben. Das Ergebnis wird als Ergebnis der DBus-Methode zurückgegeben.
Wenn der Lua-Aufruf zu einem Fehler führt, gibt der DBus-Methodenaufruf einen Fehler org.darktable.Error.LuaError mit der lua-Fehlermeldung als Anhang zum DBus-Fehler zurück.
Warnung: Diese Funktion ist sehr experimentell. Es ist bekannt, dass einige Elemente im Bibliotheksmodus noch nicht funktionieren. Sorgfältige Tests werden empfohlen.
Das lua-Interface erlaubt es Ihnen, die darktable von jedem beliebigen lua-Skript aus zu verwenden. Dies lädt darktable als Bibliothek und stellt Ihnen die meisten der lua API zur Verfügung (darktable ist headless konfiguriert, sodass die Funktionen der Benutzeroberfläche nicht verfügbar sind).
Als Beispiel wird das folgende Programm die Liste aller Bilder in Ihrer Bibliothek drucken:
#!/usr/bin/env lua package = require "package" package.cpath=package.cpath..";./lib/darktable/lib?.so" dt = require("darktable")( "--library", "./library.db", "--datadir", "./share/darktable", "--moduledir", "./lib/darktable", "--configdir", "./configdir", "--cachedir","cachedir", "--g-fatal-warnings") require("darktable.debug") for k,v in ipairs(dt.database) do print(tostring(v)) end
Beachten Sie die dritte Zeile, die auf den Speicherort der Datei libdarktable.so
zeigt.
Beachten Sie auch, dass der Aufruf von require
eine Funktion zurückgibt, die nur einmal aufgerufen werden kann und es erlaubt, den Kommandozeilenparameter von darktable
zu setzen. Die :memory:
parameter to --library
ist hier nützlich, wenn Sie nicht an Ihrer persönlichen Bibliothek arbeiten wollen.