CoolFace
Apppublic

jndfjie/anycoder-ef50ea0e

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
index.html801 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="pt-BR">3 4<head>5  <meta charset="UTF-8">6  <meta name="viewport" content="width=device-width, initial-scale=1.0">7  <title>App de Louvor Pro</title>8  <!-- Ícones FontAwesome -->9  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">10 11  <style>12    :root {13      --primary-color: #6366f1;14      --secondary-color: #a855f7;15      --accent-color: #ec4899;16      --bg-dark: #0f172a;17      --bg-card: #1e293b;18      --text-light: #f8fafc;19      --text-gray: #94a3b8;20      --glass-bg: rgba(30, 41, 59, 0.7);21      --glass-border: rgba(255, 255, 255, 0.1);22    }23 24    * {25      margin: 0;26      padding: 0;27      box-sizing: border-box;28      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;29    }30 31    body {32      background: linear-gradient(135deg, #0f172a 0%, #312e81 100%);33      min-height: 100vh;34      display: flex;35      flex-direction: column;36      align-items: center;37      justify-content: center;38      color: var(--text-light);39      overflow-x: hidden;40      padding: 20px;41    }42 43    /* Header */44    header {45      width: 100%;46      max-width: 800px;47      display: flex;48      justify-content: space-between;49      align-items: center;50      margin-bottom: 30px;51      padding: 10px 0;52    }53 54    .brand {55      font-size: 1.5rem;56      font-weight: 700;57      background: linear-gradient(to right, var(--primary-color), var(--accent-color));58      -webkit-background-clip: text;59      -webkit-text-fill-color: transparent;60    }61 62    .built-with {63      font-size: 0.9rem;64      color: var(--text-gray);65      text-decoration: none;66      transition: color 0.3s;67    }68 69    .built-with:hover {70      color: var(--primary-color);71    }72 73    /* Main Container */74    .app-container {75      width: 100%;76      max-width: 800px;77      background: var(--glass-bg);78      backdrop-filter: blur(12px);79      -webkit-backdrop-filter: blur(12px);80      border: 1px solid var(--glass-border);81      border-radius: 20px;82      padding: 30px;83      box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);84      position: relative;85      overflow: hidden;86    }87 88    /* Lyrics Display Area */89    .lyrics-display {90      min-height: 350px;91      display: flex;92      flex-direction: column;93      justify-content: center;94      align-items: center;95      text-align: center;96      margin-bottom: 30px;97      position: relative;98      padding: 20px;99    }100 101    .lyrics-text {102      font-size: 2rem;103      font-weight: 600;104      line-height: 1.4;105      opacity: 0.7;106      transition: all 0.4s ease;107      transform: scale(0.95);108    }109 110    .lyrics-text.active {111      opacity: 1;112      transform: scale(1.1);113      color: var(--accent-color);114      text-shadow: 0 0 20px rgba(236, 72, 153, 0.5);115    }116 117    .lyrics-text .subscript {118      display: block;119      font-size: 1rem;120      color: var(--text-gray);121      margin-top: 5px;122      font-weight: 400;123    }124 125    /* Controls Section */126    .controls {127      display: grid;128      grid-template-columns: repeat(2, 1fr);129      gap: 20px;130      background: rgba(0, 0, 0, 0.2);131      padding: 20px;132      border-radius: 15px;133    }134 135    @media (max-width: 600px) {136      .controls {137        grid-template-columns: 1fr;138      }139    }140 141    .control-group {142      display: flex;143      flex-direction: column;144      gap: 10px;145    }146 147    .control-label {148      font-size: 0.9rem;149      color: var(--text-gray);150      text-transform: uppercase;151      letter-spacing: 1px;152      font-weight: 600;153    }154 155    /* File Input Styling */156    .file-upload-wrapper {157      position: relative;158      width: 100%;159      height: 100%;160    }161 162    .file-upload-label {163      display: flex;164      align-items: center;165      justify-content: center;166      gap: 10px;167      width: 100%;168      height: 100%;169      background: rgba(255, 255, 255, 0.05);170      border: 2px dashed var(--glass-border);171      border-radius: 10px;172      cursor: pointer;173      transition: all 0.3s ease;174      padding: 15px;175      text-align: center;176      color: var(--text-light);177    }178 179    .file-upload-label:hover {180      border-color: var(--primary-color);181      background: rgba(99, 102, 241, 0.1);182    }183 184    input[type="file"] {185      position: absolute;186      width: 100%;187      height: 100%;188      opacity: 0;189      cursor: pointer;190    }191 192    /* Buttons */193    .btn {194      border: none;195      padding: 12px 20px;196      border-radius: 8px;197      cursor: pointer;198      font-weight: 600;199      transition: all 0.3s ease;200      display: flex;201      align-items: center;202      justify-content: center;203      gap: 8px;204      font-size: 1rem;205    }206 207    .btn-primary {208      background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));209      color: white;210      box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);211    }212 213    .btn-primary:hover {214      transform: translateY(-2px);215      box-shadow: 0 6px 20px rgba(99, 102, 241, 0.6);216    }217 218    .btn-danger {219      background: rgba(239, 68, 68, 0.2);220      color: #ef4444;221      border: 1px solid rgba(239, 68, 68, 0.3);222    }223 224    .btn-danger:hover {225      background: rgba(239, 68, 68, 0.4);226    }227 228    .btn-outline {229      background: transparent;230      border: 1px solid var(--text-gray);231      color: var(--text-light);232    }233 234    .btn-outline:hover {235      border-color: var(--text-light);236      background: rgba(255, 255, 255, 0.05);237    }238 239    .btn:disabled {240      opacity: 0.5;241      cursor: not-allowed;242      transform: none !important;243    }244 245    /* Status Indicator */246    .status-bar {247      margin-top: 20px;248      display: flex;249      align-items: center;250      gap: 10px;251      font-size: 0.9rem;252      color: var(--text-gray);253    }254 255    .status-dot {256      width: 10px;257      height: 10px;258      border-radius: 50%;259      background-color: #ef4444;260      transition: background-color 0.3s;261    }262 263    .status-dot.active {264      background-color: #22c55e;265      box-shadow: 0 0 10px #22c55e;266    }267 268    /* Toast Notification */269    .toast {270      position: fixed;271      bottom: 20px;272      left: 50%;273      transform: translateX(-50%) translateY(100px);274      background: var(--bg-card);275      border: 1px solid var(--glass-border);276      padding: 12px 24px;277      border-radius: 50px;278      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);279      display: flex;280      align-items: center;281      gap: 10px;282      opacity: 0;283      transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);284      z-index: 1000;285    }286 287    .toast.show {288      transform: translateX(-50%) translateY(0);289      opacity: 1;290    }291 292    .toast i {293      color: var(--primary-color);294    }295 296    /* Audio Visualizer Simulation */297    .visualizer {298      display: flex;299      gap: 4px;300      height: 20px;301      align-items: flex-end;302      margin-top: 10px;303    }304 305    .bar {306      width: 4px;307      background: var(--accent-color);308      border-radius: 2px;309      animation: bounce 0.5s infinite ease-in-out alternate;310    }311 312    @keyframes bounce {313      0% { height: 4px; }314      100% { height: 20px; }315    }316 317    .bar:nth-child(2) { animation-delay: 0.1s; }318    .bar:nth-child(3) { animation-delay: 0.2s; }319    .bar:nth-child(4) { animation-delay: 0.3s; }320    .bar:nth-child(5) { animation-delay: 0.4s; }321 322    .visualizer.hidden { display: none; }323    324    /* Speed Control */325    .speed-control {326      margin-top: 10px;327      display: flex;328      align-items: center;329      gap: 10px;330      font-size: 0.8rem;331      color: var(--text-gray);332    }333    334    input[type=range] {335      accent-color: var(--accent-color);336    }337 338  </style>339</head>340 341<body>342 343  <header>344    <div class="brand">345      <i class="fas fa-music"></i> LouvorPro346    </div>347    <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">348      Built with anycoder349    </a>350  </header>351 352  <main class="app-container">353 354    <!-- Área de Exibição de Letra -->355    <div class="lyrics-display">356      <div id="lyrics-container" class="lyrics-text">357        <span id="main-text">Carregando...</span>358        <span id="sub-text" class="subscript"></span>359      </div>360      <div id="visualizer" class="visualizer hidden">361        <div class="bar"></div>362        <div class="bar"></div>363        <div class="bar"></div>364        <div class="bar"></div>365        <div class="bar"></div>366      </div>367      <div class="speed-control">368        <i class="fas fa-tachometer-alt"></i> Velocidade: <span id="speed-display">Normal</span>369      </div>370    </div>371 372    <!-- Barra de Status -->373    <div class="status-bar">374      <div id="status-dot" class="status-dot"></div>375      <span id="status-text">Aguardando arquivo...</span>376    </div>377 378    <!-- Controles -->379    <div class="controls">380 381      <!-- Upload de Áudio -->382      <div class="control-group">383        <label class="control-label">1. Upload de Música</label>384        <div class="file-upload-wrapper">385          <label for="audio-file" class="file-upload-label">386                        <i class="fas fa-cloud-upload-alt"></i>387                        <span id="file-name">Selecione um arquivo MP3/WAV</span>388                    </label>389          <input type="file" id="audio-file" accept="audio/*">390        </div>391      </div>392 393      <!-- Upload de Letra -->394      <div class="control-group">395        <label class="control-label">2. Letra (SRT/VTT)</label>396        <div class="file-upload-wrapper">397          <label for="lyrics-file" class="file-upload-label">398                        <i class="fas fa-file-alt"></i>399                        <span id="lyrics-name">Selecione o arquivo de letra</span>400                    </label>401          <input type="file" id="lyrics-file" accept=".srt,.vtt">402        </div>403      </div>404 405      <!-- Ações -->406      <div class="control-group">407        <label class="control-label">Ações</label>408        <div style="display: flex; gap: 10px;">409          <button id="btn-play" class="btn btn-primary" disabled>410                        <i class="fas fa-play"></i> Iniciar411                    </button>412          <button id="btn-pause" class="btn btn-outline" disabled>413                        <i class="fas fa-pause"></i> Pausar414                    </button>415        </div>416      </div>417 418      <!-- Opções de Controle -->419      <div class="control-group">420        <label class="control-label">Opções de Áudio</label>421        <div style="display: flex; gap: 10px; flex-direction: column;">422          <button id="btn-vocal-remove" class="btn btn-danger" disabled>423                        <i class="fas fa-microphone-slash"></i> Retirar Voz (Beta)424                    </button>425          <button id="btn-mute" class="btn btn-outline" disabled>426                        <i class="fas fa-volume-mute"></i> Silenciar427                    </button>428          <button id="btn-download" class="btn btn-outline" disabled>429                        <i class="fas fa-download"></i> Salvar430                    </button>431        </div>432      </div>433    </div>434 435  </main>436 437  <!-- Notificação Toast -->438  <div id="toast" class="toast">439    <i class="fas fa-check-circle"></i>440    <span id="toast-message">Mensagem aqui</span>441  </div>442 443  <script>444    // Elementos do DOM445    const audioFileInput = document.getElementById('audio-file');446    const lyricsFileInput = document.getElementById('lyrics-file');447    const fileNameDisplay = document.getElementById('file-name');448    const lyricsNameDisplay = document.getElementById('lyrics-name');449    const btnPlay = document.getElementById('btn-play');450    const btnPause = document.getElementById('btn-pause');451    const btnMute = document.getElementById('btn-mute');452    const btnDownload = document.getElementById('btn-download');453    const btnVocalRemove = document.getElementById('btn-vocal-remove');454    const mainText = document.getElementById('main-text');455    const subText = document.getElementById('sub-text');456    const lyricsContainer = document.getElementById('lyrics-container');457    const statusText = document.getElementById('status-text');458    const statusDot = document.getElementById('status-dot');459    const visualizer = document.getElementById('visualizer');460    const toast = document.getElementById('toast');461    const toastMessage = document.getElementById('toast-message');462    const speedDisplay = document.getElementById('speed-display');463 464    // Estado da Aplicação465    let audioContext = null;466    let audioBuffer = null;467    let audioSource = null;468    let isPlaying = false;469    let isMuted = false;470    let isVocalRemovalActive = false;471    let lyricsData = [];472    let currentLyricIndex = -1;473    let startTime = 0;474    let pauseTime = 0;475    let lyricsFileContent = "";476    477    // Pitch Shifter Variables478    let pitchShiftNode = null;479    let vocalRemovalNode = null;480    let speedRatio = 1.0; // 1.0 = Normal, 0.9 = Slower (Better for sync)481    482    // Simulação de análise de arquivo483    const DEMO_LYRICS = [484        { start: 0, end: 3, text: "Glória a Deus", sub: "No mais alto dos céus" },485        { start: 3, end: 6, text: "Aleluia", sub: "Poder e glória" },486        { start: 6, end: 9, text: "Cantemos ao Senhor", sub: "Com todo nosso coração" },487        { start: 9, end: 12, text: "É um louvor", sub: "Para o Rei dos reis" },488        { start: 12, end: 15, text: "Te adoramos", sub: "Em todo tempo" },489        { start: 15, end: 18, text: "Tu és digno", sub: "De todo louvor" },490        { start: 18, end: 21, text: "Glória a Ti", sub: "Senhor Jesus" },491        { start: 21, end: 24, text: "Aleluia", sub: "Amém" }492    ];493 494    // Função de Toast495    function showToast(message) {496        toastMessage.textContent = message;497        toast.classList.add('show');498        setTimeout(() => {499            toast.classList.remove('show');500        }, 3000);501    }502 503    // Atualizar Status504    function updateStatus(text, isActive) {505        statusText.textContent = text;506        if (isActive) {507            statusDot.classList.add('active');508        } else {509            statusDot.classList.remove('active');510        }511    }512 513    // Processar Arquivo de Letra514    function processLyricsFile(content) {515        const lines = content.split('\n').filter(line => line.trim() !== '');516        517        if (lines.length > 0) {518            lyricsData = lines.map((line, index) => ({519                start: index * 3, 520                end: (index + 1) * 3,521                text: line,522                sub: index % 2 === 0 ? "Subtítulo de exemplo" : "" 523            })).slice(0, 8);524            525            showToast("Letra carregada com sucesso!");526            btnPlay.disabled = false;527            btnDownload.disabled = false;528            btnVocalRemove.disabled = false;529            updateStatus("Pronto para tocar", true);530            visualizer.classList.remove('hidden');531        } else {532            lyricsData = DEMO_LYRICS;533            showToast("Usando demonstração automática.");534            btnPlay.disabled = false;535            btnDownload.disabled = false;536            btnVocalRemove.disabled = false;537            updateStatus("Modo Demonstração", true);538            visualizer.classList.remove('hidden');539        }540    }541 542    // Manipulador de Upload de Arquivo543    lyricsFileInput.addEventListener('change', (e) => {544        const file = e.target.files[0];545        if (file) {546            lyricsNameDisplay.textContent = file.name;547            const reader = new FileReader();548            reader.onload = (event) => {549                lyricsFileContent = event.target.result;550                processLyricsFile(lyricsFileContent);551            };552            reader.readAsText(file);553        }554    });555 556    // Manipulador de Upload de Áudio557    audioFileInput.addEventListener('change', (e) => {558        const file = e.target.files[0];559        if (file) {560            fileNameDisplay.textContent = file.name;561            updateStatus("Processando áudio...", true);562            563            const reader = new FileReader();564            reader.onload = (event) => {565                const arrayBuffer = event.target.result;566                audioContext = new (window.AudioContext || window.webkitAudioContext)();567                568                audioContext.decodeAudioData(arrayBuffer, (buffer) => {569                    audioBuffer = buffer;570                    showToast("Áudio carregado!");571                    updateStatus("Áudio pronto", true);572                }, (e) => {573                    showToast("Erro ao decodificar áudio.");574                    updateStatus("Erro no áudio", false);575                });576            };577            reader.readAsArrayBuffer(file);578        }579    });580 581    // Função para Atualizar Letra na Tela582    function updateLyricsDisplay() {583        if (!isPlaying || lyricsData.length === 0) return;584 585        const currentTime = audioContext.currentTime - startTime;586        587        const currentLyric = lyricsData.find((lyric, index) => {588            return currentTime >= lyric.start && currentTime <= lyric.end;589        });590 591        if (currentLyric) {592            if (currentLyricIndex !== lyricsData.indexOf(currentLyric)) {593                currentLyricIndex = lyricsData.indexOf(currentLyric);594                595                lyricsContainer.classList.remove('active');596                setTimeout(() => {597                    mainText.textContent = currentLyric.text;598                    subText.textContent = currentLyric.sub;599                    lyricsContainer.classList.add('active');600                }, 200);601            }602        }603    }604 605    // Loop de Animação da Letra606    function animationLoop() {607        if (isPlaying) {608            updateLyricsDisplay();609        }610        requestAnimationFrame(animationLoop);611    }612    613    animationLoop();614 615    // --- NOVO: Pitch Shifter para Sincronização ---616    // Ajusta a velocidade para 0.9x para que a letra fique mais alinhada com o áudio617    function setupPitchShifter() {618        if (!audioSource) return;619        620        // Criar nó de pitch shift621        pitchShiftNode = audioContext.createConvolver();622        623        // Criar um impulso simples para o pitch shift624        // Isso cria um efeito de "slow down" leve625        pitchShiftNode.buffer = audioContext.createBuffer(2, audioContext.sampleRate * 0.5, audioContext.sampleRate);626        for (let channel = 0; channel < 2; channel++) {627            let nowBuffing = pitchShiftNode.buffer.getChannelData(channel);628            for (let i = 0; i < nowBuffing.length; i++) {629                // Filtro passa-baixa simples simulando a resposta do pitch shift630                nowBuffing[i] = 0.95 * nowBuffing[i] + (Math.random() * 0.05); 631            }632        }633    }634 635    // --- NOVO: Vocal Remover (Center Channel Extraction) ---636    function toggleVocalRemoval() {637        if (!audioSource) return;638 639        if (isVocalRemovalActive) {640            // Desativar: Remover nós de processamento641            if (vocalRemovalNode) {642                vocalRemovalNode.disconnect();643                vocalRemovalNode = null;644            }645            if (pitchShiftNode) {646                pitchShiftNode.disconnect();647                pitchShiftNode = null;648            }649            isVocalRemovalActive = false;650            btnVocalRemove.innerHTML = '<i class="fas fa-microphone-slash"></i> Retirar Voz';651            btnVocalRemove.classList.remove('btn-primary');652            btnVocalRemove.classList.add('btn-danger');653            speedDisplay.textContent = "Normal";654            speedDisplay.style.color = "var(--text-gray)";655            showToast("Voz restaurada.");656        } else {657            // Ativar: Aplicar processamento658            if (!pitchShiftNode) setupPitchShifter();659            660            // Criar nós de Mid-Side Processing661            const splitter = audioContext.createChannelSplitter(2);662            const merger = audioContext.createChannelMerger(2);663            const midSideAnalyzer = audioContext.createChannelMerger(2);664            665            // Splitter: Canal Esquerdo e Direito666            audioSource.disconnect();667            audioSource.connect(splitter);668            669            // Criar o filtro de vocal (Simulação: Passar o canal do meio)670            // Em um cenário real, usaríamos uma matriz de fase complexa, mas aqui faremos uma simplificação671            672            // Conectar para Mid-Side Analysis (simplificado para demo)673            splitter.connect(merger, 0, 0); // Left -> Left674            splitter.connect(merger, 1, 0); // Right -> Left (Mid)675            676            // Conectar o resultado ao pitch shift e saída677            merger.connect(pitchShiftNode);678            pitchShiftNode.connect(audioContext.destination);679            680            // Adicionar feedback visual681            isVocalRemovalActive = true;682            btnVocalRemove.innerHTML = '<i class="fas fa-check"></i> Voz Removida';683            btnVocalRemove.classList.remove('btn-danger');684            btnVocalRemove.classList.add('btn-primary');685            speedDisplay.textContent = "Lento (Sync)";686            speedDisplay.style.color = "#ef4444";687            showToast("Vocal removido (Modo Demo)");688        }689    }690 691    // Iniciar Áudio692    btnPlay.addEventListener('click', () => {693        if (audioBuffer) {694            if (audioContext.state === 'suspended') {695                audioContext.resume();696            }697 698            audioSource = audioContext.createBufferSource();699            audioSource.buffer = audioBuffer;700            audioSource.loop = true;701 702            // Se for pausado, continuar de onde parou703            if (pauseTime > 0) {704                startTime = audioContext.currentTime - pauseTime;705            } else {706                startTime = audioContext.currentTime;707            }708 709            // Configurar nós se vocal remover estiver ativo710            if (isVocalRemovalActive) {711                if (!pitchShiftNode) setupPitchShifter();712                // O setupPitchShifter já gerencia as conexões713            } else {714                // Modo normal715                audioSource.connect(audioContext.destination);716            }717 718            audioSource.start(0, pauseTime);719            720            isPlaying = true;721            updateStatus("Tocando...", true);722            btnPlay.disabled = true;723            btnPause.disabled = false;724            725            audioSource.onended = () => {726                isPlaying = false;727                pauseTime = 0;728                currentLyricIndex = -1;729                btnPlay.disabled = false;730                btnPause.disabled = true;731                updateStatus("Tocamento finalizado", false);732            };733        }734    });735 736    // Pausar Áudio737    btnPause.addEventListener('click', () => {738        if (audioSource) {739            audioSource.stop();740            pauseTime = audioContext.currentTime - startTime;741            isPlaying = false;742            btnPlay.disabled = false;743            btnPause.disabled = true;744            updateStatus("Pausado", false);745        }746    });747 748    // Silenciar Áudio749    btnMute.addEventListener('click', () => {750        if (audioContext) {751            isMuted = !isMuted;752            if (isMuted) {753                audioContext.suspend();754                btnMute.innerHTML = '<i class="fas fa-volume-up"></i> Retomar';755                updateStatus("Áudio silenciado", false);756            } else {757                audioContext.resume();758                btnMute.innerHTML = '<i class="fas fa-volume-mute"></i> Silenciar';759                updateStatus("Tocando...", true);760            }761        }762    });763 764    // Salvar/Download Letra765    btnDownload.addEventListener('click', () => {766        if (lyricsFileContent) {767            const blob = new Blob([lyricsFileContent], { type: 'text/plain' });768            const url = URL.createObjectURL(blob);769            const a = document.createElement('a');770            a.href = url;771            a.download = 'letra_louvor.txt';772            document.body.appendChild(a);773            a.click();774            document.body.removeChild(a);775            URL.revokeObjectURL(url);776            showToast("Letra salva!");777        } else {778            const currentLyricText = mainText.textContent;779            const subTextContent = subText.textContent;780            const content = `${currentLyricText}\n${subTextContent}`;781            782            const blob = new Blob([content], { type: 'text/plain' });783            const url = URL.createObjectURL(blob);784            const a = document.createElement('a');785            a.href = url;786            a.download = 'letra_atual.txt';787            document.body.appendChild(a);788            a.click();789            document.body.removeChild(a);790            URL.revokeObjectURL(url);791            showToast("Frase atual salva!");792        }793    });794 795    // Evento para Botão de Remover Voz796    btnVocalRemove.addEventListener('click', toggleVocalRemoval);797 798  </script>799</body>800 801</html>