Aujourd'hui, vendredi, j'aimerais parler de l'un de mes projets favoris, des choses intéressantes que j'ai dû faire en travaillant dessus et des problÚmes que je ne pourrais pas résoudre pour son développement ultérieur.
Donc, j'avais pas mal de projets familiers Ă divers degrĂ©s de rĂ©alisation. Parmi eux: un rĂ©seau social pour les Ă©crivains, un gĂ©nĂ©rateur de sprite CSS, un robot Telegram pour la datation par centres d'intĂ©rĂȘt, et bien plus encore. Aujourd'hui, nous allons parler de mon dernier dĂ©veloppement.
Comme beaucoup de gens ces jours-ci, j'apprends l'anglais. Je pense que beaucoup de gens savent aussi qu'une approche efficace dans ce domaine est l'immersion maximale dans l'environnement. Interface téléphonique en anglais, notes dans un cahier en anglais, regarder des films en anglais avec sous-titres anglais. En regardant un film dans l'original, tÎt ou tard, il est nécessaire de traduire tel ou tel mot ou phrase qui scintille à l'écran toutes les quelques minutes. Sans eux, rien n'est clair du tout.
Idée de projet
J'ai donc eu l'idée d'un lecteur vidéo avec des sous-titres traduisibles. L'application vous permet de traduire des mots et des phrases entiÚres tout en regardant un film. Avec lui, il n'est pas nécessaire de basculer entre les applications ou de prendre un smartphone. Rencontrez LinguaPlayer .
Le schĂ©ma de travail est simple. L'utilisateur ouvre le fichier vidĂ©o et le fichier de sous-titres. Regarde le film comme d'habitude. Cependant, maintenant, en plus des raccourcis clavier standard, il a des touches pour traduire chaque mot sĂ©parĂ©ment, traduire des phrases entiĂšres, revenir d'une ligne Ă l'autre. Il y a aussi une traduction en passant le curseur de la souris sur des mots ou en mettant en Ă©vidence le morceau de texte souhaitĂ©. L'application est disponible pour Windows et MacOS. Tous les dĂ©tails peuvent ĂȘtre trouvĂ©s sur la page de l'application .
Pile technologique
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;
}
,
, MVP, proof of concept. . , -, Urban Dictionary , . , LinguaLeo Skyeng. . Anki. .
, , . , , . , â Chromium. , , H.264 FLAC MP3. , . â . , , .
Ainsi, le principal facteur de blocage actuellement est le contenu. Il devrait jouer sans aucun problÚme dans l'application, il devrait pouvoir l'obtenir facilement et rapidement, et aussi, il ne devrait pas violer les licences et les droits d'auteur. DÚs que le problÚme de contenu sera résolu, je continuerai volontiers à travailler sur le projet. En attendant, si quelqu'un est intéressé, vous pouvez télécharger et essayer la version conceptuelle de l'application.