// ==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();
}
})();