CapTube

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

目前為 2016-11-09 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();