CapTube

"S"キーを押すだけでYouTubeのスクリーンショットを保存

当前为 2016-11-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        CapTube
// @namespace   https://github.com/segabito/
// @description "S"キーを押すだけでYouTubeのスクリーンショットを保存
// @include     https://www.youtube.com/*
// @include     https://youtube.com/*
// @version     0.0.1
// @grant       none
// @license     public domain
// @noframes
// ==/UserScript==

(function() {

  let previewContainer = null;
  const addStyle = function(styles, id) {
    var elm = document.createElement('style');
    elm.type = 'text/css';
    if (id) { elm.id = id; }

    var text = styles.toString();
    text = document.createTextNode(text);
    elm.appendChild(text);
    var head = document.getElementsByTagName('head');
    head = head[0];
    head.appendChild(elm);
    return elm;
  };

  const __css__ = (`
    #CapTubePreviewContainer {
      position: fixed;
      padding: 8px;
      width: 90%;
      bottom: 100px;
      left: 5%;
      z-index: 10000;
      pointer-events: none;
      transform: translateZ(0);
      background: rgba(192, 192, 192, 0.4);
      /*border: 2px solid #ccc;*/
      -webkit-user-select: none;
      user-select: none;
    }

    #CapTubePreviewContainer:empty {
      display: none;
    }
      #CapTubePreviewContainer canvas {
        display: inline-block;
        width: 128px;
        margin-right: 16px;
        margin-bottom: 16px;
        box-shadow: 4px 4px 4px #000;
        transition:
          0.4s opacity      ease,
          0.4s width        ease 0.5s,
          0.4s margin-right ease 0.5s;
      }

      #CapTubePreviewContainer canvas.is-removing {
        opacity: 0;
        margin-right: 0;
        width: 0;
      }

  `).trim();

  addStyle(__css__);

  const getVideoId = function() {
    var id = '';
    location.search.substring(1).split('&').forEach(function(item){
      if (item.split('=')[0] === 'v') { id = item.split('=')[1]; }
    });
    return id;
  };

  const toSafeName = function(text) {
    text = text.trim()
      .replace(/</g, '<')
      .replace(/>/g, '>')
      .replace(/\?/g, '?')
      .replace(/:/g, ':')
      .replace(/\|/g, '|')
      .replace(/\//g, '/')
      .replace(/\\/g, '¥')
      .replace(/"/g, '”')
      .replace(/\./g, '.')
      ;
    return text;
  };

  const getVideoTitle = function() {
    var videoId = getVideoId();
    var title = document.querySelector('.watch-title');
    var authorName = toSafeName(document.querySelector('.yt-user-info a').text);
    var titleText = toSafeName(title.textContent);
    titleText = titleText + ' - by ' + authorName + ' (v=' + videoId + ')';

    return titleText;
  };

  const createCanvasFromVideo = function(video) {
    const width = video.videoWidth;
    const height = video.videoHeight;
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const context = canvas.getContext('2d');
    context.drawImage(video, 0, 0);
    return canvas;
  };

  const getFileName = function(video) {
    const title = getVideoTitle();
    const currentTime = video.currentTime;
    const min = Math.floor(currentTime / 60);
    const sec = (currentTime % 60 + 100).toString().substr(1, 6);
    const time = `${min}_${sec}`;

    return `${title}@${time}.png`;
  };

  const createBlobLinkElement = function(canvas, fileName) {
    const dataUrl = canvas.toDataURL('image/png');
    const bin = atob(dataUrl.split(',')[1]);
    const buf = new Uint8Array(bin.length);
    for (let i = 0, len = buf.length; i < len; i++) { buf[i] = bin.charCodeAt(i); }
    const blob = new Blob([buf.buffer], {type: 'image/png'});
    const url = window.URL.createObjectURL(blob);

    const link = document.createElement('a');
    link.setAttribute('download', fileName);
    link.setAttribute('target', '_blank');
    link.setAttribute('href', url);

    return link;
  };

  const saveScreenShot = function() {
    const video = document.querySelector('.html5-main-video');
    if (!video) { return; }

    const canvas = createCanvasFromVideo(video);
    const fileName = getFileName(video);

    const link = createBlobLinkElement(canvas, fileName);

    if (previewContainer) {
      previewContainer.appendChild(canvas);
      setTimeout(function() {
        canvas.classList.add('is-removing');
        setTimeout(function() { canvas.remove(); }, 1000);
      }, 1500);
    }

    document.body.appendChild(link);
    link.click();
    setTimeout(function() { link.remove(); }, 1000);
  };

  const setPlaybackRate = function(v) {
    const video = document.querySelector('.html5-main-video');
    if (!video) { return; }
    video.playbackRate = v;
  };

  const togglePlay = function() {
    const video = document.querySelector('.html5-main-video');
    if (!video) { return; }

    if (video.paused) {
      video.play();
    } else {
      video.pause();
    }
  };

  const seekBy = function(v) {
    const video = document.querySelector('.html5-main-video');
    if (!video) { return; }

    const ct = Math.max(video.currentTime + v, 0);
    video.currentTime = ct;
  };

  let isVerySlow = false;
  const onKeyDown = (e) => {
    const key = e.key.toLowerCase();
    switch (key) {
      case 'd':
        setPlaybackRate(0.1);
        isVerySlow = true;
        break;
      case 's':
        saveScreenShot();
        break;
    }
  };

  const onKeyUp = (e) => {
    console.log('onKeyUp', e);
    const key = e.key.toLowerCase();
    switch (key) {
      case 'd':
        setPlaybackRate(1);
        isVerySlow = false;
        break;
    }
  };

  const onKeyPress = (e) => {
    const key = e.key.toLowerCase();
    switch (key) {
      case 'w':
        togglePlay();
        break;
      case 'a':
        seekBy(isVerySlow ? -0.5 : -5);
        break;
    }
  };


  const initDom = function() {
    const div = document.createElement('div');
    div.id = 'CapTubePreviewContainer';
    document.body.appendChild(div);
    previewContainer = div;
  };

  const initialize = function() {
    initDom();
    window.addEventListener('keydown',  onKeyDown);
    window.addEventListener('keyup',    onKeyUp);
    window.addEventListener('keypress', onKeyPress);
  };

  initialize();
})();