Escrevendo um plugin VLC para aprender inglês

imagem

Neste artigo, falarei sobre como escrever um plug-in em C para o VLC media player. Eu escrevi meu plugin para simplificar assistir programas de TV e filmes em inglês. A ideia de criar este plugin é descrita nas seções Idéia e Localização de uma solução . Detalhes técnicos da implementação do plug-in são fornecidos nas seções Plug - in do Hello World e Implementação . O que aconteceu no final e como usá-lo pode ser encontrado na última seção, Resultado .

O código fonte do projeto está disponível no GitHub .

Idéia


A idéia de aprender uma língua estrangeira enquanto assisto minha série favorita não é nova, mas sempre tive problemas com ela pessoalmente. É muito difícil assistir a uma série ou filme quando você não entende metade do que eles dizem. Obviamente, você pode ativar legendas, mas se uma palavra ou expressão desconhecida for encontrada em um discurso, não ficará mais claro que será duplicado pelo texto. E eu não gostei de assistir a série com legendas em russo - o cérebro muda para o idioma nativo e deixa de perceber fala estrangeira. Li em algum lugar que primeiro você precisa assistir a uma série em russo e depois no original. Mas essa abordagem também não me agradou. Em primeiro lugar, onde levar tanto tempo para assistir à mesma coisa várias vezes e, em segundo lugar, assistir à segunda vez não é mais tão interessante - a motivação está perdida.

Apesar de todas as dificuldades em assistir programas de TV estrangeiros, eu posso muito bem ler documentação técnica, artigos e livros em inglês. Gosto de ler livros no leitor eletrônico do Kindle, pois a função de dicionário é legal por lá - você pode encontrar uma tradução de uma palavra desconhecida com um toque na tela. É conveniente ler artigos e sites em inglês instalando uma extensão especial para tradução no navegador - eu uso a extensão Yandex.Translation . Essa abordagem permite que você leia e compreenda textos em inglês, sem muita distração para a busca de palavras desconhecidas.

Pensei, por que não aplicar a mesma abordagem para assistir programas de TV - ligamos a série em inglês assim que uma frase incompreensível é encontrada, mudamos para a faixa de áudio russa e retrocedemos um pouco. Em seguida, continuamos a assistir a série em inglês.

Procure uma solução


De fato, toda a funcionalidade que eu preciso já está disponível em muitos players de mídia populares. A única coisa que gostaria de mudar a faixa de áudio e retroceder o vídeo alguns segundos atrás com o clique de um botão. Também seria ótimo se, depois de traduzir um fragmento incompreensível, o próprio media player retornasse a faixa de áudio para o inglês. Bem, seria bom poder repetir o fragmento traduzido anteriormente com a faixa em inglês.

Ou seja, preciso de um media player para o qual você possa escrever plugins. Também é desejável que seja multiplataforma, pois eu uso um PC no Windows e um laptop no Linux. Minha escolha caiu imediatamente no VLC. No habr, eu até encontrei um artigo no qual @Idunno diz como escrever uma extensão VLC no LUA. A propósito, ele também escreveu esta extensão para aprender inglês. Infelizmente, essa extensão não funciona nas versões mais recentes do VLC (anteriores à 2.0.5). Devido à operação instável, a capacidade de adicionar funções de retorno de chamada através das quais foi possível processar eventos de teclado na extensão LUA foi removida da API LUA. No README , um link para a lista de discussão dos desenvolvedores do VLC que discutem esse problema leva à sua extensão no GitHub @Idunno .

Portanto, para implementar minha ideia, uma extensão para LUA não funciona, você precisa escrever um plug-in em C. E, embora eu tenha escrito algo em C pela última vez há cerca de 7 anos, na universidade, decidi tentar.

Hello world plugin


Vale a pena notar que o VLC media player possui uma documentação muito boa. Aprendi com ele que o desenvolvimento de um media player usa uma abordagem modular. O VLC consiste em vários módulos independentes que implementam determinadas funcionalidades e no kernel ( libVLCCore ), que gerencia esses módulos. Existem dois tipos de módulos: interno ( dentro da árvore ) e externo ( fora da árvore ). O código fonte dos módulos internos é armazenado em um repositório com o código do kernel. Os módulos externos são desenvolvidos e montados independentemente do media player VLC. Na verdade, estes são os chamados plugins.

A documentação também possui um artigo sobre como gravar seu plug-in (módulo) em C. Este artigo fornece o código-fonte de um plug-in simples que, quando o VLC é iniciado, exibe uma mensagem de boas-vindas " Hello, <name> " no console (o valor <name> é usado nas configurações do plug-in). Correndo um pouco à frente, direi que no exemplo acima, adicione a seguinte linha após set_category(CAT_INTERFACE) :

 set_subcategory( SUBCAT_INTERFACE_CONTROL ) 

Bem, tudo o que resta é montar o plug-in e testar seu funcionamento. Há também uma instrução para criar um plugin externo. Aqui, vale a pena prestar atenção na seção Internacionalização , que descreve como a localização funciona no VLC. Em resumo, para plug-ins externos, você precisa definir as macros N_() , _() :

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

Para montagem, propõe-se usar os bons e antigos Makefile ou Autotools. Decidi seguir o caminho mais simples e escolhi o Makefile. No Makefile, você precisa se lembrar de definir a variável MODULE_STRING - este é o identificador do nosso plugin. Também ajustei um pouco o trabalho com os diretórios - agora eles são definidos através do pkg-config . O resultado são os seguintes arquivos:

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) { 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); } 

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 

A maneira mais fácil de criar um plugin para Linux. Para fazer isso, você precisa instalar, de fato, o próprio VLC media player, bem como os arquivos e as ferramentas necessárias para criar o plug-in. No Debian / Ubuntu, isso pode ser feito com o seguinte comando:

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

Na verdade, está tudo pronto, coletamos e instalamos nosso plugin usando o comando:

 sudo make install 

Para testar o plug-in, execute o VLC também no console:

 vlc 

Infelizmente, não vimos nenhum " Olá, mundo ". A questão é que o plug-in deve primeiro ser ativado. Para fazer isso, abra as configurações ( Ferramentas > Preferências ), alterne para a visualização avançada (selecione Tudo no grupo Mostrar configurações ) e encontre na árvore no painel esquerdo Interface > Interfaces de controle - marque a caixa ao lado do nosso plug-in de interface Hello .



Salvaremos as configurações e reiniciaremos o VLC.



Crie um plug-in para Windows


Com o Windows, as coisas são um pouco mais complicadas. Para criar o plug-in, você precisa fazer o download do sdk, que contém bibliotecas, cabeçalho e arquivos de configuração do VLC. Anteriormente, o sdk fazia parte do conjunto regular do VLC e podia ser encontrado na pasta de instalação do programa. Agora, ele vem como uma montagem separada do media player. Por exemplo, para o VLC versão 3.0.8, é possível fazer o download deste assembly em ftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z (é importante fazer o download do 7z -arquivo).

Copie o conteúdo do arquivo morto para uma pasta, por exemplo, em C: \ Projetos . Além do sdk, o arquivo morto também contém o próprio media player, que pode ser usado para testar e depurar o plug-in.

Para que nosso Makefile possa ser usado para criar e instalar o plug-in, é necessário corrigir o arquivo C: \ Projects \ vlc-3.0.8 \ sdk \ lib \ pkgconfig \ vlc-plugin.pc , indicando o caminho correto para a pasta sdk nas variáveis prefix e pluginsdir e plugins, respectivamente:

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

Para criar no Windows, também precisamos instalar um compilador e outros utilitários. Todo o software necessário pode ser obtido instalando o ambiente MSYS2 . O site do projeto possui instruções detalhadas de instalação. Em resumo, imediatamente após a instalação, você precisa abrir o console ( C: \ msys64 \ msys2.exe ) e atualizar os pacotes MSYS2 usando o comando:

 pacman -Syu 

Em seguida, feche a janela do terminal MSYS2, abra-a novamente e execute o comando

 pacman -Su 

Após atualizar todos os pacotes, você precisa instalar o conjunto de ferramentas:

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

Agora que todos os pacotes necessários estão instalados, você pode começar a criar o plugin. Modifiquei um pouco o Makefile para que ele pudesse criar o plug-in no Linux e no Windows. Além disso, eu tive que remover alguns parâmetros de compilação MinGW não suportados, como resultado, o Makefile começou a ficar assim:

Makefile para 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 

Como o MSYS2 não sabe nada sobre o sdk for VLC, você precisa adicionar o caminho para a pasta pkgconfig desse sdk na variável de ambiente PKG_CONFIG_PATH . Abra o console do MinGW ( C: \ msys64 \ mingw64.exec ) e execute os comandos:

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

Para testar o plug-in, execute o VLC também no console:

 /c/projects/vlc-3.0.8/vlc 

Como no caso do Linux, vá para as configurações e ative nosso plugin. Salvaremos as configurações e reiniciaremos o VLC.

Implementação de plugins


Para implementar meu plug-in, eu precisava entender como controlar o media player (alterar a faixa de áudio, rebobinar) e como lidar com os eventos de pressionamento de tecla do teclado. Para entender tudo isso, virei-me para a documentação . Também na Internet, encontrei alguns artigos interessantes que esclarecem a arquitetura do media player: A arquitetura da estrutura de mídia VLC e a documentação da API do VLC media player .

O VLC consiste em um grande número de módulos independentes (mais de 400). Cada módulo deve fornecer informações sobre o tipo de funcionalidade que implementa, bem como as funções de inicialização / finalização. Essas informações são descritas no bloco vlc_module_begin () - vlc_module_end () usando as macros set_capability () e set_callbacks () . As funções de inicialização / finalização do módulo (geralmente chamadas de Abrir e Fechar ) têm a seguinte assinatura:

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

vlc_object_t é o tipo de base para representar dados no VLC, do qual todos os outros são herdados (consulte o artigo Gerenciamento de objetos ). Um ponteiro para vlc_object_t deve ser convertido em um tipo de dados específico, de acordo com a funcionalidade que o módulo implementa. Para controlar o media player, defino o valor da interface na macro set_capability () . Assim, nas funções Abrir e Fechar , preciso converter vlc_object_t em intf_thread_t .

A interação entre os módulos é baseada no padrão de design do observador . O VLC fornece um mecanismo de "variáveis ​​de objeto" (consulte Variáveis ), com o qual você pode adicionar variáveis ​​a instâncias do tipo vlc_object_t (e suas derivadas). Os módulos podem trocar dados através dessas variáveis. Você também pode anexar uma função de retorno de chamada à variável, que será chamada quando o valor dessa variável for alterado.

Como exemplo, considere o módulo de teclas de atalho ( modules / control / hotkeys.c ), responsável por manipular eventos de teclas de atalho. Na função Abrir, a função de retorno de chamada ActionEvent está pendurada na variável de ação-chave :

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

Um ponteiro para vlc_object_t , um nome de variável, uma função de retorno de chamada e um ponteiro para anular são passados ​​para a função var_AddCallback para transmitir dados arbitrários, que são encaminhados para a função de retorno de chamada especificada. A assinatura da função de retorno de chamada é mostrada abaixo.

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

Um ponteiro para vlc_object_t , o nome da variável, os valores antigos e novos dessa variável (nesse caso, o identificador da combinação de teclas de atalho correspondente correspondente da ação), bem como o ponteiro para quaisquer dados adicionais especificados ao adicionar a função de retorno de chamada, são passados ​​para a função de retorno de chamada .

O processamento direto de eventos de teclas de atalho é realizado na função PutAction , chamada dentro da função de retorno de chamada ActionEvent . A função PutAction aceita um identificador de um evento ao pressionar uma combinação de teclas de atalho ( i_action ) e, com a ajuda do operador do comutador, executa as ações correspondentes.

Por exemplo, um evento de retrocesso corresponde a ACTIONID_JUMP_BACKWARD_SHORT . Para executar a ação correspondente, o intervalo de retrocesso é obtido das configurações do VLC (da variável tamanho do salto curto ):

 mtime_t it = var_InheritInteger( p_input, varname ); 

Para retroceder o arquivo em reprodução, basta definir a variável de deslocamento de tempo no valor correspondente ao tempo (em microssegundos) pelo qual você deseja alterar a reprodução:

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

Para avanço rápido, você precisa especificar um valor positivo, para retrocesso rápido - negativo. A constante CLOCK_FREQ é usada para converter segundos em microssegundos.

Da mesma forma, a faixa de áudio é alterada (evento ACTIONID_AUDIO_TRACK ). Somente a variável audio-es responsável pela faixa de áudio pode aceitar um conjunto limitado de valores (de acordo com as faixas de áudio disponíveis no arquivo sendo reproduzido). Você pode obter uma lista dos possíveis valores de uma variável usando a função var_Change () :

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

Além da lista de valores, essa função também permite obter uma lista de descrições desses valores (nesse caso, o nome das faixas de áudio). Agora podemos mudar a faixa de áudio usando a função var_Set () :

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

Como gerenciar o media player descoberto, resta aprender a lidar com os eventos do teclado. Infelizmente, não foi possível adicionar uma nova tecla de atalho. Todas as teclas de atalho são codificadas no código do kernel do VLC ( src / misc / actions.c ). Portanto, adicionei um manipulador para eventos de pressionamento de tecla de teclado de nível inferior, suspendendo minha função de retorno de chamada para alterar a variável pressionada por tecla :

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

A variável pressionada por tecla armazena o código de caractere (em Unicode) correspondente à última tecla pressionada. Por exemplo, quando você pressiona a tecla com o número "1" , a variável pressionada por tecla recebe o valor 49 (0x00000031 no 16º sistema numérico). Você pode ver outros códigos de caracteres em unicode-table.com . Além disso, o valor da variável pressionada por tecla leva em consideração o pressionamento das teclas modificadoras, o quarto byte significativo é alocado para elas. Assim, por exemplo, ao pressionar a combinação de teclas " Ctrl + 1 ", a variável pressionada por tecla receberá o valor 0x 04 000031 (00000 1 00 00000000 00000000 00110001 2 ). Para maior clareza, a tabela abaixo mostra os significados de várias combinações de teclas:
Atalho do tecladoValor
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
Preste atenção ao valor ao pressionar a combinação “ Shift + 1 ”. Como neste caso o “ ! ", O valor do primeiro byte corresponderá ao código desse caractere em Unicode (0x00000021).

Resultado


Eu chamei meu plug-in de DICA de acrônimo para a frase "traduza, por favor", também a dica pode ser traduzida como "dica". O código fonte do plug-in é publicado no GitHub , onde você também pode fazer o download de assemblies de plug-in prontos para Windows e Linux.

Para instalar o plug-in, você precisa copiar o arquivo libtip_plugin.dll (libtip_plugin.so para Linux) para a pasta <path-to-vlc> / plugins . No Windows, o VLC geralmente é instalado na pasta C: \ Arquivos de Programas \ VideoLAN \ VLC . No Linux, você pode encontrar a pasta de instalação usando o comando:

 whereis vlc 

No Ubuntu, por exemplo, o VLC é instalado em / usr / lib / x86_64-linux-gnu / vlc .

Em seguida, você precisará reiniciar o VLC e, no menu principal, abra Ferramentas > Preferências , alterne para a visualização avançada (selecione Tudo no grupo Mostrar configurações ), vá para a seção Interface > Controle no painel esquerdo e marque a caixa ao lado de DICA (traduza-a, por favor) . Então, novamente, você precisará reiniciar o VLC.



Nas configurações do plug-in, você pode especificar os números das faixas de áudio principal e auxiliar (para tradução), bem como o tempo (em segundos) pelo qual o plug-in será rebobinado para repetir com a faixa de áudio auxiliar.



Para controlar o plug-in, adicionei os seguintes atalhos de teclado:

  • " / " Para tradução
  • " Shift + / " para repetir o fragmento de vídeo traduzido anteriormente com a faixa de áudio principal

Durante a execução dos comandos de conversão e nova tentativa, o plug-in exibe as mensagens "DICA: traduzir" e "DICA: repetir" no canto superior esquerdo , respectivamente.



Pela minha experiência no uso do plug-in, posso dizer que, em geral, estou satisfeito com o resultado. O principal é escolher o conteúdo certo, se pelo menos metade da fala estrangeira usada lá for entendida, o plug-in ajudará a traduzir o restante. Caso contrário, o plug-in provavelmente será inútil.

Source: https://habr.com/ru/post/pt475992/


All Articles