获取YouTube字幕

Phi*_*nov 5 javascript java video youtube-api video.js

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

最初,我尝试通过YouTube API离线进行此操作,但似乎 YouTube禁止获取您不是所有者的视频的字幕。

现在,我正在尝试在线进行。我没有找到用于字幕的YouTube Player Api方法,我也试图通过videojs播放器的方式将 YouTube字幕作为TextTrack与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>
Run Code Online (Sandbox Code Playgroud)

我还尝试了一种解析YouTube Player IFrame(内部带有当前字幕行的div)的丑陋解决方案,但是由于源不匹配的安全性问题,它无法正常工作。


用Java(对于离线解决方案)或javascript(对于在线解决方案)可以实现我的目标吗?

Mr.*_*irl 8

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

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


更新后的版本

面向对象的方法

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();
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { top: 0; max-height: 100% !important; }
Run Code Online (Sandbox Code Playgroud)

功能方法

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();
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { top: 0; max-height: 100% !important; }
Run Code Online (Sandbox Code Playgroud)


原始回复

此响应创建一个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://stackoverflow.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');
}
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { top: 0; max-height: 100% !important; }
Run Code Online (Sandbox Code Playgroud)

  • 截至 2021 年 12 月 11 日,此功能不再有效。有什么建议么? (2认同)

Wim*_*der 8

当您在 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('&#39;',"'");
console.log(subsText);
Run Code Online (Sandbox Code Playgroud)

哇,这不是很酷吗?而且又短又简单?

这是可能的,因为有一个非常酷的技巧:在 YouTube 页面上有一个全局变量,其中包含ytInitialPlayerResponse指向字幕的链接:(确保首先刷新页面)

let subsUrl = ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.captionTracks[0].baseUrl;
Run Code Online (Sandbox Code Playgroud)

因为您位于 YouTube 页面,所以您可以获取此网址(没有 cors 问题,因为您的获取源自 youtube.com)

let subs = await (await fetch(subsUrl)).text();
Run Code Online (Sandbox Code Playgroud)

该文本应解析为 xml。为此,使用 DOMParser 非常有用:

let xml = new DOMParser().parseFromString(subs,"text/xml");
Run Code Online (Sandbox Code Playgroud)

然后你想要获取文本元素

xml.getElementsByTagName('text')
Run Code Online (Sandbox Code Playgroud)

使用展开符号,您可以将其设为文本节点数组

let textNodes = [...xml.getElementsByTagName('text')]
Run Code Online (Sandbox Code Playgroud)

可以在其上执行map操作来获取参数。我们对文本内容感兴趣:

textNodes.map(x => x.textContent)
Run Code Online (Sandbox Code Playgroud)

现在你有了一系列字幕。要将其加入到一篇大文本中,您可以编写以下内容:

let subsText = textNodes.map(x => x.textContent).join("\n").replaceAll('&#39;',"'");
Run Code Online (Sandbox Code Playgroud)

请注意, ' 是 YouTube 字幕中 ' 的符号,因此我们将其替换。

在 Chrome 的开发者控制台中,你必须添加 console.log 来摆脱\n符号,所以你可以这样写:

console.log(subsText)
Run Code Online (Sandbox Code Playgroud)

如何下载字幕
如果您想自动下载 txt 文件,您可以在此讨论中添加以下代码:

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);
Run Code Online (Sandbox Code Playgroud)

PS:我使用的是let,而不是const,这样您就可以将其多次复制粘贴到同一个控制台
PS2:这个示例使用第一个baseUrl,很好的练习:尝试找到其他语言!

玩得开心!


Ser*_*are 5

我如何从youtube视频中获取字幕是通过向该网址发出一个简单的请求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();
    });
Run Code Online (Sandbox Code Playgroud)

  • 2021年停止工作 (4认同)