Получить подписи YouTube
Как программно получить субтитры при воспроизведении видео на YouTube?
Первоначально я пытался сделать это в автономном режиме через YouTube API, но, как кажется, YouTube запрещает получать субтитры к видео, вы не являетесь владельцем.
Сейчас я пытаюсь сделать это онлайн. Я не нашел методы подписки YouTube Player Api, также я пытался получить подписи YouTube как TextTrack с видеоплеером, как это можно сделать для обычных видео, но следующее не работает:
<html>
<head>
<link href="//vjs.zencdn.net/4.12/video-js.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script type="text/javascript" src="//vjs.zencdn.net/4.12/video.js"></script>
<script type="text/javascript" src="../lib/youtube.js"></script>
</head>
<body>
<video id="myvideo"
class="video-js vjs-default-skin vjs-big-play-centered"
controls
preload="auto"
width="640"
height="360">
</video>
<script type="text/javascript">
var myvideo = videojs(
"myvideo",
{
"techOrder": ["youtube"],
"src": "https://www.youtube.com/watch?v=jNhtbmXzIaM"
},
function() {
console.log('Tracks: ' + this.textTracks().length); //zero here :(
/*var aTextTrack = this.textTracks()[0];
aTextTrack.on('loaded', function() {
console.log('here it is');
cues = aTextTrack.cues();
console.log('Ready State', aTextTrack.readyState())
console.log('Cues', cues);
});
aTextTrack.show();*/
});
</script>
</body>
</html>
Я также пробовал уродливое решение с разбором YouTube Player IFrame (внутри него есть div с текущей строкой субтитров), но оно не работает из-за проблем с несовпадением источника.
Есть ли способ, которым моя цель может быть достигнута в java (для автономных решений) или javascript (для онлайн-решений)?
5 ответов
Как мне удалось получить подписи к видео на YouTube, просто запросив этот URL-адрес https://video.google.com/timedtext?lang={LANG} & v = {videoId}
Я пытался использовать Youtube API v3, но на данный момент он не работает. Когда вы делаете запрос через Youtube API v3 для определенного видео, вам нужно, чтобы тот, кто загрузил видео, одобрил загрузку подписи, если нет, у вас будет ошибка 403 в консоли. Нормально иметь ошибку, сервер не получает одобрение, поэтому он возвращает ошибку.
Вы можете скачать подписи из вашего собственного видео с помощью YouTube API v3.
Нечто подобное будет делать эту работу. Ответ придет в формате XML:
$.ajax({
type: "POST",
url: "https://video.google.com/timedtext?lang=en&v=5MgBikgcWnY"
}).done(function (response) {
console.log(response);
}).fail(function (response) {
console.log();
});
Основываясь на предложении Серджиу Маре, я написал инкапсулированную функцию, которая может возвращать подписи в консоли.
Это написано на чистом JavaScript (ES6), и вы можете протестировать его ниже, или вы можете скопировать все ниже и вставить в консоль любого видео с субтитрами.
loadYouTubeSubtitles((getYouTubeVideoId() || 'fJ9rUzIMcZQ'), {
callbackFn : function(json) {
console.log(jsonToCsv(json, {
includeHeader : false,
ignoreKeys : [ 'dur' ],
delimiter : '\t',
}));
}
}); // Queen – Bohemian Rhapsody (default ID)
function getYouTubeVideoId() {
var video_id = window.location.search.split('v=')[1];
if (video_id != null) {
var ampersandPosition = video_id.indexOf('&');
if (ampersandPosition != -1) {
return video_id.substring(0, ampersandPosition);
}
}
return null;
}
function loadYouTubeSubtitles(videoId, options) {
options = Object.assign({
baseUrl : 'https://video.google.com/timedtext',
languageId : 'en',
callbackFn : function(json) { console.log(json); } // Default
}, options || {});
// https://stackru.com/a/9609450/1762224
var decodeHTML = (function() {
let el = document.createElement('div');
function __decode(str) {
if (str && typeof str === 'string') {
str = str.replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '')
.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');
el.innerHTML = str;
str = el.textContent;
el.textContent = '';
}
return str;
}
removeElement(el); // Clean-up
return __decode;
})();
function removeElement(el) {
el && el.parentNode && el.parentNode.removeChild(el);
}
function parseTranscriptAsJSON(xml) {
return [].slice.call(xml.querySelectorAll('transcript text'))
.map(text => ({
start : formatTime(Math.floor(text.getAttribute('start'))),
dur : formatTime(Math.floor(text.getAttribute('dur'))),
text : decodeHTML(text.textContent).replace(/\s+/g, ' ')
}));
}
function formatTime(seconds) {
let date = new Date(null);
date.setSeconds(seconds);
return date.toISOString().substr(11, 8);
}
let xhr = new XMLHttpRequest();
xhr.open('POST', `${options.baseUrl}?lang=${options.languageId}&v=${videoId}`, true);
xhr.responseType = 'document';
xhr.onload = function() {
if (this.status >= 200 && this.status < 400) {
options.callbackFn(parseTranscriptAsJSON(this.response));
} else {
console.log('Error: ' + this.status);
}
};
xhr.onerror = function() {
console.log('Error!');
};
xhr.send();
}
function jsonToCsv(json, options) {
options = Object.assign({
includeHeader : true,
delimiter : ',',
ignoreKeys : []
}, options || {});
let keys = Object.keys(json[0]).filter(key => options.ignoreKeys.indexOf(key) === -1);
let lines = [];
if (options.includeHeader) { lines.push(keys.join(options.delimiter)); }
return lines.concat(json
.map(entry => keys.map(key => entry[key]).join(options.delimiter)))
.join('\n');
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
Следующее работает, когда вы находитесь на странице YouTube.
Откройте консоль разработчика. Попробуйте скопировать и вставить это: (не забудьте обновить страницу!)
let subsUrl = ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks[0].baseUrl;
let subs = await (await fetch(subsUrl)).text();
let xml = new DOMParser().parseFromString(subs,"text/xml");
let textNodes = [...xml.getElementsByTagName('text')];
let subsText = textNodes.map(x => x.textContent).join("\n").replaceAll(''',"'");
console.log(subsText);
Вау, разве это не круто? К тому же коротко и просто?
Это возможно из-за действительно классного трюка: на странице youtube есть глобальная переменнаяytInitialPlayerResponse
со ссылкой на подписи: (не забудьте сначала обновить страницу)
let subsUrl = ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks[0].baseUrl;
Поскольку вы находитесь на странице YouTube, вам разрешено получать этот URL-адрес (без проблем с корсом, поскольку ваш выбор происходит с youtube.com)
let subs = await (await fetch(subsUrl)).text();
Этот текст должен быть проанализирован как xml. Для этого очень полезно использовать DOMParser:
let xml = new DOMParser().parseFromString(subs,"text/xml");
Затем вы хотите получить текстовые элементы
xml.getElementsByTagName('text')
С расширенной нотацией вы можете сделать ее массивом текстовых узлов.
let textNodes = [...xml.getElementsByTagName('text')]
На котором вы можете выполнить операцию карты, чтобы получить параметры. Нас интересует textContent:
textNodes.map(x => x.textContent)
Теперь у вас есть массив субтитров. Чтобы объединить это в один большой текст, вы можете написать следующее:
let subsText = textNodes.map(x => x.textContent).join("\n").replaceAll(''',"'");
Обратите внимание, что ' - это обозначение для ' в подзаголовке YouTube, поэтому мы заменяем его.
В консоли разработчиков Chrome вы должны добавить console.log, от которого нужно избавиться.\n
обозначение, поэтому вы пишете:
console.log(subsText)
Как загрузить субтитры
Если вы хотите автоматически загрузить текстовый файл, вы можете добавить этот код из этого обсуждения :
function downloadText(filename, text) {
var el = document.createElement('a');
el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
el.setAttribute('download', filename);
el.style.display = 'none';
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
}
downloadText('subs.txt',subsText);
PS: я использую let, а не const, чтобы вы могли копировать и вставлять его несколько раз в одну и ту же консоль
PS2: в этом примере используется первый baseUrl, хорошее упражнение: попробуйте найти другие языки!
Получайте удовольствие!
Во-первых, вам обязательно стоит попробовать официальный API, если это ваше видео! Во-вторых, вы должны попытаться увидеть, доступны ли здесь данные:
https://video.google.com/timedtext?lang={LANG}&v={videoId}
Теперь, если вам нужен третий подход и вас интересует получение заголовка ASR (ASR = автоматическое распознавание речи), то есть другой способ сделать это, очистив данные get_video_info на YouTube .
Для этого вам необходимо:
шаг 1. возьмите файл get_video_info для своего
videoID
. здесь
https://youtube.com/get_video_info?video_id=videoID
Шаг 2. Внутри этих данных вы найдете captionTracks > baseURL.
шаг 3. Просто скопируйте ссылку baseURL, и вы сможете увидеть XML-данные ASR (если видео поддерживает это).
Если вы используете nodejs, есть простой способ сделать это программно:
npm i ytgetcaption
caption = require('ytgetcaption');
VideoID = "1RhRRRG6MBU"
caption.ytgetCaption(VideoID).then(function (data) {
console.log(data)
});
Вам, вероятно, не нужно загружать его прямо с YouTube, есть веб-сервисы, которыми вы можете манипулировать.
Например, вы можете перейти по ссылке http://keepsubs.com/?url=insert_youtube_url и загрузить подписи с сайта по ссылке, указанной в этом пути CSS для английских субтитров:
#dl > a:nth-child(2)
Вы можете сделать это в JavaScript, используя следующий метод:
function myFunction(url_to_download){
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://keepsubs.com/?url=" + url_to_download, false );
xmlHttp.send( null );
var fake_html = document.createElement("div");
fake_html.insertAdjacentHTML('beforeend', xmlHttp.responseText);
var url = fake_html.querySelector("#dl > a:nth-child(2)");
xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", url.href, false );
xmlHttp.send( null );
console.log(xmlHttp.responseText);
return xmlHttp.responseText;
}
myFunction("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
По сути, этот метод посещает KeepSubs, находит URL-адрес загрузки текста, получает текст в файле по URL-адресу и выводит его на консоль.
Имейте в виду, что, хотя это один из способов сделать это, вероятно, есть и лучшие, которые не такие хакерские. Также использование сервиса KeepSubs таким образом, вероятно, не этично. Но это только для образовательных целей.