CapTube

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

目前为 2016-11-09 提交的版本。查看 最新版本

// ==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();
})();