您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Save YouTube timestamps with simplified UI, exportable CSV, and persistent storage
// ==UserScript== // @name YouTube Timestamp Saver (Optimized UI) // @namespace http://tampermonkey.net/ // @version 1.3 // @description Save YouTube timestamps with simplified UI, exportable CSV, and persistent storage // @author You // @match https://www.youtube.com/watch* // @match https://www.youtube.com/shorts/* // @grant none // @license MIT // ==/UserScript== (function () { const timeFields = [ ['Start', 'startTime'], ['End', 'endTime'], ['Query', 'queryTime'], ['TgtStart', 'targetStartTime'], ['TgtEnd', 'targetEndTime'], ['TgtStart2', 'targetStart2'], ['TgtEnd2', 'targetEnd2'], ['TgtStart3', 'targetStart3'], ['TgtEnd3', 'targetEnd3'] ]; let label = '', videoURL = location.href.split('&')[0]; let values = Object.fromEntries(timeFields.map(([_, k]) => [k, ''])); let savedRows = JSON.parse(localStorage.getItem('yt_savedRows') || '[]'); const saveToLocal = () => localStorage.setItem('yt_savedRows', JSON.stringify(savedRows)); const formatTime = (s) => { const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = Math.floor(s % 60); const ms = Math.round((s % 1) * 1000); const formatted = `${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`; return h > 0 ? `${h}:${formatted}` : formatted; }; const parseTime = (str) => { const p = str.split(/[:.]/).map(Number); return p.length === 4 ? p[0]*3600 + p[1]*60 + p[2] + p[3]/1000 : p[0]*60 + p[1] + p[2]/1000; }; const getTime = () => { const v = document.querySelector('video'); return v ? formatTime(v.currentTime) : ''; }; const seekTo = (key) => { const v = document.querySelector('video'); const t = parseTime(inputs[key].value); if (v && !isNaN(t)) v.currentTime = t; }; const setTime = (key) => { values[key] = getTime(); inputs[key].value = values[key]; }; const saveRow = () => { const row = [videoURL, label, ...timeFields.map(([_, k]) => values[k])]; savedRows.push(row); saveToLocal(); preview.textContent = savedRows.map(r => r.join('\t')).join('\n'); labelInput.value = ''; timeFields.forEach(([_, k]) => inputs[k].value = values[k] = ''); }; const downloadCSV = () => { const header = ['videoURL', 'label', ...timeFields.map(([_, k]) => k)]; const csv = [header, ...savedRows].map(r => r.join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'timestamps.csv'; a.click(); savedRows = []; localStorage.removeItem('yt_savedRows'); preview.textContent = ''; }; // Create UI const bar = document.createElement('div'); bar.style.cssText = `position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:5px 8px;display:flex;flex-wrap:wrap;gap:4px;font:12px sans-serif;z-index:99999;box-shadow:0 -1px 6px rgba(0,0,0,0.1)`; const inputs = {}; const labelInput = document.createElement('input'); labelInput.placeholder = 'Label'; labelInput.style.width = '80px'; labelInput.oninput = () => label = labelInput.value; bar.appendChild(labelInput); timeFields.forEach(([labelText, key]) => { const input = document.createElement('input'); input.placeholder = labelText; input.style.width = '70px'; inputs[key] = input; bar.appendChild(input); bar.appendChild(Object.assign(document.createElement('button'), { textContent: 'Set', onclick: () => setTime(key), style: 'font-size:11px' })); bar.appendChild(Object.assign(document.createElement('button'), { textContent: 'Go', onclick: () => seekTo(key), style: 'font-size:11px' })); }); bar.appendChild(Object.assign(document.createElement('button'), { textContent: 'Save Row', onclick: saveRow, style: 'background:#4caf50;color:#fff;padding:3px 8px' })); bar.appendChild(Object.assign(document.createElement('button'), { textContent: 'Download CSV', onclick: downloadCSV, style: 'padding:3px 8px' })); const preview = document.createElement('div'); preview.style.cssText = 'white-space:pre;overflow:auto;max-height:60px;width:100%'; bar.appendChild(preview); document.body.appendChild(bar); preview.textContent = savedRows.map(r => r.join('\t')).join('\n'); // URL change detection let lastURL = videoURL; const observer = new MutationObserver(() => { const newURL = location.href.split('&')[0]; if (newURL !== lastURL) { lastURL = videoURL = newURL; label = ''; Object.keys(values).forEach(k => values[k] = ''); timeFields.forEach(([_, k]) => inputs[k].value = ''); } }); observer.observe(document.body, { childList: true, subtree: true }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址