您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ytarchive와 yt-dlp로 유튜브 생방송/아카이브/영상 클립을 따기 쉽게 도와주는 툴
// ==UserScript== // @name ezClip // @namespace Holobox // @version 2024-12-05 // @description ytarchive와 yt-dlp로 유튜브 생방송/아카이브/영상 클립을 따기 쉽게 도와주는 툴 // @author 물먹는하마 // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @run-at document-start // @icon https://cutecafe.art/wp-content/uploads/2023/03/fauna01.gif // @grant GM_setClipboard // @grant GM_addStyle // @license MIT // ==/UserScript== let { get: getter, set: setter } = Object.getOwnPropertyDescriptor( Object.prototype, "playerResponse" ) ?? { set(e) { this[Symbol.for("ezclip")] = e; }, get() { return this[Symbol.for("ezclip")]; }, }, isObject = (e) => null != e && "object" == typeof e, LEFT = (Object.defineProperty(Object.prototype, "playerResponse", { set(e) { var t, o; isObject(e) && (({ streamingData: t, videoDetails: o } = e), isObject(o)) && o.isLive && !o.isLiveDvrEnabled && ((o.isLiveDvrEnabled = !0), isObject(t)) && delete t.serverAbrStreamingUrl, setter.call(this, e); }, get() { return getter.call(this); }, configurable: !0, }), -1), RIGHT = 1; function formatSecondsToHHMMSS(e) { var e = Math.floor(e), t = Math.floor(e / 3600), o = Math.floor((e % 3600) / 60), e = e % 60; return [ t.toString().padStart(2, "0"), o.toString().padStart(2, "0"), e.toString().padStart(2, "0"), ].join(":"); } function waitForElm(r) { return new Promise((t) => { var e = document.querySelector(r); if (e) return t(e); let o = new MutationObserver((e) => { document.querySelector(r) && (t(document.querySelector(r)), o.disconnect()); }); o.observe(document, { childList: !0, subtree: !0 }); }); } async function ytExists(e) { e = `https://www.youtube.com/oembed?url=${e}&format=json`; try { return (await fetch(e)).ok; } catch (e) { return console.error("Error checking video existence:", e), !1; } } function formatDiffToRelativeTime(e) { var t = Math.floor(e / 3600), o = Math.floor((e % 3600) / 60), e = Math.floor(e % 60); return 0 < t ? t + `시간 ${o}분` : 0 < o ? o + `분 ${e}초` : e + "초"; } (async () => { let [i, s, e] = await Promise.all([ waitForElm("#movie_player"), waitForElm(".ytp-scrubber-button"), waitForElm(".ytp-live-badge"), ]); var t = document.querySelector(".ytp-progress-list"); let c = document.createElement("div"), a = (t.appendChild(c), (c.id = "overlay"), (c.style.position = "absolute"), (c.style.top = "0"), (c.style.height = "100%"), (c.style.background = "rgba(255, 213, 44, 0.7)"), (c.style.zIndex = "1000"), (c.style.width = "0"), document.createElement("div")), d = ((a.id = "wave-container"), i.appendChild(a), console.log(a), null), u = null, o = Promise.resolve(), r = null, n = null, y = null, m = null, l; function g() { var e = document .querySelector("iframe#chatframe") ?.contentWindow.document.querySelector( "div#input.yt-live-chat-text-input-field-renderer" ), r = e?.parentElement.querySelector( "label#label.yt-live-chat-text-input-field-renderer" ); if ( (y && e && ((n = m() - i.getCurrentTime()) <= 7 ? (e.setAttribute("contenteditable", "true"), (r.textContent = "구독자로 채팅...")) : ((n = formatDiffToRelativeTime(n)), e.setAttribute("contenteditable", "false"), (r.textContent = n + " 전 시점을 시청중입니다"))), null === d && null === u) ) (c.style.width = "0"), (s.style.background = "var(--yt-spec-static-brand-red,#f03)"); else { e = i.getCurrentTime(); let t = m(), o = 43200 < t ? t - 43200 : 0; var [r, n, a] = [l(d), l(u), l(e)]; function l(e) { return null === e ? null : ((e - o) / (t - o)) * 100; } d && u ? ((c.style.width = "auto"), (c.style.left = r + "%"), (c.style.right = 100 - n + "%"), e >= d && e <= u ? (s.style.background = "rgba(255, 213, 44, 0.9)") : (s.style.background = "var(--yt-spec-static-brand-red,#f03)"), (c.style.background = "rgba(255, 213, 44, 0.7)")) : d ? ((c.style.left = r + "%"), (c.style.right = ""), e >= d ? ((c.style.background = "rgba(255, 213, 44, 0.7)"), (c.style.width = "auto"), (s.style.background = "rgba(255, 213, 44, 0.9)"), (c.style.right = 100 - a + "%")) : ((c.style.width = Math.min(20, 100 - r) + "%"), (c.style.background = "linear-gradient(to right, rgba(255, 213, 44, 0.9), rgba(255, 213, 44, 0))"), (s.style.background = "var(--yt-spec-static-brand-red,#f03)"))) : u && ((c.style.left = ""), (c.style.right = 100 - n + "%"), e <= u ? ((c.style.width = "auto"), (c.style.background = "rgba(255, 213, 44, 0.7)"), (s.style.background = "rgba(255, 213, 44, 0.9)"), (c.style.left = a + "%")) : ((c.style.width = Math.min(20, n) + "%"), (c.style.background = "linear-gradient(to left, rgba(255, 213, 44, 0.9), rgba(255, 213, 44, 0))"), (s.style.background = "var(--yt-spec-static-brand-red,#f03)"))); } } function v(e, t) { var o = t - e, r = i.getVideoUrl(), o = y ? `ytarchive --live-from ${formatSecondsToHHMMSS( e )} --capture-duration ${formatSecondsToHHMMSS( o )} --threads 3 ${r} best` : `yt-dlp --download-sections "*${formatSecondsToHHMMSS( e )}-${formatSecondsToHHMMSS( t )}" --concurrent-fragments 3 --merge-output-format mp4 ` + r; GM_setClipboard(o), console.log("Command generated and copied to clipboard:", o); } function f(t = LEFT) { if (i.classList.contains("ytp-autohide")) { var o = a.offsetHeight, r = (3 * o) / 5, n = -r / 2 - r / 5; let e = document.createElement("div"); (e.className = "wave"), (e.style.width = r + "px"), (e.style.height = o + "px"), t === LEFT ? (e.style.left = n + "px") : (e.style.right = n + "px"), a.appendChild(e), e.addEventListener("animationend", () => e.remove()); } } (t = new Promise((e) => { l = e; })), i.addEventListener("onStateChange", (e) => { o = o.then(() => (async (e) => { var t; console.log(`state changed: ${r} -> ` + e), 1 === e && (t = i.getVideoData())?.video_id && n !== t.video_id && void 0 !== t.isLive && (console.log("video cued"), (y = t.isLive), (m = await (async (e) => { let t = await (async () => { let e; for (; 0 === (e = i.getCurrentTime()); ) await new Promise((e) => setTimeout(e, 100)); return e; })(), o = new Date().getTime() / 1e3; if ((console.log("duration by currentTime: ", t), e)) return ( console.log("Load time set: " + o), function () { var e = new Date().getTime() / 1e3; return t + (e - o); } ); { let e = i.getDuration(); return function () { return e; }; } })(y)), null === n && l(), (n = t.video_id), (d = null), (u = null), g(), console.log("videoId: ", n), console.log("isLive: ", y)), (r = e); })(e) ); }), o.catch((e) => { console.error("Error processing state change:", e); }), await t, setInterval(g, 1e3), e.addEventListener("click", g), document.addEventListener("mousedown", () => { document.addEventListener("mousemove", g); }), document.addEventListener("mouseup", () => { document.removeEventListener("mousemove", g), g(); }), document.addEventListener( "keydown", function (e) { var t; document.activeElement.isContentEditable || "INPUT" === document.activeElement.tagName || "TEXTAREA" === document.activeElement.tagName || ((location.href.includes("youtube.com/embed/") || location.href.includes("youtube.com/watch")) && ("KeyE" !== e.code || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey ? "KeyR" !== e.code || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey ? ("KeyC" !== e.code || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || (e.preventDefault(), e.stopPropagation(), (d = null), (u = null), console.log("Start and end times reset"), g()), (t = i.getCurrentTime()), "KeyD" !== e.code || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey ? "KeyF" !== e.code || e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || (e.preventDefault(), e.stopPropagation(), null !== d && t <= d && (d = null), (u = i.getCurrentTime()), console.log("End time set to", u), g(), null !== d && v(d, u), f(RIGHT)) : (e.preventDefault(), e.stopPropagation(), null !== u && t >= u && (u = null), (d = i.getCurrentTime()), console.log("Start time set to", d), g(), null !== u && v(d, u), f(LEFT))) : (e.preventDefault(), e.stopPropagation(), y ? (i.seekTo(m() + 1e3, !0), console.log("Moved to live part of the stream")) : null !== u && (i.seekTo(u, !0), console.log("Moved to end time", u))) : (e.preventDefault(), e.stopPropagation(), null !== d && (i.seekTo(d, !0), console.log("Moved to start time", d))))); }, !0 ), GM_addStyle(` #wave-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; pointer-events: none; /* 클릭 이벤트 무시 */ /*background: blue;*/ /*z-index: 9999;*/ } .wave { position: absolute; left: auto; right: auto; width: 300px; height: 500px; background: rgba(255, 222, 5, 0.8); /* wave color - yellow */ border-radius: 50%; transform: scale(0); animation: wave 0.6s ease-out forwards; z-index: 100; } @keyframes wave { 0% { transform: scale(0); opacity: 1; } 100% { transform: scale(1.2); opacity: 0; } } `); })();