Хабрахабр

[Из песочницы] Пишем VLC плагин для изучения английского

image

Я написал свой плагин для упрощения просмотра сериалов и фильмов на английском языке. В данной статье я расскажу о том, как написать плагин на языке C для медиаплеера VLC. Технические детали реализации плагина приведены в разделах Hello World плагин и Реализация. Идея создания этого плагина описывается в разделах Идея и Поиск решения. О том, что получилось в итоге и как этим пользоваться можно прочитать в последнем разделе, Результат.

Исходный код проекта доступен на GitHub.

Идея изучать иностранный язык за просмотром любимого сериала не нова, но вот с ее воплощением в жизнь лично у меня всегда возникали проблемы. Очень сложно смотреть сериал или фильм, когда не понимаешь половину того, что там говорят. Конечно, можно включить субтитры, но если в речи встретится незнакомое слово или выражение, то от того, что оно будет продублировано текстом, яснее не станет. А смотреть сериал с русскими субтитрами мне совсем не понравилось — мозг переключается на родной язык и перестает воспринимать иностранную речь. Я где-то читал, что сначала нужно смотреть серию на русском языке, а потом уже в оригинале. Но меня такой подход тоже не устроил. Во-первых, где взять столько времени, чтобы по нескольку раз смотреть одно и то же, а во-вторых, смотреть второй раз уже не так интересно — теряется мотивация.

Книги мне нравится читать на электронной читалке Kindle, так как там классно реализована функция работы со словарями — можно одним касанием экрана найти перевод незнакомого слова. Несмотря на все сложности с просмотром иностранных сериалов, я довольно неплохо могу читать техническую документацию, статьи и книги на английском. Такой подход позволяет читать и понимать английские тексты, не сильно отвлекаясь на поиск незнакомых слов. Англоязычные статьи и сайты удобно читать, установив в браузере специальное расширение для перевода, — я пользуюсь расширением Яндекс.Перевод.

Далее продолжаем смотреть серию на английском. Я подумал, почему бы не применить тот же подход для просмотра сериалов — включаем серию на английском, как только встречается непонятная фраза, переключаемся на русскую аудиодорожку и отматываем немного назад.

На самом деле, вся необходимая мне функциональность уже доступна во многих популярных медиаплеерах. Единственное, мне бы хотелось переключать аудиодорожку и отматывать видео на несколько секунд назад нажатием одной кнопки. Также было бы здорово, если бы после перевода непонятного фрагмента медиаплеер сам переключал аудиодорожку обратно на английскую. Ну и еще неплохо было бы иметь возможность повторять переведенный ранее фрагмент с английской дорожкой. 

Желательно еще, чтобы он был кроссплатформенным, так как я пользуюсь ПК под Windows и ноутбуком под Linux. То есть, мне нужен медиаплеер, под который можно писать плагины. На хабре я даже нашел статью, в которой @Idunno рассказывает, как написать расширение VLC на LUA. Мой выбор сразу же пал на VLC. 0. Кстати, это расширение он тоже написал для изучения английского) К сожалению, данное расширение не работает в последних версиях VLC (старше 2. Из-за нестабильной работы из LUA API убрали возможность добавления callback-функций, через которые в LUA-расширении можно было обрабатывать события клавиатуры. 5). В README к своему расширению на GitHub @Idunno приводит ссылку на mailing list разработчиков VLC с обсуждением данной проблемы.

И хоть я и писал что-либо на C последний раз лет 7 назад, еще в университете, решил попробовать. Таким образом, для реализации моей идеи расширение на LUA не подойдет, нужно писать плагин на C.

Стоит отметить, что у медиаплеера VLC достаточно хорошая документация. Из нее я узнал, что при разработке медиаплеера используется модульный подход. VLC состоит из нескольких независимых модулей, реализующих определенную функциональность, и ядра (libVLCCore), которое управляет этими модулями. При этом модули бывают двух типов: внутренние (in-tree) и внешние (out-of-tree). Исходный код внутренних модулей хранится в одном репозитории с кодом ядра. Внешние модули разрабатываются и собираются независимо от медиаплеера VLC. Собственно, последние и есть то, что называют плагинами.

В этой статье приводится исходный код простого плагина, который при запуске VLC выводит на консоль приветственное сообщение «Hello, <name>» (значение <name> берется из настроек плагина). В документации также есть статья о том, как написать свой плагин (модуль) на языке C. Забегая немного вперед, скажу, что в приведенном примере нужно добавить следующую строчку после set_category(CAT_INTERFACE):

set_subcategory( SUBCAT_INTERFACE_CONTROL )

Отлично, осталось только собрать плагин и протестировать его работу. По сборке внешнего плагина тоже есть инструкция. Тут стоит обратить внимание на раздел Internationalization, в котором описывается, как работает локализация в VLC. Если коротко, то для внешних плагинов требуется определять макросы N_(), _():

#define DOMAIN "vlc-myplugin"
#define _(str) dgettext(DOMAIN, str)
#define N_(str) (str)

Для сборки предлагается использовать старый добрый Makefile либо Autotools. Я решил пойти простым путем и выбрал Makefile. В Makefile нужно не забыть определить переменную MODULE_STRING — это идентификатор нашего плагина. Также я немного подправил работу с директориями — теперь они определяются через pkg-config. В итоге получились следующие файлы:

hello.c

/** * @file hello.c * @brief Hello world interface VLC module example */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif #define DOMAIN "vlc-myplugin"
#define _(str) dgettext(DOMAIN, str)
#define N_(str) (str) #include <stdlib.h>
/* VLC core API headers */
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_interface.h> /* Forward declarations */
static int Open(vlc_object_t *);
static void Close(vlc_object_t *); /* Module descriptor */
vlc_module_begin() set_shortname(N_("Hello")) set_description(N_("Hello interface")) set_capability("interface", 0) set_callbacks(Open, Close) set_category(CAT_INTERFACE) set_subcategory( SUBCAT_INTERFACE_CONTROL ) add_string("hello-who", "world", "Target", "Whom to say hello to.", false)
vlc_module_end () /* Internal state for an instance of the module */
struct intf_sys_t
{ char *who;
}; /** * Starts our example interface. */
static int Open(vlc_object_t *obj)
sys->who = who; msg_Info(intf, "Hello %s!", who); return VLC_SUCCESS; error: free(sys); return VLC_EGENERIC; } /** * Stops the interface. */
static void Close(vlc_object_t *obj)
{ intf_thread_t *intf = (intf_thread_t *)obj; intf_sys_t *sys = intf->p_sys; msg_Info(intf, "Good bye %s!", sys->who); /* Free internal state */ free(sys->who); free(sys);
}

Makefile

LD = ld
CC = cc
PKG_CONFIG = pkg-config
INSTALL = install
CFLAGS = -g -O2 -Wall -Wextra
LDFLAGS =
LIBS =
VLC_PLUGIN_CFLAGS := $(shell $(PKG_CONFIG) --cflags vlc-plugin)
VLC_PLUGIN_LIBS := $(shell $(PKG_CONFIG) --libs vlc-plugin)
VLC_PLUGIN_DIR := $(shell $(PKG_CONFIG) --variable=pluginsdir vlc-plugin) plugindir = $(VLC_PLUGIN_DIR)/misc override CC += -std=gnu99
override CPPFLAGS += -DPIC -I. -Isrc
override CFLAGS += -fPIC
override LDFLAGS += -Wl,-no-undefined,-z,defs override CPPFLAGS += -DMODULE_STRING=\"hello\"
override CFLAGS += $(VLC_PLUGIN_CFLAGS)
override LIBS += $(VLC_PLUGIN_LIBS) all: libhello_plugin.so install: all mkdir -p -- $(DESTDIR)$(plugindir) $(INSTALL) --mode 0755 libhello_plugin.so $(DESTDIR)$(plugindir) install-strip: $(MAKE) install INSTALL="$(INSTALL) -s" uninstall: rm -f $(plugindir)/libhello_plugin.so clean: rm -f -- libhello_plugin.so src/*.o mostlyclean: clean SOURCES = hello.c $(SOURCES:%.c=src/%.o): $(SOURCES:%.c=src/%.c) libhello_plugin.so: $(SOURCES:%.c=src/%.o) $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) .PHONY: all install install-strip uninstall clean mostlyclean

Проще всего собрать плагин под Linux. Для этого потребуется установить, собственно, сам медиаплеер VLC, а также необходимые для сборки плагина файлы и инструменты. В Debian/Ubuntu это можно сделать с помощью следующей команды:

sudo apt-get install vlc libvlc-dev libvlccore-dev gcc make pkg-config

Собственно, все готово, собираем и устанавливаем наш плагин с помощью команды:

sudo make install

Для проверки работы плагина запускаем VLC также из консоли:

vlc

К сожалению, никакого «Hello world» мы не увидели. Все дело в том, что плагин нужно сначала включить. Для этого открываем настройки (Tools > Preferences), переключаемся на расширенный вид (выбираем All в группе Show settings) и находим в дереве на панели слева Interface > Control interfaces — ставим галочку напротив нашего плагина Hello interface.

Сохраняем настройки и перезапускаем VLC.

С Windows все немного сложнее. Для сборки плагина потребуется скачать sdk, который содержит библиотеки, заголовочные и конфигурационные файлы VLC. Раньше sdk входил в обычную сборку VLC и найти его можно было в папке установки программы. Теперь же он поставляется в виде отдельной сборки медиаплеера. Например, для VLC версии 3.0.8 эту сборку можно скачать по ссылке ftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z (важно скачивать именно 7z-архив).

Кроме sdk архив содержит и сам медиаплеер, который можно использовать для тестирования и отладки плагина. Копируем содержимое архива в какую-нибудь папку, например, в С:\Projects.

0. Чтобы наш Makefile можно было использовать для сборки и установки плагина, нужно поправить файл C:\Projects\vlc-3. 8\sdk\lib\pkgconfig\vlc-plugin.pc, указав в переменных prefix и pluginsdir корректный путь до папки с sdk и plugins соответственно:

prefix=/c/Projects/vlc-3.0.8/sdk
pluginsdir=/c/Projects/vlc-3.0.8/plugins

Для сборки под Windows нам также потребуется установить компилятор и другие утилиты. Все необходимое ПО можно получить, установив среду MSYS2. На сайте проекта есть подробная инструкция по установке. Если коротко, то сразу после установки нужно открыть консоль (C:\msys64\msys2.exe) и обновить пакеты MSYS2 с помощью команды:

pacman -Syu

Далее нужно закрыть окно терминала MSYS2, затем открыть его еще раз и выполнить команду

pacman -Su

После обновления всех пакетов нужно установить тулчейн:

pacman -S base-devel mingw-w64-x86_64-toolchain

Теперь, когда все необходимые пакеты установлены, можно приступать к сборке плагина. Я немного доработал Makefile, чтобы он мог собирать плагин как под Linux так и под Windows. Кроме того, пришлось убрать некоторые неподдерживаемые MinGW параметры сборки, в итоге Makefile стал выглядеть так:

Makefile для Windows

LD = ld
CC = cc
PKG_CONFIG = pkg-config
INSTALL = install
CFLAGS = -g -O2 -Wall -Wextra
LDFLAGS =
LIBS =
VLC_PLUGIN_CFLAGS := $(shell $(PKG_CONFIG) --cflags vlc-plugin)
VLC_PLUGIN_LIBS := $(shell $(PKG_CONFIG) --libs vlc-plugin)
VLC_PLUGIN_DIR := $(shell $(PKG_CONFIG) --variable=pluginsdir vlc-plugin) plugindir = $(VLC_PLUGIN_DIR)/misc override CC += -std=gnu99
override CPPFLAGS += -DPIC -I. -Isrc
override CFLAGS += -fPIC
override LDFLAGS += -Wl,-no-undefined override CPPFLAGS += -DMODULE_STRING=\"hello\"
override CFLAGS += $(VLC_PLUGIN_CFLAGS)
override LIBS += $(VLC_PLUGIN_LIBS) SUFFIX := so
ifeq ($(OS),Windows_NT) SUFFIX := dll
endif all: libhello_plugin.$(SUFFIX) install: all mkdir -p -- $(DESTDIR)$(plugindir) $(INSTALL) --mode 0755 libhello_plugin.$(SUFFIX) $(DESTDIR)$(plugindir) install-strip: $(MAKE) install INSTALL="$(INSTALL) -s" uninstall: rm -f $(plugindir)/libhello_plugin.$(SUFFIX) clean: rm -f -- libhello_plugin.$(SUFFIX) src/*.o mostlyclean: clean SOURCES = hello.c $(SOURCES:%.c=src/%.o): $(SOURCES:%.c=src/%.c) libhello_plugin.$(SUFFIX): $(SOURCES:%.c=src/%.o) $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) .PHONY: all install install-strip uninstall clean mostlyclean

Так как MSYS2 ничего не знает о нашем sdk для VLC, то перед сборкой нужно добавить в переменную окружения PKG_CONFIG_PATH путь до папки pkgconfig из этого sdk. Открываем консоль MinGW (C:\msys64\mingw64.exec) и выполняем команды:

export PKG_CONFIG_PATH=/c/projects/vlc-3.0.8/sdk/lib/pkgconfig:$PKG_CONFIG_PATH
make install

Для проверки работы плагина запускаем VLC также из консоли:

/c/projects/vlc-3.0.8/vlc

Как и в случае с Linux, идем в настройки и включаем наш плагин. Сохраняем настройки и перезапускаем VLC.
Для реализации моего плагина мне необходимо было понять, как управлять медиаплеером (менять аудиодорожку, перематывать назад) и как обрабатывать события нажатия клавиш клавиатуры. Чтобы разобраться во всем этом, я обратился к документации. Также в Интернете я нашел пару интересных статей, проливающих свет на архитектуру медиаплеера: The architecture of VLC media framework и VLC media player API Documentation.

Каждый модуль должен предоставлять информацию о типе реализуемой им функциональности, а также функции инициализации/финализации. VLC состоит из большого количества независимых модулей (400+). Функции инициализации/финализации модуля (обычно они называются Open и Close) имеют следующую сигнатуру: Данная информация описывается в блоке vlc_module_begin()vlc_module_end() с помощью макросов set_capability() и set_callbacks().

static int Open(vlc_object_t *)
static void Close(vlc_object_t *)

vlc_object_t — это базовый тип для представления данных в VLC, от которого наследуются все остальные (см. статью Object_Management). Указатель на vlc_object_t нужно приводить к конкретному типу данных в соответствии с той функциональностью, которую реализует модуль. Для управления медиаплеером я указал в макросе set_capability() значение interface. Соответственно, в функциях Open и Close мне нужно привести vlc_object_t к intf_thread_t.

VLC предоставляет механизм «object variables» (см. Взаимодействие между модулями построено на основе шаблона проектирования observer. Через эти переменные модули могут обмениваться данными. статью Variables), с помощью которого в экземпляры типа vlc_object_t (и его производные) можно добавлять переменные. Также на переменную можно навесить callback-функцию, которая будет вызываться при изменении значения этой переменной.

В функции Open на переменную key-action навешивается callback-функция ActionEvent: В качестве примера можно рассмотреть модуль Hotkeys (modules/control/hotkeys.c), который отвечает за обработку событий нажатия горячих клавиш.

var_AddCallback( p_intf->obj.libvlc, "key-action", ActionEvent, p_intf );

В функцию var_AddCallback передается указатель на vlc_object_t, имя переменной, callback-функция и указатель на void для передачи произвольных данных, которые потом пробрасываются в указанную callback-функцию. Сигнатура callback-функции приведена ниже.

static int ActionEvent(vlc_object_t *, char const *, vlc_value_t, vlc_value_t, void *)

В качестве параметров в callback-функцию передается указатель на vlc_object_t, имя переменной, старое и новое значение этой переменной (в данном случае, идентификатор соответствующего нажатой комбинации горячих клавиш действия), а также заданный при добавлении callback-функции указатель на какие-либо дополнительные данные.

Функция PutAction принимает на вход идентификатор события нажатия комбинации горячих клавиш (i_action) и с помощью оператора switch выполняет соответствующие действия.  Непосредственно обработка событий горячих клавиш выполняется в функции PutAction, которая вызывается внутри callback-функции ActionEvent.

Для выполнения соответствующего действия из настроек VLC берется интервал перемотки (из переменной short-jump-size): Например, событию перемотки назад соответствует значение ACTIONID_JUMP_BACKWARD_SHORT.

mtime_t it = var_InheritInteger( p_input, varname );

Чтобы перемотать проигрываемый файл, достаточно присвоить переменной time-offset значение, соответствующее времени (в микросекундах), на которое нужно сместить воспроизведение:

var_SetInteger( p_input, "time-offset", it * sign * CLOCK_FREQ );

Для перемотки вперед нужно указать положительное значение, для перемотки назад — отрицательное. Константа CLOCK_FREQ используется для конвертации секунд в микросекунды.

Только отвечающая за аудиодорожку переменная audio-es может принимать ограниченный набор значений (в соответствии с имеющимися в проигрываемом файле аудиодорожками). Аналогичным образом происходит смена аудиодорожки (событие ACTIONID_AUDIO_TRACK). Получить список возможных значений переменной можно с помощью функции var_Change():

vlc_value_t list, list2;
var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES, &list, &list2 );

Помимо списка значений эта функция также позволяет получить список описаний этих значений (в данном случае название аудиодорожек). Теперь мы можем поменять аудиодорожку с помощью функции var_Set():

var_Set( p_input, "audio-es", list.p_list->p_values[i] );

Как управлять медиаплеером разобрались, осталось научиться обрабатывать события клавиатуры. К сожалению, добавить новую горячую клавишу у меня не получилось. Все горячие клавиши жестко зашиты в коде ядра VLC (src/misc/actions.c). Поэтому я добавил обработчик более низкоуровневых событий нажатия клавиш клавиатуры, повесив свою callback-функцию на изменение переменной key-pressed:

var_AddCallback( p_intf->obj.libvlc, "key-pressed", KeyboardEvent, p_intf );

В переменной key-pressed хранится код символа (в Unicode), соответствующего последней нажатой клавише. Например, при нажатии клавиши с цифрой «1» переменной key-pressed будет присвоено значение 49 (0x00000031 в 16ой системе счисления). Посмотреть коды других символов можно на сайте unicode-table.com. Кроме того, в значении переменной key-pressed учитывается нажатие клавиш-модификаторов, для них отведен  четвертый значащий байт. Так, например, при нажатии комбинации клавиш «Ctrl + 1» переменной key-pressed будет присвоено значение 0x04000031 (00000100 00000000 00000000 001100012). Для наглядности в таблице ниже приведены значения различных комбинаций клавиш:
Обратите внимание на значение при нажатии комбинации «Shift + 1&raquo. Так как в этом случае будет выведен символ «!», то и значение первого байта будет соответствовать коду этого символа в Unicode (0x00000021).
Я назвал свой плагин TIP — акроним от фразы «translate it, please», также tip можно перевести как «подсказка». Исходный код плагина опубликован на GitHub, там же можно скачать готовые сборки плагина под Windows и Linux.

В Windows VLC обычно устанавливается в папку C:\Program Files\VideoLAN\VLC. Для установки плагина нужно скопировать файл libtip_plugin.dll (libtip_plugin.so для Linux) в папку <path-to-vlc>/plugins. В Linux найти папку установки можно с помощью команды:

whereis vlc

В Ubuntu, например, VLC ставится в /usr/lib/x86_64-linux-gnu/vlc

После чего снова потребуется перезапуск VLC. Далее потребуется перезапустить VLC, затем в главном меню открыть Tools > Preferences, переключиться на расширенный вид (выбрать All в группе Show settings), на панели слева перейти в раздел Interface > Control и поставить галочку напротив пункта TIP (translate it, please).

В настройках плагина можно указать номера основной и вспомогательной (для перевода) аудиодорожек, а также время (в секундах), на которое плагин будет отматывать назад для повтора со вспомогательной аудиодорожкой.

Для управления плагином я добавил следующие горячие клавиши:

  • «/» для перевода
  • «Shift + /» для повтора переведенного ранее фрагмента видео с основной аудиодорожкой

Во время выполнения команд перевода и повтора плагин отображает в верхнем левом углу сообщения «TIP: translate» и «TIP: repeat» соответственно.

Главное, правильно подобрать контент, если будет понятна хотя бы половина используемой там иностранной речи, плагин поможет перевести остальное. По своему опыту использования плагина могу сказать, что, в целом, я доволен результатом. В противном случае плагин, наверное, будет бесполезен.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Проверьте также

Закрыть
Кнопка «Наверх»
Закрыть