Feat/env-manager #1
@@ -2,9 +2,11 @@ import { useEffect, useState } from "react";
|
|||||||
import { Download, RefreshCw, Save } from "lucide-react";
|
import { Download, RefreshCw, Save } from "lucide-react";
|
||||||
import type { SubtitleLanguage } from "@/managers/stores/useSettingsStore";
|
import type { SubtitleLanguage } from "@/managers/stores/useSettingsStore";
|
||||||
import type {
|
import type {
|
||||||
|
DialogueManifest,
|
||||||
DialogueSpeaker,
|
DialogueSpeaker,
|
||||||
DialogueVoiceId,
|
DialogueVoiceId,
|
||||||
} from "@/types/dialogues/dialogues";
|
} from "@/types/dialogues/dialogues";
|
||||||
|
import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest";
|
||||||
import { parseSrt } from "@/utils/subtitles/parseSrt";
|
import { parseSrt } from "@/utils/subtitles/parseSrt";
|
||||||
|
|
||||||
interface SrtVoiceOption {
|
interface SrtVoiceOption {
|
||||||
@@ -14,6 +16,7 @@ interface SrtVoiceOption {
|
|||||||
|
|
||||||
interface SrtDiagnostic {
|
interface SrtDiagnostic {
|
||||||
cueCount: number;
|
cueCount: number;
|
||||||
|
expectedCueCount: number;
|
||||||
errors: string[];
|
errors: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +45,10 @@ function createEmptySrtTemplate(speaker: DialogueSpeaker): string {
|
|||||||
return `1\n00:00:00,000 --> 00:00:02,000\n${speaker}: Nouveau sous-titre\n`;
|
return `1\n00:00:00,000 --> 00:00:02,000\n${speaker}: Nouveau sous-titre\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSrtDiagnostic(content: string): SrtDiagnostic {
|
function getSrtDiagnostic(
|
||||||
|
content: string,
|
||||||
|
expectedCueIndexes: number[],
|
||||||
|
): SrtDiagnostic {
|
||||||
const normalizedContent = content.replace(/^\uFEFF/, "").replace(/\r/g, "");
|
const normalizedContent = content.replace(/^\uFEFF/, "").replace(/\r/g, "");
|
||||||
const blocks = normalizedContent
|
const blocks = normalizedContent
|
||||||
.trim()
|
.trim()
|
||||||
@@ -92,12 +98,39 @@ function getSrtDiagnostic(content: string): SrtDiagnostic {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cueIndexes = new Set(cues.map((cue) => cue.index));
|
||||||
|
const missingCueIndexes = expectedCueIndexes.filter(
|
||||||
|
(cueIndex) => !cueIndexes.has(cueIndex),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (missingCueIndexes.length > 0) {
|
||||||
|
errors.push(
|
||||||
|
`Cues attendues par le manifeste manquantes: ${missingCueIndexes.join(", ")}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cueCount: cues.length,
|
cueCount: cues.length,
|
||||||
|
expectedCueCount: expectedCueIndexes.length,
|
||||||
errors,
|
errors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExpectedCueIndexes(
|
||||||
|
manifest: DialogueManifest | null,
|
||||||
|
voice: DialogueVoiceId,
|
||||||
|
): number[] {
|
||||||
|
if (!manifest) return [];
|
||||||
|
|
||||||
|
return manifest.dialogues
|
||||||
|
.filter((dialogue) => dialogue.voice === voice)
|
||||||
|
.map((dialogue) => dialogue.subtitleCueIndex)
|
||||||
|
.filter(
|
||||||
|
(cueIndex, index, cueIndexes) => cueIndexes.indexOf(cueIndex) === index,
|
||||||
|
)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
|
||||||
function downloadSrtFile(
|
function downloadSrtFile(
|
||||||
voice: DialogueVoiceId,
|
voice: DialogueVoiceId,
|
||||||
language: SubtitleLanguage,
|
language: SubtitleLanguage,
|
||||||
@@ -137,9 +170,11 @@ export function EditorSrtPanel(): React.JSX.Element {
|
|||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [status, setStatus] = useState("Chargement du SRT...");
|
const [status, setStatus] = useState("Chargement du SRT...");
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [manifest, setManifest] = useState<DialogueManifest | null>(null);
|
||||||
const selectedVoice =
|
const selectedVoice =
|
||||||
SRT_VOICES.find((item) => item.id === voice) ?? DEFAULT_SRT_VOICE;
|
SRT_VOICES.find((item) => item.id === voice) ?? DEFAULT_SRT_VOICE;
|
||||||
const diagnostic = getSrtDiagnostic(content);
|
const expectedCueIndexes = getExpectedCueIndexes(manifest, voice);
|
||||||
|
const diagnostic = getSrtDiagnostic(content, expectedCueIndexes);
|
||||||
const isSrtValid = diagnostic.errors.length === 0;
|
const isSrtValid = diagnostic.errors.length === 0;
|
||||||
|
|
||||||
async function handleSave(): Promise<void> {
|
async function handleSave(): Promise<void> {
|
||||||
@@ -162,6 +197,22 @@ export function EditorSrtPanel(): React.JSX.Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
|
void loadDialogueManifest()
|
||||||
|
.then((loadedManifest) => {
|
||||||
|
if (mounted) setManifest(loadedManifest);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (mounted) setManifest(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
const srtPath = getSrtPath(voice, language);
|
const srtPath = getSrtPath(voice, language);
|
||||||
@@ -276,7 +327,7 @@ export function EditorSrtPanel(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<strong>
|
<strong>
|
||||||
{isSrtValid
|
{isSrtValid
|
||||||
? `${diagnostic.cueCount} cue${diagnostic.cueCount > 1 ? "s" : ""} valide${diagnostic.cueCount > 1 ? "s" : ""}`
|
? `${diagnostic.cueCount} cue${diagnostic.cueCount > 1 ? "s" : ""} valide${diagnostic.cueCount > 1 ? "s" : ""} / ${diagnostic.expectedCueCount} attendue${diagnostic.expectedCueCount > 1 ? "s" : ""}`
|
||||||
: `${diagnostic.errors.length} erreur${diagnostic.errors.length > 1 ? "s" : ""} SRT`}
|
: `${diagnostic.errors.length} erreur${diagnostic.errors.length > 1 ? "s" : ""} SRT`}
|
||||||
</strong>
|
</strong>
|
||||||
{!isSrtValid && (
|
{!isSrtValid && (
|
||||||
|
|||||||
Reference in New Issue
Block a user