@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.
- package/LICENSE.md +25 -0
- package/README.md +275 -0
- 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
|
+
}
|