YouTube Timestamp Manager | Userscript จาก chat GPT

20 January 2025 virusfowl

แอบซุ่มทำ timestamp ให้คลิปของ WiTcast มาพักนึง.... และแน่นอนว่าคนขี้เกียจอย่างเรา ก็ต้องหาตัวช่วย/เครื่องมืออะไรมาทำให้ภารกิจนี้มันสะดวกดายยิ่งขึ้น ตอนแรกก็ทำเองแบบพื้นๆ โดยใช้ AutoHotkey ที่เราถนัด แต่ก็ออกมาแค่พอใช้ได้ แถมใช้ได้คนเดียวด้วย จะแชร์ให้คนอื่นเอาไปใช้คือลำบากเลย ไม่นับว่ามันยังไม่สมบูรณ์พออีก เนื่องจากต้องใช้ร่วมกับ NVDA เท่านั้น ....

พอทำมาสักพักก็เลยนึกได้ว่า ถ้าให้ chat GPT ช่วย ตอนแรกก็คิดแค่ว่าให้มันแก้ปัญหาหลักคือการจับเวลาจากคลิปขณะที่เล่นอยู่มาให้เราก่อน มันจะทำได้ไหมนะ ก็สั่งไปง่ายๆ ปรากฏว่า เฮ้ย! พี่แกจัดให้ออกมาได้อย่างถูกต้องแบบที่ทดสอบแล้วใช้ได้เลยในครั้งแรก.... พอเห็นว่า A.I. เริ่มเก่งกว่าเราละ ทีนี้ก็ทยอยเพิ่มเงื่อนไข เพิ่มฟีเจอร์ต่างๆ จนออกมาเป็น userscript ที่ใช้จัดการสร้าง Youtube timestamp ได้อย่างสะดวกเลยทีเดียว...

และต่อไปนี้คือคำแนะนำสคริปต์ที่ chat GPT generate ออกมาให้

สคริปต์นี้ช่วยให้คุณสามารถจัดการการแสดงผลเวลา (timestamps) บน YouTube ได้อย่างง่ายดาย คุณสามารถเพิ่มและแก้ไข timestamps บันทึกลงในเครื่องของคุณ, นำเข้า (import) หรือส่งออก (export) ข้อมูลได้สะดวก นอกจากนี้ยังมีฟีเจอร์ในการแสดงผลเวลาแม้ว่าจะไม่มีข้อมูล timestamps อยู่เลยก็ตาม

คุณสมบัติ:

  • เพิ่ม timestamps พร้อมข้อความที่กำหนดเอง
  • แสดง timestamps ในแท็บใหม่พร้อมลิงก์ไปยังเวลาที่กำหนด
  • ส่งออก timestamps เป็นไฟล์ .txt
  • นำเข้า timestamps จากไฟล์ .txt

และอันนี้คือที่ข้าพเจ้าเขียนอธิบายเอง 555

userscript อันนี้จะมีคีย์ลัดสำหรับให้เราเพิ่ม timestamp ณ เวลานั้นๆ โดยเราจะหยุดหรือไม่หยุดคลิปที่เล่นอยู่ก็ได้ เมื่อต้องการเพิ่ม timestamp ให้กด alt+shift+y จะมี dialogue เด้งขึ้นมาให้เรากรอกข้อความที่เป็นคำอธิบายของ chahpter นั้นๆ ซึ่งเราสามารถใส่ไปได้เรื่อยๆ จนกว่าจะจบคลิป

และเราก็มีอีกหนึ่งคีย์ลัด เพื่อเปิดหน้าจัดการ timestamp คือ alt+shift+s (show timestamps) โดยจะเด้งหน้าใหม่ขึ้นมา และมี preview link ให้เรากดดูว่าตำแหน่งที่ใส่ไว้ตรงตามที่ต้องการหรือไม่ ถ้าไม่ตรงหรือต้องการแก้ไขคำอธิบาย ก็มีช่อง edit ด้านล่างที่เราสามารถแก้ไข และ save เพื่อ refresh link preview ใหม่ได้ทันที

note: จากการทดสอบถ้าใช้บน Firefox ส่วนนี้จะทำงานได้ถูกต้อง สคริปต์จะ refresh ข้อมูลให้ทันทีหลังจากกด save แต่หากใช้งานบน Chrome จะต้องปิดหน้าต่างนี้ แล้วกด show timestamps ใหม่อีกรอบ ข้อมูลถึงจะ refresh ให้

นอกจากนี้ในหน้า show timestamps ยังมีปุ่ม clear เผื่อต้องการแก้ไขใหม่ทั้งหมด ปุ่ม export สำหรับสำรองข้อมูล timestamps ไว้จัดการต่อภายหลัง (เมื่อปิดคลิปหรือ refresh หน้าคลิป ข้อมูลจะถูก reset) ปุ่ม import สำหรับนำเข้า timestamps ที่เคยทำไว้แล้วมาจัดการต่อ

วิธีการใช้งาน userscript

หากต้องการใช้งาน ผู้ใช้ต้องติดตั้งโปรแกรมจัดการ Userscript เช่น Tampermonkey และกด ติดตั้ง userscript หรือจะคัดลอกโค้ดด้านล่างไปสร้างสคริปต์ใหม่ด้วยตัวเองก็ได้เช่นกัน


คัดลอกโค้ด Userscript

คัดลอกโค้ดจากกล่องด้านล่างแล้วนำไปใช้ในโปรแกรมจัดการ Userscript เช่น Tampermonkey:


// ==UserScript==
// @name         YouTube Timestamp Manager
// @namespace    http://tampermonkey.net/
// @version      5.2
// @description  Add and manage YouTube timestamps with hyperlinks, and save/edit timestamps locally.
// @author       Chat GPT
// @match        https://www.youtube.com/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let clipboardData = ''; // Clear clipboardData at the start
    const preURL = 'https://www.youtube.com/watch?v=';

    // Function to format time in hh:mm:ss
    function formatTime(seconds) {
        const hrs = Math.floor(seconds / 3600).toString().padStart(2, '0');
        const mins = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0');
        const secs = Math.floor(seconds % 60).toString().padStart(2, '0');
        return `${hrs}:${mins}:${secs}`;
    }

    // Convert time in hh:mm:ss to seconds
    function timeToSeconds(time) {
        const [hrs, mins, secs] = time.split(':').map(Number);
        return hrs * 3600 + mins * 60 + secs;
    }

    // Add timestamp to clipboardData (stored in localStorage)
    function addTimestamp() {
        const video = document.querySelector('video');
        if (video) {
            const formattedTime = formatTime(video.currentTime);

            // Prompt user to enter custom text
            const userText = prompt(`Enter a text to add after the time (${formattedTime}):`, '');
            if (userText !== null) {
                const entry = `${formattedTime} ${userText.trim()}`;
                clipboardData += clipboardData ? `\n${entry}` : entry;

                // Save updated clipboard data to localStorage
                localStorage.setItem('youtube-timestamps', clipboardData);
            }
        }
    }

    // Extract clip ID from current URL
    function getClipId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v');
    }

    // Load data from localStorage
    function loadClipboardData() {
        clipboardData = localStorage.getItem('youtube-timestamps') || '';
    }

    // Clear data when starting a new video
    function clearLocalStorageForNewVideo() {
        const clipId = getClipId();
        const storedClipId = localStorage.getItem('youtube-clip-id');
        if (clipId !== storedClipId) {
            localStorage.setItem('youtube-clip-id', clipId); // Update stored clip ID
            localStorage.removeItem('youtube-timestamps'); // Clear timestamps for the new clip
            clipboardData = ''; // Reset clipboardData variable
        }
    }

    // Show timestamps in a new tab
    function showTimestamps() {
        loadClipboardData(); // Ensure clipboardData is up to date
        const clipId = getClipId();

        const videoTitle = document.title.replace('- YouTube', '').trim(); // Use page title
        const timestampHTML = clipboardData.trim() ? generateTimestampsHTML(clipboardData, clipId) : '

No timestamps available.

'; // Create a new Blob with HTML content const htmlContent = ` Timestamps of ${videoTitle}

Timestamps of ${videoTitle}

${timestampHTML}

Edit Timestamps



`; const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); window.open(url, '_blank'); } // Generate HTML for timestamps with hyperlinks function generateTimestampsHTML(clipboardData, clipId) { const lines = clipboardData.split('\n'); return lines .map((line, index) => { const [time, ...textParts] = line.split(' '); const text = textParts.join(' '); const seconds = timeToSeconds(time); const href = `${preURL}${clipId}&t=${seconds}s`; return ``; }) .join(''); } // Add controls (buttons) to the page function addControls() { const container = document.querySelector('.ytp-right-controls'); if (!container || document.getElementById('add-timestamp')) return; const addButton = document.createElement('button'); addButton.id = 'add-timestamp'; addButton.textContent = 'Add Timestamp'; addButton.style.cssText = ` margin-left: 10px; background-color: #ff0000; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; font-size: 12px; `; addButton.accessKey = 'y'; addButton.addEventListener('click', addTimestamp); container.appendChild(addButton); const showButton = document.createElement('button'); showButton.id = 'show-timestamp'; showButton.textContent = 'Show Timestamp'; showButton.style.cssText = ` margin-left: 10px; background-color: #007bff; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; font-size: 12px; `; showButton.addEventListener('click', showTimestamps); container.appendChild(showButton); // Add access keys to buttons addButton.accessKey = 'y'; // Access key for Add Timestamp showButton.accessKey = 's'; // Access key for Show Timestamp } // Keyboard shortcut listener for timestamps page document.addEventListener('keydown', function (event) { if (event.key === 's' || event.key === 'S') { showTimestamps(); } if (event.key === 'c' || event.key === 'C') { document.getElementById('clear-timestamps').click(); } if (event.key === 'e' || event.key === 'E') { document.getElementById('export-timestamps').click(); } if (event.key === 'i' || event.key === 'I') { document.getElementById('import-timestamps').click(); } }); const observer = new MutationObserver(() => addControls()); observer.observe(document.body, { childList: true, subtree: true }); clearLocalStorageForNewVideo(); // Clear localStorage if a new video starts loadClipboardData(); // Load timestamps from localStorage })();