darktable page lede image
darktable page lede image

Scripting con Lua

Capitolo 9. Scripting con Lua

darktable ha una versatile interfaccia di scripting per aumentarne le funzionalità.

9.1. Utilizzo di Lua

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 utilizza la versione 5.2 di Lua. Descrivere i principi e la sintassi di Lua va oltre lo scopo del presente manuale. Per istruzioni dettagliate potete consultare il manuale di Lua.

9.1.1. Principi base

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.

9.1.2. Un semplice esempio di 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 !")

Avviando darktable potremo vedere la frase Ciao Mondo ! sulla console. Nulla di fantastico ma è un inizio...

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_error("Hello World !")

Eseguite questo script e... non accadrà nulla. La funzione darktable.print_error è uguale all'istruzione 'print' ma visualizzerà la stringa solo se avrete precedentemente abilitato il tracciamento in Lua con l'opzione -d lua sulla linea di comando. Questo è il modo più idoneo per effettuare un tracciamento con uno script Lua in darktable.

9.1.3. Stampare le immagini etichettate

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.

9.1.4. Aggiungere una semplice scorciatoia

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")
      

Ora avviate darktable, andate in preferences => shortcut => Lua => Una scorciatoia che visualizza i propri parametri assegnate una scorciatoia e provate ad eseguirla. Dovreste vedere un messaggio sul vostro schermo.

Ora guardiamo il codice in dettaglio. Prima di tutto abbiamo definito una funzione con due parametri. Questi parametri sono stringhe. La prima è il tipo di evento da agganciare ( "shortcut" ) e la seconda indica esattamente quale ( "Una scorciatoia che visualizza i propri parametri" ). La funzione richiama darktable.print che visualizzerà il messaggio a schermo.

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.

Ora proviamo una scorciatoia un po' più interattiva. Dovremo cercare le immagini che interessano all'utente in questo momento (selezionate o con il puntatore del mouse sopra di esse) ed andremo ad aumentare la valutazione.

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

9.1.5. Esportare le immagini con Lua

Abbiamo imparato a usare Lua per adattare darktable al nostro personale flusso di lavoro: diamo un'occhiata a come usare Lua per esportare facilmente le immagini. darktable può facilmente esportare le immagini su diversi servizi on-line, ma ce ne sono sempre di più. Se riusciamo a caricare un'immagine su di un servizio tramite la riga di comando, allora è possibile utilizzare Lua per integrare questa possibilità sull'interfaccia utente di darktable.

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 coroutine.yield("RUN_COMMAND","scp "..filename.." "..
      darktable.preferences.read("scp_export",
         "export_path","string")) then
      darktable.print_error("scp failed for "..tostring(image))
    end
end)

darktable.preferences.register aggiungerà una nuova preferenza al menu delle preferenze di darktable. scp_export e export_path ci permette invece di identificare in modo univoco la nostra preferenza. Questi campi vengono riutilizzati nel momento in cui leggiamo il valore della preferenza. Il campo string informa il motore di Lua che la preferenza è una stringa. Potrebbe anche essere un numero intero, un nome di file o un qualsiasi tipo dettagliato nel manuale delle API al punto types_lua_pref_type. Alla fine troviamo l'etichetta da assegnare alla preferenza all'interno del menu, l'etichetta da mostrare quando si muove il mouse sopra la scorciatoia e un valore predefinito.

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.

  • Utilizziamo coroutine.yield per richiamare un programma esterno dato che il comando os.execute impedirebbe all'altro codice Lua di essere eseguito.

9.1.6. Disegnare elementi dell'interfaccia utente

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 coroutine.yield("RUN_COMMAND","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. Condividere gli scripts

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.

--[[
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 coroutine.yield("RUN_COMMAND","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 cercherà gli script (come normalmente richiesto da Lua) nelle cartelle standard più $CONFIGDIR/lua/?.lua. In questo modo il nostro script può essere richiamato aggiungendo semplicemente require "scp-storage" nel file luarc. Un paio di note extra:

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

  • Accertatevi anche di non lasciare messaggi di debug nel vostro codice. darktable.print_error in particolare vi permette di lasciare dei messaggi di debug nel vostro codice definitivo senza che questi appaiano a schermo.

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

Quando tutti i campi saranno stati completati e avrete verificato il vostro codice, potrete caricalo sulla nostra pagina degli script qui.

9.1.8. Chiamare Lua da DBus

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.

9.1.9. Usare darktable da uno script Lua

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.