darktable page lede image
darktable page lede image

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

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.

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

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

9.1.5. Esportare le immagini con Lua

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.

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

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.