获取 YouTube 字幕更新版本原始响应

2022-09-03 01:33:33

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

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

现在我正试图在网上做。我还没有找到用于字幕的YouTube播放器Api方法,我也尝试将YouTube字幕作为带有videojs播放器的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(对于在线解决方案)中实现我的目标吗?


答案 1

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

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

答案 2

基于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();
.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://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');
}
.as-console-wrapper { top: 0; max-height: 100% !important; }