编写用于学习英语的VLC插件

图片

在本文中,我将讨论如何在C中为VLC媒体播放器编写插件。 我写了我的插件来简化用英语观看电视节目和电影的过程。 在创建想法查找解决方案部分中介绍了创建此插件的想法。 插件实现的技术细节在Hello World插件实现部分中提供。 最后的结果以及如何使用它可以在最后一部分Result中找到

该项目的源代码可在GitHub找到

主意


在观看我最喜欢的电视剧时学习外语的想法并不是什么新鲜事,但是我个人一直在实现它方面遇到问题。 如果您听不懂他们说的一半,就很难看连续剧或电影。 当然,您可以打开字幕,但是如果语音中遇到陌生的单词或表达,将很难被文字复制。 而且我根本不喜欢看带有俄文字幕的电视剧-大脑切换到母语,并且不再能听外国语言。 我读过某个地方,首先您需要看俄语的系列节目,然后才看原始版本。 但是这种方法也不适合我。 首先,在哪里花那么多时间去看几次相同的东西,其次,再去看第二遍不再那么有趣了-失去了动力。

尽管在观看外国电视节目时遇到了种种困难,但我可以很好地阅读英文技术文档,文章和书籍。 我喜欢在Kindle电子阅读器上阅读书籍,因为词典功能很酷,您只需触摸一下屏幕,就可以找到不熟悉的单词的翻译。 通过在浏览器中安装用于翻译的特殊扩展名,可以方便地阅读英语文章和网站-我使用Yandex.Translation扩展名。 这种方法使您可以阅读和理解英语文本,而不会因为搜索陌生单词而分心。

我想,为什么不采用相同的方法观看电视节目-一旦发现一个难以理解的词组,我们便以英语打开该系列节目,切换到俄语音轨并向后退​​一点。 接下来,我们继续观看英语系列赛。

寻找解决方案


实际上,我所需的所有功能已经在许多流行的媒体播放器中提供。 我想在几秒钟前单击一下按钮来切换音轨和倒带视频。 如果在翻译了一个令人费解的片段之后,媒体播放器本身将音轨切换回英语,那也很好。 好吧,能够在英语轨道上重复以前翻译的片段会很好。

也就是说,我需要一个可以为其编写插件的媒体播放器。 还希望它是跨平台的,因为我在Windows下使用PC,在Linux下使用笔记本电脑。 我的选择立即落在了VLC上。 在habr上,我什至发现了一篇文章 ,其中@Idunno讲述了如何在LUA上编写VLC扩展。 顺便说一下,他还写了这个扩展程序来学习英语。不幸的是,这个扩展程序在最新版本的VLC(低于2.0.5)中不起作用。 由于操作不稳定,因此从LUA API中删除了添加回调函数的功能,通过该函数可以处理LUA扩展中的键盘事件。 在README中 ,指向讨论此问题的VLC开发人员的邮件列表的链接导致其在GitHub @Idunno上的扩展

因此,要实现我的想法,对LUA的扩展不起作用,您需要用C编写一个插件。尽管大约7年前我上大学时最后一次用C编写了东西,但我还是决定尝试一下。

你好世界插件


值得注意的是,VLC媒体播放器具有非常好的文档 。 我从中了解到,媒体播放器的开发使用模块化方法。 VLC包含几个实现某些功能的独立模块,以及管理这些模块的内核( libVLCCore )。 模块有两种类型:内部( 树内 )和外部( 树外 )。 内部模块的源代码与内核代码一起存储在一个存储库中。 外部模块的开发和组装独立于VLC媒体播放器。 实际上,后者就是所谓的插件。

该文档还包含有关如何使用C语言编写插件(模块)的文章。本文提供了一个简单插件的源代码,该插件在VLC启动时向控制台显示欢迎消息“ Hello,<name> ”(采用值<name>从插件设置)。 向前走一点,我会说在上面的示例中,在set_category(CAT_INTERFACE)之后添加以下行:

 set_subcategory( SUBCAT_INTERFACE_CONTROL ) 

好吧,剩下的就是组装插件并测试其运行了。 还有一个有关构建外部插件的说明。 这里值得关注国际化部分,该部分描述了本地化在VLC中的工作方式。 简而言之,对于外部插件,您需要定义宏N_() _()_()

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

对于组装,建议使用良好的旧Makefile或Autotools。 我决定采用简单的方法,并选择了Makefile。 在Makefile中,您需要记住定义MODULE_STRING变量-这是我们插件的标识符。 我还对目录进行了一些调整-现在它们是通过pkg-config定义的。 结果是以下文件:

你好
 /** * @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) { intf_thread_t *intf = (intf_thread_t *)obj; /* Allocate internal state */ intf_sys_t *sys = malloc(sizeof (*sys)); if (unlikely(sys == NULL)) return VLC_ENOMEM; intf->p_sys = sys; /* Read settings */ char *who = var_InheritString(intf, "hello-who"); if (who == NULL) { msg_Err(intf, "Nobody to say hello to!"); goto error; } 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); } 

生成文件
 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 ”。 问题是必须首先启用该插件。 为此,请打开设置(“ 工具” > “首选项” ),切换到高级视图(在“ 显示设置”组中选择“ 全部 ”),然后在左面板的树中找到“ 接口” >“ 控制接口” -选中“ Hello接口插件”旁边的复选框。



我们保存设置并重新启动VLC。



为Windows构建插件


使用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 -存档)。

将存档的内容复制到一个文件夹,例如,复制到C:\ Projects 。 除sdk外,归档文件还包含媒体播放器本身,可用于测试和调试插件。

为了使我们的Makefile可以用于构建和安装插件,您需要修复文件C:\ Projects \ vlc-3.0.8 \ sdk \ lib \ pkgconfig \ vlc-plugin.pc ,在前缀pluginsdir变量中指示sdk文件夹的正确路径。和插件

 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开始看起来像这样:

Windows的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 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对VLC的sdk一无所知,因此在构建之前,有必要将该sdk中pkgconfig文件夹的路径添加到PKG_CONFIG_PATH环境变量中 。 打开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。

插件实现


要实现我的插件,我需要了解如何控制媒体播放器(更改音轨,倒带)以及如何处理键盘按键事件。 为了理解所有这些,我转向了文档 。 同样在Internet上,我发现了一些有趣的文章,它们阐明了媒体播放器的体系结构:VLC媒体框架的体系结构VLC媒体播放器API文档

VLC包含大量独立模块(400+)。 每个模块都应提供有关其实现的功能类型以及初始化/完成功能的信息。 使用set_capability()set_callbacks()宏在vlc_module_begin() - vlc_module_end()块中描述了此信息。 模块的初始化/完成函数(通常称为OpenClose )具有以下签名:

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

vlc_object_t是在VLC中表示数据的基本类型,所有其他数据都将继承自该类型(请参阅文章Object_Management )。 必须根据模块实现的功能将指向vlc_object_t的指针转换为特定的数据类型。 为了控制媒体播放器,我在set_capability()宏中设置了 接口值。 因此,在打开关闭函数中,我需要将vlc_object_t强制转换intf_thread_t

模块之间的交互基于观察者设计模式。 VLC提供了一种“对象变量”机制(请参见Variables ),您可以使用该机制将变量添加到vlc_object_t类型及其实例的实例中。 模块可以通过这些变量交换数据。 您还可以将回调函数附加到该变量,该变量的值更改时将调用该函数。

例如,考虑负责处理热键事件的热键模块( modules / control / hotkeys.c )。 在Open函数中, ActionEvent回调函数挂在key-action 变量上

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

指向vlc_object_t的指针,变量名,回调函数和指向void的指针传递给var_AddCallback函数以传递任意数据,然后将其转发给指定的回调函数。 回调函数的签名如下所示。

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

指向vlc_object_t的指针,变量名称,该变量的旧值和新值(在这种情况下,是该操作对应的按下的热键组合的标识符)以及添加回调函数时指定的任何其他数据的指针,将传递给回调函数。

热键事件的直接处理在PutAction函数中执行,该函数在ActionEvent回调函数中调用。 PutAction函数接受按下热键组合( i_action )的事件的标识符,并在切换操作符的帮助下执行相应的动作。

例如,倒带事件对应于ACTIONID_JUMP_BACKWARD_SHORT 。 要执行相应的操作,请从VLC设置(从变量short-jump-size )获取倒带间隔:

 mtime_t it = var_InheritInteger( p_input, varname ); 

要倒带正在播放的文件,只需将time-offset变量设置为与您要转换播放的时间对应的值(以微秒为单位):

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

对于快进,您需要指定一个正值,对于快退,则需要指定-负值。 CLOCK_FREQ常数用于将秒转换为微秒。

同样,音轨也会发生变化( ACTIONID_AUDIO_TRACK事件)。 只有负责音轨的audio-es变量才能接受有限的一组值(根据正在播放的文件中可用的音轨)。 您可以使用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 )中。 因此,我为低级键盘按键事件添加了一个处理程序,挂起了我的回调函数来更改按键变量:

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

按下按键的变量存储与最后按下的按键相对应的字符代码(Unicode)。 例如,当您按下带有数字“ 1”按键时,按键变量将被分配值为49 (在第16个数字系统中为0x00000031)。 您可以在unicode-table.com上查看其他字符代码。 此外, 按键变量的值考虑了修改键的按下,为它们分配了第四个有效字节。 因此,例如,当按下“ Ctrl + 1 ”组合键时,按键变量将被赋予值0x 04 000031(00000 1 00 00000000 00000000 00110001 2 )。 为了清楚起见,下表显示了各种组合键的含义:
键盘快捷键价值
Ctrl + 100000 1 00 00000000 00000000 00110001 2
Alt + 10000000 1 00000000 00000000 00110001 2
Ctrl + Alt + 100000 1 0 1 00000000 00000000 00110001 2
Shift + 1000000 1 0 00000000 00000000 00100001 2
按下“ Shift + 1 ”组合时请注意该值。 由于在这种情况下,“ “,那么第一个字节的值将对应于Unicode(0x00000021)中此字符的代码。

结果


我将我的TIP插件称为“请翻译”的缩写,也可以将提示翻译为“提示”。 该插件的源代码发布在GitHub上 ,您还可以在其中下载适用于Windows和Linux的现成插件程序集。

要安装插件,您需要将文件libtip_plugin.dll (对于Linux为libtip_plugin.so)复制到<path-to-vlc> / plugins文件夹。 在Windows上,VLC通常安装在C:\ Program Files \ VideoLAN \ VLC文件夹中。 在Linux上,可以使用以下命令找到安装文件夹:

 whereis vlc 

例如,在Ubuntu中,VLC安装在/ usr / lib / x86_64-linux-gnu / vlc中

接下来,您将需要重新启动VLC,然后在主菜单中打开`` 工具'' > ``首选项'' ,切换到高级视图(在``显示设置''组中选择` ` 全部'' ),转到左侧面板的`` 界面'' > ` ` 控制''部分,然后选中``提示''旁边的框(请翻译) 。 然后再次需要重新启动VLC。



在插件设置中,您可以指定主要和辅助(用于翻译)音轨的编号,以及插件倒带与辅助音轨重复播放的时间(以秒为单位)。



为了控制插件,我添加了以下键盘快捷键:

  • / ”用于翻译
  • Shift + / ”重复以前翻译的视频片段和主要音轨

在执行翻译和重试命令期间,该插件分别在左上角显示消息“提示:翻译”“提示:重复”



根据我使用插件的经验,我可以说总体上我对结果感到满意。 最主要的是选择正确的内容,如果至少能理解那里使用的外国语音的一半,则该插件将有助于翻译其余内容。 否则,该插件将可能无用。

Source: https://habr.com/ru/post/zh-CN475992/


All Articles