YouTube Screenshot Helper

YouTube Screenshot Tool – supports hotkey capture, burst mode, customizable hotkeys, burst interval settings, and menu language switch between Chinese and English.

Installer dette script?
Skaberens foreslåede script

Du vil måske også kunne lide Twitch Screenshot Helper

Installer dette script
// ==UserScript==
// @name         YouTube Screenshot Helper
// @name:zh-TW   YouTube 截圖助手
// @name:zh-CN   YouTube 截图助手
// @namespace    https://www.tampermonkey.net/
// @version      2.1
// @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 CONFIG = {
        defaultHotkey: 's',
        defaultInterval: 1000,
        minInterval: 100,
        defaultLang: 'EN',
    };

    // 取得設定值
    let screenshotKey = GM_getValue('screenshotKey', CONFIG.defaultHotkey);
    let interval = Math.max(parseInt(GM_getValue('captureInterval', CONFIG.defaultInterval)), CONFIG.minInterval);
    let lang = GM_getValue('lang', CONFIG.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 ${CONFIG.minInterval}ms):`,
        },
        ZH: {
            langToggle: '語言 中文',
            setHotkey: `設定截圖快捷鍵(目前:${screenshotKey.toUpperCase()})`,
            setInterval: `設定連拍間隔(目前:${interval}ms)`,
            promptKey: '請輸入新的快捷鍵(單一字母):',
            promptInterval: `請輸入新的連拍間隔(單位ms,最低 ${CONFIG.minInterval}ms):`,
        },
    };
    const t = I18N[lang];

    // 狀態變數
    let keyDown = false;
    let intervalId = null;
    let observer = null;
    let lastHref = location.href;

    // 取得影片元素
    function getVideoElement() {
        const videos = Array.from(document.querySelectorAll('video'));
        if (window.location.href.includes('/shorts/')) {
            return videos.find(v => v.offsetParent !== null);
        }
        return videos[0] || null;
    }

    // 格式化時間
    function formatTime(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 };
    }

    // 取得影片ID
    function getVideoID() {
        let match = window.location.href.match(/\/shorts\/([a-zA-Z0-9_-]+)/);
        if (match) return match[1];
        match = window.location.href.match(/\/live\/([a-zA-Z0-9_-]+)/);
        if (match) return match[1];
        match = window.location.href.match(/[?&]v=([^&]+)/);
        return match ? match[1] : 'unknown';
    }

    // 取得影片標題
    function getVideoTitle() {
        if (window.location.href.includes('/shorts/')) {
            let h2 = document.querySelector('ytd-reel-video-renderer[is-active] h2');
            if (h2 && h2.textContent.trim()) return h2.textContent.trim().replace(/[\\/:*?"<>|]/g, '').trim();
            h2 = document.querySelector('ytd-reel-video-renderer h2');
            if (h2 && h2.textContent.trim()) return h2.textContent.trim().replace(/[\\/:*?"<>|]/g, '').trim();
            let meta = document.querySelector('meta[name="title"]');
            if (meta) return meta.getAttribute('content').replace(/[\\/:*?"<>|]/g, '').trim();
            return (document.title || 'unknown').replace(/[\\/:*?"<>|]/g, '').trim();
        }
        if (window.location.href.includes('/live/')) {
            let title = document.querySelector('meta[name="title"]')?.getAttribute('content')
                || document.title
                || 'unknown';
            return title.replace(/[\\/:*?"<>|]/g, '').trim();
        }
        let title = document.querySelector('h1.ytd-watch-metadata')?.textContent
            || document.querySelector('h1.title')?.innerText
            || document.querySelector('h1')?.innerText
            || document.querySelector('meta[name="title"]')?.getAttribute('content')
            || document.title
            || 'unknown';
        return title.replace(/[\\/:*?"<>|]/g, '').trim();
    }

    // 截圖主程式
    function takeScreenshot() {
        const video = getVideoElement();
        if (!video || video.videoWidth === 0 || video.videoHeight === 0) {
            return;
        }
        const canvas = document.createElement('canvas');
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);

        const link = document.createElement('a');
        const timeObj = formatTime(video.currentTime);
        const title = getVideoTitle();
        const id = getVideoID();
        const resolution = `${canvas.width}x${canvas.height}`;
        // 修改檔名格式:影片標題_小時_分鐘_秒_毫秒_ID_解析度
        link.download = `${title}_${timeObj.h}_${timeObj.m}_${timeObj.s}_${timeObj.ms}_${id}_${resolution}.png`;
        link.href = canvas.toDataURL('image/png');
        link.click();
    }

    // 初始化腳本
    function init() {
        // 清除舊的監聽與計時器
        if (observer) {
            observer.disconnect();
            observer = null;
        }
        clearInterval(intervalId);
        keyDown = false;

        // 監聽 DOM 變化(Shorts 專用,可保留或移除,這裡保留以利未來擴充)
        if (window.location.href.includes('/shorts/')) {
            observer = new MutationObserver(() => {});
            observer.observe(document.body, { childList: true, subtree: true });
        }

        // 快捷鍵事件(先移除再加,避免重複)
        document.removeEventListener('keydown', keydownHandler);
        document.removeEventListener('keyup', keyupHandler);
        document.addEventListener('keydown', keydownHandler);
        document.addEventListener('keyup', keyupHandler);
    }

    // 快捷鍵事件處理
    function keydownHandler(e) {
        if (
            e.key.toLowerCase() === screenshotKey &&
            !keyDown &&
            !['INPUT', 'TEXTAREA'].includes(e.target.tagName)
        ) {
            keyDown = true;
            takeScreenshot();
            intervalId = setInterval(takeScreenshot, interval);
        }
    }
    function keyupHandler(e) {
        if (e.key.toLowerCase() === screenshotKey) {
            keyDown = false;
            clearInterval(intervalId);
        }
    }

    // 油猴選單
    GM_registerMenuCommand(t.setHotkey, () => {
        const input = prompt(t.promptKey, screenshotKey);
        if (input && /^[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 >= CONFIG.minInterval) {
            GM_setValue('captureInterval', input);
            location.reload();
        }
    });

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

    // 監聽 SPA 路由變化,自動重新初始化
    setInterval(() => {
        if (location.href !== lastHref) {
            lastHref = location.href;
            setTimeout(init, 300); // 延遲初始化,確保 DOM 已切換
        }
    }, 200);

    // 首次初始化
    init();
})();
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元