Meu animal de estimação é LinguaPlayer

Hoje, sexta-feira, gostaria de falar sobre um dos meus projetos favoritos, as coisas interessantes que tive de fazer enquanto trabalhava nele e que problemas não consegui resolver para o seu desenvolvimento posterior.





Então, eu tinha alguns projetos favoritos com vários graus de conclusão. Entre eles: uma rede social para escritores, um gerador de sprites CSS, um bot do Telegram para namoro por interesses e muito mais. Hoje vamos falar sobre meu último desenvolvimento.





Como muitas pessoas hoje em dia, estou aprendendo inglês. Acho que muitas pessoas também sabem que uma abordagem eficaz neste assunto é a máxima imersão no meio ambiente. Interface do telefone em inglês, anotações em notebook em inglês, assistir filmes em inglês com legendas em inglês. Assistindo a um filme no original, mais cedo ou mais tarde é necessário traduzir esta ou aquela palavra ou frase que pisca na tela a cada poucos minutos. Sem eles, nada é claro.





Ideia de projeto

Então, tive a ideia de um reprodutor de vídeo com legendas traduzíveis. O aplicativo permite que você traduza palavras e frases inteiras enquanto assiste a um filme. Com ele, não há necessidade de alternar entre aplicativos ou pegar um smartphone. Conheça LinguaPlayer .





O esquema de trabalho é simples. O usuário abre o arquivo de filme e o arquivo de legenda. Assiste ao filme normalmente. Porém, agora, além das teclas de atalho padrão, ele tem chaves para traduzir cada palavra separadamente, traduzir frases inteiras, retroceder de uma linha para outra. Também há uma tradução passando o cursor do mouse sobre as palavras ou destacando o trecho de texto desejado. O aplicativo está disponível para Windows e MacOS. Todos os detalhes podem ser encontrados na página de inscrição .





Pilha de tecnologia

Electron, . . Chromium, -. – . Visual Studio Code, Skype, Slack. Electron API, JavaScript, . . – , . JavaScript, Angular, jQuery, Vue – .





LinguaPlayer , : TypeScript, React, MobX, Webpack. , : , . . , , . . , DOM . , , , .





, . — srt- . – , .





 node-webvtt. « ». video- «timeupdate», . , «timeupdate» , . .





hash map. (, ), – , . :





{
	//    2 
	5: [1, 2]
	//    3  
	7: [3, 4, 5]
}
      
      



0 4 — . , , —  hash map. , . , , . 4 , . , :





//  :  ,    ( ),   , 
class Cue {
  public readonly index: number;
  public readonly startTime: number;
  public readonly endTime: number;
  public readonly text: string;

  constructor(index: number, startTime: number, endTime: number, text: string) {
    this.index = index;
    this.startTime = startTime;
    this.endTime = endTime;
    this.text = text;
  }
}

interface CueIndex {
  //      ( )     ,
  //        
  [key: number]: number[];
}

class SubtitlesTrack {
  private readonly cues: Cue[];
  private index: CueIndex = {};

  constructor(cues: Cue[]) {
    this.cues = cues;

    //       ,  
    this.indexCues();
  }

  private indexCues() {
    this.cues.forEach((cue: Cue) => {
      //               
      const startSecond = Math.floor(cue.startTime / 1000);
      const endSecond = Math.floor(cue.endTime / 1000);

      //   (  )  
      this.addToIndex(startSecond, cue);
      // ,      ,         
      //         
      if (endSecond !== startSecond) {
        this.addToIndex(endSecond, cue);
      }
    });
  }

  private addToIndex(secondNumber: number, cue: Cue): void {
    //       ,     
    if (!this.index[secondNumber]) {
      this.index[secondNumber] = [];
    }

    //         
    this.index[secondNumber].push(cue.index);
  }

  //   
  public findCueForTime(timeInSeconds: number): Cue|null {
    //   timeupdate     
    //     
    const flooredTime = Math.floor(timeInSeconds);
    //      
    const cues = this.index[flooredTime];
    let currentCue = null;

    //      
    if (cues) {
      //   
      for (let index of cues) {
        const cue = this.cues[index];

        //  ,             
        if (this.isCueInTime(timeInSeconds, cue)) {
          //  ,        
          currentCue = cue;
          break;
        }
      }
    }

    //     null,      
    return currentCue;
  }

  public isCueInTime(timeInSeconds: number, cue: Cue): boolean {
    const timeInMilliseconds: number = timeInSeconds * 1000;

    return timeInMilliseconds >= cue.startTime && timeInMilliseconds <= cue.endTime;
  }
}
      
      



, , 4 , , 1 4.





 node-sentence-tokenizer. div sentence word , . :





import Tokenizer from 'sentence-tokenizer';

function formatCue(text: string): string {
  const brMark: string = '[br]';
  const tokenizer = new Tokenizer();

  //            
  text = text
    .replace(/\r\n/g, ` ${brMark}`)
    .replace(/\r/g, ` ${brMark}`)
    .replace(/\n/g, ` ${brMark}`);

  //  text    
  tokenizer.setEntry(text);

  //    
  const sentenceTokens: string[] = tokenizer.getSentences();
  //   
  const sentencesHtml: string[] = sentenceTokens.map((sentenceToken: string, index: number) => {
    //    
    const wordTokens: string[] = tokenizer.getTokens(index);
    //    
    const wordsHtml: string[] = wordTokens.map((wordToken: string) => {
      let brTag: string = '';

      //      ,     html   
      if (wordToken.includes(brMark)) {
        wordToken = wordToken.replace(brMark, '');
        brTag = '<br/>';
      }

      //    span   word    br,  
      return `${brTag}<span class="word">${wordToken}</span>`;
    });

    //   ,  ,     span   sentence
    return `<span class="sentence">${wordsHtml.join(' ')}</span>`;
  });

  //     
  const html: string = sentencesHtml.join(' ');

  return html;
}
      
      



 Microsoft Translator  , .





,

, MVP, proof of concept. . , -, Urban Dictionary , . ,  LinguaLeo  Skyeng. .  Anki. .





, , . , , . , – Chromium.   , , H.264 FLAC MP3. , . – . , , .





Portanto, o principal fator de bloqueio agora é o conteúdo. Deve jogar sem problemas no aplicativo, deve ser capaz de obtê-lo de forma fácil e rápida, e também, não deve violar licenças e direitos autorais. Assim que o problema de conteúdo for resolvido, terei o maior prazer em continuar trabalhando no projeto. Nesse ínterim, se alguém estiver interessado, você pode  baixar e experimentar a  versão conceitual do aplicativo.








All Articles