您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extract subtitles and replace them inline with DeepL translations (500k chars/month free tier)!
// ==UserScript== // @name Nebula.tv Auto Translate // @namespace http://tampermonkey.net/ // @version 1.0 // @description Extract subtitles and replace them inline with DeepL translations (500k chars/month free tier)! // @author samu126 // @match https://nebula.tv/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; console.log('Nebula.tv Auto Translate loaded!'); // Load config from localStorage or use default values const defaultConfig = { targetLang: 'HU', deepLApiKey: '', subSize: 24, }; const config = JSON.parse(localStorage.getItem('subtitleTranslatorConfig')) || defaultConfig; // Apply saved configuration const { targetLang, deepLApiKey, subSize } = config; const apiURL = 'https://api-free.deepl.com/v2/translate'; // Free tier api const seenSubtitles = new Set(); // Configuration page HTML function createConfigPage() { const configPage = document.createElement('div'); configPage.id = 'configPage'; configPage.innerHTML = ` <div style="position: absolute; top: 10%; right: 10%; background: white; color: black; border: 1px solid black; padding: 20px; z-index: 9999; width: 300px;"> <h3>DeepL Subtitle Translator Config</h3> <label for="targetLang">Target Language (e.g., HU for Hungarian):</label> <input type="text" id="targetLang" value="${targetLang}" style="width: 100%; margin-bottom: 10px;"/> <br/> <label for="deepLApiKey">DeepL API Key:</label> <input type="text" id="deepLApiKey" value="${deepLApiKey}" style="width: 100%; margin-bottom: 10px;"/> <br/> <label for="subSize">Subtitle size:</label> <input type="number" id="subSize" value="${subSize}" style="width: 100%; margin-bottom: 10px;"/> <br/> <button id="saveConfig" style="width: 100%; background-color: #4CAF50; color: white; padding: 10px;">Save</button> <button id="closeConfig" style="width: 100%; margin-top: 10px; background-color: red; color: white; padding: 10px;">Close</button> </div> `; document.body.appendChild(configPage); // Close the configuration page document.getElementById('closeConfig').addEventListener('click', () => { document.body.removeChild(configPage); }); // Save the configuration document.getElementById('saveConfig').addEventListener('click', () => { const newTargetLang = document.getElementById('targetLang').value.trim().toUpperCase(); const newDeepLApiKey = document.getElementById('deepLApiKey').value.trim(); const newSubSize = document.getElementById('subSize').value; if (newTargetLang && newDeepLApiKey && newSubSize) { const newConfig = { targetLang: newTargetLang, deepLApiKey: newDeepLApiKey, subSize: newSubSize }; localStorage.setItem('subtitleTranslatorConfig', JSON.stringify(newConfig)); // Reload the page to apply the new config location.reload(); } else { alert('Please fill in all fields.'); } }); } // Register the command in Tampermonkey's menu GM_registerMenuCommand('Open Subtitle Translator Config', createConfigPage); function waitForElement(selector, callback) { const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { console.log('Found element:', element); observer.disconnect(); callback(element); } }); observer.observe(document.body, { childList: true, subtree: true }); } async function translateTextDeepL(text) { const params = new URLSearchParams(); params.append('auth_key', deepLApiKey); params.append('text', text); params.append('target_lang', targetLang); try { const response = await fetch(apiURL, { method: 'POST', body: params }); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const data = await response.json(); if (data.translations && data.translations.length > 0) { return data.translations[0].text; } else { return '[No translation returned]'; } } catch (error) { console.error('DeepL translation error:', error); return '[Translation failed] ' + text; } } function displayTranslatedSubtitle(text, subtitleDiv) { const translatedDiv = document.createElement('div'); translatedDiv.classList.add('translated-subtitle'); // Add an identifier class translatedDiv.textContent = text; Object.assign(translatedDiv.style, { position: 'absolute', bottom: '50px', left: '50%', transform: 'translateX(-50%)', textAlign: 'center', fontSize: subSize + 'px', color: 'white', backgroundColor: 'rgba(0,0,0,0.7)', padding: '10px', borderRadius: '5px', textShadow: '2px 2px 4px black', zIndex: 9999, fontFamily: 'Arial, sans-serif', width: 'auto' }); // Replace the original subtitle with the translated subtitle subtitleDiv.parentNode.replaceChild(translatedDiv, subtitleDiv); } waitForElement('div[data-subtitles-container="true"]', (container) => { console.log('Subtitle container found!', container); // Observe subtitle container for new subtitle text const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { const subtitleDivs = container.querySelectorAll('div > div > div'); subtitleDivs.forEach(async subDiv => { // Check if this subtitle has already been translated (based on an added class) if (subDiv.classList.contains('translated-subtitle')) { return; // Skip if already translated } const subtitleText = subDiv.innerText.trim(); if (subtitleText && !seenSubtitles.has(subtitleText)) { seenSubtitles.add(subtitleText); console.log('Original subtitle:', subtitleText); // Hide the original subtitle immediately subDiv.style.visibility = 'hidden'; const translated = await translateTextDeepL(subtitleText); console.log('Translated subtitle:', translated); // Display the translated subtitle in place of the original displayTranslatedSubtitle(translated, subDiv); } }); } } }); observer.observe(container, { childList: true, subtree: true }); console.log('Now observing and translating subtitles via DeepL...'); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址