您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
精确控制视频播放进度/生成剪辑脚本的工具栏
当前为
- // ==UserScript==
- // @name Precise video playback (YouTube)
- // @name:zh-CN 精确控制视频播放进度 (YouTube)
- // @description A toolbar to set precise video play time and generate clip script
- // @description:zh-CN 精确控制视频播放进度/生成剪辑脚本的工具栏
- // @namespace moe.suisei.pvp.youtube
- // @match https://www.youtube.com/watch*
- // @grant none
- // @version 0.5.3
- // @author Outvi V
- // ==/UserScript==
- "use strict";
- console.log("Precise Video Playback is up");
- function getVideoId(url) {
- return String(url).match(/v=([^&#]+)/)[1];
- }
- function applyStyle(elem, styles) {
- for (const [key, value] of Object.entries(styles)) {
- elem.style[key] = value;
- }
- }
- function parseTime(str) {
- if (!isNaN(Number(str))) return Number(str);
- let time = str.match(/([0-9]?)?:([0-9]+)(\.([0-9]+))?/);
- if (time === null) return -1;
- let ret =
- Number(time[1] || 0) * 60 + Number(time[2]) + Number(time[4] || 0) * 0.1;
- if (ret == NaN) return -1;
- return ret;
- }
- function generateControl() {
- let app = document.createElement("div");
- let inputFrom = document.createElement("input");
- inputFrom.placeholder = "from time";
- let inputTo = document.createElement("input");
- inputTo.placeholder = "to time";
- let currentTime = document.createElement("span");
- let btn = document.createElement("button");
- let btnStop = document.createElement("button");
- let btnExport = document.createElement("button");
- applyStyle(app, {
- display: "flex",
- alignItems: "center",
- justifyContent: "space-between",
- maxWidth: "700px",
- marginTop: "15px",
- marginLeft: "auto",
- marginRight: "auto",
- });
- applyStyle(currentTime, {
- fontSize: "1.3rem",
- color: "var(--yt-spec-text-primary)",
- });
- let inputCommonStyle = {
- width: "120px",
- };
- applyStyle(inputFrom, inputCommonStyle);
- applyStyle(inputTo, inputCommonStyle);
- btn.innerText = "Repeat play";
- btnStop.innerText = "Stop";
- btnExport.innerText = "Export";
- app.appendChild(inputFrom);
- app.appendChild(inputTo);
- app.appendChild(currentTime);
- app.appendChild(btn);
- app.appendChild(btnStop);
- app.appendChild(btnExport);
- return {
- app,
- inputFrom,
- inputTo,
- currentTime,
- btn,
- btnStop,
- btnExport,
- };
- }
- async function sleep(time) {
- await new Promise((resolve) => {
- setTimeout(() => {
- resolve();
- }, time);
- });
- }
- async function main() {
- // Player fetching
- console.log("Waiting for the player...");
- let player;
- while (true) {
- player = document.querySelector("ytd-app #player");
- if (player && !player.hidden) break;
- await sleep(500);
- }
- let videoElement = document.querySelector("video");
- if (!videoElement || !player) {
- console.warn("Player not found. Exiting.");
- return;
- }
- console.log("Player detected.");
- // Layout
- let control = generateControl();
- console.log(player);
- player.appendChild(control.app);
- // States
- let fromValue = 0,
- toValue = 0;
- // Initial state update attempt
- let urlTime = window.location.hash.match(
- /#pvp([0-9]+\.?[0-9]?)-([0-9]+\.?[0-9]?)/
- );
- if (urlTime !== null) {
- console.log("Attempting to recover time from URL...");
- control.inputFrom.value = fromValue = Number(urlTime[1]) || 0;
- control.inputTo.value = toValue = Number(urlTime[2]) || 0;
- }
- // Current playback time
- function updateCurrentTime() {
- control.currentTime.innerText = Number(videoElement.currentTime).toFixed(2);
- requestAnimationFrame(updateCurrentTime);
- }
- requestAnimationFrame(updateCurrentTime);
- // Repeat playback
- function onTimeUpdate() {
- if (videoElement.currentTime >= Number(toValue)) {
- videoElement.currentTime = Number(fromValue);
- }
- }
- control.btn.addEventListener("click", (evt) => {
- evt.preventDefault();
- videoElement.pause();
- videoElement.currentTime = fromValue;
- if (fromValue < toValue) {
- videoElement.play();
- videoElement.addEventListener("timeupdate", onTimeUpdate);
- } else {
- videoElement.removeEventListener("timeupdate", onTimeUpdate);
- }
- });
- control.btnStop.addEventListener("click", (evt) => {
- evt.preventDefault();
- videoElement.removeEventListener("timeupdate", onTimeUpdate);
- videoElement.pause();
- });
- // Start/end time setting
- function updateURL() {
- history.pushState(null, null, `#pvp${fromValue}-${toValue}`);
- }
- control.inputFrom.addEventListener("change", () => {
- let input = control.inputFrom.value;
- if (input === "") {
- fromValue = 0;
- control.inputFrom.placeholder = "from 0";
- return;
- }
- let time = parseTime(input);
- if (time == -1) {
- control.btn.disabled = true;
- return;
- }
- control.btn.disabled = false;
- fromValue = time;
- updateURL();
- });
- control.inputTo.addEventListener("change", () => {
- let input = control.inputTo.value;
- if (input === "") {
- toValue = videoElement.duration || 0;
- control.inputTo.placeholder = `to ${toValue.toFixed(2)}`;
- return;
- }
- let time = parseTime(input);
- if (time == -1) {
- control.btn.disabled = true;
- return;
- }
- control.btn.disabled = false;
- toValue = time;
- updateURL();
- });
- // Button export
- control.btnExport.addEventListener("click", (evt) => {
- evt.preventDefault();
- let videoId = getVideoId(window.location);
- alert(`ffmpeg -i $(youtube-dl -f bestaudio -g "https://www.youtube.com/watch?v=${videoId}") \
- -ss ${fromValue} \
- -to ${toValue} \
- -acodec libmp3lame \
- -ab 192k \
- -af loudnorm=I=-16:TP=-2:LRA=11 \
- -vn \
- output-${videoId}-${fromValue}-${toValue}.mp3`);
- });
- }
- main();