fix: queue TTS regeneration for shifted cues when deleting AD cue
When an AD cue is deleted, all subsequent cues shift positions but their MP3 files remain at the old indices. This adds handling to automatically queue TTS regeneration for all cues that shifted after a deletion. Changes: - VttEditor: Add onCueDeleted callback to notify parent of deletions - QCDetail: Track deletion context and queue TTS for all shifted cues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
577ed44dab
commit
df721850e0
2 changed files with 35 additions and 3 deletions
|
|
@ -41,11 +41,12 @@ interface VttEditorProps {
|
|||
onChange: (content: string) => void;
|
||||
onCueSave?: (cueIndex: number, vttContent: string) => Promise<void>;
|
||||
onCueInserted?: (insertedIndex: number, totalCues: number) => void;
|
||||
onCueDeleted?: (deletedIndex: number, totalCuesAfterDelete: number) => void;
|
||||
title: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export function VttEditor({ vttContent, onChange, onCueSave, onCueInserted, title, readOnly = false }: VttEditorProps) {
|
||||
export function VttEditor({ vttContent, onChange, onCueSave, onCueInserted, onCueDeleted, title, readOnly = false }: VttEditorProps) {
|
||||
const [cues, setCues] = useState<VTTCue[]>([]);
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const [editingCue, setEditingCue] = useState<number | null>(null);
|
||||
|
|
@ -170,6 +171,8 @@ export function VttEditor({ vttContent, onChange, onCueSave, onCueInserted, titl
|
|||
const updatedCues = cues.filter((_, i) => i !== index);
|
||||
const newVttContent = updateCuesLocal(updatedCues);
|
||||
setCueToDelete(null);
|
||||
// Notify parent about deletion so it can queue TTS for shifted cues
|
||||
onCueDeleted?.(index, updatedCues.length);
|
||||
// Save after delete - use index of deleted cue (will save full VTT)
|
||||
saveCue(index, newVttContent);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ export function QCDetail() {
|
|||
totalCues: number;
|
||||
} | null>(null);
|
||||
|
||||
// Track AD cue deletions to queue TTS for shifted cues
|
||||
const [lastDeletedAdCue, setLastDeletedAdCue] = useState<{
|
||||
deletedIndex: number;
|
||||
totalCuesAfterDelete: number;
|
||||
} | null>(null);
|
||||
|
||||
|
||||
// Fetch VTT content for selected language
|
||||
const { data: vttContent, isLoading: vttLoading } = useJobVttContent(id!, selectedLanguage);
|
||||
|
|
@ -104,6 +110,7 @@ export function QCDetail() {
|
|||
setPendingRegenerations([]);
|
||||
setPausePointsModified(false);
|
||||
setLastInsertedAdCue(null);
|
||||
setLastDeletedAdCue(null);
|
||||
}, [selectedLanguage]);
|
||||
|
||||
// Sync pending regenerations from server edit state
|
||||
|
|
@ -235,6 +242,7 @@ export function QCDetail() {
|
|||
|
||||
// Determine which cues need TTS regeneration
|
||||
let cueIndicesToRegenerate: number[];
|
||||
let actionDescription = 'saved';
|
||||
|
||||
if (lastInsertedAdCue && cueIndex === lastInsertedAdCue.insertIndex) {
|
||||
// This is the newly inserted cue being saved
|
||||
|
|
@ -244,12 +252,27 @@ export function QCDetail() {
|
|||
(_, i) => lastInsertedAdCue.insertIndex + i
|
||||
);
|
||||
setLastInsertedAdCue(null); // Clear insertion context
|
||||
actionDescription = 'inserted';
|
||||
} else if (lastDeletedAdCue && cueIndex === lastDeletedAdCue.deletedIndex) {
|
||||
// A cue was deleted - queue TTS for all cues that shifted (from deleted index to end)
|
||||
// Note: cueIndex here points to the cue that now occupies the deleted position
|
||||
cueIndicesToRegenerate = Array.from(
|
||||
{ length: lastDeletedAdCue.totalCuesAfterDelete - lastDeletedAdCue.deletedIndex },
|
||||
(_, i) => lastDeletedAdCue.deletedIndex + i
|
||||
);
|
||||
setLastDeletedAdCue(null); // Clear deletion context
|
||||
actionDescription = 'deleted';
|
||||
} else {
|
||||
// Normal edit, just this cue
|
||||
cueIndicesToRegenerate = [cueIndex];
|
||||
}
|
||||
|
||||
// Queue TTS regeneration for the cues
|
||||
// Queue TTS regeneration for the cues (skip if no cues to regenerate, e.g., deleted last cue)
|
||||
if (cueIndicesToRegenerate.length === 0) {
|
||||
toast.toastOnly.success('AD cue deleted');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await queueTTSRegenerationMutation.mutateAsync({
|
||||
jobId: id,
|
||||
|
|
@ -262,7 +285,7 @@ export function QCDetail() {
|
|||
const cueCount = cueIndicesToRegenerate.length;
|
||||
if (cueCount > 1) {
|
||||
toast.toastOnly.success(
|
||||
`AD cue inserted. Queued TTS for ${cueCount} cues (indices ${cueIndicesToRegenerate.join(', ')})`
|
||||
`AD cue ${actionDescription}. Queued TTS for ${cueCount} cues (indices ${cueIndicesToRegenerate.join(', ')})`
|
||||
);
|
||||
} else {
|
||||
toast.toastOnly.success(`AD cue ${cueIndex + 1} saved and queued for TTS`);
|
||||
|
|
@ -278,6 +301,11 @@ export function QCDetail() {
|
|||
setLastInsertedAdCue({ insertIndex: insertedIndex, totalCues });
|
||||
};
|
||||
|
||||
// Handler for AD cue deletions - tracks context for TTS queueing
|
||||
const handleAdCueDeleted = (deletedIndex: number, totalCuesAfterDelete: number) => {
|
||||
setLastDeletedAdCue({ deletedIndex, totalCuesAfterDelete });
|
||||
};
|
||||
|
||||
const handleApprove = async () => {
|
||||
if (!id) return;
|
||||
|
||||
|
|
@ -732,6 +760,7 @@ export function QCDetail() {
|
|||
onChange={handleAdChange}
|
||||
onCueSave={handleAdCueSave}
|
||||
onCueInserted={handleAdCueInserted}
|
||||
onCueDeleted={handleAdCueDeleted}
|
||||
title={`Audio Description (${selectedLanguage.toUpperCase()})`}
|
||||
readOnly={isProcessing}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue