января 03, 2019

darktable 2.4 > Создание сценариев на Lua

ПРЕД.


Глава 9. Создание сценариев на Lua

Для повышения функциональности darktable поставляется с универсальным интерфейсом для использования языка сценариев.

9.1. Использование Lua

Lua может использоваться для выполнения определённых действий, которые darktable будет выполнять всякий раз, когда запускается указанное событие. Один из примеров позволяет вызвать внешнее приложение во время экспорта файла, чтобы применить дополнительные шаги обработки за пределами darktable.

Darktable использует Lua [http://www.lua.org/], который является независимым проектом, основанным в 1993 году и предоставляющим мощный, быстрый, легкий, встраиваемый язык сценариев. Lua широко используется многими приложениями с открытым исходным кодом, в коммерческих программах и для программирования игр.

Darktable использует версию Lua 5.2. Описание принципов и синтаксиса Lua выходит за рамки этого руководства пользователя. Подробное введение см. в "Руководство Lua" [http://lua.org.ru/contents_ru.html] и [http://www.lua.org/manual/5.2/manual.html].

Прим. переводчика: В версиях darktable 2.4.4. и более новых используется версия Lua 5.3.

9.1.1. Основные принципы

При запуске darktable автоматически запускает два сценария Lua:
  • скрипт с названием luarc в $DARKTABLE/share/darktable
  • скрипт luarc в каталоге конфигурации пользователя
$DARKTABLE обозначает каталог установки darktable в системе.

Это единственный раз, когда darktable будет запускать сценарии Lua самостоятельно. Сценарий может зарегистрировать вызов выполнения некоторых действий при наступлении определённых событий в darktable. Этот механизм вызова является основным способом запуска сценариев lua.

9.1.2. Пример простого lua сценария

Начнем с простого примера. Выведем некоторое сообщение на консоль. Создадим файл luarc в каталоге конфигурации пользователя darktable (обычно "~/.config/darktable/" или "%homepath%\\AppData\\Local\\darktable\\" для ОС Windows.) и добавим следующую строку:
print(“Hello World!”)
Запустим darktable из консоли и увидим сообщение "Hello World!", напечатанное в консоли. Ничего особенного, но это только начало...

На данный момент в сценарии нет ничего особенного для darktable. Мы используем стандартную функцию print для вывода строки на консоль. Это хорошо, но мало, мы можем сделать больше и лучше. Чтобы получить доступ к API darktable, нам сначала нужно вызвать его командой require и сохранить возвращаемый объект в переменной. Как только это будет сделано, мы можем получить доступ к API darktable в качестве подполей возвращаемого объекта. Все это задокументировано в справочном руководстве по Lua API darktable (см. раздел 9.2, "Lua API").
local darktable = require “darktable”
darktable.print_error(“Hello World !”)
Запустим скрипт... и ничего не происходит. Функция darktable.print_error аналогична print, но будет выводить сообщение только в том случае, если мы включили вывод отладочных данных на терминал для подсистемы lua (запуск darktable с параметрами -d lua в командной строке). Это рекомендуемый способ вывода отладочных данных darktable сценария lua.

9.1.3. Вывод списка изображений с цветовой меткой

Первый пример представил нам начальные основы lua и позволил проверить, что все работает правильно. Давайте сделаем что-нибудь более сложное. Например, попробуем вывести на консоль список изображений, которые отмечены красной цветовой меткой. Но прежде всего, узнаем, как представлено изображение в darktable?
local darktable = require “darktable”
local debug = require “darktable.debug”
print(darktable.debug.dump(darktable.database[1]))
Выполнение приведенного выше кода выведет на экран много информации. Мы посмотрим на неё через мгновение, но сначала давайте рассмотрим сам код.

Во-первых, мы получаем доступ к API darktable и, во-вторых, доступ к необязательным разделам API со вспомогательными функциями darktable.debug, которые будут помогать нам отлаживать сценарии lua.

darktable.database представляет собой таблицу, предоставленную API, которая содержит все изображения из базы данных. Каждая запись в базе данных является объектом изображения. Объекты изображения - это сложные объекты, которые позволяют нам манипулировать нашим изображением различными способами (все это описано в разделе руководства по API -types_dt_lua_image_t). Чтобы вывести информацию об изображениях, мы используем функцию darktable.debug.dump, которая предоставляет рекурсивный доступ к содержимому передаваемого параметра. Поскольку изображения являются сложными объектами, которые косвенно ссылаются на другие сложные объекты, результирующий результат огромен. Ниже приведен сокращённый пример одного такого объекта изображения:
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
Как мы видим, объект изображения имеет большое количество полей, которые предоставляют всю информацию о нём. Нас интересует поле "red" (красная цветовая метка). Это поле является логическим, и документация сообщает нам, что оно может быть выбрано. Теперь нам просто нужно найти все изображения с этим полем и распечатать их.
darktable = require “darktable”
   for _,v in ipairs(darktable.database) do
      if v.red then
print(tostring(v))
   end
end
Этот код должен быть достаточно прост для понимания на данный момент, но он содержит несколько интересных аспектов относительно lua, которые стоит выделить:
  • ipairs является стандартной функцией lua, которая будет перебирать все числовые индексы таблицы. Мы используем её здесь, потому что darktable.database имеет нечисловые индексы, которые являются функциями управления самой базой данных (например, добавление или удаление изображений).
  • Перебор таблицы возвращает 2 значения: ключ и объект изображения. В lua принято использовать переменную с именем "_" для хранения значений, которые нам не нужны в дальнейшем.
  • Обратите внимание, что мы используем стандартную функцию lua, tostring, а не специфическую для darktable darktable.debug.dump. Стандартная функция вернет имя для объекта, тогда как функция отладки будет печатать содержимое. Использование функция debug будет излишним. Это отличный инструмент отладки, но его нельзя использовать ни для чего другого.

9.1.4. Добавление простого вызова сценария комбинацией клавиш

До сих пор все наши скрипты выполнялись во время запуска приложения. Это ограниченное использование и не позволяет нам реагировать на реальные действия пользователя. Чтобы сделать более сложные вещи, нам нужно зарегистрировать функцию, которая будет вызываться при определённом событии. Наиболее распространенным событием для реагирования является сочетание клавиш.
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”)
Теперь запустим darktable, перейдём в "Параметры darktable" => "Клавиатурные комбинации" => "Lua" => "A shortcut that print its parameters", назначим клавиатурную комбинацию и воспользуемся ею. На экране должно появиться сообщение.

Давайте детально рассмотрим код. Сначала мы определяем функцию с двумя параметрами. Эти параметры являются строками. Первый параметр - это тип события, которое запускает скрипт (в нашем случае это будет "shortcut"), а второй параметр – текстовое описание этого события ("A shortcut that print its parameters"). Сама функция вызывает darktable.print для вывода сообщения в всплывающем окне.

Как только эта функция определена, мы регистрируем её для быстрого вызова. Для этого мы вызываем функцию darktable.register_event, которая является общей для всех типов событий. Мы указываем ей, что регистрируем событие на комбинацию клавиш, затем мы указываем функцию, которую должны вызвать, и предоставляем строку для описания комбинации клавиш в окне параметров.

Попробуем сделать комбинацию клавиш немного более интерактивной. Сценарий будет просматривать изображение, которое пользователь в настоящее время выбрал или над которым находится курсор мыши, и повышать его рейтинг.
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”)
На этом этапе большая часть кода должна быть понятной. Всего несколько замечаний:
  • Вместо того чтобы отдельно объявлять функцию и ссылаться на нее, мы объявляем ее непосредственно в вызове darktable.register_event, этот способ эквивалентен ранее рассмотренному, но немного более компактен и сложнее для восприятия.
  • image.rating - это поле объекта изображения, которое содержит рейтинг (от 0 до 5 звезд, -1 означает отклонение изображения).
  • darktable.gui.action_images представляет собой таблицу, содержащую все изображения, представляющие интерес (входят в текущую коллекцию). Darktable будет воздействовать на выбранные изображения, если выбрано какое-либо изображение, или на изображение под курсором мыши, если изображений не выбрано. Эта функция позволяет легко следовать логике пользовательского интерфейса darktable в lua.

Если вы выберете изображение и несколько раз нажмете назначенную комбинацию клавиш, то всё будет работать правильно, но когда вы достигнете пяти звезд, darktable начнет выводить на консоль следующую ошибку:
                                      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
Таким образом сценарии на Lua сообщают нам об ошибках. Мы попытались установить рейтинг 6 для изображения, но рейтинг может достигать всего 5. Было бы правильно добавить проверку на правильность устанавливаемого рейтинга, но давайте рассмотрим сложный способ и отследим ошибку.
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 улавливает любое исключение сгенерированное выполнением своего первого аргумента. Если нет исключения, то будет возвращено значение true плюс любой результат, возвращаемый функцией; если есть исключение, то будет возвращено false и сообщение об ошибке исключения. Далее мы просто проверяем эти результаты и выводим их на консоль...

9.1.5. Экспорт изображений с использованием Lua

Мы научились использовать lua для адаптации darktable к нашему конкретному рабочему процессу, давайте посмотрим, как использовать lua для несложного экспорта изображений. Darktable может экспортировать изображения в несколько интернет-сервисов, но всегда есть другие сервисы, которые ещё не поддерживаются. Если вы можете загрузить изображение в сервис через командную строку, вы можете использовать lua для её интеграции в пользовательский интерфейс darktable.

В следующем примере мы будем использовать lua для экспорта через scp (прим. переводчика: утилита удалённого копирования файлов, использующая в качестве транспорта ssh). Новое хранилище появится в пользовательском интерфейсе darktable и позволит экспортировать изображения в удаленное хранилище с помощью протокола 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 добавляет новый параметр в меню "Параметры darktable" во вкладку "Параметры Lua". Параметры scp_export и export_path позволяют нам однозначно определить необходимые настройки. Эти поля используются повторно, когда мы читаем значение параметра. Параметр string сообщает Lua , что параметр является строкой. Он также может быть целым числом, именем файла или любым из типов, подробно описанных в руководстве по API, относящемся к types_lua_pref_type. Далее указывается текст параметра, отображаемый в меню "Параметры darktable", всплывающая подсказка при наведении мыши на поле ввода значения и значение по умолчанию.

Функция darktable.register_storage осуществляет вызов, который фактически регистрирует новое хранилище. Первый аргумент - это имя хранилища, второй - строка, которая будет отображаться в пользовательском интерфейсе, а последний - функция вызова для каждого экспортируемого изображения. Эта функция имеет множество параметров, но только параметр filename мы используем в этом примере. Он содержит имя временного файла, которое будет экспортировано darktable.

Этот код будет работать, но у него есть несколько ограничений. Это всего лишь простой пример:

  • Мы используем параметр для настройки целевого пути. Было бы лучше добавить элемент в пользовательский интерфейс модуля экспорта. Мы подробно расскажем, как это сделать в следующем разделе
  • Мы не проверяем возвращаемое значение scp. Команда может выполняться с ошибками, в частности, если пользователь неправильно установил параметр.
  • Этот сценарий не может считывать данные от пользователя. Доступ к удаленному хранилищу не должен использовать пароль. Пароль scp не может быть легко предоставлен, поэтому мы оставим сценарий таким каким он есть.
  • После выполнения сценария сообщения о выполнении не отображаются, только в левой нижней части приложения пользователю сообщается, что задание выполнено.
  • Мы используем функцию coroutine.yield для вызова внешней программы. Обычный os.execute блокирует другой код lua.

9.1.6. Создание элемента пользовательского интерфейса

Наш предыдущий пример был немного ограничен. В частности, использование параметра пути экспорта было не очень удобным. Мы можем сделать лучше, добавив элементы в пользовательский интерфейс диалогового окна экспорта.

Элементы пользовательского интерфейса создаются с помощью функции darktable_new_widget. Эта функция принимает вид виджета в качестве параметра и возвращает новый объект, соответствующий этому виджету. Затем можно установить для этого виджета различные поля, чтобы настроить его. Мы будем использовать этот объект в качестве параметра для различных функций, которые добавят его в пользовательский интерфейс darktable. Следующий простой пример добавляет "библиотеку" в режиме "Обзор" в виде простой надписи
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)
Существует хороший синтаксический трюк, который упрощает чтение и запись кода элемента интерфейса пользователя. Вы можете вызвать эти объекты как функции с таблицей ключевых значений в качестве аргументов. Это демонстрирует следующий пример. Он создает виджет контейнера с двумя встроенными виджетами: надпись и поле ввода текста.
   local my_widget = darktable.new_widget("box"){
      darktable.new_widget("label"){ label = "here => " },
      darktable.new_widget("entry"){ tooltip = "please enter text
here" }
   }
Теперь, когда мы это знаем, давайте немного улучшим наш скрипт.
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. Распространение сценариев

До сих пор наш сценарий lua был в расположен в файле luarc. Это хороший способ для разработки своего скрипта, но не очень практичный для его распространения. Для этого мы должны разместить его в отдельном файле (модуле lua). Например, в файле scp-storage.lua как в примере:
--[[
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 будет искать скрипты (следуя обычным правилам lua) в стандартных каталогах плюс в каталоге $CONFIGDIR/lua/?.lua. Поэтому наш скрипт можно вызвать, просто добавив require “scp-storage” в файл luarc. Несколько дополнительных заметок ...

  • Функция darktable.configuration.check_version проверяет совместимость. В параметр ... будет возвращено имя вашего скрипта, а параметр {2,0,0} будет версией API, с которой вы протестировали свой скрипт. Вы можете добавить несколько версий API, если вы разработает свой скрипт для нескольких версий darktable.
  • Обязательно объявите все свои функции как local, чтобы не загрязнять общее пространство имен.
  • Убедитесь, что вы не оставляете отладочные строки в своем коде, в частности darktable.print_error позволяет оставлять отладочные данные в конечном коде, не нарушая работу консоли.
  • Вы можете выбрать любую лицензию для своего скрипта, но скрипты, загруженные на веб-сайт darktable, должны соответствовать лицензии GPLv2.
  • После того, как вы заполнили все поля, проверили ваш код, вы можете загрузить его на нашу страницу сценариев [https://darktable.org/redmine/projects/darktable/wiki/LuaScripts] (прим. переводчика: в настоящий момент данный сайт предлагает искать скрипты по новому адресу [https://github.com/darktable-org/lua-scripts]).

9.1.8. Вызов Lua через DBus

Можно отправить команду lua в darktable через интерфейс DBus. Метод org.darktable.service.Remote.Lua принимает единственный строковый параметр, который интерпретируется как команда lua. Команда будет выполнена в текущем контексте lua и должна возвращать либо nil, либо строку. Результат будет возвращен в результат метода DBus.

Если вызов Lua приводит к ошибке, вызов метода DBus возвращает ошибку org.darktable.Error.LuaError с сообщением об ошибке lua в качестве сообщения, прикрепленного к ошибке DBus.

9.1.9. Использование darktable из сценариев lua

Предупреждение: эта возможность экспериментальная. Известно, что несколько элементов еще не работает в режиме использования darktable как библиотеки. Тщательное тестирование настоятельно рекомендуется.

Интерфейс lua позволяет использовать darktable из любого сценария lua. Загружая darktable в качестве библиотеки и предоставляя вам большую часть lua API (darktable используется без графического интерфейса, поэтому функции связанные с ним недоступны).

В качестве примера следующая программа будет распечатывать список всех изображений вашей библиотеки:
#!/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
Обратите внимание на третью строку, указывающую на местоположение файла libdarktable.so.

Также обратите внимание, что вызов require возвращает функцию, которая может быть вызвана только один раз, и позволяет установить параметр командной строки darktable. В качестве файла библиотеки вы можете использовать :memory: в параметре –library, он будет особенно полезен здесь, если вы не хотите работать в своей библиотеке изображений.

Комментариев нет:

Отправить комментарий