srt / index.html
hiojo's picture
Add 3 files
c9e4851 verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conversor Avançado de Texto para SRT com Terminação em Pontos</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
.slide-in {
animation: slideIn 0.3s ease-out forwards;
}
.pulse {
animation: pulse 2s infinite;
}
.rotate {
animation: rotate 2s linear infinite;
}
.text-area-container {
position: relative;
}
.char-count {
position: absolute;
right: 10px;
bottom: 10px;
background: rgba(255, 255, 255, 0.8);
padding: 2px 8px;
border-radius: 10px;
font-size: 0.8rem;
}
.dark .char-count {
background: rgba(31, 41, 55, 0.8);
color: white;
}
.progress-bar {
height: 4px;
background: #e2e8f0;
border-radius: 2px;
overflow: hidden;
}
.dark .progress-bar {
background: #374151;
}
.progress-fill {
height: 100%;
background: #3b82f6;
transition: width 0.3s ease;
}
.srt-block {
border-left: 4px solid #3b82f6;
transition: all 0.3s ease;
}
.dark .srt-block {
background-color: #1f2937;
border-left-color: #60a5fa;
}
.srt-block:hover {
transform: translateX(5px);
background-color: #f8fafc;
}
.dark .srt-block:hover {
background-color: #374151;
}
.copy-btn {
opacity: 0;
transition: opacity 0.3s ease;
}
.srt-block:hover .copy-btn {
opacity: 1;
}
.tooltip {
position: relative;
}
.tooltip-text {
visibility: hidden;
width: 120px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.dark .tooltip-text {
background-color: #111827;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease-out;
}
.image-block {
transition: all 0.3s ease;
}
.image-block:hover {
transform: scale(1.02);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.dark .image-block:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
.clock {
font-family: 'Courier New', monospace;
letter-spacing: 2px;
}
.dark {
background-color: #111827;
color: #f3f4f6;
}
.dark .bg-white {
background-color: #1f2937;
}
.dark .text-gray-800 {
color: #f3f4f6;
}
.dark .text-gray-600 {
color: #d1d5db;
}
.dark .border-gray-300 {
border-color: #4b5563;
}
.dark .bg-gray-200 {
background-color: #374151;
color: #f3f4f6;
}
.dark .bg-gray-50 {
background-color: #1f2937;
}
.dark .border-gray-200 {
border-color: #374151;
}
.dark .shadow-md {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.dark .shadow-inner {
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col dark:bg-gray-900 transition-colors duration-300">
<div class="container mx-auto px-4 py-8 flex-grow">
<div class="max-w-5xl mx-auto">
<!-- Header with Dark Mode Toggle and Clock -->
<header class="text-center mb-8 fade-in relative">
<div class="absolute top-0 right-0 flex items-center space-x-4">
<div class="clock bg-gray-200 dark:bg-gray-700 px-3 py-1 rounded-md text-sm font-mono">
<span id="hours">00</span>:<span id="minutes">00</span>:<span id="seconds">00</span>
</div>
<button id="darkModeToggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200">
<i class="fas fa-moon dark:hidden"></i>
<i class="fas fa-sun hidden dark:inline"></i>
</button>
</div>
<h1 class="text-3xl md:text-4xl font-bold text-gray-800 dark:text-white mb-2">
<i class="fas fa-closed-captioning text-blue-500 mr-2"></i>
Conversor de Texto para SRT com Terminação em Pontos
</h1>
<p class="text-gray-600 dark:text-gray-300">Transforme texto em legendas SRT respeitando a pontuação</p>
</header>
<!-- Main Form -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-md overflow-hidden mb-8">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800 dark:text-white">
<i class="fas fa-edit text-blue-500 mr-2"></i>
Insira seu texto
</h2>
<div class="flex space-x-2">
<button id="sampleBtn" type="button" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md text-sm hover:bg-gray-300 dark:hover:bg-gray-600 transition">
<i class="fas fa-lightbulb mr-1"></i> Exemplo
</button>
<button id="clearInputBtn" type="button" class="px-3 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md text-sm hover:bg-gray-300 dark:hover:bg-gray-600 transition">
<i class="fas fa-trash-alt mr-1"></i> Limpar
</button>
</div>
</div>
<div class="text-area-container mb-2">
<textarea id="textoInput" class="w-full h-64 p-4 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none dark:bg-gray-700 dark:text-white" placeholder="Cole ou digite o texto que deseja converter para formato SRT..."></textarea>
<div class="char-count dark:bg-gray-700 dark:text-gray-300">
<span id="charCount">0</span> caracteres |
<span id="wordCount">0</span> palavras
</div>
</div>
<div class="progress-bar mb-4">
<div id="progressFill" class="progress-fill" style="width: 0%"></div>
</div>
<div class="flex flex-wrap justify-between items-center">
<div class="mb-4 md:mb-0">
<div class="flex items-center space-x-4">
<div class="flex items-center">
<label for="charsPerBlock" class="mr-2 text-sm text-gray-600 dark:text-gray-300">Caracteres/bloco:</label>
<input type="number" id="charsPerBlock" value="490" min="50" max="1000" class="w-20 p-1 border border-gray-300 dark:border-gray-600 rounded text-sm dark:bg-gray-700 dark:text-white">
</div>
<div class="flex items-center">
<label for="blockDuration" class="mr-2 text-sm text-gray-600 dark:text-gray-300">Duração (s):</label>
<input type="number" id="blockDuration" value="50" min="1" max="120" class="w-16 p-1 border border-gray-300 dark:border-gray-600 rounded text-sm dark:bg-gray-700 dark:text-white">
</div>
<div class="flex items-center">
<input type="checkbox" id="endOnPeriod" checked class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<label for="endOnPeriod" class="ml-2 text-sm text-gray-600 dark:text-gray-300">Terminar em pontos</label>
</div>
</div>
</div>
<button id="convertBtn" type="button" class="px-6 py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition flex items-center">
<i class="fas fa-exchange-alt mr-2"></i> Converter para SRT
</button>
</div>
</div>
</div>
<!-- Results Section -->
<div id="resultsSection" class="hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800 dark:text-white">
<i class="fas fa-file-alt text-green-500 mr-2"></i>
Legendas SRT Geradas
</h2>
<div class="flex space-x-2">
<button id="downloadBtn" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition flex items-center">
<i class="fas fa-download mr-2"></i> Baixar SRT
</button>
<button id="copyAllBtn" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition flex items-center">
<i class="fas fa-copy mr-2"></i> Copiar Tudo
</button>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-4 mb-4">
<div class="flex justify-between items-center mb-3">
<div class="text-sm text-gray-600 dark:text-gray-300">
<span id="blockCount">0</span> blocos gerados |
<span id="totalDuration">00:00:00</span> de duração total |
<span id="totalWords">0</span> palavras
</div>
<div class="flex items-center">
<label for="fontSize" class="mr-2 text-sm text-gray-600 dark:text-gray-300">Tamanho:</label>
<select id="fontSize" class="p-1 border border-gray-300 dark:border-gray-600 rounded text-sm bg-white dark:bg-gray-700 dark:text-white">
<option value="text-sm">Pequeno</option>
<option value="text-base" selected>Normal</option>
<option value="text-lg">Grande</option>
</select>
</div>
</div>
<div id="resultado" class="space-y-4"></div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="bg-white dark:bg-gray-800 py-4 shadow-inner">
<div class="container mx-auto px-4 text-center text-gray-600 dark:text-gray-300 text-sm">
<p>© 2023 Conversor SRT Avançado | Desenvolvido por <a href="#" class="text-blue-600 dark:text-blue-400 hover:underline">Renatim Dark</a></p>
<div class="flex justify-center space-x-4 mt-2">
<a href="#" class="hover:text-blue-600 dark:hover:text-blue-400"><i class="fab fa-github"></i></a>
<a href="#" class="hover:text-blue-600 dark:hover:text-blue-400"><i class="fab fa-twitter"></i></a>
<a href="#" class="hover:text-blue-600 dark:hover:text-blue-400"><i class="fab fa-linkedin"></i></a>
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Elements
const textoInput = document.getElementById('textoInput');
const convertBtn = document.getElementById('convertBtn');
const resultado = document.getElementById('resultado');
const downloadBtn = document.getElementById('downloadBtn');
const clearInputBtn = document.getElementById('clearInputBtn');
const sampleBtn = document.getElementById('sampleBtn');
const copyAllBtn = document.getElementById('copyAllBtn');
const resultsSection = document.getElementById('resultsSection');
const charCount = document.getElementById('charCount');
const wordCount = document.getElementById('wordCount');
const blockCount = document.getElementById('blockCount');
const totalDuration = document.getElementById('totalDuration');
const totalWords = document.getElementById('totalWords');
const progressFill = document.getElementById('progressFill');
const charsPerBlock = document.getElementById('charsPerBlock');
const blockDuration = document.getElementById('blockDuration');
const fontSize = document.getElementById('fontSize');
const darkModeToggle = document.getElementById('darkModeToggle');
const endOnPeriod = document.getElementById('endOnPeriod');
const hoursElement = document.getElementById('hours');
const minutesElement = document.getElementById('minutes');
const secondsElement = document.getElementById('seconds');
// Sample text
const sampleText = `Este é um exemplo de texto que será convertido para o formato SRT. O formato SRT é usado para legendas em vídeos e cada bloco contém:
1. Um número sequencial
2. O tempo de início e fim no formato HH:MM:SS,mmm
3. O texto da legenda
4. Uma linha em branco para separar os blocos
Você pode editar este texto ou apagá-lo para inserir o seu próprio conteúdo. O conversor irá dividir automaticamente o texto em blocos de acordo com as configurações de caracteres por bloco e duração. Cada bloco será terminado no ponto final mais próximo do limite de caracteres, garantindo que as frases não sejam cortadas no meio.`;
// Initialize
updateCounts();
updateClock();
setInterval(updateClock, 1000);
checkDarkModePreference();
// Events
textoInput.addEventListener('input', updateCounts);
sampleBtn.addEventListener('click', insertSampleText);
clearInputBtn.addEventListener('click', clearInput);
convertBtn.addEventListener('click', convertToSRT);
downloadBtn.addEventListener('click', downloadSRT);
copyAllBtn.addEventListener('click', copyAllSRT);
fontSize.addEventListener('change', changeFontSize);
darkModeToggle.addEventListener('click', toggleDarkMode);
// Functions
function updateClock() {
const now = new Date();
hoursElement.textContent = now.getHours().toString().padStart(2, '0');
minutesElement.textContent = now.getMinutes().toString().padStart(2, '0');
secondsElement.textContent = now.getSeconds().toString().padStart(2, '0');
}
function checkDarkModePreference() {
if (localStorage.getItem('darkMode') === 'enabled' ||
(!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.body.classList.add('dark');
}
}
function toggleDarkMode() {
document.body.classList.toggle('dark');
if (document.body.classList.contains('dark')) {
localStorage.setItem('darkMode', 'enabled');
} else {
localStorage.setItem('darkMode', 'disabled');
}
}
function countWords(text) {
if (!text.trim()) return 0;
return text.trim().split(/\s+/).length;
}
function updateCounts() {
const text = textoInput.value;
const charCountValue = text.length;
const wordCountValue = countWords(text);
charCount.textContent = charCountValue.toLocaleString();
wordCount.textContent = wordCountValue.toLocaleString();
// Update progress bar (now up to 1,000,000 characters)
const maxChars = 1000000;
const percentage = Math.min((charCountValue / maxChars) * 100, 100);
progressFill.style.width = `${percentage}%`;
// Change color based on percentage
if (percentage > 90) {
progressFill.style.backgroundColor = '#ef4444';
} else if (percentage > 70) {
progressFill.style.backgroundColor = '#f59e0b';
} else {
progressFill.style.backgroundColor = '#3b82f6';
}
}
function insertSampleText() {
textoInput.value = sampleText;
updateCounts();
animateButton(sampleBtn);
}
function clearInput() {
textoInput.value = '';
updateCounts();
resultsSection.classList.add('hidden');
animateButton(clearInputBtn);
}
function formatTime(seconds) {
const date = new Date(seconds * 1000);
const hours = date.getUTCHours().toString().padStart(2, '0');
const minutes = date.getUTCMinutes().toString().padStart(2, '0');
const secs = date.getUTCSeconds().toString().padStart(2, '0');
const ms = date.getUTCMilliseconds().toString().padStart(3, '0').substring(0, 3);
return `${hours}:${minutes}:${secs},${ms}`;
}
function formatBlockSRT(counter, start, text) {
return `${counter}\n${formatTime(start)} --> ${formatTime(start + parseInt(blockDuration.value))}\n${text.trim()}\n\n`;
}
function convertToSRT() {
const text = textoInput.value.trim();
if (!text) {
showAlert('Por favor, insira algum texto para converter.', 'error');
return;
}
animateButton(convertBtn);
// Simulate processing delay for better UX
convertBtn.disabled = true;
convertBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Processando...';
setTimeout(() => {
try {
const srtBlocks = [];
let counter = 1;
let startTime = 0;
const intervalBetweenBlocks = 5; // seconds between blocks
const maxChars = parseInt(charsPerBlock.value) || 490;
const duration = parseInt(blockDuration.value) || 50;
const shouldEndOnPeriod = endOnPeriod.checked;
if (shouldEndOnPeriod) {
// Split text into sentences ending with periods
const sentences = splitIntoSentences(text);
// Group sentences into blocks respecting maxChars
let currentBlock = '';
let currentBlockLength = 0;
for (const sentence of sentences) {
if (currentBlockLength + sentence.length <= maxChars) {
currentBlock += sentence + ' ';
currentBlockLength += sentence.length + 1;
} else {
if (currentBlock.trim()) {
srtBlocks.push({
counter,
startTime,
text: currentBlock.trim(),
html: formatBlockSRT(counter, startTime, currentBlock.trim())
});
counter++;
startTime += duration + intervalBetweenBlocks;
}
currentBlock = sentence + ' ';
currentBlockLength = sentence.length + 1;
}
}
// Add the last block if there's any content
if (currentBlock.trim()) {
srtBlocks.push({
counter,
startTime,
text: currentBlock.trim(),
html: formatBlockSRT(counter, startTime, currentBlock.trim())
});
}
} else {
// Original behavior - split by words respecting maxChars
const words = text.split(/\s+/);
let block = '';
let blockLength = 0;
for (const word of words) {
if (blockLength + word.length <= maxChars) {
block += word + ' ';
blockLength += word.length + 1;
} else {
if (block.trim()) {
srtBlocks.push({
counter,
startTime,
text: block.trim(),
html: formatBlockSRT(counter, startTime, block.trim())
});
counter++;
startTime += duration + intervalBetweenBlocks;
}
block = word + ' ';
blockLength = word.length + 1;
}
}
// Add the last block if there's any content
if (block.trim()) {
srtBlocks.push({
counter,
startTime,
text: block.trim(),
html: formatBlockSRT(counter, startTime, block.trim())
});
}
}
displaySRTResults(srtBlocks, countWords(text));
} catch (error) {
console.error('Error during conversion:', error);
showAlert('Ocorreu um erro durante a conversão.', 'error');
} finally {
convertBtn.disabled = false;
convertBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Converter para SRT';
}
}, 500);
}
function splitIntoSentences(text) {
// This regex splits text into sentences ending with .!? followed by space or end of string
const sentenceRegex = /[^.!?]*[.!?](?:\s|$)/g;
const sentences = [];
let match;
while ((match = sentenceRegex.exec(text)) !== null) {
sentences.push(match[0].trim());
}
// Handle remaining text that doesn't end with punctuation
const remainingText = text.substring(sentenceRegex.lastIndex).trim();
if (remainingText) {
sentences.push(remainingText);
}
return sentences;
}
function displaySRTResults(blocks, totalWordCount) {
resultado.innerHTML = '';
blockCount.textContent = blocks.length;
totalWords.textContent = totalWordCount.toLocaleString();
// Calculate total duration
const lastBlock = blocks[blocks.length - 1];
const totalSecs = lastBlock.startTime + parseInt(blockDuration.value);
totalDuration.textContent = formatTime(totalSecs).split(',')[0];
blocks.forEach(block => {
const blockElement = document.createElement('div');
blockElement.className = `srt-block bg-white dark:bg-gray-700 p-4 rounded-lg shadow-sm ${fontSize.value}`;
blockElement.innerHTML = `
<div class="flex justify-between items-start mb-2">
<span class="font-mono text-sm text-gray-500 dark:text-gray-400">Bloco ${block.counter} | ${formatTime(block.startTime)}${formatTime(block.startTime + parseInt(blockDuration.value))} | ${countWords(block.text)} palavras</span>
<button class="copy-btn px-2 py-1 bg-gray-100 dark:bg-gray-600 text-gray-600 dark:text-gray-300 rounded text-sm hover:bg-gray-200 dark:hover:bg-gray-500 tooltip" data-text="${escapeHtml(block.text)}">
<i class="fas fa-copy"></i>
<span class="tooltip-text">Copiar bloco</span>
</button>
</div>
<div class="whitespace-pre-wrap dark:text-gray-300">${block.text}</div>
`;
resultado.appendChild(blockElement);
});
// Add copy events for each block
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', function() {
const text = this.getAttribute('data-text');
copyToClipboard(text);
showAlert('Bloco copiado!', 'success');
animateButton(this);
});
});
resultsSection.classList.remove('hidden');
window.scrollTo({
top: resultsSection.offsetTop - 20,
behavior: 'smooth'
});
}
function downloadSRT() {
const allBlocks = Array.from(document.querySelectorAll('.srt-block'));
let srtContent = '';
allBlocks.forEach((block, index) => {
const timeInfo = block.querySelector('span').textContent;
const text = block.querySelector('div:last-child').textContent;
srtContent += `${index + 1}\n${timeInfo.split(' | ')[1].replace(' → ', ' --> ')}\n${text}\n\n`;
});
const blob = new Blob([srtContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'legendas.srt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showAlert('Arquivo SRT baixado!', 'success');
animateButton(downloadBtn);
}
function copyAllSRT() {
const allBlocks = Array.from(document.querySelectorAll('.srt-block'));
let srtContent = '';
allBlocks.forEach((block, index) => {
const timeInfo = block.querySelector('span').textContent;
const text = block.querySelector('div:last-child').textContent;
srtContent += `${index + 1}\n${timeInfo.split(' | ')[1].replace(' → ', ' --> ')}\n${text}\n\n`;
});
copyToClipboard(srtContent);
showAlert('Todo o conteúdo SRT copiado!', 'success');
animateButton(copyAllBtn);
}
function changeFontSize() {
const blocks = document.querySelectorAll('.srt-block');
blocks.forEach(block => {
block.classList.remove('text-sm', 'text-base', 'text-lg');
block.classList.add(fontSize.value);
});
}
// Helper functions
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
function showAlert(message, type) {
const alert = document.createElement('div');
alert.className = `fixed top-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${
type === 'error' ? 'bg-red-500' : 'bg-green-500'
} animate-bounce`;
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => {
alert.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(alert);
}, 300);
}, 3000);
}
function animateButton(button) {
button.classList.add('animate-pulse');
setTimeout(() => {
button.classList.remove('animate-pulse');
}, 300);
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=hiojo/srt" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>