{Youtube} – Effectuer des recherches dans les dialogues d’une vidéo

 

Salut à tous,

 

Aujourd'hui je suis vraiment très très près de l'heure de ma soutenance ... ce qui explique un peu le manque de frénésie que j'ai dans la publication de mes articles  ;)
10 ans se sont passé depuis la dernière fois que j'ai obtenu un diplôme, autant dire que la pression est à son comble !

Cela dit, j'étais l'autre jour sur une vidéo avec un titre un peu ... "putaclick" (ou accrocheur comme diraient certains utilisateurs de spip ;) )
Et j'avais très envie de me rendre à l'instant qui traitait du sujet évoqué dans ce titre ... sauf que j'ai dû me faire toute la vidéo d'une vingtaine de minutes ... pour trouver ce que je cherchais ( qui ne valait pas grand chose d'ailleurs)

Alors je me suis dit que ça serait sympa de développer un petit outil pour faire le job de rechercher dans les dialogues d'une vidéo.
Bien entendu, certaines personnes l'ont déjà fait, et je ne vous cacherais pas qu'il existe déjà des extensions qui le proposent.

Mais moi, je vous propose de vous expliquer techniquement comment j'ai fait ;)

 

Utilisation des sous-titres dans Youtube

Ouvrez vos consoles (touche F12 de votre clavier), et allez sur Youtube.
Sélectionnez une vidéo de votre choix, en vous assurant que celle-ci dispose de sous-titre (automatique ou non)

Vous êtes donc fin prêt !

Vous devriez avoir un onglet "réseau" dans la console, c'est le moment de l'ouvrir et de voir ce qu'il s'y passe lorsque vous activez ou désactivez les sous-titres.

 

Super, vous avez trouvé la requête que Youtube effectue pour télécharger les sous-titres d'une vidéo.
L'url de cette requête est accessible depuis : window.ytInitialPlayerResponse.captions.playerCaptionsRenderer.baseUrl

Variable que vous pourrez retrouver en regardant le code source de la page Youtube que vous consultez en faisant une simple recherche du terme timedText.

 

 

Cependant cette URL ne vous sera pas d'un très grand secours car pour la générer il y a tout un tas de valeurs qui sont envoyées dans l'entête de cette requête ( Youtube se protège ;) )

Réécriture d'une fonction utilisée pour faire des requêtes

Souvent lorsque vous utilisez du JQuery (ou autres lib / framework) et que vous faite du $.get, $.post .... etc ... le vrai objet natif de Javascript qui est utilisé c'est "XMLHttpRequest", il dispose de tout un arsenal de fonctions permettant de faire de belles requêtes

.

J'ai donc pris la liberté de le modifier pour me renvoyer le résultat de toutes les requêtes faites par Youtube.
Dès que je reconnais des sous-titres c'est bueno !

 

XMLHttpRequest.prototype.send2 = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(e){
this.addEventListener('load', function(e){
var dom = new DOMParser(), doc = dom.parseFromString(e.target.response, 'text/html');
if (doc.getElementsByTagName('timedtext'))
console.log(e.target.response)
});
this.send2(e);
}

 

Le Javascript ci-dessus modifie la méthode "send" de l'objet XMLHttpRequest, ainsi dès qu'une requête est envoyée, je reçois immédiatement le retour de celle-ci, je le parse, et je vérifie la présence d'une balise "<timedtext>"  !

J'obtiens donc assez facilement les sous-titres

 

 

Chaque phrase est contenue dans une balise <p>, et chaque mot dans une balise <s>.
Chaque phrase dispose dans sa balise <p> d'un attribut "t=", il s'agit en millisecondes du moment où celui-ci est utilisé dans la vidéo, vous avez également un attribut "t=" sur les mots <s>celui-ci est un temps "additionnel", lorsque les sous-titres sont affichés mot à mot et non pas phrase par phrase.

 

 

 

Le script

Je vous ai donc comme à mon habitude concocté un super script qui vous permettra via une jolie barre de recherche, d'effectuer dans les sous-titres d'une vidéo (en fonction de la langue choisie)  la recherche de votre choix. Il vous suffira de cliquer sur un mot pour que la vidéo se positionne automatiquement à l'endroit où celui-ci est utilisé.

 

/*
* Dyrk(c) 2019-2020
* Youtube Subtitle Finder
*
*
*/
var subtitles_options = document.getElementsByClassName('ytp-subtitles-button ytp-button')[0], nbClick = 1,
listenerActive = false, searchSubtitle;
/*
Overwrite Ajax Request to detect and catch the subtitle Request Response
*/
if (!XMLHttpRequest.prototype.send2){
XMLHttpRequest.prototype.send2 = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(e){
if (!listenerActive)
this.addEventListener('load', function(e){
/*
Detect if the request is for loading subtitles
*/
var dom = new DOMParser(), doc = dom.parseFromString(e.target.response.
replace(/<s /g, '<s style="text-decoration:none!important"').
replace(/<p /g, '<p style="border-bottom:1px solid black;display:block"'), 'text/html');
if (doc.getElementsByTagName('timedtext')){
console.log(e.target.response)
/*
Make a form to display / find subtitles
*/
searchSubtitle = document.createElement('div'),
searchSubtitleField = document.createElement('input');
searchSubtitleField.setAttribute("style","width: 100%;height: 30px;font-size: 30px;");
searchSubtitleField.setAttribute("placeholder","Search ...");
searchSubtitle.appendChild(searchSubtitleField);
searchSubtitle.appendChild(doc.getElementsByTagName('body')[0]);
searchSubtitle.setAttribute('style', 'background:#FFFF;position:fixed;top:0px;right:0px;width:350px;height:500px;overflow:auto;z-index:10000;font-size: 18px;');
var puzzlesString = searchSubtitle.getElementsByTagName('p');
for (var i in puzzlesString){
if (puzzlesString[i].addEventListener){
puzzlesString[i].addEventListener('click', function(e){
/*
When User click on the Subtitle, go on the specific time of this subtitles in the video
*/
document.getElementsByTagName('video')[0].currentTime = parseInt(e.target.parentNode.getAttribute('t'))/1000;
});
}
}
document.getElementsByTagName('html')[0].appendChild(searchSubtitle);
listenerActive = true;
//Filter result when user's searching
searchSubtitleField.addEventListener('keyup', (function(subtitles, evt){
var res = subtitles.getElementsByTagName('p');
for (var i in res){
if (typeof res[i].textContent != "string") continue;
res[i].setAttribute('style',
res[i].getAttribute('style').replace(/display[ ]*?\:[ ]*?(block|none)/g,'')+
( res[i].textContent.indexOf(evt.target.value)!= -1 || evt.target.value == ""? 'display:block': 'display:none')
);

}
}).bind(null, searchSubtitle));
}
});
this.send2(e);
}
}
/*
Detect if subtitles is active or not, if this is, click two time to reload the request, if not, click once.
*/
if (subtitles_options.getAttribute('aria-pressed') == "true")
nbClick = 2;
for (var i=0; i<nbClick;i++)
subtitles_options.click()

 

Vidéo de démonstration

Petite vidéo de comment utiliser mon script.
Bon visionnage, pensez à vous abonner à la chaîne et / ou laisser un petit j'aime / commentaire d'encouragement. Merci à tous

 

 

Les idées d'évolution possible

Cet article n'apporte rien de spécialement extraordinaire, cependant avec l’avènement du html5, il est parfaitement possible d'extraire des images d'une vidéo, il serait donc assez rigolo de coupler des images de la vidéos avec la technologie tensorflow (qui permet de faire de la reconnaissance) pour faire des recherches d'objets ou d'élément d'une vidéo  ! Libre à l'imagination des développeurs :)

 

 

Conclusion

Les vacances arrivent, de mon côté j'ai déposé ma démission car je quitte le monde des SSII / ESN pour travailler chez un éditeur ... avec une période d'essaie de 8 mois (outch), le tout couplé à un futur déménagement, l'année s'annonce corsée ^^

Je crois que le plus moche c'est surtout de n'avoir pas l'autorisation de communiquer directement à mon client mon projet de départ ...

Cela dit, peut-être lira-t-il mon blog :D

Merci à tous pour vos commentaires et encouragements, bon weekend à tous !

 

 

 

4 comments

  • Encore une fois merci pour ce super article !

    Et surtout bonne suite pour tous ces changements :-)

  • Le script ne marche pas sur ma machine, j’ai du remplacer la ligne

     

    [pastacode lang="javascript" manual="document.getElementsByTagName('video')%5B0%5D.currentTime%20%3D%20parseInt(e.target.parentNode.getAttribute('t'))%2F1000%3B" message="" highlight="" provider="manual"/]

    par

    [pastacode lang="javascript" manual="document.getElementsByTagName('video')%5B0%5D.currentTime%20%3D%20parseInt(e.target.outerHTML.match(%2F.*t%3D%22(%5Cd%2B).*%22%2F)%5B1%5D)%2F1000%3B" message="" highlight="" provider="manual"/]

    • Petit ajustement:

       

      [pastacode lang="javascript" manual="document.getElementsByTagName('video')%5B0%5D.currentTime%20%3D%20parseInt(e.target.parentNode.getAttribute('t')%20%7C%7C%20e.target.outerHTML.match(%2F.*%3Ft%3D%22(%5Cd%2B).*%22%2F)%5B1%5D)%2F1000%3B" message="" highlight="" provider="manual"/]

  • Et pour une recherche insensible à la casse

    remplacer :

    res[i].textContent.indexOf(evt.target.value)!= -1

    par :

    res[i].textContent.match(new RegExp(evt.target.value, ‘gi’)) != null

Laisser une réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site est protégé par reCAPTCHA et le GooglePolitique de confidentialité etConditions d'utilisation appliquer.