add: audio preview
This commit is contained in:
@@ -2,6 +2,7 @@ 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 {
|
||||||
|
DialogueDefinition,
|
||||||
DialogueManifest,
|
DialogueManifest,
|
||||||
DialogueSpeaker,
|
DialogueSpeaker,
|
||||||
DialogueVoiceId,
|
DialogueVoiceId,
|
||||||
@@ -144,10 +145,7 @@ function getExpectedCueIndexes(
|
|||||||
manifest: DialogueManifest | null,
|
manifest: DialogueManifest | null,
|
||||||
voice: DialogueVoiceId,
|
voice: DialogueVoiceId,
|
||||||
): number[] {
|
): number[] {
|
||||||
if (!manifest) return [];
|
return getExpectedDialogues(manifest, voice)
|
||||||
|
|
||||||
return manifest.dialogues
|
|
||||||
.filter((dialogue) => dialogue.voice === voice)
|
|
||||||
.map((dialogue) => dialogue.subtitleCueIndex)
|
.map((dialogue) => dialogue.subtitleCueIndex)
|
||||||
.filter(
|
.filter(
|
||||||
(cueIndex, index, cueIndexes) => cueIndexes.indexOf(cueIndex) === index,
|
(cueIndex, index, cueIndexes) => cueIndexes.indexOf(cueIndex) === index,
|
||||||
@@ -155,6 +153,17 @@ function getExpectedCueIndexes(
|
|||||||
.sort((a, b) => a - b);
|
.sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExpectedDialogues(
|
||||||
|
manifest: DialogueManifest | null,
|
||||||
|
voice: DialogueVoiceId,
|
||||||
|
): DialogueDefinition[] {
|
||||||
|
if (!manifest) return [];
|
||||||
|
|
||||||
|
return [...manifest.dialogues]
|
||||||
|
.filter((dialogue) => dialogue.voice === voice)
|
||||||
|
.sort((a, b) => a.subtitleCueIndex - b.subtitleCueIndex);
|
||||||
|
}
|
||||||
|
|
||||||
function downloadSrtFile(
|
function downloadSrtFile(
|
||||||
voice: DialogueVoiceId,
|
voice: DialogueVoiceId,
|
||||||
language: SubtitleLanguage,
|
language: SubtitleLanguage,
|
||||||
@@ -197,6 +206,7 @@ export function EditorSrtPanel(): React.JSX.Element {
|
|||||||
const [manifest, setManifest] = useState<DialogueManifest | null>(null);
|
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 expectedDialogues = getExpectedDialogues(manifest, voice);
|
||||||
const expectedCueIndexes = getExpectedCueIndexes(manifest, voice);
|
const expectedCueIndexes = getExpectedCueIndexes(manifest, voice);
|
||||||
const diagnostic = getSrtDiagnostic(content, expectedCueIndexes);
|
const diagnostic = getSrtDiagnostic(content, expectedCueIndexes);
|
||||||
const isSrtValid = diagnostic.errors.length === 0;
|
const isSrtValid = diagnostic.errors.length === 0;
|
||||||
@@ -204,6 +214,11 @@ export function EditorSrtPanel(): React.JSX.Element {
|
|||||||
selectedVoice.label,
|
selectedVoice.label,
|
||||||
expectedCueIndexes,
|
expectedCueIndexes,
|
||||||
);
|
);
|
||||||
|
const [selectedDialogueId, setSelectedDialogueId] = useState("");
|
||||||
|
const selectedDialogue =
|
||||||
|
expectedDialogues.find((dialogue) => dialogue.id === selectedDialogueId) ??
|
||||||
|
expectedDialogues[0] ??
|
||||||
|
null;
|
||||||
|
|
||||||
async function handleSave(): Promise<void> {
|
async function handleSave(): Promise<void> {
|
||||||
if (!isSrtValid) {
|
if (!isSrtValid) {
|
||||||
@@ -310,6 +325,38 @@ export function EditorSrtPanel(): React.JSX.Element {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="editor-srt-preview">
|
||||||
|
<label>
|
||||||
|
Dialogue audio
|
||||||
|
<select
|
||||||
|
value={selectedDialogue?.id ?? ""}
|
||||||
|
onChange={(event) => setSelectedDialogueId(event.target.value)}
|
||||||
|
disabled={expectedDialogues.length === 0}
|
||||||
|
>
|
||||||
|
{expectedDialogues.length === 0 && (
|
||||||
|
<option value="">Aucun dialogue</option>
|
||||||
|
)}
|
||||||
|
{expectedDialogues.map((dialogue) => (
|
||||||
|
<option key={dialogue.id} value={dialogue.id}>
|
||||||
|
Cue {dialogue.subtitleCueIndex} - {dialogue.id}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{selectedDialogue && (
|
||||||
|
<div className="editor-srt-audio-card">
|
||||||
|
<span>Cue {selectedDialogue.subtitleCueIndex}</span>
|
||||||
|
<strong>{selectedDialogue.id}</strong>
|
||||||
|
<audio
|
||||||
|
key={selectedDialogue.audio}
|
||||||
|
controls
|
||||||
|
src={selectedDialogue.audio}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
className="editor-srt-textarea"
|
className="editor-srt-textarea"
|
||||||
value={content}
|
value={content}
|
||||||
|
|||||||
+52
-1
@@ -1450,11 +1450,62 @@ canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor-srt-textarea:focus,
|
.editor-srt-textarea:focus,
|
||||||
.editor-srt-controls select:focus {
|
.editor-srt-controls select:focus,
|
||||||
|
.editor-srt-preview select:focus {
|
||||||
border-color: #ffffff;
|
border-color: #ffffff;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-srt-preview {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #1f1f1f;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #070707;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-srt-preview label {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
color: #8d8d8d;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-srt-preview select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border: 1px solid #242424;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #101010;
|
||||||
|
color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-srt-audio-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-srt-audio-card span {
|
||||||
|
color: #8d8d8d;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-srt-audio-card strong {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-srt-audio-card audio {
|
||||||
|
width: 100%;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-srt-actions {
|
.editor-srt-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
|||||||
Reference in New Issue
Block a user