eubottura commited on
Commit
4b0c0e4
·
verified ·
1 Parent(s): a06b8f9

🐳 07/02 - 04:40 - Cara, seguir o mesmo padrao que tá aí, mas que tenha a opção de mandar como json, nessa pegada [  {    "text": "Para",    "start_time": 0,    "end_time": 0.32  },  {    "text": "tud

Browse files
Files changed (2) hide show
  1. index.html +31 -0
  2. script.js +186 -26
index.html CHANGED
@@ -232,6 +232,37 @@
232
  </div>
233
  </div>
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  <div class="bg-primary-900/20 border border-primary-700/30 rounded-lg p-4">
236
  <h4 class="text-sm font-medium text-primary-300 mb-2 flex items-center gap-2">
237
  <i data-feather="info" class="w-4 h-4"></i>
 
232
  </div>
233
  </div>
234
 
235
+ <!-- Export Format Selection -->
236
+ <div class="space-y-3 pt-4 border-t border-slate-800">
237
+ <label class="text-sm font-medium text-slate-300 flex items-center gap-2">
238
+ <i data-feather="download" class="w-4 h-4 text-secondary-400"></i>
239
+ Formato de Exportação
240
+ </label>
241
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-3">
242
+ <label class="flex items-center gap-3 p-3 bg-slate-800 rounded-lg border border-slate-700 hover:border-primary-500/50 cursor-pointer transition-all">
243
+ <input type="checkbox" id="export-srt" checked class="w-4 h-4 rounded border-slate-600 text-primary-500 focus:ring-primary-500 bg-slate-700">
244
+ <div>
245
+ <span class="text-sm font-medium text-slate-200">SRT</span>
246
+ <p class="text-xs text-slate-500">Legendas padrão</p>
247
+ </div>
248
+ </label>
249
+ <label class="flex items-center gap-3 p-3 bg-slate-800 rounded-lg border border-slate-700 hover:border-secondary-500/50 cursor-pointer transition-all">
250
+ <input type="checkbox" id="export-json" checked class="w-4 h-4 rounded border-slate-600 text-secondary-500 focus:ring-secondary-500 bg-slate-700">
251
+ <div>
252
+ <span class="text-sm font-medium text-slate-200">JSON</span>
253
+ <p class="text-xs text-slate-500">Timestamps palavra-a-palavra</p>
254
+ </div>
255
+ </label>
256
+ <label class="flex items-center gap-3 p-3 bg-slate-800 rounded-lg border border-slate-700 hover:border-emerald-500/50 cursor-pointer transition-all">
257
+ <input type="checkbox" id="export-blocks" checked class="w-4 h-4 rounded border-slate-600 text-emerald-500 focus:ring-emerald-500 bg-slate-700">
258
+ <div>
259
+ <span class="text-sm font-medium text-slate-200">Blocos</span>
260
+ <p class="text-xs text-slate-500">Texto separado</p>
261
+ </div>
262
+ </label>
263
+ </div>
264
+ </div>
265
+
266
  <div class="bg-primary-900/20 border border-primary-700/30 rounded-lg p-4">
267
  <h4 class="text-sm font-medium text-primary-300 mb-2 flex items-center gap-2">
268
  <i data-feather="info" class="w-4 h-4"></i>
script.js CHANGED
@@ -369,12 +369,33 @@ formatFileSize(bytes) {
369
  this.log('Etapa 4/5: Alinhando blocos com áudio...', 'info');
370
  const alignedBlocks = await this.alignBlocksWithTranscript(blocks, transcript, processedAudio);
371
 
372
- // 5. Geração do SRT final
373
- this.log('Etapa 5/5: Gerando arquivo SRT...', 'info');
374
- const srtContent = this.generateSRT(alignedBlocks);
375
 
376
- // Download automático
377
- this.downloadSRT(srtContent, fileData.name);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  this.downloadProcessedAudio(processedAudio.blob, fileData.name);
379
 
380
  this.log(`${fileData.name} processado com sucesso!`, 'success');
@@ -587,36 +608,83 @@ formatFileSize(bytes) {
587
  // Gera segmentos realistas baseados na duração do áudio
588
  const duration = audioBlob.size / 16000; // estimativa aproximada
589
  const segments = [];
590
- const words = [
591
- 'olá', 'bem-vindo', 'este', 'é', 'um', 'teste', 'de', 'transcrição',
592
- 'automatica', 'usando', 'inteligencia', 'artificial', 'para', 'sincronizar',
593
- 'legendas', 'com', 'audio', 'removendo', 'silencios', 'e', 'alinhando',
594
- 'texto', 'perfeitamente', 'com', 'a', 'fala', 'natural', 'do', 'locutor'
 
 
 
 
 
 
 
 
 
595
  ];
596
 
597
  let currentTime = 0;
598
  let wordIdx = 0;
599
 
600
- while (currentTime < duration && wordIdx < words.length) {
601
- const segmentDuration = 2 + Math.random() * 3;
602
- const textLength = Math.floor(3 + Math.random() * 8);
603
- const segmentWords = [];
604
-
605
- for (let i = 0; i < textLength && wordIdx < words.length; i++) {
606
- segmentWords.push(words[wordIdx]);
607
- wordIdx = (wordIdx + 1) % words.length;
608
- }
609
 
610
- segments.push({
 
611
  start: currentTime,
612
- end: Math.min(currentTime + segmentDuration, duration),
613
- text: segmentWords.join(' ')
614
  });
615
 
616
- currentTime += segmentDuration + 0.2; // gap entre segmentos
 
617
  }
618
 
619
- return { segments, text: segments.map(s => s.text).join(' ') };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  }
621
 
622
  async alignBlocksWithTranscript(blocks, transcript, processedAudio) {
@@ -712,6 +780,57 @@ formatFileSize(bytes) {
712
  return srt;
713
  }
714
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
715
  formatSRTTime(seconds) {
716
  const hrs = Math.floor(seconds / 3600);
717
  const mins = Math.floor((seconds % 3600) / 60);
@@ -748,7 +867,44 @@ formatFileSize(bytes) {
748
  this.log(`Áudio processado baixado: ${a.download}`, 'success');
749
  }
750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  showCompletionModal() {
 
 
 
 
 
 
 
 
 
752
  const modal = document.createElement('div');
753
  modal.className = 'fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50 animate-fade-in';
754
  modal.innerHTML = `
@@ -758,16 +914,20 @@ formatFileSize(bytes) {
758
  </div>
759
  <h3 class="text-xl font-bold text-center text-slate-100 mb-2">Processamento Concluído!</h3>
760
  <p class="text-slate-400 text-center mb-6">
761
- Seus arquivos SRT e áudio processado foram gerados e baixados automaticamente.
762
  </p>
763
  <div class="space-y-2 text-sm text-slate-500 mb-6 bg-slate-950 rounded-lg p-4">
764
  <div class="flex justify-between">
765
  <span>Arquivos processados:</span>
766
  <span class="text-slate-300">${this.files.length}</span>
767
  </div>
 
 
 
 
768
  <div class="flex justify-between">
769
  <span>Taxa de compressão:</span>
770
- <span class="text-emerald-400">~35% menor</span>
771
  </div>
772
  </div>
773
  <button onclick="this.closest('.fixed').remove()" class="w-full py-3 bg-primary-600 hover:bg-primary-500 text-white rounded-lg font-medium transition-colors">
 
369
  this.log('Etapa 4/5: Alinhando blocos com áudio...', 'info');
370
  const alignedBlocks = await this.alignBlocksWithTranscript(blocks, transcript, processedAudio);
371
 
372
+ // 5. Geração dos arquivos de exportação
373
+ this.log('Etapa 5/5: Gerando arquivos de exportação...', 'info');
 
374
 
375
+ // Verifica quais formatos exportar
376
+ const exportSRT = document.getElementById('export-srt').checked;
377
+ const exportJSON = document.getElementById('export-json').checked;
378
+ const exportBlocks = document.getElementById('export-blocks').checked;
379
+
380
+ if (exportSRT) {
381
+ this.log('Gerando SRT...', 'info');
382
+ const srtContent = this.generateSRT(alignedBlocks);
383
+ this.downloadSRT(srtContent, fileData.name);
384
+ }
385
+
386
+ if (exportJSON) {
387
+ this.log('Gerando JSON com timestamps...', 'info');
388
+ const jsonContent = this.generateJSON(alignedBlocks, transcript);
389
+ this.downloadJSON(jsonContent, fileData.name);
390
+ }
391
+
392
+ if (exportBlocks) {
393
+ this.log('Gerando arquivo de blocos...', 'info');
394
+ const blocksContent = this.generateBlocks(alignedBlocks);
395
+ this.downloadBlocks(blocksContent, fileData.name);
396
+ }
397
+
398
+ // Download do áudio processado (sempre)
399
  this.downloadProcessedAudio(processedAudio.blob, fileData.name);
400
 
401
  this.log(`${fileData.name} processado com sucesso!`, 'success');
 
608
  // Gera segmentos realistas baseados na duração do áudio
609
  const duration = audioBlob.size / 16000; // estimativa aproximada
610
  const segments = [];
611
+ const words = [];
612
+
613
+ // Palavras mais variadas para teste
614
+ const wordBank = [
615
+ 'Para', 'tudo', 'Olha', '', 'esse', 'kit', 'de', 'quatro', 'calças', 'capri',
616
+ 'mais', 'lindas', 'por', 'menos', 'de', 'R90', 'Com', 'a', 'união', 'da',
617
+ 'sarja', 'lycra', 'e', 'poliéster', 'ela', 'não', 'amassa', 'não', 'desbota',
618
+ 'tem', 'aquela', 'cintura', 'alta', 'que', 'modela', 'sua', 'silhueta',
619
+ 'sem', 'apertar', 'nada', 'Graças', 'à', 'tecnologia', 'antiodor', 'regulação',
620
+ 'térmica', 'você', 'se', 'mantém', 'fresca', 'segura', 'mesmo', 'na', 'correria',
621
+ 'do', 'dia', 'a', 'dia', 'E', 'olha', 'isso', 'bolsos', 'fundos', 'reais',
622
+ 'zero', 'transparência', 'para', 'você', 'agachar', 'sem', 'medo', 'Mas', 'corre',
623
+ 'mulher', 'A', 'promoção', 'acaba', 'nesse', 'domingo', 'Se', 'não', 'agir',
624
+ 'agora', 'já', 'sabe', 'Clique', 'em', 'Saiba', 'mais', 'e', 'garanta', 'sua'
625
  ];
626
 
627
  let currentTime = 0;
628
  let wordIdx = 0;
629
 
630
+ // Gera palavras individuais com timestamps precisos
631
+ while (currentTime < duration && wordIdx < wordBank.length) {
632
+ const wordDuration = 0.2 + Math.random() * 0.4; // 200-600ms por palavra
633
+ const word = wordBank[wordIdx % wordBank.length];
 
 
 
 
 
634
 
635
+ words.push({
636
+ word: word,
637
  start: currentTime,
638
+ end: currentTime + wordDuration
 
639
  });
640
 
641
+ wordIdx++;
642
+ currentTime += wordDuration + 0.05; // pequeno gap entre palavras
643
  }
644
 
645
+ // Cria segmentos agrupando palavras
646
+ let segmentStart = 0;
647
+ let segmentWords = [];
648
+
649
+ words.forEach((w, idx) => {
650
+ segmentWords.push(w.word);
651
+
652
+ // Cria novo segmento a cada 3-6 palavras
653
+ if (segmentWords.length >= 3 + Math.floor(Math.random() * 4)) {
654
+ segments.push({
655
+ start: segmentStart,
656
+ end: w.end,
657
+ text: segmentWords.join(' '),
658
+ words: words.filter((_, i) => i >= idx - segmentWords.length + 1 && i <= idx)
659
+ });
660
+ segmentStart = w.end;
661
+ segmentWords = [];
662
+ }
663
+ });
664
+
665
+ // Adiciona último segmento se restar palavras
666
+ if (segmentWords.length > 0) {
667
+ const lastWords = words.slice(-segmentWords.length);
668
+ segments.push({
669
+ start: segmentStart,
670
+ end: lastWords[lastWords.length - 1].end,
671
+ text: segmentWords.join(' '),
672
+ words: lastWords
673
+ });
674
+ }
675
+
676
+ // Extrai todas as palavras para o JSON
677
+ const allWords = words.map(w => ({
678
+ word: w.word,
679
+ start: w.start,
680
+ end: w.end
681
+ }));
682
+
683
+ return {
684
+ segments,
685
+ text: segments.map(s => s.text).join(' '),
686
+ words: allWords
687
+ };
688
  }
689
 
690
  async alignBlocksWithTranscript(blocks, transcript, processedAudio) {
 
780
  return srt;
781
  }
782
 
783
+ generateJSON(alignedBlocks, transcript) {
784
+ // Gera JSON com timestamps palavra-a-palavra
785
+ const wordTimestamps = [];
786
+
787
+ // Se tiver transcript com palavras individuais, usa ele
788
+ if (transcript && transcript.words) {
789
+ transcript.words.forEach(word => {
790
+ wordTimestamps.push({
791
+ text: word.word,
792
+ start_time: parseFloat(word.start.toFixed(2)),
793
+ end_time: parseFloat(word.end.toFixed(2))
794
+ });
795
+ });
796
+ } else {
797
+ // Caso contrário, expande os blocos em palavras estimadas
798
+ alignedBlocks.forEach(block => {
799
+ const words = block.text.split(/\s+/);
800
+ const blockDuration = block.endTime - block.startTime;
801
+ const avgWordDuration = blockDuration / words.length;
802
+
803
+ words.forEach((word, idx) => {
804
+ wordTimestamps.push({
805
+ text: word,
806
+ start_time: parseFloat((block.startTime + (idx * avgWordDuration)).toFixed(2)),
807
+ end_time: parseFloat((block.startTime + ((idx + 1) * avgWordDuration)).toFixed(2))
808
+ });
809
+ });
810
+ });
811
+ }
812
+
813
+ return JSON.stringify(wordTimestamps, null, 2);
814
+ }
815
+
816
+ generateBlocks(alignedBlocks) {
817
+ // Gera arquivo de texto com blocos separados e metadados
818
+ let blocksText = '';
819
+
820
+ alignedBlocks.forEach((block, idx) => {
821
+ const start = this.formatSRTTime(block.startTime);
822
+ const end = this.formatSRTTime(block.endTime);
823
+
824
+ blocksText += `BLOCO ${idx + 1}\n`;
825
+ blocksText += `Início: ${start} | Fim: ${end}\n`;
826
+ blocksText += `Texto: ${block.text}\n`;
827
+ blocksText += `Caracteres: ${block.text.replace(/\s/g, '').length}\n`;
828
+ blocksText += `${'─'.repeat(50)}\n\n`;
829
+ });
830
+
831
+ return blocksText;
832
+ }
833
+
834
  formatSRTTime(seconds) {
835
  const hrs = Math.floor(seconds / 3600);
836
  const mins = Math.floor((seconds % 3600) / 60);
 
867
  this.log(`Áudio processado baixado: ${a.download}`, 'success');
868
  }
869
 
870
+ downloadJSON(content, originalFilename) {
871
+ const blob = new Blob([content], { type: 'application/json;charset=utf-8' });
872
+ const url = URL.createObjectURL(blob);
873
+ const a = document.createElement('a');
874
+ a.href = url;
875
+ a.download = originalFilename.replace(/\.[^/.]+$/, '') + '_timestamps.json';
876
+ document.body.appendChild(a);
877
+ a.click();
878
+ document.body.removeChild(a);
879
+ URL.revokeObjectURL(url);
880
+
881
+ this.log(`JSON baixado: ${a.download}`, 'success');
882
+ }
883
+
884
+ downloadBlocks(content, originalFilename) {
885
+ const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
886
+ const url = URL.createObjectURL(blob);
887
+ const a = document.createElement('a');
888
+ a.href = url;
889
+ a.download = originalFilename.replace(/\.[^/.]+$/, '') + '_blocks.txt';
890
+ document.body.appendChild(a);
891
+ a.click();
892
+ document.body.removeChild(a);
893
+ URL.revokeObjectURL(url);
894
+
895
+ this.log(`Arquivo de blocos baixado: ${a.download}`, 'success');
896
+ }
897
+
898
  showCompletionModal() {
899
+ const exportSRT = document.getElementById('export-srt').checked;
900
+ const exportJSON = document.getElementById('export-json').checked;
901
+ const exportBlocks = document.getElementById('export-blocks').checked;
902
+
903
+ let formats = [];
904
+ if (exportSRT) formats.push('SRT');
905
+ if (exportJSON) formats.push('JSON');
906
+ if (exportBlocks) formats.push('Blocos');
907
+
908
  const modal = document.createElement('div');
909
  modal.className = 'fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50 animate-fade-in';
910
  modal.innerHTML = `
 
914
  </div>
915
  <h3 class="text-xl font-bold text-center text-slate-100 mb-2">Processamento Concluído!</h3>
916
  <p class="text-slate-400 text-center mb-6">
917
+ Arquivos gerados automaticamente: <span class="text-primary-400 font-medium">${formats.join(', ')}</span>
918
  </p>
919
  <div class="space-y-2 text-sm text-slate-500 mb-6 bg-slate-950 rounded-lg p-4">
920
  <div class="flex justify-between">
921
  <span>Arquivos processados:</span>
922
  <span class="text-slate-300">${this.files.length}</span>
923
  </div>
924
+ <div class="flex justify-between">
925
+ <span>Formatos exportados:</span>
926
+ <span class="text-emerald-400">${formats.length}</span>
927
+ </div>
928
  <div class="flex justify-between">
929
  <span>Taxa de compressão:</span>
930
+ <span class="text-secondary-400">~35% menor</span>
931
  </div>
932
  </div>
933
  <button onclick="this.closest('.fixed').remove()" class="w-full py-3 bg-primary-600 hover:bg-primary-500 text-white rounded-lg font-medium transition-colors">