Écrire un plugin VLC pour apprendre l'anglais

image

Dans cet article, je vais vous expliquer comment écrire un plugin en C pour le lecteur multimédia VLC. J'ai écrit mon plugin pour simplifier le visionnage d'émissions de télévision et de films en anglais. L'idée de créer ce plugin est décrite dans les sections Idée et Recherche de solutions . Les détails techniques de l'implémentation du plugin sont fournis dans les sections plugin et implémentation de Hello World . Ce qui s'est passé à la fin et comment l'utiliser peut être trouvé dans la dernière section, Résultat .

Le code source du projet est disponible sur GitHub .

Idée


L'idée d'apprendre une langue étrangère en regardant ma série préférée n'est pas nouvelle, mais j'ai toujours eu des problèmes avec elle personnellement. Il est très difficile de regarder une série ou un film lorsque vous ne comprenez pas la moitié de ce qu’ils disent. Bien sûr, vous pouvez activer les sous-titres, mais si un mot ou une expression inconnue est rencontré dans un discours, il ne sera pas plus clair qu'il sera dupliqué par le texte. Et je n'aimais pas du tout regarder la série avec des sous-titres russes - le cerveau passe à sa langue maternelle et cesse de percevoir la langue étrangère. J'ai lu quelque part que vous devez d'abord regarder une série en russe, puis dans l'original. Mais cette approche ne me convenait pas non plus. Premièrement, où prendre autant de temps pour regarder la même chose plusieurs fois, et deuxièmement, regarder la deuxième fois n'est plus aussi intéressant - la motivation est perdue.

Malgré toutes les difficultés à regarder des émissions de télévision étrangères, je peux assez bien lire la documentation technique, les articles et les livres en anglais. J'aime lire des livres sur le lecteur électronique Kindle, car la fonction de dictionnaire est cool, vous pouvez trouver une traduction d'un mot inconnu avec une seule touche de l'écran. Il est pratique de lire des articles et des sites en anglais en installant une extension spéciale pour la traduction dans le navigateur - j'utilise l'extension Yandex.Translation . Cette approche vous permet de lire et de comprendre des textes en anglais, sans trop de distraction pour la recherche de mots inconnus.

J'ai pensé, pourquoi ne pas appliquer la même approche pour regarder des émissions de télévision - nous allumons la série en anglais dès qu'une phrase incompréhensible est trouvée, passons à la piste audio russe et revenons un peu en arrière. Ensuite, nous continuons de regarder la série en anglais.

Rechercher une solution


En fait, toutes les fonctionnalités dont j'ai besoin sont déjà disponibles dans de nombreux lecteurs multimédias populaires. La seule chose que je voudrais changer de piste audio et rembobiner la vidéo il y a quelques secondes en cliquant sur un bouton. Il serait également formidable que, après avoir traduit un fragment incompréhensible, le lecteur multimédia lui-même ait rétabli la piste audio en anglais. Eh bien, ce serait bien de pouvoir répéter le fragment traduit précédemment avec la piste anglaise.

Autrement dit, j'ai besoin d'un lecteur multimédia pour lequel vous pouvez écrire des plugins. Il est également souhaitable qu'il soit multiplateforme, car j'utilise un PC sous Windows et un ordinateur portable sous Linux. Mon choix s'est immédiatement porté sur le VLC. Le habr, j'ai même trouvé un article dans lequel @Idunno explique comment écrire une extension VLC sur LUA. Soit dit en passant, il a également écrit cette extension pour apprendre l'anglais). Malheureusement, cette extension ne fonctionne pas dans les dernières versions de VLC (antérieures à 2.0.5). En raison d'un fonctionnement instable, la possibilité d'ajouter des fonctions de rappel permettant de traiter les événements de clavier dans l'extension LUA a été supprimée de l'API LUA. Dans README , un lien vers la liste de diffusion des développeurs VLC discutant de ce problème mène à son extension sur GitHub @Idunno .

Ainsi, pour implémenter mon idée, une extension de LUA ne fonctionne pas, vous devez écrire un plugin en C. Et bien que j'aie écrit quelque chose en C la dernière fois il y a environ 7 ans, de retour à l'université, j'ai décidé de l'essayer.

Bonjour le plugin du monde


Il convient de noter que le lecteur multimédia VLC possède une assez bonne documentation . J'en ai appris que le développement d'un lecteur multimédia utilise une approche modulaire. VLC se compose de plusieurs modules indépendants qui implémentent certaines fonctionnalités et du noyau ( libVLCCore ), qui gère ces modules. Il existe deux types de modules: internes ( dans l'arborescence ) et externes ( hors de l'arborescence ). Le code source des modules internes est stocké dans un référentiel avec le code du noyau. Les modules externes sont développés et assemblés indépendamment du lecteur multimédia VLC. En fait, ces derniers sont ce qu'on appelle des plugins.

La documentation contient également un article sur la façon d'écrire votre plug-in (module) en C. Cet article fournit le code source d'un plug-in simple qui, au démarrage de VLC, affiche un message de bienvenue « Bonjour, <nom> » sur la console (la valeur <nom> est prise depuis les paramètres du plugin). En courant un peu plus loin, je dirai que dans l'exemple ci-dessus, ajoutez la ligne suivante après set_category(CAT_INTERFACE) :

 set_subcategory( SUBCAT_INTERFACE_CONTROL ) 

Eh bien, il ne reste plus qu'à construire le plugin et tester son fonctionnement. Il y a aussi une instruction pour construire un plugin externe. Ici, il convient de prêter attention à la section Internationalisation , qui décrit le fonctionnement de la localisation dans VLC. En bref, pour les plug-ins externes, vous devez définir les macros N_() , _() :

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

Pour l'assemblage, il est proposé d'utiliser le bon vieux Makefile ou Autotools. J'ai décidé de suivre la voie simple et j'ai choisi le Makefile. Dans le Makefile, vous devez vous rappeler de définir la variable MODULE_STRING - c'est l'identifiant de notre plugin. J'ai également modifié un peu le travail avec les répertoires - maintenant ils sont définis via pkg-config . Le résultat est les fichiers suivants:

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

Le moyen le plus simple de créer un plugin pour Linux. Pour ce faire, vous devez en fait installer le lecteur multimédia VLC lui-même, ainsi que les fichiers et les outils nécessaires à la création du plug-in. Sur Debian / Ubuntu, cela peut être fait avec la commande suivante:

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

En fait, tout est prêt, nous collectons et installons notre plugin en utilisant la commande:

 sudo make install 

Pour tester le plugin, exécutez également VLC à partir de la console:

 vlc 

Malheureusement, nous n'avons vu aucun « Bonjour tout le monde ». Le fait est que le plugin doit d'abord être activé. Pour ce faire, ouvrez les paramètres ( Outils > Préférences ), passez à la vue avancée (sélectionnez Tout dans le groupe Afficher les paramètres ) et recherchez dans l'arborescence du panneau de gauche Interface > Interfaces de contrôle - cochez la case à côté de notre plugin d' interface Hello .



Nous enregistrons les paramètres et redémarrons le VLC.



Créer un plugin pour Windows


Avec Windows, les choses sont un peu plus compliquées. Pour construire le plugin, vous devez télécharger sdk, qui contient les bibliothèques, les en-têtes et les fichiers de configuration de VLC. Auparavant, sdk faisait partie de l'assembly VLC standard et se trouvait dans le dossier d'installation du programme. Maintenant, il s'agit d'un assemblage de lecteur multimédia séparé. Par exemple, pour VLC version 3.0.8, cet assemblage peut être téléchargé sur ftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z (il est important de télécharger 7z -archive).

Copiez le contenu de l'archive dans un dossier, par exemple, dans C: \ Projects . En plus de sdk, l'archive contient également le lecteur multimédia lui-même, qui peut être utilisé pour tester et déboguer le plug-in.

Pour que notre Makefile puisse être utilisé pour construire et installer le plugin, vous devez corriger le fichier C: \ Projects \ vlc-3.0.8 \ sdk \ lib \ pkgconfig \ vlc-plugin.pc , indiquant le chemin correct vers le dossier sdk dans les variables préfixe et pluginsdir et plugins respectivement:

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

Pour construire sous Windows, nous devons également installer un compilateur et d'autres utilitaires. Tous les logiciels nécessaires peuvent être obtenus en installant l'environnement MSYS2 . Le site Web du projet contient des instructions d'installation détaillées. En bref, immédiatement après l'installation, vous devez ouvrir la console ( C: \ msys64 \ msys2.exe ) et mettre à jour les packages MSYS2 à l'aide de la commande:

 pacman -Syu 

Ensuite, fermez la fenêtre du terminal MSYS2, puis ouvrez-la à nouveau et exécutez la commande

 pacman -Su 

Après avoir mis à jour tous les packages, vous devez installer la chaîne d'outils:

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

Maintenant que tous les packages nécessaires sont installés, vous pouvez commencer à construire le plugin. J'ai un peu modifié le Makefile pour qu'il puisse construire le plugin sous Linux et sous Windows. De plus, j'ai dû supprimer certains paramètres de construction MinGW non pris en charge, en conséquence le Makefile a commencé à ressembler à ceci:

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

Étant donné que MSYS2 ne sait rien de notre sdk pour VLC, vous devez ajouter le chemin d'accès au dossier pkgconfig de ce sdk à la variable d' environnement PKG_CONFIG_PATH . Ouvrez la console MinGW ( C: \ msys64 \ mingw64.exec ) et exécutez les commandes:

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

Pour tester le plugin, exécutez également VLC à partir de la console:

 /c/projects/vlc-3.0.8/vlc 

Comme dans le cas de Linux, allez dans les paramètres et activez notre plugin. Nous enregistrons les paramètres et redémarrons le VLC.

Implémentation du plugin


Pour implémenter mon plugin, j'avais besoin de comprendre comment contrôler le lecteur multimédia (changer la piste audio, rembobiner) et comment gérer les événements de frappe de clavier. Pour comprendre tout cela, je me suis tourné vers la documentation . Également sur Internet, j'ai trouvé quelques articles intéressants qui éclairent l'architecture du lecteur multimédia: l'architecture du cadre multimédia VLC et la documentation de l'API du lecteur multimédia VLC .

VLC se compose d'un grand nombre de modules indépendants (400+). Chaque module doit fournir des informations sur le type de fonctionnalité qu'il implémente, ainsi que les fonctions d'initialisation / finalisation. Ces informations sont décrites dans les blocs vlc_module_begin () - vlc_module_end () à l'aide des macros set_capability () et set_callbacks () . Les fonctions d'initialisation / finalisation du module (généralement appelées Open et Close ) ont la signature suivante:

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

vlc_object_t est le type de base pour représenter les données dans VLC, dont toutes les autres sont héritées (voir l'article Object_Management ). Un pointeur vers vlc_object_t doit être converti en un type de données spécifique conformément à la fonctionnalité implémentée par le module. Pour contrôler le lecteur multimédia, j'ai défini la valeur de l' interface dans la macro set_capability () . Par conséquent, dans les fonctions Ouvrir et Fermer , je dois convertir vlc_object_t en intf_thread_t .

L'interaction entre les modules est basée sur le modèle de conception de l' observateur . VLC fournit un mécanisme de «variables d'objet» (voir Variables ), avec lequel vous pouvez ajouter des variables aux instances de type vlc_object_t (et ses dérivés). Les modules peuvent échanger des données via ces variables. Vous pouvez également attacher une fonction de rappel à la variable, qui sera appelée lorsque la valeur de cette variable change.

Par exemple, considérons le module Hotkeys ( modules / control / hotkeys.c ), qui est responsable de la gestion des événements de raccourci clavier. Dans la fonction Ouvrir, la fonction de rappel ActionEvent est bloquée sur la variable action-clé :

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

Un pointeur sur vlc_object_t , un nom de variable, une fonction de rappel et un pointeur sur void sont transmis à la fonction var_AddCallback pour transmettre des données arbitraires, qui sont ensuite transmises à la fonction de rappel spécifiée. La signature de la fonction de rappel est illustrée ci-dessous.

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

Un pointeur vers vlc_object_t , le nom de la variable, les anciennes et nouvelles valeurs de cette variable (dans ce cas, l'identifiant de la combinaison de touches de raccourci enfoncée correspondante de l'action), ainsi que le pointeur vers toutes les données supplémentaires spécifiées lors de l'ajout de la fonction de rappel, sont passés à la fonction de rappel. .

Le traitement direct des événements de raccourci clavier est effectué dans la fonction PutAction , qui est appelée dans la fonction de rappel ActionEvent . La fonction PutAction accepte un identifiant d'un événement d'appuyer sur une combinaison de touches de raccourci ( i_action ) et, avec l'aide de l'opérateur de commutation, effectue les actions correspondantes.

Par exemple, un événement de rembobinage correspond à ACTIONID_JUMP_BACKWARD_SHORT . Pour effectuer l'action correspondante, l'intervalle de rembobinage est pris à partir des paramètres VLC (à partir de la taille de saut rapide variable):

 mtime_t it = var_InheritInteger( p_input, varname ); 

Pour rembobiner le fichier en cours de lecture, définissez simplement la variable de décalage temporel sur la valeur correspondant à l'heure (en microsecondes) à laquelle vous souhaitez décaler la lecture:

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

Pour l'avance rapide, vous devez spécifier une valeur positive, pour le retour rapide - négatif. La constante CLOCK_FREQ est utilisée pour convertir les secondes en microsecondes.

De même, la piste audio change (événement ACTIONID_AUDIO_TRACK ). Seule la variable audio-es responsable de la piste audio peut accepter un ensemble limité de valeurs (conformément aux pistes audio disponibles dans le fichier en cours de lecture). Vous pouvez obtenir une liste des valeurs possibles d'une variable en utilisant la fonction var_Change () :

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

En plus de la liste des valeurs, cette fonction vous permet également d'obtenir une liste des descriptions de ces valeurs (dans ce cas, le nom des pistes audio). Maintenant, nous pouvons changer la piste audio en utilisant la fonction var_Set () :

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

Comment gérer le lecteur multimédia compris, il reste à apprendre à gérer les événements du clavier. Malheureusement, je n'ai pas pu ajouter de nouveau raccourci clavier. Tous les raccourcis clavier sont codés en dur dans le code du noyau VLC ( src / misc / actions.c ). Par conséquent, j'ai ajouté un gestionnaire pour les événements de frappe de clavier de niveau inférieur, suspendant ma fonction de rappel pour modifier la variable appuyée sur une touche :

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

La variable appuyée sur la touche stocke le code de caractère (en Unicode) correspondant à la dernière touche appuyée. Par exemple, lorsque vous appuyez sur la touche portant le numéro «1» , la variable appuyée sur la touche se verra attribuer la valeur 49 (0x00000031 dans le système de 16e numéro). Vous pouvez afficher d'autres codes de caractères sur unicode-table.com . De plus, la valeur de la variable pressée prend en compte la pression des touches de modification, le quatrième octet significatif leur est alloué. Ainsi, par exemple, lorsque vous appuyez sur la combinaison de touches " Ctrl + 1 ", la variable pressée se verra attribuer la valeur 0x 04 000031 (00000 1 00 00000000 00000000 00110001 2 ). Pour plus de clarté, le tableau ci-dessous montre la signification de différentes combinaisons de touches:
Raccourci clavierValeur
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
Maj + 1000000 1 0 00000000 00000000 00100001 2
Faites attention à la valeur lorsque vous appuyez sur la combinaison « Maj + 1 ». Puisque dans ce cas le « ! ", Alors la valeur du premier octet correspondra au code de ce caractère en Unicode (0x00000021).

Résultat


J'ai appelé mon plugin TIP un acronyme pour l'expression «traduisez-le, s'il vous plaît», aussi astuce peut être traduit par «indice». Le code source du plugin est publié sur GitHub , où vous pouvez également télécharger des assemblys de plug-in prêts à l'emploi pour Windows et Linux.

Pour installer le plugin, vous devez copier le fichier libtip_plugin.dll (libtip_plugin.so pour Linux) dans le dossier <path-to-vlc> / plugins . Sous Windows, VLC est généralement installé dans le dossier C: \ Program Files \ VideoLAN \ VLC . Sous Linux, vous pouvez trouver le dossier d'installation à l'aide de la commande:

 whereis vlc 

Dans Ubuntu, par exemple, VLC est installé dans / usr / lib / x86_64-linux-gnu / vlc .

Ensuite, vous devrez redémarrer VLC, puis dans le menu principal, ouvrez Outils > Préférences , passez à la vue avancée (sélectionnez Tout dans le groupe Afficher les paramètres ), dans le panneau de gauche, accédez à la section Interface > Contrôle et cochez la case à côté de TIP (traduisez-le, s'il vous plaît) . Là encore, vous devrez redémarrer le VLC.



Dans les paramètres du plugin, vous pouvez spécifier les numéros des pistes audio principale et auxiliaire (pour la traduction), ainsi que le temps (en secondes) par lequel le plugin se rembobinera pour répéter avec la piste audio auxiliaire.



Pour contrôler le plugin, j'ai ajouté les raccourcis clavier suivants:

  • " / " Pour la traduction
  • " Shift + / " pour répéter le fragment vidéo précédemment traduit avec la piste audio principale

Lors de l'exécution des commandes de traduction et de nouvelle tentative, le plug-in affiche respectivement les messages «TIP: translate» et «TIP: repeat» dans le coin supérieur gauche.



D'après mon expérience en utilisant le plugin, je peux dire qu'en général, je suis satisfait du résultat. L'essentiel est de choisir le bon contenu, si au moins la moitié du discours étranger utilisé y est compris, le plugin aidera à traduire le reste. Sinon, le plugin sera probablement inutile.

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


All Articles