darktable ha una versatile interfaccia di scripting per aumentarne le funzionalità.
Lua può essere utilizzato per definire delle azioni che darktable effettuerà al verificarsi di determinati eventi (triggers). Ad es. richiamare un'applicazione esterna durante l'operazione di esportazione di un file per effettuare ulteriori elaborazioni all'esterno di darktable.
darktable utilizza Lua che è un progetto indipendente fondato nel 1993 di un linguaggio di script potente, veloce, leggero ed integrabile. Lua viene utilizzato ampiamente da molte applicazioni open source, in software commerciali, e per programmare videogiochi.
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.
All'avvio darktable esegue automaticamente due script Lua:
uno script chiamato luarc in $DARKTABLE/share/darktable
uno script chiamato luarc nella cartella di configurazione dell'utente
$DARKTABLE rappresenta la cartella di installazione di darktable sul vostro sistema.
Questo è l'unico momento in cui darktable avvia uno script Lua in autonomia. Lo script può catturare le chiamate per attivare delle azioni in risposta a vari eventi in darktable. Il meccanismo delle chiamate è la modalità primaria per avviare le azioni con Lua.
Iniziamo con un esempio semplice. Vogliamo stampare del codice sulla console. Creiamo un file chiamato luarc nella cartella di configurazione di darktable (solitamente ~/.config/darktable/) e aggiungiamo la seguente linea:
print("Ciao Mondo !")
Start darktable and you will see the sentence Hello World ! printed on the console. Nothing fancy but it's a start...
In questo caso non c'è nulla di specifico riguardante darktable nello script. Semplicemente abbiamo usato la funzione standard
print
per visualizzare una stringa. E' una cosa carina ma possiamo fare molto di più! Per interagire con le API di darktable dovete
prima di tutto inserire un'istruzione di require
quindi salvare l'oggetto che verrà ritornato in una variabile. Fatto questo potete avere accesso alle API usando le sotto-stringhe
dell'oggetto che vi è stato ritornato. Quanto detto fino ad ora è documentato nel manuale utente per le API Lua di darktable
(vedi Sezione 9.2, «API Lua»).
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.
Questo primo esempio ci ha mostrato un utilizzo molto elementare di Lua ma ci ha permesso di verificare il buon funzionamento del programma. Ora faremo qualcosa di più complesso. Vogliamo provare a stampare la lista delle immagini che sono state etichettate di rosso. Ma, prima di tutto, che cos'è un'immagine?
local darktable = require "darktable" local debug = require "darktable.debug" print(darktable.debug.dump(darktable.database[1]))
Eseguendo il codice qui sopra otteniamo un output molto corposo. Tra un attimo lo analizzeremo ma ora dobbiamo guardare proprio la stringa di codice.
Sappiamo già richiamare darktable
. In questo caso, invece, dobbiamo richiamare darktable.debug
che è una sezione opzionale delle API che fornisce delle funzioni di aiuto per il debug attraverso uno script Lua.
darktable.database
è una tabella fornita dalle API che contiene tutte le immagini contenute nel database (visibili o no, duplicate o no, ecc.).
Ogni record del database è un oggetto immagine. Gli oggetti immagine sono degli oggetti complessi che vi permettono di manipolare
l'immagine in vari modi (è tutto documentato nella sezione types_dt_lua_image_t
del manuale delle API). Per visualizzare le immagini utilizziamo il codice darktable.debug.dump
che è una funzione che non accetta parametri ma stampa ricorsivamente il suo contenuto. Dal momento che le immagini sono
oggetti complessi che indirettamente sono collegate ad altri oggetti complessi, l'output di questa funzione è veramente enorme.
Qui sotto è possibile vedere una parte molto ridotta dell'output.
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
Noterete che un'immagine contiene tantissimi campi che servono a memorizzare qualsiasi tipo di informazione riguardante l'immagine stessa. A noi interessa l'etichetta rossa. Questo è un campo booleano e la documentazione ci dice che può essere scritto. Per il momento noi abbiamo semplicemente bisogno di trovare le immagini corredate di questo campo e di stamparle.
darktable = require "darktable" for _,v in ipairs(darktable.database) do if v.red then print(tostring(v)) end end
A questo punto dovremmo aver compreso il codice, ma contiene altri aspetti interessanti riguardanti Lua che vale la pena di sottolineare:
ipairs
è una funzione standard di Lua che scorre tutti gli indici di una tabella. Qui la utilizziamo perché darktable.database
ha degli indici non numerici che sono funzioni per manipolare il database stesso (ad es, aggiungere o cancellare immagini).
Scorrendo la tabella sarà possibile ottenere sia la chiave e il valore utilizzato. In Lua è una convenzione chiamare una variabile
«_
» per memorizzare valori che non ci interessano.
Notare anche l'utilizzo della funzione standard di Lua tostring
e non di quella specifica di darktable darktable.debug.dump
. La funzione standard restituirà un nome per l'oggetto mentre la funzione di debug ne visualizzerà il contenuto. La funzione
di debug avrebbe un output troppo verboso in questo caso. Di nuovo, è un formidabile strumento di debug ma non deve essere
utilizzato per altri scopi.
Fino ad ora abbiamo visto script che fanno cose all'avvio. Questo uso ha un'utilità limitata e, soprattutto, non ci permette di reagire alle reazioni di un utente reale. Per fare qualcosa di più avanzato abbiamo bisogno di registrare una funzione da richiamare al verificarsi di un evento. L'evento più comune a cui reagire è una scorciatoia da tastiera.
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.
Una volta definita la funzione, la registreremo come una chiamata della scorciatoia. Per farlo dovremo invocare darktable.register_event
che è una funzione generica per tutti i tipi di eventi e le diremo che stiamo registrando un evento scorciatoia quindi le
forniremo la chiamata da fare e - per ultimo - inseriremo la stringa da utilizzare per descrivere la scorciatoia nella finestra
delle preferenze.
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")
A questo punto gran parte del codice dovrebbe essere abbastanza chiara. Solo un paio di altre note:
Invece di dichiarare una funzione quindi costruire un riferimento ad essa, la dichiareremo direttamente all'interno della
chiamata darktable.register_event
che è esattamente la stessa cosa ma ci permette di usare meno codice.
image.rating
è un campo dell'immagine che registra la relativa valutazione (tra 0 e 5 stelle, -1 significa rifiutata).
darktable.gui.action_images
è una tabella che contiene tutte le immagini di interesse. darktable opera sulle immagini selezionate - se ce ne sono - oppure
sulle immagini che si trovano sotto il puntatore del mouse se nessuna immagine è stata selezionata. Questa funzione ci permette
di seguire la logica dell'interfaccia grafica di darktable anche in Lua.
Se selezionate un'immagine e usate la vostra scorciatoia un paio di volte noterete che all'inizio funzionerà bene ma quando avrete raggiunto le cinque stelle darktable visualizzerà il seguente messaggio di errore in console:
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
Questo è il modo in cui Lua riporta gli errori. Abbiamo tentato di assegnare ad un'immagine una valutazione uguale a 6 mentre il valore massimo ammesso per una valutazione è 5. Potrebbe essere complicato aggiungere un controllo quindi complichiamo un po' le cose e cerchiamo invece di intercettare l'errore.
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
eseguirà il primo argomento e catturerà qualsiasi eccezione rigettata da quest'ultimo. Se non dovesse esserci nessuna eccezione
restituirà vero
unitamente a qualsiasi risultato la funzione dovesse ritornare, se invece dovesse esserci un eccezione allora restituirà
falso
insieme al messaggio di errore dell'eccezione trovata. Noi semplicemente analizziamo questi risultati e li stampiamo a schermo...
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.
Nel prossimo esempio useremo Lua per l'esportazione via scp (Secure Copy). Un nuovo storage apparirà sull'interfaccia di darktable per esportare le immagini su un servizio remoto tramite il meccanismo di copia di ssh.
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
è la chiamata che registra un nuovo archivio. Il primo argomento è un nome per l'archivio, il secondo è l'etichetta che verrà
visualizzata nell'interfaccia utente e l'ultimo è una funzione da richiamare per ogni immagine. Questa funzione ha molti parametri,
ma filename
è l'unico che usiamo in questo esempio. Esso contiene il nome di un file temporaneo che il motore di darktable userà per
l'esportazione.
Questo codice funziona ma ha un paio di limitazioni. Dopo tutto si tratta di un esempio molto semplice:
Usiamo le preferenze per configurare il percorso di destinazione. Sarebbe bello aggiungere un elemento all'interfaccia utente per l'esportazione in darktable. Preciseremo il modo per farlo nella sezione successiva
Non controlliamo il valore restituito da scp. Questo comando potrebbe fallire, in particolare se l'utente non ha impostato correttamente la preferenza.
Questo script non può nemmeno leggere un input da parte dell'utente. L'scp remoto deve utilizzare un sistema di copia senza password. Inoltre non è semplice fornire una password ad scp, quindi dobbiamo abbandonare questa idea
Non verrà visualizzato nessun messaggio al termine dell'operazione ma l'utente capirà che l'operazione è terminata dalla bara di avanzamento che sarà visualizzata in basso a sinistra.
We use
darktable.control.execute
to call an external program. The normal
os.execute
would block other lua codes from happening.
L'esempio precedente era un po' limitato. In particolare l'utilizzo delle preferenze per il percorso di esportazione non era proprio adeguato. Ma noi possiamo fare meglio di questo aggiungendo degli elementi all'interfaccia del pannello di esportazione.
Gli elementi grafici dell'interfaccia vengono generati con la funzione darktable_new_widget
. Questa funzione accetta come parametro un tipo di elemento grafico e restituisce un nuovo oggetto che corrisponde a quell'elemento.
E' quindi possibile aggiungere a questo oggetto diversi campi per definire dei parametri. A questo punto potete usare questo
oggetto come parametro per diverse funzioni che potrete aggiungere all'interfaccia di darktable. Nel semplice esempio che
segue aggiungeremo una scheda alla vista tavolo luminoso con una semplice etichetta
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)
C'è un simpatico trucco sintattico che permette di codificare gli elementi grafici affinché sia più semplice scriverli e leggerli. Potete chiamare questi oggetti come funzioni con una tabella di valori chiave come argomenti. Questo metodo permette al seguente esempio di funzionare. Andremo a creare un contenitore riquadro con due riquadri al suo interno, un'etichetta e un campo per l'inserimento di testo.
local my_widget = darktable.new_widget("box"){ orientation = "horizontal", darktable.new_widget("label"){ label = "here => " }, darktable.new_widget("entry"){ tooltip = "please enter text here" } }
Partiamo da questa base per migliorare un po' il nostro script.
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, })
Fino ad ora abbiamo scritto tutto il nostro codice lua in luarc. Questo è sicuramente un buon metodo per sviluppare i nostri script ma non è molto pratico se vogliamo condividerli. Abbiamo quindi bisogno di farlo in un apposito modulo lua. A questo scopo, salviamo il codice in un file separato (scp-storage.lua nel nostro caso):
--[[ 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...
La funzione darktable.configuration.check_version
verificherà la compatibilità per vostro conto. I caratteri ...
assumeranno il nome del vostro script e {2,0,0}
è la versione delle API che avete usato per provare lo script. Potete aggiungere versioni multiple se il vostro script funziona
con varie versioni di darktable.
Accertatevi di dichiarare tutte le vostre funzioni come local
per non sporcare il namespace generale.
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.
Siete liberi di distribuire il vostro script con la licenza che preferite ma tutti gli script che vengono caricati sul sito di darktable devono essere rilasciati con licenza GPLv2.
Once you have filled all the fields, checked your code, you can upload it to our script page here.
E' possibile inviare a darktable un comando Lua anche attraverso l'interfaccia DBus. Il metodo org.darktable.service.Remote.Lua accetta un singolo parametro a stringa che viene interpretato come un comando Lua. Il comando viene eseguito nel contesto Lua corrente e dovrebbe restituire o nil o una stringa. Il risultato verrà quindi restituito come risultato del metodo DBus.
Se la chiamata Lua restituisce un errore allora la chiamata del metodo DBus restituirà un errore org.darktable.Error.LuaError con il messaggio di errore di Lua allegato all'errore DBus.
Attenzione: questa caratteristica è sperimentale. E' risaputo che tanti elementi ancora non funzionano in modalità libreria. Raccomandiamo di fare dei test molto approfonditi.
L'interfaccia Lua vi permette di utilizzare darktable attraverso degli script Lua. Con questo metodo darktable viene caricato come una libreria mettendo a disposizione la maggior parte delle API Lua (darktable è configurato senza intestazioni perciò le funzioni relative all'interfaccia utente non sono disponibili).
Come esempio, il programma seguente stamperà la l'elenco di tutte le immagini nella vostra libreria:
#!/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
Notate la terza riga che punta alla posizione del file libdarktable.so
.
Notate inoltre che la chiamata a require
restituisce una funzione che può essere richiamata una volta sola e che permette di impostare i parametri della linea di
comando di darktable. Il parametro :memory:
impostato a --library
è utile se non desiderate lavorare sulla vostra libreria personale.