YouTube 截图助手

YouTube截图工具,支援快捷键截图、连拍模式、支援自定义快捷键、连拍间隔设定、中英菜单切换

目前为 2025-04-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube Screenshot Helper
// @name:zh-TW   YouTube 截圖助手
// @name:zh-CN   YouTube 截图助手
// @namespace    https://www.tampermonkey.net/
// @version      1.3
// @description  YouTube Screenshot Tool – supports hotkey capture, burst mode, customizable hotkeys, burst interval settings, and menu language switch between Chinese and English.
// @description:zh-TW YouTube截圖工具,支援快捷鍵截圖、連拍模式、支援自定義快捷鍵、連拍間隔設定、中英菜單切換
// @description:zh-CN YouTube截图工具,支援快捷键截图、连拍模式、支援自定义快捷键、连拍间隔设定、中英菜单切换
// @author       ChatGPT
// @match        https://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const defaultHotkey = 's';
    const defaultInterval = 1000;
    const minInterval = 100;
    const defaultLang = 'EN';

    let screenshotKey = GM_getValue('screenshotKey', defaultHotkey);
    let interval = parseInt(GM_getValue('captureInterval', defaultInterval));
    if (interval < minInterval) interval = minInterval;
    let lang = GM_getValue('lang', defaultLang);

    const i18n = {
        EN: {
            langToggle: 'LANG EN',
            setHotkey: `Set Screenshot Key (Now: ${screenshotKey.toUpperCase()})`,
            setInterval: `Set Burst Interval (Now: ${interval}ms)`,
            promptKey: 'Enter new hotkey (a-z):',
            promptInterval: `Enter new interval (min ${minInterval}ms):`,
        },
        ZH: {
            langToggle: '語言 中文',
            setHotkey: `設定截圖快捷鍵(目前:${screenshotKey.toUpperCase()})`,
            setInterval: `設定連拍間隔(目前:${interval}ms)`,
            promptKey: '請輸入新的快捷鍵(單一字母):',
            promptInterval: `請輸入新的連拍間隔(單位ms,最低 ${minInterval}ms):`,
        },
    };

    const t = i18n[lang];

    let keyDown = false;
    let intervalId = null;

    function getVideoElement() {
        return document.querySelector('video');
    }

    function formatTime3(seconds) {
        const h = String(Math.floor(seconds / 3600)).padStart(2, '0');
        const m = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
        const s = String(Math.floor(seconds % 60)).padStart(2, '0');
        const ms = String(Math.floor((seconds % 1) * 1000)).padStart(3, '0');
        return `${h}_${m}_${s}_${ms}`;
    }

    function getVideoID() {
        const match = window.location.href.match(/[?&]v=([^&]+)/);
        return match ? match[1] : 'unknown';
    }

    function takeScreenshot() {
        const video = getVideoElement();
        if (!video) return;

        const canvas = document.createElement('canvas');
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

        const link = document.createElement('a');
        const timestamp = formatTime3(video.currentTime);
        const id = getVideoID();
        const resolution = `${canvas.width}x${canvas.height}`;
        link.download = `${timestamp}_${id}_${resolution}.png`;
        link.href = canvas.toDataURL('image/png');
        link.click();
    }

    document.addEventListener('keydown', (e) => {
        if (e.key.toLowerCase() === screenshotKey && !keyDown && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
            keyDown = true;
            takeScreenshot();
            intervalId = setInterval(() => {
                takeScreenshot();
            }, interval);
        }
    });

    document.addEventListener('keyup', (e) => {
        if (e.key.toLowerCase() === screenshotKey) {
            keyDown = false;
            clearInterval(intervalId);
        }
    });

    // 註冊油猴選單指令
    GM_registerMenuCommand(t.setHotkey, () => {
        const input = prompt(t.promptKey, screenshotKey);
        if (input && input.length === 1 && /^[a-zA-Z]$/.test(input)) {
            GM_setValue('screenshotKey', input.toLowerCase());
            location.reload();
        }
    });

    GM_registerMenuCommand(t.setInterval, () => {
        const input = parseInt(prompt(t.promptInterval, interval));
        if (!isNaN(input) && input >= minInterval) {
            GM_setValue('captureInterval', input);
            location.reload();
        }
    });

    GM_registerMenuCommand(t.langToggle, () => {
        GM_setValue('lang', lang === 'EN' ? 'ZH' : 'EN');
        location.reload();
    });
})();