YouTube Screenshot Frame

Capture YouTube screenshots with a hotkey (default is key = 'b'). Filename includes title, timestamp, and capture time.

// ==UserScript==
// @name         YouTube Screenshot Frame
// @namespace    https://github.com/dpi0/scripts/blob/main/greasyfork/youtube-screenshot-frame.js
// @version      1.0
// @description  Capture YouTube screenshots with a hotkey (default is key = 'b'). Filename includes title, timestamp, and capture time.
// @author       dpi0
// @match        https://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @homepageURL https://github.com/dpi0/scripts/blob/main/greasyfork/youtube-screenshot-frame.js
// @originalhomepageURL https://greasyfork.org/en/scripts/532893-youtube-screenshot-helper/code
// @supportURL  https://github.com/dpi0/scripts/issues
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const defaultHotkey = 'b';
    let screenshotKey = GM_getValue('screenshotKey', defaultHotkey);

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

    function getVideoTitle() {
        const selectors = [
            'h1.title.ytd-video-primary-info-renderer',
            'h1.title.style-scope.ytd-video-primary-info-renderer',
            'ytd-watch-metadata h1',
            'meta[name="title"]'
        ];
        for (const sel of selectors) {
            const el = document.querySelector(sel);
            let rawTitle = el?.textContent || el?.content;
            if (rawTitle) {
                return rawTitle
                    .trim()
                    .replace(/[\\/:*?"<>|]/g, '_')   // illegal chars
                    .replace(/\s+/g, '-')            // spaces to dashes
                    .toLowerCase();
            }
        }
        return 'unknown-title';
    }

    function formatTimestamp(date) {
        const pad = (n) => n.toString().padStart(2, '0');
        return `${pad(date.getDate())}-${date.toLocaleString('default', { month: 'short' })}-${date.getFullYear()}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
    }

    function formatFrameTime(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');
        return `${h}-${m}-${s}`;
    }

    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 now = new Date();
        const title = getVideoTitle();
        const timestamp = formatTimestamp(now);
        const frameTime = formatFrameTime(video.currentTime);

        const filename = `YS_${title}_${frameTime}_${timestamp}.png`;
        const link = document.createElement('a');
        link.download = filename;
        link.href = canvas.toDataURL('image/png');
        link.click();
    }

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

    GM_registerMenuCommand(`Set Screenshot Key (Current: ${screenshotKey.toUpperCase()})`, () => {
        const input = prompt('Enter new hotkey (a-z):', screenshotKey);
        if (input && /^[a-zA-Z]$/.test(input)) {
            GM_setValue('screenshotKey', input.toLowerCase());
            location.reload();
        }
    });
})();