Mi mascota es LinguaPlayer

Hoy, viernes, me gustaría hablar sobre uno de mis proyectos favoritos, qué cosas interesantes tuve que hacer mientras trabajaba en él y qué problemas no pude resolver para su posterior desarrollo.





Entonces, tuve bastantes proyectos favoritos de diversos grados de finalización. Entre ellos: una red social para escritores, un generador de sprites CSS, un bot de Telegram para citas por intereses y mucho más. Hoy hablaremos de mi último desarrollo.





Como mucha gente en estos días, estoy aprendiendo inglés. Creo que mucha gente también sabe que un enfoque eficaz en esta materia es la máxima inmersión en el medio ambiente. Interfaz de teléfono en inglés, notas en un cuaderno en inglés, ver películas en inglés con subtítulos en inglés. Al ver una película en el original, tarde o temprano es necesario traducir esta o aquella palabra o frase que parpadea en la pantalla cada pocos minutos. Sin ellos, nada está claro en absoluto.





Idea de proyecto

Entonces se me ocurrió la idea de un reproductor de video con subtítulos traducibles. La aplicación te permite traducir palabras y frases completas mientras miras una película. Con él, no es necesario cambiar de aplicación o utilizar un teléfono inteligente. Conoce LinguaPlayer .





El esquema de trabajo es simple. El usuario abre el archivo de película y el archivo de subtítulos. Mira la película como de costumbre. Sin embargo, ahora, además de las teclas de acceso rápido estándar, tiene teclas para traducir cada palabra por separado, traducir oraciones completas, rebobinar de una línea a otra. También hay una traducción colocando el cursor del mouse sobre las palabras o resaltando el fragmento de texto deseado. La aplicación está disponible para Windows y MacOS. Todos los detalles se pueden encontrar en la página de la aplicación .





Pila de tecnología

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. , . – . , , .





Por tanto, el principal factor de bloqueo en este momento es el contenido. Debería jugar sin problemas en la aplicación, debería poder obtenerla fácil y rápidamente, y además, no debería violar licencias y derechos de autor. Tan pronto como se resuelva el problema del contenido, continuaré felizmente trabajando en el proyecto. Mientras tanto, si alguien está interesado, puede  descargar y probar la  versión conceptual de la aplicación.








All Articles