update: gros commit fix editor srt panel 2

This commit is contained in:
Tom Boullay
2026-05-10 00:37:28 +01:00
parent 64ebeee014
commit 2757b5c389
2 changed files with 165 additions and 0 deletions
+143
View File
@@ -26,6 +26,9 @@ interface TextRange {
end: number; end: number;
} }
type CueTimeEdge = "start" | "end";
const CUE_NUDGE_SECONDS = 0.1;
const SRT_VOICES: SrtVoiceOption[] = [ const SRT_VOICES: SrtVoiceOption[] = [
{ id: "narrateur", label: "Narrateur" }, { id: "narrateur", label: "Narrateur" },
{ id: "fermier", label: "Fermier" }, { id: "fermier", label: "Fermier" },
@@ -75,6 +78,21 @@ function formatPreviewTime(totalSeconds: number): string {
return `${Math.floor(totalSeconds)}.${Math.floor((totalSeconds % 1) * 10)}s`; return `${Math.floor(totalSeconds)}.${Math.floor((totalSeconds % 1) * 10)}s`;
} }
function parseSrtTime(value: string): number | null {
const match = value.match(/^(\d{2}):(\d{2}):(\d{2}),(\d{3})$/);
if (!match) return null;
const [, hours, minutes, seconds, milliseconds] = match;
if (!hours || !minutes || !seconds || !milliseconds) return null;
return (
Number(hours) * 3600 +
Number(minutes) * 60 +
Number(seconds) +
Number(milliseconds) / 1000
);
}
function padTime(value: number): string { function padTime(value: number): string {
return value.toString().padStart(2, "0"); return value.toString().padStart(2, "0");
} }
@@ -190,6 +208,58 @@ function findCueBlockRange(
return { start, end }; return { start, end };
} }
function updateCueTimecode(
content: string,
cueIndex: number,
edge: CueTimeEdge,
time: number,
): string | null {
const range = findCueBlockRange(content, cueIndex);
if (!range) return null;
const block = content.slice(range.start, range.end);
const lines = block.split("\n");
const timecodeLine = lines[1];
if (!timecodeLine) return null;
const [start, end] = timecodeLine.split(" --> ");
if (!start || !end) return null;
lines[1] =
edge === "start"
? `${formatSrtTime(time)} --> ${end}`
: `${start} --> ${formatSrtTime(time)}`;
return `${content.slice(0, range.start)}${lines.join("\n")}${content.slice(range.end)}`;
}
function nudgeCueTimecode(
content: string,
cueIndex: number,
delta: number,
): string | null {
const range = findCueBlockRange(content, cueIndex);
if (!range) return null;
const block = content.slice(range.start, range.end);
const lines = block.split("\n");
const timecodeLine = lines[1];
if (!timecodeLine) return null;
const [start, end] = timecodeLine.split(" --> ");
if (!start || !end) return null;
const startTime = parseSrtTime(start);
const endTime = parseSrtTime(end);
if (startTime === null || endTime === null) return null;
const nextStartTime = Math.max(0, startTime + delta);
const nextEndTime = Math.max(nextStartTime + 0.001, endTime + delta);
lines[1] = `${formatSrtTime(nextStartTime)} --> ${formatSrtTime(nextEndTime)}`;
return `${content.slice(0, range.start)}${lines.join("\n")}${content.slice(range.end)}`;
}
function downloadSrtFile( function downloadSrtFile(
voice: DialogueVoiceId, voice: DialogueVoiceId,
language: SubtitleLanguage, language: SubtitleLanguage,
@@ -287,6 +357,39 @@ export function EditorSrtPanel(): React.JSX.Element {
setStatus(`Cue ${cueIndex} selectionnee dans le SRT.`); setStatus(`Cue ${cueIndex} selectionnee dans le SRT.`);
} }
function handleSetCueTime(cueIndex: number, edge: CueTimeEdge): void {
const updatedContent = updateCueTimecode(
content,
cueIndex,
edge,
audioCurrentTime,
);
if (!updatedContent) {
setStatus(`Cue ${cueIndex} introuvable ou timecode invalide.`);
return;
}
setContent(updatedContent);
setStatus(
`Cue ${cueIndex}: ${edge === "start" ? "debut" : "fin"} place a ${formatSrtTime(audioCurrentTime)}.`,
);
}
function handleNudgeCue(cueIndex: number, delta: number): void {
const updatedContent = nudgeCueTimecode(content, cueIndex, delta);
if (!updatedContent) {
setStatus(`Cue ${cueIndex} introuvable ou timecode invalide.`);
return;
}
setContent(updatedContent);
setStatus(
`Cue ${cueIndex} decalee de ${delta > 0 ? "+" : ""}${delta.toFixed(1)}s.`,
);
}
useEffect(() => { useEffect(() => {
let mounted = true; let mounted = true;
@@ -414,6 +517,46 @@ export function EditorSrtPanel(): React.JSX.Element {
<p>Aucune cue active a ce moment.</p> <p>Aucune cue active a ce moment.</p>
)} )}
</div> </div>
<div className="editor-srt-time-actions">
<button
type="button"
onClick={() =>
handleSetCueTime(selectedDialogue.subtitleCueIndex, "start")
}
>
Set start
</button>
<button
type="button"
onClick={() =>
handleSetCueTime(selectedDialogue.subtitleCueIndex, "end")
}
>
Set end
</button>
<button
type="button"
onClick={() =>
handleNudgeCue(
selectedDialogue.subtitleCueIndex,
-CUE_NUDGE_SECONDS,
)
}
>
-100ms
</button>
<button
type="button"
onClick={() =>
handleNudgeCue(
selectedDialogue.subtitleCueIndex,
CUE_NUDGE_SECONDS,
)
}
>
+100ms
</button>
</div>
<button <button
className="editor-srt-jump-button" className="editor-srt-jump-button"
type="button" type="button"
+22
View File
@@ -1532,6 +1532,28 @@ canvas {
color: #ffffff; color: #ffffff;
} }
.editor-srt-time-actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 6px;
}
.editor-srt-time-actions button {
padding: 8px 10px;
border: 1px solid rgba(125, 211, 252, 0.24);
border-radius: 12px;
background: rgba(125, 211, 252, 0.08);
color: #bae6fd;
cursor: pointer;
font-size: 0.74rem;
font-weight: 800;
}
.editor-srt-time-actions button:hover {
border-color: #7dd3fc;
background: rgba(125, 211, 252, 0.14);
}
.editor-srt-jump-button { .editor-srt-jump-button {
width: 100%; width: 100%;
padding: 8px 10px; padding: 8px 10px;