获取 YouTube 字幕

Posted

技术标签:

【中文标题】获取 YouTube 字幕【英文标题】:Get YouTube captions 【发布时间】:2015-11-15 12:52:33 【问题描述】:

如何以编程方式获取正在播放的 YouTube 视频的字幕?

最初我尝试通过YouTube API 离线进行,但as it seems YouTube 禁止获取非所有者视频的字幕。

现在我正在尝试在线进行。我还没有找到 YouTube Player Api 的字幕方法,我也尝试在 the way it could be done for usual videos 中使用 videojs 播放器将 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" 
         
        >
</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(在线解决方案)中实现我的目标?

【问题讨论】:

我对 YouTube API 了解不多,我知道这是一个 Ruby 而不是 Java 解决方案,但希望 this GitHub repo 可以引导您走向正确的方向。 @NobleMushtak,谢谢,我已经注意到这个解决方案) 【参考方案1】:

更新:来自谷歌的 URL 停止工作。这种方法目前暂停。

解决方案: 我如何设法从 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();
    );

【讨论】:

这太棒了!我在下面对此进行了扩展。 由于某些原因,这不适用于所有视频 ID,例如youtube.com/watch?v=VjbMqU-3mYE 如果视频没有字幕,它将无法播放。 如果视频有自动生成的字幕,同样的,它不会工作 2021年停止工作【参考方案2】:

根据 Sergiu Mare 的建议,我编写了一个封装函数,可以在控制台中返回字幕。

这是用纯 JavaScript (ES6) 编写的,您可以在下面对其进行测试,或者您可以复制下面的所有内容并将其粘贴到任何有字幕的视频的控制台中。


更新版本

OOP 方法

const main = async () => 
  const
    defaultId = 'fJ9rUzIMcZQ', /* Queen – Bohemian Rhapsody */
    json = await YouTubeCaptionUtil
      .fetchCaptions(YouTubeCaptionUtil.videoId() || defaultId),
    csv = CsvUtil.fromJson(json);
  console.log(csv);
;

class YouTubeCaptionUtil 
  static async fetchCaptions(videoId, options) 
    const
      opts =  ...YouTubeCaptionUtil.defaultOptions, ...options ,
      response = await fetch(YouTubeCaptionUtil.__requestUrl(videoId, opts)),
      json = await response.json();
    return YouTubeCaptionUtil.__parseTranscript(json);
  
  static videoId() 
    const video_id = window.location.search.split('v=')[1];
    if (video_id != null) 
      const ampersandPosition = video_id.indexOf('&');
      if (ampersandPosition != -1) 
        return video_id.substring(0, ampersandPosition);
      
    
    return null;
  
  static __requestUrl(videoId,  baseUrl, languageId ) 
    return `$baseUrl?lang=$languageId&v=$videoId&fmt=json3`;
  
  static __parseTranscript(events) 
    return events.map((tStartMs, dDurationMs, segs: [utf8]) => (
      start: YouTubeCaptionUtil.__formatTime(tStartMs),
      dur: YouTubeCaptionUtil.__formatTime(dDurationMs),
      text: utf8
    ));
  
  static __formatTime(seconds) 
    const date = new Date(null);
    date.setSeconds(seconds);
    return date.toISOString().substr(11, 8);
  ;

YouTubeCaptionUtil.defaultOptions = 
  baseUrl: 'https://video.google.com/timedtext',
  languageId: 'en'
;

class CsvUtil 
  static fromJson(json, options) 
    const
      opts =  ...CsvUtil.defaultOptions, ...options ,
      keys = Object.keys(json[0]).filter(key =>
        opts.ignoreKeys.indexOf(key) === -1),
      lines = [];
    if (opts.includeHeader) lines.push(keys.join(opts.delimiter));
    return lines.concat(json
        .map(entry => keys.map(key => entry[key]).join(opts.delimiter)))
        .join('\n');
  

CsvUtil.defaultOptions = 
  includeHeader: false,
  ignoreKeys: ['dur'],
  delimiter: '\t'
;

main();
.as-console-wrapper  top: 0; max-height: 100% !important; 

功能方法

const main = async() => 
  const defaultId = 'fJ9rUzIMcZQ'; // Queen – Bohemian Rhapsody (default ID)
  const json = await loadYouTubeSubtitles(getYouTubeVideoId() || defaultId);
  const csv = jsonToCsv(json, 
    includeHeader: false,
    ignoreKeys: ['dur'],
    delimiter: '\t',
  );

  console.log(csv);
;

const parseTranscript = ( events ) => 
  return events.map(( tStartMs, dDurationMs, segs: [ utf8 ] ) => (
    start: formatTime(tStartMs),
    dur: formatTime(dDurationMs),
    text: utf8
  ));
;

const formatTime = (seconds) => 
  let date = new Date(null);
  date.setSeconds(seconds);
  return date.toISOString().substr(11, 8);
;

const 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;
;

const loadYouTubeSubtitles = async(videoId, options) => 
  options = Object.assign(
    baseUrl: 'https://video.google.com/timedtext',
    languageId: 'en',
  , options || );

  const requestUrl = `$options.baseUrl?lang=$options.languageId&v=$videoId&fmt=json3`;
  const response = await fetch(requestUrl);
  const json = await response.json();

  return parseTranscript(json);
;

const 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');
;

main();
.as-console-wrapper  top: 0; max-height: 100% !important; 

原始回复

此响应会创建一个XMLHttpRequest

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://***.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; 

【讨论】:

自 2021 年 12 月 11 日起,这不再有效。有什么建议吗? @tonnoz 我认为 YouTube 最近更改了他们的 API 以要求 OAuth 或其他东西,请参阅:developers.google.com/youtube/v3/docs/captions/list【参考方案3】:

你可能不需要直接从 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 服务可能是不道德的。但这仅用于教育目的。

【讨论】:

首先,我猜这里有一些与跨站点 http-requests 问题有关的安全问题。比如我刚刚试了一下,得到XMLHttpRequest cannot load http://keepsubs.com/?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:34974' is therefore not allowed access. 呵呵,奇怪,我直接在网站上试了一下,把源码复制粘贴到网站上的控制台。那时它奏效了。 在你的情况下这是一个同源请求,所以它对我不起作用)谢谢你的尝试)【参考方案4】:

首先,如果是您的视频,您绝对应该尝试使用官方 API! 其次,你应该试试看这里有没有数据:https://video.google.com/timedtext?lang=LANG&amp;v=videoId

现在,如果您想要第三种方法,并且您对获取 ASR 字幕(ASR = 自动语音识别)感兴趣,那么还有另一种方法可以通过抓取 youtube get_video_info 数据。

为了做到这一点,您需要:

步骤 1. 为您的 videoID 获取 get_video_info 文件。这里https://youtube.com/get_video_info?video_id=videoID

第 2 步。在此数据中,您将找到 captionTracks > baseURL

step3.只需复制baseURL链接,就可以看到ASR xml数据(如果视频支持的话)。

如果您使用的是 nodejs,有一种简单的方法可以通过编程方式完成:

npm i ytgetcaption
caption = require('ytgetcaption');

VideoID = "1RhRRRG6MBU"

caption.ytgetCaption(VideoID).then(function (data) 
    console.log(data)
);

https://www.npmjs.com/package/ytgetcaption

【讨论】:

以上是关于获取 YouTube 字幕的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 R 获取 YouTube 视频的字幕?

获取只有字幕 Youtube API 的视频列表

怎样快速下载YouTube视频字幕

如何使用 Youtube API 为 Youtube 视频添加字幕?

从 API 获取 YouTube 自动转录?

如何为 YouTube 直播添加字幕?