YouTube Customizable Subtitles / Youtube Subtitulos Personalizables

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

Устаревшая версия за 12.02.2025. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например 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();
}
})();