@tricoteuses/transcription-videos 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LICENSE.md +25 -0
  2. package/README.md +275 -0
  3. package/package.json +70 -0
package/LICENSE.md ADDED
@@ -0,0 +1,25 @@
1
+ # Tricoteuses-Transcription-Videos
2
+
3
+ ## _Node.js/TypeScript pipeline to **transcribe French Parliament videos**, from either a **`.m3u8`** URL or a **WAV** file extracted via `ffmpeg`._
4
+
5
+ By:
6
+
7
+ - Henry Boisgibault <mailto:henry.boisgibault@proton.me>
8
+ - Pierre Drege <mailto:dregop@proton.me>
9
+
10
+ Copyright © 2025, 2026 Logora
11
+
12
+ https://git.tricoteuses.fr/logiciels/tricoteuses-transcription-videos
13
+
14
+ > Tricoteuses-Transcription-Videos is free software; you can redistribute
15
+ > it and/or modify it under the terms of the GNU Affero General Public
16
+ > License as published by the Free Software Foundation, either version 3
17
+ > of the License, or (at your option) any later version.
18
+ >
19
+ > Tricoteuses-Transcription-Videos is distributed in the hope that it
20
+ > will be useful, but WITHOUT ANY WARRANTY; without even the implied
21
+ > warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22
+ > See the GNU Affero General Public License for more details.
23
+ >
24
+ > You should have received a copy of the GNU Affero General Public License
25
+ > along with this program. If not, see <http://www.gnu.org/licenses/>.
package/README.md ADDED
@@ -0,0 +1,275 @@
1
+ # tricoteuses-transcription-videos
2
+
3
+ Node.js/TypeScript pipeline to **transcribe French Parliament videos** (with speaker diarization), from either a **`.m3u8`** URL or a **WAV** file extracted via `ffmpeg`.
4
+
5
+ - **Output**: a **JSON array** in the Compte-Rendu format of Assemblée's Data:
6
+
7
+ ```json
8
+ [
9
+ {
10
+ "code_grammaire": "PAROLE_GENERIQUE",
11
+ "ordre_absolu_seance": "4",
12
+ "orateurs": {
13
+ "orateur": {
14
+ "nom": "speaker A",
15
+ "id": "",
16
+ "qualite": ""
17
+ }
18
+ },
19
+ "texte": {
20
+ "_": "Merci monsieur le rapporteur général."
21
+ }
22
+ },
23
+ {
24
+ "code_grammaire": "PAROLE_GENERIQUE",
25
+ "ordre_absolu_seance": "5",
26
+ "orateurs": {
27
+ "orateur": {
28
+ "nom": "speaker D",
29
+ "id": "",
30
+ "qualite": ""
31
+ }
32
+ },
33
+ "texte": {
34
+ "_": "Merci monsieur le président, mesdames et messieurs, ..."
35
+ }
36
+ }
37
+ ]
38
+ ```
39
+
40
+ > Timestamps are **milliseconds**. `speaker` is a **letter** (A, B, C…).
41
+
42
+ - **Plug-and-play architecture** via **providers**: currently `AssemblyAI` and `Deepgram`. You can plug additional models later without changing application code.
43
+
44
+ ---
45
+
46
+ ## Table of Contents
47
+
48
+ - [Prerequisites](#prerequisites)
49
+ - [Installation](#installation)
50
+ - [Usage (single video)](#usage-single-video)
51
+ - [Usage (batch by reunion UIDs)](#usage-batch-by-reunion-uids)
52
+ - [Code Architecture](#code-architecture)
53
+ - [Swap providers later](#swap-providers-later)
54
+ - [License](#license)
55
+
56
+ ---
57
+
58
+ ## Prerequisites
59
+
60
+ - **Node.js** ≥ 20
61
+ - **npm**
62
+ - **ffmpeg** available in your `PATH` (to extract audio from `.m3u8`):
63
+ ```bash
64
+ ffmpeg -version
65
+ ```
66
+ - **Assemblée dataset prepared** :
67
+ - Must contain: Agenda\_<LEGISLATURE>\_nettoye/ for the target legislature.
68
+
69
+ ---
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ npm install
75
+ cp .env.example .env # add your models key
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Usage (single video, useful for model testing)
81
+
82
+ ### 1) Select your provider in `.env`
83
+
84
+ Set `TRANSCRIPTION_PROVIDER` to one of: `deepgram` or `assemblyai`
85
+
86
+ ### 2) From a `.m3u8` URL (audio extraction + transcription)
87
+
88
+ ```bash
89
+ # create the output folders if needed
90
+ mkdir -p ./out ./audios
91
+
92
+ npm run transcribe -- --m3u8 "https://videos-an.vodalys.com/.../master.m3u8" --out ./audios/reunion.wav --ss 0 --t 800 --save ./out/transcript-{model_name}.json
93
+ ```
94
+
95
+ ### CLI Options
96
+
97
+ - `--ss` : start offset (seconds)
98
+ - `--t` : duration (seconds)
99
+ - `--out`: WAV path; if omitted, we default to `os.tmpdir()`
100
+ - `--save`: output **JSON** path (default: `./transcript.json`)
101
+ - `--lang fr` to force language (otherwise uses `.env` default)
102
+ - `--diarize false` to disable diarization (enabled by default)
103
+
104
+ ### 3) From an existing audio file **WAV**
105
+
106
+ ```bash
107
+ npm run transcribe -- --file C:/path/to/reunion.wav --save ./out/transcript-{model_name}.json
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Usage (batch by reunion UIDs, useful in prod)
113
+
114
+ Process only specific Assemblée **reunion UIDs** using the dataset loaders. For each UID:
115
+
116
+ 1. read `reunion.urlVideo`,
117
+ 2. extract audio to **`./audios/<uid>.wav`** (skip ffmpeg if the WAV already exists),
118
+ 3. transcribe + diarize the **full** video with the current provider,
119
+ 4. write segments to **`$ASSEMBLEE_DATA_DIR/Videos_<ROMAN_LEGISLATURE>_nettoye/<uid>/transcript.json`**
120
+ (+ `info.json` with basic metadata).
121
+
122
+ ### CLI Options
123
+
124
+ - `--dataDir`: Absolute path to Assemblée dataset (or as 1st positional). **Required.**
125
+ - `-l, --legislature`: Legislature number (e.g., `16` or `17`).
126
+ - `-s, --fromSession`: Session number to start from (Senat only)
127
+ - `--uids`: Comma-separated UIDs (e.g., `uid1,uid2`).
128
+ - `--uid`: Repeatable UID flag (can be used multiple times).
129
+ - `--lang`, `--language`: Language code (e.g., `fr`).
130
+ - `--diarize`: Enable diarization (default: `true`).
131
+ - `--no-diarize`: Disable diarization.
132
+ - `--keepWav`: Keep extracted WAV files (default: `true`).
133
+ - `--no-keepWav`: Delete WAV after successful transcription.
134
+ - `--audioDir`: Directory for WAV files (default: `./audios`).
135
+ - `--reextract`: Force re-extraction even if WAV exists (default: `false`).
136
+ - `--ss`: Start offset (seconds).
137
+ - `--t`: Duration (seconds).
138
+ - `-p, --provider`: Transcription provider (`assemblyai` | `deepgram`).
139
+
140
+ ### Examples
141
+
142
+ **Transcribe all Reunions from 17th legislature (max 50):**
143
+
144
+ ```bash
145
+ npm run transcribe:reunions ../assemblee-data -- --legislature 17 --provider assemblyai --max 50
146
+ ```
147
+
148
+ **Force re-extraction + startTimecode to optimize wav:**
149
+
150
+ ```bash
151
+ npm run transcribe:reunions -- --dataDir /abs/path/assemblee-data -l 17 --uid RUANR5... --reextract true --ss 796
152
+
153
+ # Transcribe one AN
154
+ npm run transcribe:reunions ../assemblee-data -- --legislature 17 --transcriptsDir ../assemblee-data/transcripts --audioDir ../assemblee-data/audios --provider deepgram --chambre AN --uid RUANR5L17S2025IDC453375
155
+ # Transcribe one SN
156
+ npm run transcribe:reunions ../senat-data -- --fromSession 2025 --transcriptsDir ../senat-data/transcripts --audioDir ../senat-data/audios --provider deepgram --chambre SN --uid RUSN20251016IDODDF-900
157
+ ```
158
+
159
+ ## Usage - Transcription Live
160
+
161
+ Live transcription continuously transcribes an HLS `.m3u8` stream, with automatic retries and a clean stop when the stream ends.
162
+ It is designed for **one job per live** (e.g. one Kubernetes pod per debate).
163
+
164
+ ### Basic CLI usage
165
+
166
+ ```bash
167
+ npm run transcribe:live -- --url "https://videos-an.vodalys.com/live/.../index.m3u8" --out ./live-transcripts/live-$(date +%s).ndjson
168
+ ```
169
+
170
+ ### Live CLI options
171
+
172
+ - `--url` (**required**): HLS `.m3u8` live URL
173
+ - `--out`: NDJSON output file (default: `./live-transcripts/live-<timestamp>.ndjson`)
174
+ - `--lang`: language code (default from `.env`)
175
+ - `--diarize` / `--no-diarize`: enable/disable diarization (default: enabled)
176
+ - `--provider`: transcription provider (`deepgram`, `assemblyai`, …)
177
+ - `--model`: provider-specific model (optional)
178
+ - `--punctuate` / `--no-punctuate`: enable/disable punctuation
179
+ - `--maxMinutes`: stop automatically after N minutes (POC / safety)
180
+
181
+ ### Output format (NDJSON)
182
+
183
+ The output file is append-only, one JSON object per line:
184
+
185
+ ```json
186
+ {"type":"meta","msg":"live transcription start","url":"..."}
187
+ {"type":"segment","start_ms":123400,"end_ms":127800,"speaker":"Speaker A","text":"Hello everyone"}
188
+ {"type":"segment","start_ms":128000,"end_ms":132200,"speaker":"Speaker B","text":"Thank you"}
189
+ {"type":"meta","msg":"session ended","durSec":6400}
190
+ ```
191
+
192
+ This format allows streaming ingestion, retries without duplicates, and easy replay.
193
+
194
+ ### Error handling & retries
195
+
196
+ - On stream errors or disconnects, the script retries automatically with a short backoff.
197
+ - If a session ends too quickly, it is retried until the minimum valid duration is reached.
198
+
199
+ ### Production integration
200
+
201
+ Typical flow:
202
+
203
+ 1. API detects a new `DebatDirect`
204
+ 2. A job/pod is started for this live
205
+ 3. `transcribe:live` runs for this single stream
206
+ 4. Segments are pushed incrementally to the API
207
+ 5. When the live ends, the job exits and the debate is marked `TERMINE`
208
+
209
+ **Rule:** 1 live = 1 process.
210
+
211
+ ## Code Architecture
212
+
213
+ ```
214
+ src/
215
+ ├─ config/
216
+ │ └─ env.ts # .env loading & validation
217
+ ├─ types/
218
+ │ └─ transcription.ts # common types (segments in ms, speakers, metadata)
219
+ ├─ providers/
220
+ │ ├─ TranscriptionProvider.ts # generic interface
221
+ │ ├─ assemblyai.ts # AssemblyAI implementation
222
+ │ ├─ deepgram.ts # Deepgram implementation
223
+ │ ├─ mistral.ts # Mistral implementation
224
+ │ └─ index.ts # provider factory based on .env
225
+ ├─ utils/
226
+ │ └─ ffmpeg.ts # .m3u8 → WAV mono 16k extraction
227
+ │ └─ transcribe.ts # single function used by scripts/services
228
+ ├─ scripts/
229
+ │ └─ transcribe_reunions.ts
230
+ └─ └─ transcribe_live.ts
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Swap providers later
236
+
237
+ Application code always calls:
238
+
239
+ ```ts
240
+ const result = await transcribeVideo({
241
+ filePath: '/tmp/reunion.wav',
242
+ language: 'fr',
243
+ diarize: true,
244
+ });
245
+ ```
246
+
247
+ To add another provider:
248
+
249
+ 1. Create `src/providers/myProvider.ts` implementing `TranscriptionProvider`.
250
+ 2. Add a `case` in `src/providers/index.ts` and a `.env` value (`TRANSCRIPTION_PROVIDER=myProvider`).
251
+ 3. Map the new API’s response to the **same types** (segments in ms, letter speakers).
252
+
253
+ ---
254
+
255
+ ## Docker :
256
+
257
+ ### Build the image
258
+
259
+ ```bash
260
+ docker build -t transcriber:dev .
261
+ ```
262
+
263
+ ### Run (change the ABSOLUTE_PATH_TO_ASSEMBLEE_DATA)
264
+
265
+ ```bash
266
+ docker run \
267
+ --env-file .env \
268
+ -e LEGISLATURE=17 \
269
+ -v "/ABSOLUTE_PATH_TO_ASSEMBLEE_DATA:/app/assemblee-data" \
270
+ transcriber:dev
271
+ ```
272
+
273
+ ## License
274
+
275
+ AGPL-3.0-or-later
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@tricoteuses/transcription-videos",
3
+ "version": "0.1.1",
4
+ "description": "Permet d'obtenir la transcription des vidéos de l'assemblée/sénat en fournissant un lien vidéo m3u8 en entrée",
5
+ "keywords": [
6
+ "Assemblée nationale",
7
+ "Sénat",
8
+ "France",
9
+ "m3u8",
10
+ "transcription",
11
+ "vidéo",
12
+ "modèle IA"
13
+ ],
14
+ "author": "Pierre Drege <dregop@proton.me>",
15
+ "contributors": [
16
+ "Henry Boisgibault <henry.boisgibault@proton.me>"
17
+ ],
18
+ "homepage": "https://tricoteuses.fr/",
19
+ "license": "AGPL-3.0-or-later",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://git.tricoteuses.fr/logiciels/tricoteuses-transcription-videos.git"
23
+ },
24
+ "type": "module",
25
+ "engines": {
26
+ "node": ">=20.0.0"
27
+ },
28
+ "files": [
29
+ "lib"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "transcribe:reunions": "tsx src/scripts/transcribe_reunions.ts",
37
+ "test": "vitest run",
38
+ "gen:expected": "tsx tests/generate-expected.ts",
39
+ "transcribe:live": "tsx src/scripts/transcribe_live.ts",
40
+ "lint": "eslint .",
41
+ "prepare": "npm run build",
42
+ "prettier": "prettier --write \"src/**/*.ts\""
43
+ },
44
+ "dependencies": {
45
+ "@deepgram/sdk": "^4.11.2",
46
+ "@mistralai/mistralai": "^1.14.0",
47
+ "@speechmatics/batch-client": "^5.1.0",
48
+ "@tricoteuses/assemblee": "^2.5.18",
49
+ "@tricoteuses/senat": "^2.20.11",
50
+ "assemblyai": "^4.15.0",
51
+ "dotenv": "^17.2.1",
52
+ "execa": "^9.6.0",
53
+ "fast-xml-parser": "^5.2.5",
54
+ "fs-extra": "^11.3.2",
55
+ "sade": "^1.8.1",
56
+ "vitest": "^4.0.17",
57
+ "ws": "^8.19.0",
58
+ "zod": "^4.0.17"
59
+ },
60
+ "devDependencies": {
61
+ "@types/fs-extra": "^11.0.4",
62
+ "@types/node": "^24.3.0",
63
+ "@types/ws": "^8.18.1",
64
+ "eslint": "^9.27.0",
65
+ "prettier": "^3.5.3",
66
+ "tslib": "^2.0.2",
67
+ "tsx": "^4.0.0",
68
+ "typescript": "^5.8.3"
69
+ }
70
+ }