@tricoteuses/senat 2.20.8 → 2.20.9

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.
@@ -9,6 +9,7 @@ import { getSessionsFromStart } from "../types/sessions";
9
9
  import { commonOptions } from "./shared/cli_helpers";
10
10
  import { decodeHtmlEntities } from "../model/util";
11
11
  import { DateTime } from "luxon";
12
+ import { getFirstInterventionStartTimecode } from "../utils/nvs-timecode";
12
13
  // ===================== Constants =====================
13
14
  const MATCH_THRESHOLD = 0.5;
14
15
  const MAX_CANDIDATES = 15;
@@ -274,6 +275,8 @@ async function processGroupedReunion(agenda, session, dataDir) {
274
275
  }
275
276
  }
276
277
  let master = null;
278
+ let dataTxt = null;
279
+ let finalTxt = null;
277
280
  let accepted = false;
278
281
  if (!skipDownload) {
279
282
  STATS.total++;
@@ -398,8 +401,8 @@ async function processGroupedReunion(agenda, session, dataDir) {
398
401
  await writeIfChanged(path.join(baseDir, "metadata.json"), JSON.stringify(metadata, null, 2));
399
402
  const dataUrl = `${SENAT_DATAS_ROOT}/${best.id}_${best.hash}/content/data.nvs`;
400
403
  const finalUrl = `${SENAT_DATAS_ROOT}/${best.id}_${best.hash}/content/finalplayer.nvs`;
401
- const dataTxt = await fetchText(dataUrl);
402
- const finalTxt = await fetchText(finalUrl);
404
+ dataTxt = await fetchText(dataUrl);
405
+ finalTxt = await fetchText(finalUrl);
403
406
  if (dataTxt)
404
407
  await fsp.writeFile(path.join(baseDir, "data.nvs"), dataTxt, "utf-8");
405
408
  if (finalTxt)
@@ -414,14 +417,21 @@ async function processGroupedReunion(agenda, session, dataDir) {
414
417
  else {
415
418
  // Skipped download, but need to read data.nvs for urlVideo
416
419
  try {
417
- const dataTxt = await fsp.readFile(path.join(baseDir, "data.nvs"), "utf-8");
418
- const finalTxt = await fsp.readFile(path.join(baseDir, "finalplayer.nvs"), "utf-8");
420
+ dataTxt = await fsp.readFile(path.join(baseDir, "data.nvs"), "utf-8");
421
+ finalTxt = await fsp.readFile(path.join(baseDir, "finalplayer.nvs"), "utf-8");
419
422
  master = buildSenatVodMasterM3u8FromNvs(dataTxt);
420
423
  }
421
424
  catch (e) {
422
425
  console.warn(e);
423
426
  }
424
427
  }
428
+ let timecodeDebutVideo = null;
429
+ if (dataTxt && finalTxt) {
430
+ timecodeDebutVideo = getFirstInterventionStartTimecode(dataTxt, finalTxt);
431
+ if (timecodeDebutVideo === null) {
432
+ console.warn(`[warn] Cannot retrieve start video timecode from reunion` + reunionUid);
433
+ }
434
+ }
425
435
  // ==== 4) Update agenda file (only if accepted + m3u8) ====
426
436
  if ((accepted || skipDownload) && master) {
427
437
  const agendaJsonPath = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session), `${agenda.uid}.json`);
@@ -437,9 +447,13 @@ async function processGroupedReunion(agenda, session, dataDir) {
437
447
  }
438
448
  if (obj && typeof obj === "object" && !Array.isArray(obj)) {
439
449
  const next = { ...obj, urlVideo: master };
450
+ if (timecodeDebutVideo != null) {
451
+ next.timecodeDebutVideo = timecodeDebutVideo;
452
+ }
440
453
  await writeIfChanged(agendaJsonPath, JSON.stringify(next, null, 2));
441
454
  if (!options["silent"]) {
442
- console.log(`[write] ${agenda.uid} urlVideo ← ${master}`);
455
+ console.log(`[write] ${agenda.uid} urlVideo ← ${master}` +
456
+ (timecodeDebutVideo != null ? ` (timecodeDebutVideo ← ${timecodeDebutVideo}s)` : ""));
443
457
  }
444
458
  }
445
459
  else {
@@ -0,0 +1 @@
1
+ export declare function getFirstInterventionStartTimecode(dataNvs: string, finalPlayerNvs: string): number | null;
@@ -0,0 +1,62 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ const xmlParser = new XMLParser({
3
+ ignoreAttributes: false,
4
+ attributeNamePrefix: "@_",
5
+ });
6
+ function getFirstInterventionChapterId(dataNvs) {
7
+ const xml = xmlParser.parse(dataNvs);
8
+ const rootChapters = xml?.data?.chapters?.chapter;
9
+ if (!rootChapters)
10
+ return null;
11
+ const chaptersArray = Array.isArray(rootChapters) ? rootChapters : [rootChapters];
12
+ let foundId = null;
13
+ function dfsChapter(chapter) {
14
+ if (foundId)
15
+ return;
16
+ const metas = chapter.metadata ? (Array.isArray(chapter.metadata) ? chapter.metadata : [chapter.metadata]) : [];
17
+ const isIntervention = metas.some((m) => m?.["@_name"] === "type" && (m?.["@_value"] === "IN" || m?.["@_label"] === "Intervention"));
18
+ const hasSpeaker = !!chapter.speaker;
19
+ if (isIntervention && hasSpeaker && chapter["@_id"]) {
20
+ foundId = String(chapter["@_id"]);
21
+ return;
22
+ }
23
+ const children = chapter.chapter;
24
+ if (!children)
25
+ return;
26
+ const childArray = Array.isArray(children) ? children : [children];
27
+ for (const child of childArray) {
28
+ dfsChapter(child);
29
+ if (foundId)
30
+ return;
31
+ }
32
+ }
33
+ for (const ch of chaptersArray) {
34
+ dfsChapter(ch);
35
+ if (foundId)
36
+ break;
37
+ }
38
+ return foundId;
39
+ }
40
+ function getTimecodeForChapterId(finalPlayerNvs, chapterId) {
41
+ const xml = xmlParser.parse(finalPlayerNvs);
42
+ const synchros = xml?.player?.synchro;
43
+ if (!synchros)
44
+ return null;
45
+ const synchsArray = Array.isArray(synchros) ? synchros : [synchros];
46
+ const match = synchsArray.find((s) => String(s["@_id"]) === String(chapterId));
47
+ if (!match)
48
+ return null;
49
+ const rawTimecode = match["@_timecode"];
50
+ if (rawTimecode == null)
51
+ return null;
52
+ const ms = Number(rawTimecode);
53
+ if (Number.isNaN(ms))
54
+ return null;
55
+ return Math.floor(ms / 1000);
56
+ }
57
+ export function getFirstInterventionStartTimecode(dataNvs, finalPlayerNvs) {
58
+ const firstChapterId = getFirstInterventionChapterId(dataNvs);
59
+ if (!firstChapterId)
60
+ return null;
61
+ return getTimecodeForChapterId(finalPlayerNvs, firstChapterId);
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tricoteuses/senat",
3
- "version": "2.20.8",
3
+ "version": "2.20.9",
4
4
  "description": "Handle French Sénat's open data",
5
5
  "keywords": [
6
6
  "France",
@@ -65,6 +65,7 @@
65
65
  "cheerio": "^1.1.2",
66
66
  "command-line-args": "^6.0.1",
67
67
  "dotenv": "^17.2.3",
68
+ "fast-xml-parser": "^5.3.2",
68
69
  "fs-extra": "^11.3.2",
69
70
  "jsdom": "^27.2.0",
70
71
  "kysely": "^0.28.8",