您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
将YouTube上的沉浸式翻译中文字幕转换为语音播放,支持更改音色和根据视频倍速调整语音速度
当前为
// ==UserScript== // @name YouTube字幕文本转语音TTS(适用于沉浸式翻译) // @namespace http://tampermonkey.net/ // @version 1.8 // @description 将YouTube上的沉浸式翻译中文字幕转换为语音播放,支持更改音色和根据视频倍速调整语音速度 // @author Sean2333 // @match https://www.youtube.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; let lastCaptionText = ''; const synth = window.speechSynthesis; let selectedVoice = null; let pendingText = null; // 存储等待朗读的文本 let isWaitingToSpeak = false; // 是否正在等待朗读 function loadVoices() { return new Promise(function(resolve) { let voices = synth.getVoices(); if (voices.length !== 0) { resolve(voices); } else { synth.onvoiceschanged = function() { voices = synth.getVoices(); resolve(voices); }; } }); } function selectVoice() { loadVoices().then(function(voices) { selectedVoice = voices.find(voice => voice.name === 'Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland)'); if (!selectedVoice) { selectedVoice = voices.find(voice => voice.lang === 'zh-CN'); } console.log('已选择的语音:', selectedVoice ? selectedVoice.name : '未找到合适的语音'); }); } function speakText(text, isNewCaption = false) { const video = document.querySelector('video'); // 如果有新字幕出现且当前正在朗读 if (isNewCaption && synth.speaking) { console.log('新字幕出现,但当前语音未完成'); pendingText = text; // 保存新字幕文本 if (video && !video.paused) { video.pause(); // 暂停视频 isWaitingToSpeak = true; console.log('视频已暂停,等待当前语音完成'); } return; } console.log('准备朗读文本:', text); if (synth.speaking) { console.log('正在停止当前语音播放'); synth.cancel(); } if (text) { const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'zh-CN'; if (selectedVoice) { utterance.voice = selectedVoice; } if (video) { utterance.rate = video.playbackRate; console.log('设置语音速率为:', utterance.rate); } else { utterance.rate = 1; } // 语音结束时的处理 utterance.onend = () => { console.log('当前语音播放完成'); // 如果有等待的文本,播放它 if (pendingText) { console.log('播放等待的文本'); const nextText = pendingText; pendingText = null; speakText(nextText); } // 如果视频是因为等待语音而暂停的,则恢复播放 else if (isWaitingToSpeak && video && video.paused) { isWaitingToSpeak = false; video.play(); console.log('所有语音播放完成,视频继续播放'); } }; // 语音出错时的处理 utterance.onerror = () => { console.error('语音播放出错'); if (isWaitingToSpeak && video && video.paused) { isWaitingToSpeak = false; video.play(); console.log('语音播放出错,视频继续播放'); } pendingText = null; // 清除等待的文本 }; synth.speak(utterance); console.log('开始朗读'); } else { console.log('文本为空,跳过朗读'); } } function getCaptionText() { const immersiveCaptionWindow = document.querySelector('#immersive-translate-caption-window'); if (immersiveCaptionWindow && immersiveCaptionWindow.shadowRoot) { const captionContainer = immersiveCaptionWindow.shadowRoot.querySelector('div > div > div'); if (captionContainer) { const targetCaptions = captionContainer.querySelectorAll('.target-cue'); let captionText = ''; targetCaptions.forEach(span => { captionText += span.textContent + ' '; }); captionText = captionText.trim(); return captionText; } } return ''; } function setupCaptionObserver() { function waitForCaptionContainer() { const immersiveCaptionWindow = document.querySelector('#immersive-translate-caption-window'); if (immersiveCaptionWindow && immersiveCaptionWindow.shadowRoot) { const captionContainer = immersiveCaptionWindow.shadowRoot.querySelector('div > div > div'); if (captionContainer) { console.log('找到字幕容器,开始监听变化'); const observer = new MutationObserver((mutations) => { if (mutations.some(mutation => mutation.type === 'childList')) { const currentText = getCaptionText(); if (currentText && currentText !== lastCaptionText) { lastCaptionText = currentText; speakText(currentText, true); // 标记这是新字幕 } } }); const config = { childList: true, subtree: true }; observer.observe(captionContainer, config); const initialText = getCaptionText(); if (initialText) { lastCaptionText = initialText; speakText(initialText, true); } } else { setTimeout(waitForCaptionContainer, 1000); } } else { setTimeout(waitForCaptionContainer, 1000); } } waitForCaptionContainer(); } window.addEventListener('load', function() { console.log('脚本已启动,等待视频播放'); selectVoice(); setupCaptionObserver(); }); })();