add: dialoguejson
This commit is contained in:
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user