darktable page lede image
darktable page lede image

Lua-Skripte erstellen

Kapitel 9. Lua-Skripte erstellen

darktable verfügt über eine vielseitige Skript-Schnittstelle zur Erweiterung der Funktionalität.

9.1. Nutzung von Lua

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.

9.1.1. Grundprinzipien

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.

9.1.2. Ein einfaches Lua Beispiel

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.

9.1.3. Markierte Bilder drucken

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

9.1.4. Beispiel: Tastenhürzel hinzufügen

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....

9.1.5. Bilder mit Lua exportieren

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.

9.1.6. Erstellen von Benutzeroberflächenelementen

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,
})

9.1.7. Scripte teilen

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.

9.1.8. Lua über den DBus aufrufen

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.

9.1.9. darktable aus einem Lua-Skript verwenden

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.