// ==UserScript==
// @name YouTube Timestamp Tool by Vat5aL
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Enhanced timestamp tool for YouTube videos
// @author Vat5aL
// @match https://www.youtube.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
function formatTime(e, t) {
var n, a = Math.floor(t / 3600), o = Math.floor(t / 60) % 60, i = Math.floor(t) % 60;
e.textContent = (a ? a + ":" + String(o).padStart(2, "0") : o) + ":" + String(i).padStart(2, "0");
e.dataset.time = t;
var vid = location.search.split(/.+v=|&/)[1] || location.href.split(/\/live\/|\/shorts\/|\?|&/)[1];
e.href = "https://youtu.be/" + vid + "?t=" + t;
}
function parseTimeRange(str) {
var matches = str.match(/(\d*:*\d+):(\d+)(?:\s*[-–]\s*)(\d*:*\d+):(\d+)/);
if (!matches) return null;
var startParts = matches[1].split(':').map(Number),
startSec = parseInt(matches[2]),
endParts = matches[3].split(':').map(Number),
endSec = parseInt(matches[4]);
var startMin = startParts.length === 1 ? startParts[0] : startParts[0] * 60 + startParts[1],
endMin = endParts.length === 1 ? endParts[0] : endParts[0] * 60 + endParts[1];
return { start: startMin * 60 + startSec, end: endMin * 60 + endSec };
}
function parseCopiedFormat(str) {
var matches = str.match(/^(?:.*?\?v=[^&\s]+&.*?\s)?(\d*:*\d+:\d+)\s*-\s*(\d*:*\d+:\d+)\s*"(.+)"$/);
if (!matches) return null;
var startParts = matches[1].split(':').map(Number),
endParts = matches[2].split(':').map(Number),
comment = matches[3];
var startMin = startParts.length === 2 ? startParts[0] : startParts[0] * 60 + startParts[1],
startSec = startParts.length === 2 ? startParts[1] : startParts[2],
endMin = endParts.length === 2 ? endParts[0] : endParts[0] * 60 + endParts[1],
endSec = endParts.length === 2 ? endParts[1] : endParts[2];
return { start: startMin * 60 + startSec, end: endMin * 60 + endSec, comment: comment };
}
function handleClick(e) {
if (e.target.dataset.time) {
e.preventDefault();
document.querySelector("video").currentTime = e.target.dataset.time;
} else if (e.target.dataset.increment) {
e.preventDefault();
var t = e.target.parentElement.querySelector('a[data-time]');
var currTime = parseInt(t.dataset.time);
formatTime(t, Math.max(0, currTime + parseInt(e.target.dataset.increment)));
} else if (e.target.dataset.action === "end") {
e.preventDefault();
var li = e.target.parentElement, startLink = li.querySelector('a[data-time]');
if (!li.querySelector('.end-time')) {
var endLink = document.createElement("a");
formatTime(endLink, Math.floor(document.querySelector("video").currentTime));
endLink.className = 'end-time';
startLink.textContent += " - " + endLink.textContent;
var timeRow = li.querySelector('.time-row');
timeRow.insertBefore(endLink, e.target.nextSibling);
e.target.remove();
updateSeekbarMarkers();
}
} else if (e.target.dataset.action === "clear") {
e.preventDefault();
list.textContent = "";
updateSeekbarMarkers();
updateScroll();
}
}
function addTimestamp(e, t) {
var li = document.createElement("li"), timeRow = document.createElement("div"), minus = document.createElement("span"),
plus = document.createElement("span"), a = document.createElement("a"), endBtn = document.createElement("button"),
commentInput = document.createElement("input"), del = document.createElement("button");
timeRow.className = "time-row";
minus.textContent = "➖"; minus.dataset.increment = 1; minus.style.cursor = "pointer";
plus.textContent = "➕"; plus.dataset.increment = -1; plus.style.cursor = "pointer";
formatTime(a, e);
endBtn.textContent = "End"; endBtn.dataset.action = "end"; endBtn.style = "background:#555;color:white;border:none;padding:2px 5px;border-radius:3px;cursor:pointer;";
commentInput.value = t || ""; commentInput.style = "width:200px;margin-top:5px;display:block;";
del.textContent = "🗑️"; del.style = "background:transparent;border:none;color:white;cursor:pointer;margin-left:5px;";
del.onclick = () => { li.remove(); updateSeekbarMarkers(); updateScroll(); };
timeRow.append(minus, plus, a, endBtn, del);
li.append(timeRow, commentInput);
li.style = "display:flex;flex-direction:column;gap:5px;padding:5px;background:rgba(255,255,255,0.05);border-radius:3px;";
list.appendChild(li);
updateScroll();
updateSeekbarMarkers();
return commentInput;
}
function updateScroll() {
var tsCount = list.children.length;
if (tsCount > 2) {
list.style.maxHeight = "200px";
list.style.overflowY = "auto";
} else {
list.style.maxHeight = "none";
list.style.overflowY = "hidden";
}
}
function updateSeekbarMarkers() {
var video = document.querySelector("video");
var progressBar = document.querySelector(".ytp-progress-bar");
if (!video || !progressBar || !isFinite(video.duration)) return;
var existingMarkers = document.querySelectorAll(".ytls-marker, .ytls-ts-bar");
existingMarkers.forEach(marker => marker.remove());
var timestamps = Array.from(list.children).map(li => {
var startLink = li.querySelector('a[data-time]');
var endLink = li.querySelector('.end-time');
var comment = li.querySelector('input').value;
var startTime = parseInt(startLink.dataset.time);
var endTime = endLink ? parseInt(endLink.dataset.time) : null;
return { start: startTime, end: endTime, comment: comment };
});
timestamps.forEach(ts => {
if (ts.start) {
var marker = document.createElement("div");
marker.className = "ytls-marker";
marker.style.position = "absolute";
marker.style.height = "100%";
marker.style.width = "2px";
marker.style.backgroundColor = "#ff0000";
marker.style.cursor = "pointer";
marker.style.left = (ts.start / video.duration * 100) + "%";
marker.dataset.time = ts.start;
marker.addEventListener("click", () => video.currentTime = ts.start);
progressBar.appendChild(marker);
if (ts.end) {
var endMarker = document.createElement("div");
endMarker.className = "ytls-marker end";
endMarker.style.position = "absolute";
endMarker.style.height = "100%";
endMarker.style.width = "2px";
endMarker.style.backgroundColor = "#00ff00";
endMarker.style.cursor = "pointer";
endMarker.style.left = (ts.end / video.duration * 100) + "%";
endMarker.dataset.time = ts.end;
endMarker.addEventListener("click", () => video.currentTime = ts.end);
progressBar.appendChild(endMarker);
// Add TS bar between start and end
var tsBar = document.createElement("div");
tsBar.className = "ytls-ts-bar";
tsBar.style.position = "absolute";
tsBar.style.height = "100%";
tsBar.style.backgroundColor = "rgba(255, 255, 0, 0.3)"; // Yellow with transparency
tsBar.style.cursor = "pointer";
var startPos = ts.start / video.duration * 100;
var endPos = ts.end / video.duration * 100;
tsBar.style.left = startPos + "%";
tsBar.style.width = (endPos - startPos) + "%";
tsBar.title = ts.comment; // Tooltip with comment on hover
progressBar.appendChild(tsBar);
}
}
});
}
function resetCopy() { isCopyList = true; copyBtn.textContent = "Copy List"; }
function importTimestamps(text) {
var lines = text.split("\n").map(line => line.trim()).filter(line => line);
var i = 0;
while (i < lines.length) {
var copiedMatch = parseCopiedFormat(lines[i]);
if (copiedMatch) {
var start = copiedMatch.start, end = copiedMatch.end, comment = copiedMatch.comment;
addTimestamp(start, comment);
if (end) {
var li = list.lastChild;
var startLink = li.querySelector('a[data-time]');
var endLink = document.createElement("a");
formatTime(endLink, end);
endLink.className = 'end-time';
startLink.textContent += " - " + endLink.textContent;
var timeRow = li.querySelector('.time-row');
timeRow.insertBefore(endLink, timeRow.lastChild);
}
i++;
} else {
var timeMatch = lines[i].match(/(\d*:*\d+:\d+)(?:\s*[-–]\s*)(\d*:*\d+:\d+)/);
if (timeMatch) {
var timeRange = parseTimeRange(lines[i]);
var start = timeRange.start, end = timeRange.end;
var comment = "";
i++;
while (i < lines.length && !lines[i].match(/(\d*:*\d+:\d+)(?:\s*[-–]\s*)(\d*:*\d+:\d+)/) && !parseCopiedFormat(lines[i])) {
comment += (comment ? " " : "") + lines[i];
i++;
}
addTimestamp(start, comment);
if (end) {
var li = list.lastChild;
var startLink = li.querySelector('a[data-time]');
var endLink = document.createElement("a");
formatTime(endLink, end);
endLink.className = 'end-time';
startLink.textContent += " - " + endLink.textContent;
var timeRow = li.querySelector('.time-row');
timeRow.insertBefore(endLink, timeRow.lastChild);
}
} else {
i++;
}
}
}
updateScroll();
updateSeekbarMarkers();
}
if (!document.querySelector("#ytls-pane")) {
var pane = document.createElement("div"), header = document.createElement("div"), close = document.createElement("span"),
list = document.createElement("ul"), textarea = document.createElement("textarea"), btns = document.createElement("div"),
importBtn = document.createElement("button"), addBtn = document.createElement("button"), isCopyList = true,
copyBtn = document.createElement("button"), clearBtn = document.createElement("button"), timeDisplay = document.createElement("span"),
credit = document.createElement("span"), style = document.createElement("style"), minimizeBtn = document.createElement("button");
pane.id = "ytls-pane";
pane.classList.add("minimized");
header.style = "display:flex;justify-content:space-between;align-items:center;padding-bottom:5px;padding-left:20px;";
timeDisplay.id = "ytls-current-time"; timeDisplay.textContent = "CT: "; timeDisplay.style = "color:white;font-size:14px;";
close.textContent = "×"; close.style = "cursor:pointer;font-size:18px;margin-left:5px;";
credit.textContent = "Made By Vat5aL"; credit.style = "color:white;font-size:12px;margin-left:5px;";
minimizeBtn.textContent = "▶️"; minimizeBtn.style = "background:transparent;border:none;color:white;cursor:pointer;font-size:16px;position:absolute;top:5px;left:5px;";
minimizeBtn.id = "ytls-minimize";
function updateTime() {
var v = document.querySelector("video");
if (v) {
var t = Math.floor(v.currentTime), h = Math.floor(t / 3600), m = Math.floor(t / 60) % 60, s = t % 60;
timeDisplay.textContent = `CT: ${h ? h + ":" + String(m).padStart(2, "0") : m}:${String(s).padStart(2, "0")}`;
}
requestAnimationFrame(updateTime);
}
updateTime();
textarea.id = "ytls-box";
btns.id = "ytls-buttons";
importBtn.textContent = "Import List";
addBtn.textContent = "Add TS";
copyBtn.textContent = "Copy List";
clearBtn.textContent = "Clear"; clearBtn.dataset.action = "clear"; clearBtn.style = "background:#555;color:white;font-size:12px;padding:5px 10px;border:none;border-radius:5px;cursor:pointer;";
style.textContent = "#ytls-pane{background:rgba(0,0,0,0.8);text-align:right;position:fixed;bottom:0;right:0;padding:10px;border-radius:10px 0 0 0;opacity:0.9;z-index:5000;font-family:Arial,sans-serif;width:300px;}#ytls-pane.minimized{width:30px;height:30px;overflow:hidden;background:rgba(0,0,0,0.8);padding:0;}#ytls-pane.minimized #ytls-content{display:none;}#ytls-pane.minimized #ytls-minimize{display:block;}#ytls-pane:hover{opacity:1;}#ytls-pane ul{list-style:none;padding:0;margin:0;}#ytls-pane li{display:flex;flex-direction:column;gap:5px;margin:5px 0;background:rgba(255,255,255,0.05);padding:5px;border-radius:3px;}#ytls-pane .time-row{display:flex;gap:5px;align-items:center;}#ytls-pane .ytls-marker{position:absolute;height:100%;width:2px;background-color:#ff0000;cursor:pointer;}#ytls-pane .ytls-marker.end{background-color:#00ff00;}#ytls-pane .ytls-ts-bar{position:absolute;height:100%;background-color:rgba(255,255,0,0.3);cursor:pointer;}#ytls-pane span,#ytls-pane a,#ytls-pane input{background:none;color:white;font-family:inherit;font-size:14px;text-decoration:none;border:none;outline:none;}#ytls-box{font-family:monospace;width:100%;display:block;padding:5px;border:none;outline:none;resize:none;background:rgba(255,255,255,0.1);color:white;border-radius:5px;}#ytls-buttons{display:flex;gap:5px;justify-content:space-between;margin-top:10px;}#ytls-buttons button{background:rgba(255,255,255,0.1);color:white;font-size:12px;padding:5px 10px;border:none;border-radius:5px;cursor:pointer;}#ytls-buttons button:hover{background:rgba(255,255,255,0.2);}";
close.onclick = () => { if (confirm("Close timestamp tool?")) pane.remove(); };
minimizeBtn.onclick = () => pane.classList.toggle("minimized");
list.onclick = handleClick;
list.ontouchstart = handleClick;
importBtn.onclick = () => {
var text = textarea.value;
importTimestamps(text);
};
addBtn.onclick = () => {
var timeStampBuffer = 2;
var input = addTimestamp(Math.max(0, Math.floor(document.querySelector("video").currentTime - timeStampBuffer)));
input.focus();
};
copyBtn.onclick = () => {
var url = location.href;
var text = url + "\n";
if (isCopyList) {
isCopyList = false; copyBtn.textContent = "Copy Links";
setTimeout(resetCopy, 500);
for (var i = 0; i < list.children.length; i++) {
var start = list.children[i].querySelector('a[data-time]').textContent,
comment = list.children[i].querySelector('input').value,
end = list.children[i].querySelector('.end-time') ? list.children[i].querySelector('.end-time').textContent : "";
text += (i ? "\n" : "") + `${start}${end ? " - " + end : ""} "${comment}"`;
}
} else {
resetCopy();
for (var j = 0; j < list.children.length; j++) {
var commentText = list.children[j].querySelector('input').value,
tsUrl = list.children[j].querySelector('a[data-time]').href;
text += (j ? "\n" : "") + `${commentText} ${tsUrl}`;
}
}
textarea.value = text; textarea.select(); document.execCommand("copy");
};
header.append(timeDisplay, credit, close);
var content = document.createElement("div"); content.id = "ytls-content";
content.append(header, list, textarea, btns);
pane.append(minimizeBtn, content, style);
btns.append(importBtn, addBtn, copyBtn, clearBtn);
document.body.appendChild(pane);
updateSeekbarMarkers();
}
})();