YouTube Customizable Subtitles / Youtube Subtitulos Personalizables

Allows you to customize YouTube subtitles / Le permite personalizar los subtítulos de YouTube.

2025-02-12 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         YouTube Customizable Subtitles / Youtube Subtitulos Personalizables
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Allows you to customize YouTube subtitles / Le permite personalizar los subtítulos de YouTube.
// @author       Eterve Nallo - Diam
// @license      MIT
// @match        *://*.youtube.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    const languages = {
        en: {
            configTitle: 'Subtitle Settings',
            outlineColor: 'Outline Color:',
            outlineWidth: 'Outline Width:',
            textColor: 'Text Color:',
            saveButton: 'Save',
            closeButton: 'X',
            languageButton: 'Language',
            languageLabel: 'Choose Language',
            menuCommand: 'Show/Hide Subtitle Settings'
        },
        es: {
            configTitle: 'Configuración de Subtítulos',
            outlineColor: 'Color del Contorno:',
            outlineWidth: 'Grosor del Contorno:',
            textColor: 'Color del Texto:',
            saveButton: 'Guardar',
            closeButton: 'X',
            languageButton: 'Idioma',
            languageLabel: 'Elegir Idioma',
            menuCommand: 'Mostrar/Ocultar Configuración de Subtítulos'
        },
        ja: {
            configTitle: '字幕設定',
            outlineColor: 'アウトラインカラー:',
            outlineWidth: 'アウトラインの幅:',
            textColor: '文字色:',
            saveButton: '保存',
            closeButton: 'X',
            languageButton: '言語',
            languageLabel: '言語を選択',
            menuCommand: '字幕設定を表示/非表示'
        },
        ru: {
            configTitle: 'Настройки субтитров',
            outlineColor: 'Цвет контура:',
            outlineWidth: 'Ширина контура:',
            textColor: 'Цвет текста:',
            saveButton: 'Сохранить',
            closeButton: 'X',
            languageButton: 'Язык',
            languageLabel: 'Выберите язык',
            menuCommand: 'Показать/Скрыть настройки субтитров'
        },
        ko: {
            configTitle: '자막 설정',
            outlineColor: '윤곽선 색상:',
            outlineWidth: '윤곽선 너비:',
            textColor: '글자 색상:',
            saveButton: '저장',
            closeButton: 'X',
            languageButton: '언어',
            languageLabel: '언어 선택',
            menuCommand: '자막 설정 표시/숨기기'
        },
        zh: {
            configTitle: '字幕设置',
            outlineColor: '轮廓颜色:',
            outlineWidth: '轮廓宽度:',
            textColor: '文字颜色:',
            saveButton: '保存',
            closeButton: 'X',
            languageButton: '语言',
            languageLabel: '选择语言',
            menuCommand: '显示/隐藏字幕设置'
        }
    };

    const defaultConfig = {
        outlineColor: 'black',
        outlineWidth: 1,
        textColor: 'white',
        showPanel: false,
        language: 'en'
    };

    function loadConfig() {
        const savedConfig = GM_getValue('ytSubtitleConfig');
        return savedConfig ? JSON.parse(savedConfig) : defaultConfig;
    }

    function saveConfig(config) {
        GM_setValue('ytSubtitleConfig', JSON.stringify(config));
    }

    const config = loadConfig();
    const lang = languages[config.language] || languages.en;

    function applySubtitleStyles() {
        const shadowOffset = `${config.outlineWidth / 2}px`;

        GM_addStyle(`
            .ytp-caption-segment {
                color: ${config.textColor} !important;
                text-shadow:
                    ${shadowOffset} ${shadowOffset} 0 ${config.outlineColor},
                    -${shadowOffset} -${shadowOffset} 0 ${config.outlineColor},
                    ${shadowOffset} -${shadowOffset} 0 ${config.outlineColor},
                    -${shadowOffset} ${shadowOffset} 0 ${config.outlineColor};
                background: transparent !important;
                font-weight: bold;
                font-size: calc(16px + 1vw);
            }
        `);
    }

function createConfigPanel() {
    const videoPlayer = document.querySelector('.html5-video-player');
    if (!videoPlayer) {
        console.error('No se encontró el reproductor de video');
        return;
    }

    const panel = document.createElement('div');
    panel.id = 'subtitleConfigPanel';
    panel.style.position = 'absolute';
    panel.style.top = '10px';
    panel.style.right = '10px';
    panel.style.padding = '10px';
    panel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
    panel.style.color = 'white';
    panel.style.borderRadius = '5px';
    panel.style.zIndex = '9999';
    panel.style.display = 'block';

    panel.innerHTML = `
        <h4>${lang.configTitle}</h4>
        <label>${lang.outlineColor}
            <input type="color" id="outlineColor" value="${config.outlineColor}">
        </label><br>
        <label>${lang.outlineWidth}
            <div id="outlineWidthControl" style="display: flex; align-items: center;">
                <input type="range" id="outlineWidthSlider" min="0.5" max="5" step="0.5" value="${config.outlineWidth}" style="width: 150px;">
                <div id="outlineWidthDisplay" style="margin-left: 10px; font-size: 16px; width: 35px; text-align: center;">${config.outlineWidth}</div>
            </div>
        </label><br>
        <label>${lang.textColor}
            <input type="color" id="textColor" value="${config.textColor}">
        </label><br>
        <button id="saveConfig">${lang.saveButton}</button>
        <button id="closePanel" style="margin-top: 10px;">${lang.closeButton}</button>
        <br><button id="changeLanguage">${lang.languageButton}</button>
    `;

    videoPlayer.appendChild(panel);

    // Actualizar el grosor del contorno en tiempo real con el slider
    document.getElementById('outlineWidthSlider').addEventListener('input', (event) => {
        const newOutlineWidth = parseFloat(event.target.value);
        document.getElementById('outlineWidthDisplay').textContent = newOutlineWidth;
        config.outlineWidth = newOutlineWidth;
        applySubtitleStyles();
    });

    document.getElementById('saveConfig').addEventListener('click', () => {
        config.outlineColor = document.getElementById('outlineColor').value;
        config.textColor = document.getElementById('textColor').value;
        applySubtitleStyles();
        saveConfig(config);
    });

    document.getElementById('closePanel').addEventListener('click', () => {
        panel.remove();
        config.showPanel = false;
        saveConfig(config);
    });

    document.getElementById('changeLanguage').addEventListener('click', () => {
        toggleLanguageMenu(panel);
    });
}

function toggleLanguageMenu(panel) {
    const languageMenu = panel.querySelector('#languageMenu');

    if (!languageMenu) {
        // Crear nueva ventana de idiomas
        const languageMenu = document.createElement('div');
        languageMenu.id = 'languageMenu';
        languageMenu.style.position = 'absolute';
        languageMenu.style.top = '50px';
        languageMenu.style.left = '10px';
        languageMenu.style.padding = '10px';
        languageMenu.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        languageMenu.style.color = 'white';
        languageMenu.style.borderRadius = '5px';
        languageMenu.style.zIndex = '9999';
        languageMenu.innerHTML = `
            <h4>${lang.languageLabel}</h4>
            <button id="language_en">English</button>
            <button id="language_es">Español</button>
            <button id="language_ja">日本語</button>
            <button id="language_ru">Русский</button>
            <button id="language_ko">한국어</button>
            <button id="language_zh">中文</button>
            <button id="closeLanguageMenu" style="margin-top: 10px;">${lang.closeButton}</button>
        `;

        panel.appendChild(languageMenu);

        // Añadir eventos a los botones de idioma
        document.getElementById('language_en').addEventListener('click', () => {
            changeLanguage('en');
        });
        document.getElementById('language_es').addEventListener('click', () => {
            changeLanguage('es');
        });
        document.getElementById('language_ja').addEventListener('click', () => {
            changeLanguage('ja');
        });
        document.getElementById('language_ru').addEventListener('click', () => {
            changeLanguage('ru');
        });
        document.getElementById('language_ko').addEventListener('click', () => {
            changeLanguage('ko');
        });
        document.getElementById('language_zh').addEventListener('click', () => {
            changeLanguage('zh');
        });

        // Cerrar el menú de idiomas
        document.getElementById('closeLanguageMenu').addEventListener('click', () => {
            languageMenu.remove();
        });
    }
}

function changeLanguage(newLanguage) {
    config.language = newLanguage;
    saveConfig(config);
    location.reload(); // Recarga la página para aplicar el nuevo idioma
}

function toggleConfigPanel() {
    const videoPlayer = document.querySelector('.html5-video-player');
    if (!videoPlayer) {
        console.error('No se encontró el reproductor de video');
        return;
    }

    const panel = document.getElementById('subtitleConfigPanel');
    if (panel) {
        panel.remove();
        config.showPanel = false;
    } else {
        createConfigPanel();
    }

    saveConfig(config);
}

GM_registerMenuCommand(lang.menuCommand, toggleConfigPanel);

applySubtitleStyles();
if (config.showPanel) {
    createConfigPanel();
}
})();