From 79c12baa9aea47213417912fbf90f91ad5cd4766 Mon Sep 17 00:00:00 2001 From: lutangar Date: Tue, 23 Apr 2024 18:19:02 +0200 Subject: [PATCH] chore(test): wip ctranslate 2 adapat --- .../transcriber/whisper-ctranslate2.spec.ts | 46 ++++++++++++++++ .../transcriber/ctranslate2-transcriber.ts | 52 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/packages/tests/src/transcription/whisper/transcriber/whisper-ctranslate2.spec.ts b/packages/tests/src/transcription/whisper/transcriber/whisper-ctranslate2.spec.ts index d2b17a680..c1909d867 100644 --- a/packages/tests/src/transcription/whisper/transcriber/whisper-ctranslate2.spec.ts +++ b/packages/tests/src/transcription/whisper/transcriber/whisper-ctranslate2.spec.ts @@ -11,6 +11,7 @@ config.truncateThreshold = 0 describe('Whisper CTranslate2 transcriber', function () { const transcriptDirectory = join(root(), 'test-transcript') const shortVideoPath = buildAbsoluteFixturePath('video_short.mp4') + const frVideoPath = buildAbsoluteFixturePath('transcription/communiquer-lors-dune-classe-transplantee.mp4') const transcriber = new Ctranslate2Transcriber( { name: 'anyNameShouldBeFineReally', @@ -80,6 +81,51 @@ You `) }) + it('May transcribe a media file using a local CTranslate2 model', async function () { + const transcript = await transcriber.transcribe( + shortVideoPath, + { name: 'myLocalModel', path: buildAbsoluteFixturePath('transcription/tiny-ctranslate2.bin') }, + 'en', + 'txt' + ) + expect(transcript).to.deep.equals({ + path: join(transcriptDirectory, 'video_short.txt'), + language: 'en', + format: 'txt' + }) + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(existsSync(transcript.path), `Transcript file ${transcript.path} doesn't exist.`).to.be.true + expect(await readFile(transcript.path, 'utf8')).to.equal(`You +`) + }) + + it('May transcribe a media file in french', async function () { + this.timeout(45000) + const transcript = await transcriber.transcribe(frVideoPath, { name: 'tiny' }, 'fr', 'txt') + expect(transcript).to.deep.equals({ + path: join(transcriptDirectory, 'communiquer-lors-dune-classe-transplantee.txt'), + language: 'fr', + format: 'txt' + }) + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(existsSync(transcript.path), `Transcript file ${transcript.path} doesn't exist.`).to.be.true + expect(await readFile(transcript.path, 'utf8')).to.equal( + `Communiquez lors d'une classe transplante. Utilisez les photos prises lors de cette classe pour raconter quotidiennement le séjour vécu. +C'est le scénario P-Dagujic présenté par monsieur Navoli, professeur ainsi que le 3 sur une école alimentaire de Montpellier. +La première application a utilisé ce ralame déatec. L'enseignant va alors transférer les différentes photos réalisés lors de la classe transplante. +Dans un dossier, spécifique pour que les élèves puissent le retrouver plus facilement. Il téléverse donc ses photos dans le dossier, dans le venté, dans la médiatèque de la classe. +Pour terminer, il s'assure que le dossier soit bien ouvert aux utilisateurs afin que tout le monde puisse l'utiliser. +Les élèves par la suite utilisera le blog. A partir de leurs nantes, il pourront se loi de parposte rédigeant un article d'un reinté. +Ils illustront ses articles à l'aide des photos de que mon numérique mise à n'accélier dans le venté. +Pour se faire, il pourront utiliser les diteurs avancés qui les renvèrent directement dans la médiatèque de la classe où il pourront retrouver le dossier créé par leurs enseignants. +Une fois leur article terminée, les élèves soumétront se lui-ci au professeur qui pourra soit la noté pour correction ou le public. +Ensuite, il pourront lire et commenter ce de leurs camarades ou répondre aux commentaires de la veille. +` + ) + }) + it('Should produce the same transcript text as openai-whisper given the same parameters', async function () { const transcribeArguments: Parameters = [ shortVideoPath, diff --git a/packages/transcription/src/whisper/transcriber/ctranslate2-transcriber.ts b/packages/transcription/src/whisper/transcriber/ctranslate2-transcriber.ts index 0b98708b1..6b4b7835e 100644 --- a/packages/transcription/src/whisper/transcriber/ctranslate2-transcriber.ts +++ b/packages/transcription/src/whisper/transcriber/ctranslate2-transcriber.ts @@ -1,5 +1,57 @@ import { OpenaiTranscriber } from './openai-transcriber.js' +import { TranscriptionModel } from '../../transcription-model.js' +import { Transcript, TranscriptFormat } from '../../transcript.js' +import { $ } from 'execa' +import { getFileInfo } from '../../file-utils.js' +import { join } from 'path' +import { copyFile, rm } from 'node:fs/promises' +import { dirname, basename } from 'node:path' export class Ctranslate2Transcriber extends OpenaiTranscriber { + public static readonly MODEL_FILENAME = 'model.bin' + async transcribe ( + mediaFilePath: string, + model: TranscriptionModel = { name: 'tiny' }, + language: string = 'en', + format: TranscriptFormat = 'vtt' + ): Promise { + this.createPerformanceMark() + // Shall we run the command with `{ shell: true }` to get the same error as in sh ? + // ex: ENOENT => Command not found + const $$ = $({ verbose: true }) + const { baseName } = getFileInfo(mediaFilePath) + + let modelFilepath = model.path + const shouldCreateModelCopy = (model.path && basename(model.path) !== Ctranslate2Transcriber.MODEL_FILENAME) + if (shouldCreateModelCopy) { + modelFilepath = join(dirname(model.path), Ctranslate2Transcriber.MODEL_FILENAME) + await copyFile(model.path, modelFilepath) + } + + const modelArgs = model.path ? [ '--model_directory', dirname(model.path) ] : [ '--model', model.name ] + + await $$`${this.engine.binary} ${[ + mediaFilePath, + ...modelArgs, + '--output_format', + format, + '--output_dir', + this.transcriptDirectory, + '--language', + language + ]}` + + if (shouldCreateModelCopy) { + // await rm(modelFilepath) + } + + this.measurePerformanceMark() + + return { + language, + path: join(this.transcriptDirectory, `${baseName}.${format}`), + format + } + } }