add: dialoguejson

This commit is contained in:
Tom Boullay
2026-05-10 00:00:36 +01:00
parent d9525b0aaf
commit f5bc7cb08e
3 changed files with 352 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
{
"version": 1,
"voices": [
{
"id": "narrateur",
"speaker": "Narrateur",
"subtitles": {
"fr": "/sounds/dialogue/subtitles/fr/narrateur.srt",
"en": "/sounds/dialogue/subtitles/en/narrateur.srt"
}
},
{
"id": "fermier",
"speaker": "Fermier",
"subtitles": {
"fr": "/sounds/dialogue/subtitles/fr/fermier.srt",
"en": "/sounds/dialogue/subtitles/en/fermier.srt"
}
},
{
"id": "leonie",
"speaker": "Leonie",
"subtitles": {
"fr": "/sounds/dialogue/subtitles/fr/leonie.srt",
"en": "/sounds/dialogue/subtitles/en/leonie.srt"
}
}
],
"dialogues": [
{
"id": "narrateur_bienvenueaaltera",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_bienvenueaaltera.mp3",
"subtitleCueIndex": 1,
"timecode": 0
},
{
"id": "narrateur_intro_prenom",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_intro_prenom.mp3",
"subtitleCueIndex": 2
},
{
"id": "narrateur_intro_apresprenom",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_intro_apresprenom.mp3",
"subtitleCueIndex": 3
},
{
"id": "narrateur_ordreebike",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_ordreebike.mp3",
"subtitleCueIndex": 4
},
{
"id": "narrateur_ebikecasse",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_ebikecassé.mp3",
"subtitleCueIndex": 5
},
{
"id": "narrateur_galetscan",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_galetscan.mp3",
"subtitleCueIndex": 6
},
{
"id": "narrateur_ebikerepare",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_ebikeréparé.mp3",
"subtitleCueIndex": 7
},
{
"id": "narrateur_ordredemandedelaide",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_ordredemandedelaide.mp3",
"subtitleCueIndex": 8
},
{
"id": "narrateur_coupureelec",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_coupureélec.mp3",
"subtitleCueIndex": 9
},
{
"id": "narrateur_poteaueleccasse",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_poteauéleccassé.mp3",
"subtitleCueIndex": 10
},
{
"id": "narrateur_courantrepare",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_courantréparé.mp3",
"subtitleCueIndex": 11
},
{
"id": "narrateur_routeversferme",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_routeversferme.mp3",
"subtitleCueIndex": 12
},
{
"id": "narrateur_arriveferme",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_arrivéferme.mp3",
"subtitleCueIndex": 13
},
{
"id": "narrateur_fouillelecentre",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_fouillelecentre.mp3",
"subtitleCueIndex": 14
},
{
"id": "narrateur_interactiontuyauxlac",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_interactiontuyauxlac.mp3",
"subtitleCueIndex": 15
},
{
"id": "narrateur_interactionrefroidisseur",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_interactionrefroidisseur.mp3",
"subtitleCueIndex": 16
},
{
"id": "narrateur_refroidisseurcasse",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_refroidisseurcassé.mp3",
"subtitleCueIndex": 17
},
{
"id": "narrateur_createurdepluiecree",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_createurdepluiecréé.mp3",
"subtitleCueIndex": 18
},
{
"id": "narrateur_remerciement",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_remerciement.mp3",
"subtitleCueIndex": 19
},
{
"id": "narrateur_bonnechance",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_bonnechance.mp3",
"subtitleCueIndex": 20
},
{
"id": "narrateur_presentationatelier",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_présentationatelier.mp3",
"subtitleCueIndex": 21
},
{
"id": "narrateur_presentationoutils",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_présentationoutils.mp3",
"subtitleCueIndex": 22
},
{
"id": "narrateur_histoireleonie",
"voice": "narrateur",
"audio": "/sounds/dialogue/narrateur_histoireleonie.mp3",
"subtitleCueIndex": 23
},
{
"id": "fermier_coupdemain",
"voice": "fermier",
"audio": "/sounds/dialogue/fermier_coupdemain.mp3",
"subtitleCueIndex": 1
},
{
"id": "fermier_coupdemain_2",
"voice": "fermier",
"audio": "/sounds/dialogue/fermier_coupdemain_2.mp3",
"subtitleCueIndex": 2
},
{
"id": "fermier_findemission",
"voice": "fermier",
"audio": "/sounds/dialogue/fermier_findemission.mp3",
"subtitleCueIndex": 3
}
]
}
+24
View File
@@ -0,0 +1,24 @@
import type { SubtitleLanguage } from "@/managers/stores/useSettingsStore";
export type DialogueVoiceId = "narrateur" | "fermier" | "leonie";
export type DialogueSpeaker = "Narrateur" | "Fermier" | "Leonie";
export interface DialogueVoice {
id: DialogueVoiceId;
speaker: DialogueSpeaker;
subtitles: Partial<Record<SubtitleLanguage, string>>;
}
export interface DialogueDefinition {
id: string;
voice: DialogueVoiceId;
audio: string;
subtitleCueIndex: number;
timecode?: number;
}
export interface DialogueManifest {
version: 1;
voices: DialogueVoice[];
dialogues: DialogueDefinition[];
}
@@ -0,0 +1,140 @@
import type {
DialogueDefinition,
DialogueManifest,
DialogueSpeaker,
DialogueVoice,
DialogueVoiceId,
} from "@/types/dialogues/dialogues";
const VALID_VOICE_IDS = new Set<DialogueVoiceId>([
"narrateur",
"fermier",
"leonie",
]);
const VALID_SPEAKERS = new Set<DialogueSpeaker>([
"Narrateur",
"Fermier",
"Leonie",
]);
export function parseDialogueManifest(data: unknown): DialogueManifest {
if (!isRecord(data)) {
throw new Error("Dialogue manifest must be an object");
}
if (data.version !== 1) {
throw new Error("Unsupported dialogue manifest version");
}
if (!Array.isArray(data.voices) || !Array.isArray(data.dialogues)) {
throw new Error("Dialogue manifest requires voices and dialogues arrays");
}
const voices = data.voices.map(parseDialogueVoice);
const voiceIds = new Set(voices.map((voice) => voice.id));
const dialogues = data.dialogues.map((dialogue) =>
parseDialogueDefinition(dialogue, voiceIds),
);
return {
version: 1,
voices,
dialogues,
};
}
function parseDialogueVoice(data: unknown): DialogueVoice {
if (!isRecord(data)) {
throw new Error("Dialogue voice must be an object");
}
if (!isDialogueVoiceId(data.id)) {
throw new Error("Dialogue voice has an invalid id");
}
if (!isDialogueSpeaker(data.speaker)) {
throw new Error(`Dialogue voice ${data.id} has an invalid speaker`);
}
if (!isRecord(data.subtitles)) {
throw new Error(`Dialogue voice ${data.id} must define subtitles`);
}
const subtitles: DialogueVoice["subtitles"] = {};
const frSubtitle = getOptionalPath(data.subtitles.fr);
const enSubtitle = getOptionalPath(data.subtitles.en);
if (frSubtitle) subtitles.fr = frSubtitle;
if (enSubtitle) subtitles.en = enSubtitle;
return {
id: data.id,
speaker: data.speaker,
subtitles,
};
}
function parseDialogueDefinition(
data: unknown,
voiceIds: Set<DialogueVoiceId>,
): DialogueDefinition {
if (!isRecord(data)) {
throw new Error("Dialogue definition must be an object");
}
if (typeof data.id !== "string" || data.id.length === 0) {
throw new Error("Dialogue definition has an invalid id");
}
if (!isDialogueVoiceId(data.voice) || !voiceIds.has(data.voice)) {
throw new Error(`Dialogue ${data.id} references an unknown voice`);
}
if (typeof data.audio !== "string" || data.audio.length === 0) {
throw new Error(`Dialogue ${data.id} has an invalid audio path`);
}
const subtitleCueIndex = data.subtitleCueIndex;
if (
typeof subtitleCueIndex !== "number" ||
!Number.isInteger(subtitleCueIndex) ||
subtitleCueIndex < 1
) {
throw new Error(`Dialogue ${data.id} has an invalid subtitle cue index`);
}
const timecode = data.timecode;
if (timecode !== undefined && typeof timecode !== "number") {
throw new Error(`Dialogue ${data.id} has an invalid timecode`);
}
const dialogue: DialogueDefinition = {
id: data.id,
voice: data.voice,
audio: data.audio,
subtitleCueIndex,
};
if (timecode !== undefined) dialogue.timecode = timecode;
return dialogue;
}
function getOptionalPath(value: unknown): string | undefined {
return typeof value === "string" && value.length > 0 ? value : undefined;
}
function isDialogueVoiceId(value: unknown): value is DialogueVoiceId {
return (
typeof value === "string" && VALID_VOICE_IDS.has(value as DialogueVoiceId)
);
}
function isDialogueSpeaker(value: unknown): value is DialogueSpeaker {
return (
typeof value === "string" && VALID_SPEAKERS.has(value as DialogueSpeaker)
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}