您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Добавляет кнопку рядом с <video> для записи и сохранения аудио как WebM/OGG. Только для вашего или разрешённого контента без DRM.
当前为
// ==UserScript== // @name Save Audio from HTML5 Video (WebM/OGG) — own/allowed content only // @namespace https://gf.qytechs.cn/users/your-name // @license MIT // @version 1.0 // @description Добавляет кнопку рядом с <video> для записи и сохранения аудио как WebM/OGG. Только для вашего или разрешённого контента без DRM. // @match *://*/* // @run-at document-idle // @grant none // ==/UserScript== (function () { "use strict"; const BTN_CLASS = "gf-save-audio-btn"; const getSupportedMime = () => { const types = [ "audio/webm;codecs=opus", "audio/ogg;codecs=opus", "audio/webm", "audio/ogg" ]; return types.find(t => typeof MediaRecorder !== "undefined" && MediaRecorder.isTypeSupported(t)) || null; }; const sanitize = (s) => s.replace(/[\\/:*?"<>|]+/g, "_").trim(); const makeButton = (video) => { if (video.dataset.gfsabInit === "1") return; video.dataset.gfsabInit = "1"; // Контейнер под кнопки const bar = document.createElement("div"); bar.style.display = "flex"; bar.style.gap = "8px"; bar.style.alignItems = "center"; bar.style.margin = "6px 0"; bar.style.font = "14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial"; const btn = document.createElement("button"); btn.textContent = "Сохранить аудио"; btn.className = BTN_CLASS; Object.assign(btn.style, { padding: "6px 10px", borderRadius: "8px", border: "1px solid #ccc", background: "#f7f7f7", cursor: "pointer" }); const hint = document.createElement("span"); hint.textContent = "Запись идёт в реальном времени; видео должно проигрываться."; hint.style.opacity = "0.8"; bar.appendChild(btn); bar.appendChild(hint); // Вставляем панель сразу после видео if (video.parentElement) { video.parentElement.insertBefore(bar, video.nextSibling); } else { video.insertAdjacentElement("afterend", bar); } let recorder = null; let chunks = []; let stopping = false; const stopAndSave = () => { if (recorder && recorder.state !== "inactive" && !stopping) { stopping = true; recorder.stop(); } }; btn.addEventListener("click", async () => { // Если уже пишем — останавливаем if (recorder && recorder.state === "recording") { stopAndSave(); return; } const mime = getSupportedMime(); if (!mime) { alert("MediaRecorder не поддерживает аудио-форматы (WebM/OGG) в этом браузере."); return; } const capture = video.captureStream || video.mozCaptureStream; if (!capture) { alert("Этот браузер/страница не поддерживает captureStream для видео."); return; } const stream = capture.call(video); const audioTracks = stream.getAudioTracks(); if (!audioTracks || audioTracks.length === 0) { alert("У видео не обнаружена аудио-дорожка или она недоступна (возможны ограничения CORS/DRM)."); return; } const audioStream = new MediaStream([audioTracks[0]]); chunks = []; stopping = false; try { recorder = new MediaRecorder(audioStream, { mimeType: mime }); } catch (e) { console.error(e); alert("Не удалось запустить MediaRecorder."); return; } recorder.ondataavailable = (ev) => { if (ev.data && ev.data.size > 0) chunks.push(ev.data); }; recorder.onstop = () => { try { const blob = new Blob(chunks, { type: mime }); const url = URL.createObjectURL(blob); const title = sanitize(document.title || "audio"); const ext = mime.includes("ogg") ? "ogg" : "webm"; const ts = new Date().toISOString().replace(/[:.]/g, "-"); const a = document.createElement("a"); a.href = url; a.download = `${title}-${ts}.${ext}`; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 5000); } finally { btn.textContent = "Сохранить аудио"; } }; // Авто-стоп в конце const onEnded = () => stopAndSave(); video.addEventListener("ended", onEnded, { once: true }); recorder.start(); btn.textContent = "Стоп и сохранить аудио"; // Если видео на паузе — пробуем запустить воспроизведение try { if (video.paused) await video.play(); } catch { // Браузер может требовать пользовательского взаимодействия } }); }; const scan = () => document.querySelectorAll("video").forEach(makeButton); scan(); // Отслеживаем динамически добавленные видео const mo = new MutationObserver(() => scan()); mo.observe(document.documentElement, { childList: true, subtree: true }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址