您需要先安装一个扩展,例如 篡改猴、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或关注我们的公众号极客氢云获取最新地址