darktable page lede image
darktable page lede image

Capítulo 9. Programar con Lua

darktable viene con una versátil interfaz de secuencia de comandos para mejoras en sus funcionalidades.

9.1. Uso de Lua

Lua puede ser utilizado para definir las acciones que darktable realizará, cuando un evento específico sea desencadenado. Un ejemplo puede ser una llamada a una aplicación externa durante la exportación de un archivo para aplicar pasos adicionales de procesamiento fuera de darktable.

darktable utiliza Lua, el cual es un proyecto independiente fundado en 1993, quien provee un lenguaje de programación poderoso, rápido, ligero e integrado. Lua es ampliamente utilizado en muchas aplicaciones de código abierto, en programas comerciales y por programadores de videojuegos.

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. Principios básicos

Al inicio, darktable correrá dos scripts de Lua automáticamente.

  • un script llamado luarc en $DARKTABLE/share/darktable

  • un script llamado luarc en el directorio de configuración del usuario

$DARKTABLE es utilizado aquí para representar el directorio de su sistema de instalación de darktable

Esta es la única vez que darktable correrá un script de Lua por si mismo. El script puede registrar callbacks para realizar acciones sobre varios eventos de darktable. Este mecanismo de llamadas es la forma principal de activar las acciones lua.

9.1.2. Un simple ejemplo de lua

Comencemos con un ejemplo simple. Imprimiremos algunos códigos en la consola. Cree un archivo llamado luarc en el directorio de configuración de darktable (usualmente ~/.config/darktable/) y agregue la siguiente línea:

print("Hola Mundo !")

Start darktable and you will see the sentence Hello World ! printed on the console. Nothing fancy but it's a start...

En este punto, no hay nada específico para darktable en el script. Simplemente utilizamos una función de print para imprimir una cadena. Eso está bien y todo, pero podemos hacer algo mejor que eso. Para acceder a la API de darktable primero necesita requerirlo y almacenar el objeto regresado en una variable. Una vez realice esto podrá acceder a la API de darktable como un sub-campo del objeto retornado. Todo esto está documentado en el manual de referencias del API de Lua (vea el Sección 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. Imprimiendo imágenes etiquetadas

Este primer ejemplo nos muestra lo básico de lua y nos permite verificar que todo funcione propiamente. Hagamos algo un poco más complejo. Intentemos imprimir la lista de imágenes que tienen una etiqueta roja adjunta a ellas. Pero primero, ¿Qué es una imagen?

local darktable = require "darktable"
local debug = require "darktable.debug"
print(darktable.debug.dump(darktable.database[1]))

Corriendo el código anterior se producirá una gran salida. Ya la veremos en un momento, pero primero, demos un vistazo al código en sí.

Conocemos los requerimientos de darktable . Aquí, necesitamos requerir por separado darktable.debug la cual es una sección opcional de la API que provee funciones de ayuda para depurar los scripts lua.

darktable.database es una tabla provista por la API que contiene todas las imágenes en la base de datos (visible o no, duplicada o no...). Cada entrada en la base de datos es un objeto de imagen. Los objetos de imágenes son objetos complejos que le permiten manipular su imagen de varias formas (todo esto está documentado en la sección types_dt_lua_image_t del manual de la API). Para mostrar nuestras imágenes, utilizamos darktable.debug.dump la cual es una función que tomará todo lo que sea un parámetro y mostrará recursivamente su contenido. Ya que las imágenes son objetos complejos que hacen referencia indirecta a otros objetos complejos, la salida resultante es inmensa. Debajo encontrará un ejemplo seccionado de la salida.



          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


        

Como es evidente, una imagen tiene una gran cantidad de campos que proveen todo tipo de información sobre si misma. Nos interesa la etiqueta roja. Este campo es un boleano, y la documentación nos indica que puede ser escrito. Ahora solo necesitamos encontrar todas las imágenes con dicho campo e imprimirlas.

darktable = require "darktable"
for _,v in ipairs(darktable.database) do
  if v.red then
    print(tostring(v))
  end
end

Este código es bastante sencillo de entender en este punto, pero contiene algunos aspectos interesantes sobre lua que vale la pena resaltar:

  • ipairs es una funcionalidad estándar de lua que iterará todos los indices numéricos de una tabla. Lo usamos aquí porque darktable.database tiene indices no-numéricos que son funciones para manipular la base de datos (por ejemplo, agregando o eliminando imágenes).

  • Realizar la iteración de una table retornará tanto las llaves como los valores utilizados. Es convencional utilizar en lua una variable llamada _ para almacenar los valores que no nos interesan.

  • Note que utilizamos la función estándar de lua tostring y no la específica de darktable darktable.debug.dump. La función estándar retornará un nombre para el objeto, donde la función de depurar imprimirá el contenido. La función de depurado sería demasiado verbosa para ser utilizada aquí. De nuevo, es una gran herramienta de depurado pero no debe ser utilizada para mas nada.

9.1.4. Agregando un simple un atajo de teclado

Hasta ahora, todos nuestros scripts han realizado algo durante el inicio. Esto da un uso limitado y no nos permite reaccionar a acciones reales del usuario. Pare realizar cosas mas avanzadas necesitamos registrar una función que será llamada en un evento dado. El evento más común al cual reaccionar es un atajo de teclado.

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 vez que la función sea definida, lo registraremos como un atajo de teclado. Para realizar esto, llamaremos a darktable.register_event la cual es una función genérica para todos los tipos de eventos. Le diremos que estamos registrando un atajo como evento, luego le daremos la llamada y por último, le indicaremos la cadena que se utilizará para describir el atajo en la ventana de preferencias.

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

En este punto, la mayoría de este código debería ser auto explicativo. Solo un par de notas:

  • En vez de declarar una función y hacerle referencia, la declararemos directamente en la llamada a darktable.register_event esto es estrictamente equivalente pero un poco más compacto.

  • image.rating es el campo de cualquier imagen que otorga el puntaje (entre 0 y 5 estrellas, -1 significa rechazada).

  • darktable.gui.action_images es una tabla que contiene todas las imágenes de interés. darktable actuará sobre las imágenes seleccionadas si cualquier imagen es seleccionada, y sobre otras imágenes con el puntero del ratón sobre ellas si ninguna imagen ha sido seleccionada. Esta función le permite seguir fácilmente la lógica en lua de la interfaz de darktable.

Si selecciona una imagen y presiona su atajo un par de veces, funcionará correctamente al principio, pero luego de que haya llegado a las cinco estrellas, darktable comenzará a mostrá un mensaje de error en la consola:



          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

        

Esta es la forma que tiene lua para reportar errores. Hemos estado tentados a ajustar un puntaje de 6 a una imagen, pero un puntaje solo puede subir hasta 5. Sería trivial agregar una revisión, pero en cambio vayamos a la forma complicada y obtengamos el error.

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 correrá su primer argumento y atajará cualquier excepción que sea lanzada. Si no hay excepciones devolverá un true (verdadero) además de cualquier resultado retornado por la función; si hay una excepción retornará false (falso) y el mensaje de error de la excepción. Simplemente probaremos estos resultados y los imprimiremos en la consola...

9.1.5. Exportando imágenes 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.

En este nuevo ejemplo utilizaremos lua para exportar vía scp. Un nuevo almacenamiento aparecerá en la interfaz de darktable, la cual exportará las imágenes a un objetivo remoto vía el mecanismo de copia 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 es la llamada que realmente registra un nuevo almacenamiento. El primero argumento es el nombre del almacenamiento, el segundo es la etiqueta que se mostrará en la Interfaz y el último es la función de llamada de cada imagen. Esta función tiene muchos mas parámetros, pero filename (nombre de archivo) es la única que utilizaremos en este ejemplo. Esta contiene el nombre del archivo temporal donde la imagen fue exportada por el motor de darktable.

Este código funcionará pero tiene un par de limitaciones. Esto es un simple ejemplo después de todo:

  • Utilizamos preferencias para configurar la ruta del objetivo. Sería mucho mejor agregar un elemento a la Interfaz de exportado en darktable. Detallaremos como realizar esto en la próxima sección

  • Nosotros no revisamos el valor retornado del scp. Ese comando puede fallar, particularmente si el usuario no ha ajustado correctamente la preferencia.

  • Este script no puede leer las entradas del usuario. El scp remoto debe utilizar una copia menor de la clave. Scp no puede proveer una clave fácilmente, así que lo dejaremos tal como está

  • No se mostrará un mensaje una vez que el ejemplo se haya realizado, solo la barra de progreso a la izquierda le indicará al usuario que el trabajo ha sido realizado.

  • We use darktable.control.execute to call an external program. The normal os.execute would block other lua codes from happening.

9.1.6. Construyendo elementos de la Interfaz de Usuario

Nuestro ejemplo anterior fue un poco limitado. Particularmente, el uso de una preferencia para una ruta de exportado no fue muy agradable. Podemos hacer algo mejor que eso al agregar elementos a la interfaz de usuario dentro del diálogo de exportar.

Los elementos de la interfaz son creados con la función darktable_new_widget. Esta función toma un tipo de widget como un parámetro y retorna un nuevo objeto que le corresponderá a dicho widget. Puede ajustar varios campos para dicho widget para ajustar sus parámetros. Entonces podrá utilizar objetos como parámetros de varias funciones, las cuales se agregarán a la Interfaz de darktable. El siguiente ejemplo simple agrega una lib en la vista de mesa de luz con una etiqueta sencilla

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)    

Aquí tiene un buen truco de sintaxis para que el código de los elementos de su Interfaz se pueda leer y escribir más fácilmente. Puede llamar estos objetos como funciones con una tabla de valores clave como un argumento. Esto permite que el siguiente ejemplo pueda funcionar. Crea un widget contenedor con dos sub-widgets. Una etiqueta y un campo de entrada de texto.

	local my_widget = darktable.new_widget("box"){
		orientation = "horizontal",
		darktable.new_widget("label"){ label = "here => " },
		darktable.new_widget("entry"){ tooltip = "please enter text here" }
	}

Ahora que sabemos esto, mejoremos un poco nuestro 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. Compartiendo scripts

Hasta ahora, nuestro código lua estaba en luarc. Esa es una buena forma de desarrollar su script pero no muy práctico para ser distribuido. Necesitamos convertir esto en un módulo propio de lua. Para hacerlo, guardamos el código en un archivo separado (scp-storage.lua en nuestro 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 función darktable.configuration.check_version revisará la compatibilidad por usted. El ... se convertirá en el nombre de su script {2,0,0} es la versión de API con la que ha probado su script. Puede agregar múltiples versiones de API si actualiza su script para múltiples versiones de darktable.

  • Asegúrese de declarar todas sus funciones como local para no contaminar el nombre en general.

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

  • Es libre de escoger cualquier licencia para su script, pero los scripts que son cargados en el sitio web de darktable necesitan ser GPLv2.

Once you have filled all the fields, checked your code, you can upload it to our script page here.

9.1.8. Llamando Lua desde DBus

Es posible enviar un comando lua a darkable vía la interfaz del DBus. El método org.darktable.service.Remote.Lua toma una sola cadena de parámetro, la cua es interpretada como un comando lua. El comando será ejecutado en el contexto actual de lua y debería bien sea retornar un nil o una cadena. El resultado se devolverá como resultado del método DBus.

Si la llamada a Lua resulta en un error, la llamada del método DBus retornará un error org.darktable.Error.LuaError con el mensaje de error de lua como un mensaje adjunto al error de DBus.

9.1.9. Utilizando darktable desde un script de lua

Precaución: Esta propiedad es bastante experimental. Se sabe que varios elementos aún no funcionan en el modo de librería. Se recomienda tener mucho cuidado al realizar estas pruebas.

La interfaz de lua le permite utilizar darktable desde cualquier script lua. Esto cargará darktable como una librería y le proveerá con la mayoría de la API lua (darktable está configurado sin cabeceras, así que las funciones relacionadas a la interfaz no están disponibles).

Como ejemplo, el siguiente programa imprimirá la lista de todas las imágenes en su librería:

#!/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

Note que la tercera línea apunta a la ubicación del archivo libdarktable.so.

También tenga en cuenta que la llamada a require devuelve una función que solo se puede llamar una vez y le permite configurar el parámetro de la línea de comando de darktable. El parámetro :memory: para --library es útil aquí si no desea trabajar en la librería personal.