您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Injects local SRT/VTT files as fully styled, native <track> elements. Works with direct player bypass.
// ==UserScript== // @name Pixeldrain SRT Subtitle Injector // @namespace http://tampermonkey.net/ // @version 2.3.2 // @license MIT // @description Injects local SRT/VTT files as fully styled, native <track> elements. Works with direct player bypass. // @author medy17 // @match *://pixeldrain.com/u/* // @match *://pixeldrain.com/l/* // @icon https://pixeldrain.com/res/img/pixeldrain_196.png // @resource NetflixSans https://github.com/skb10x/Netflix-Sans-FONT-CSS-FontFace/raw/refs/heads/main/Netflix%20Sans%20Medium.ttf // @grant unsafeWindow // @grant GM_getResourceURL // ==/UserScript== (function() { 'use strict'; let video = null; let uiInitialized = false; let activeSubtitleTrack = null; let activeBlobUrl = null; // Variables for direct player support let preloadedSubtitles = null; let preloadedFileName = null; let isDirectPlayerMode = false; function srtToVtt(srtText) { return 'WEBVTT\n\n' + srtText .replace(/\r\n/g, '\n') .replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2'); } function clearSubtitles() { if (activeSubtitleTrack && activeSubtitleTrack.parentNode) { activeSubtitleTrack.parentNode.removeChild(activeSubtitleTrack); activeSubtitleTrack = null; } if (activeBlobUrl) { URL.revokeObjectURL(activeBlobUrl); activeBlobUrl = null; } } function injectSubtitles(subtitleText, targetVideo = null) { const videoElement = targetVideo || video; if (!videoElement) { console.log("SRT Injector: No video element available for subtitle injection"); return; } // Clear existing subtitles from the target video const existingTracks = videoElement.querySelectorAll('track[label="Local (Custom)"]'); existingTracks.forEach(track => track.remove()); // Revoke old blob URL if it exists if (activeBlobUrl) { URL.revokeObjectURL(activeBlobUrl); } const vttText = srtToVtt(subtitleText); const subtitleBlob = new Blob([vttText], { type: 'text/vtt' }); activeBlobUrl = URL.createObjectURL(subtitleBlob); const track = document.createElement('track'); track.kind = 'subtitles'; track.label = 'Local (Custom)'; track.srclang = 'en'; track.src = activeBlobUrl; track.default = true; activeSubtitleTrack = track; videoElement.appendChild(track); // Enable the track if (videoElement.textTracks && videoElement.textTracks.length > 0) { for (let i = 0; i < videoElement.textTracks.length; i++) { if (videoElement.textTracks[i].label === 'Local (Custom)') { videoElement.textTracks[i].mode = 'showing'; break; } } } console.log("SRT Injector: Subtitles injected successfully"); } function preloadSubtitles(subtitleText, fileName) { preloadedSubtitles = subtitleText; preloadedFileName = fileName; console.log(`SRT Injector: Subtitles preloaded from "${fileName}"`); } function injectCustomStyles() { const fontURL = GM_getResourceURL('NetflixSans'); const style = document.createElement('style'); style.textContent = ` @font-face { font-family: 'Netflix Sans'; src: url('${fontURL}') format('truetype'); } /* Target the native subtitle track text - regular video */ video::cue { font-family: 'Netflix Sans', Arial, sans-serif !important; font-size: 75% !important; background-color: transparent !important; text-shadow: 0 2px 5px rgba(0,0,0,0.9) !important; color: white !important; } /* Enhanced styling for modal context with higher z-index */ .modal video::cue, .pd-player-scope video::cue { font-family: 'Netflix Sans', Arial, sans-serif !important; font-size: 75% !important; color: white !important; background-color: transparent !important; text-shadow: 0 2px 8px rgba(0,0,0,1) !important; z-index: 2147483647 !important; position: relative !important; } /* Ensure subtitle container has proper z-index in modals */ .modal video::-webkit-media-text-track-display, .pd-player-scope video::-webkit-media-text-track-display { z-index: 2147483647 !important; position: relative !important; } /* Firefox subtitle container */ .modal video::cue-region, .pd-player-scope video::cue-region { z-index: 2147483647 !important; } /* Toast notifications */ .srt-toast { position: fixed; top: 20px; right: 20px; z-index: 2147483647; padding: 12px 20px; border-radius: 6px; color: white; font-family: system-ui, sans-serif; font-size: 14px; font-weight: 500; opacity: 0; transform: translateX(100%); transition: all 0.3s ease-in-out; max-width: 350px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .srt-toast.show { opacity: 1; transform: translateX(0); } .srt-toast-success { background-color: #198754; } .srt-toast-info { background-color: #0dcaf0; } `; document.head.appendChild(style); } function watchForDirectPlayerModal() { const modalObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const modal = node.querySelector('#pd-direct-player-modal') || (node.id === 'pd-direct-player-modal' ? node : null); if (modal) { console.log("SRT Injector: Direct player modal detected"); // Multiple attempts to find and inject into the video const attemptInjection = (attempt = 0) => { const modalVideo = modal.querySelector('video'); if (modalVideo && preloadedSubtitles) { console.log("SRT Injector: Injecting preloaded subtitles into direct player"); injectSubtitles(preloadedSubtitles, modalVideo); } else if (attempt < 5) { // Retry up to 5 times with increasing delays setTimeout(() => attemptInjection(attempt + 1), 500 * (attempt + 1)); } }; attemptInjection(); } } }); }); }); modalObserver.observe(document.body, { childList: true, subtree: true }); } function detectPlayerMode() { // Wait 2 seconds and check if we have a video element setTimeout(() => { const mainVideo = document.querySelector('video'); if (!mainVideo) { console.log("SRT Injector: No video detected after 2 seconds - assuming direct player mode"); isDirectPlayerMode = true; } else { console.log("SRT Injector: Main video detected - normal mode"); video = mainVideo; isDirectPlayerMode = false; // Set up video event listeners for normal mode video.addEventListener('loadstart', clearSubtitles); } }, 2000); } // ---- UI Integration and Initialization Logic ---- function setupUI() { const toolbar = document.querySelector('.toolbar'); const templateButton = document.querySelector('.toolbar_button'); const separatorTemplate = document.querySelector('.toolbar .separator'); if (!toolbar || !templateButton || !separatorTemplate) { return; } uiInitialized = true; console.log("SRT/VTT Overlay: UI Initialized. Injecting custom styles."); // Inject enhanced styles for both normal and modal contexts injectCustomStyles(); // Start detection and modal watching detectPlayerMode(); watchForDirectPlayerModal(); // --- Set up the hidden file input --- const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.srt,.vtt'; fileInput.style.display = 'none'; document.body.appendChild(fileInput); fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { const subtitleText = e.target.result; // Always preload subtitles for flexibility preloadSubtitles(subtitleText, file.name); // If we have a current video and not in direct player mode, also inject immediately if (video && !isDirectPlayerMode) { injectSubtitles(subtitleText); showToast(`Loaded subtitles from "${file.name}".`, 'success'); } else { showToast(`Subtitles from "${file.name}" preloaded for direct player.`, 'info'); } }; reader.readAsText(file); } }); // --- Create and inject the button into the toolbar --- const srtButton = templateButton.cloneNode(true); srtButton.removeAttribute('href'); srtButton.removeAttribute('title'); srtButton.querySelector('i').textContent = 'subtitles'; srtButton.querySelector('span').textContent = 'Load Subs'; srtButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); fileInput.click(); }); const newSeparator = separatorTemplate.cloneNode(true); separatorTemplate.parentNode.insertBefore(newSeparator, separatorTemplate.nextSibling); newSeparator.parentNode.insertBefore(srtButton, newSeparator.nextSibling); } function showToast(message, type = 'success') { // Remove any existing toast const existingToast = document.querySelector('.srt-toast'); if (existingToast) existingToast.remove(); const toast = document.createElement('div'); toast.className = `srt-toast srt-toast-${type}`; toast.textContent = message; document.body.appendChild(toast); // Show toast setTimeout(() => toast.classList.add('show'), 100); // Hide and remove toast after 3 seconds setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 3000); } // --- Observer to wait for the player to be ready --- const masterObserver = new MutationObserver((mutations, obs) => { if (uiInitialized) { obs.disconnect(); return; } // Only need toolbar elements to initialize UI if (document.querySelector('.toolbar_button')) { setupUI(); obs.disconnect(); } }); masterObserver.observe(document.body, { childList: true, subtree: true }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址