mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
fix: captions view ui + use BatchCommand to add text elements in transcription at once
This commit is contained in:
@@ -11,6 +11,11 @@ import { useState, useRef } from "react";
|
|||||||
import { extractTimelineAudio } from "@/lib/media/mediabunny";
|
import { extractTimelineAudio } from "@/lib/media/mediabunny";
|
||||||
import { useEditor } from "@/hooks/use-editor";
|
import { useEditor } from "@/hooks/use-editor";
|
||||||
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
|
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
|
||||||
|
import {
|
||||||
|
BatchCommand,
|
||||||
|
AddTrackCommand,
|
||||||
|
InsertElementCommand,
|
||||||
|
} from "@/lib/commands";
|
||||||
import { TRANSCRIPTION_LANGUAGES } from "@/constants/transcription-constants";
|
import { TRANSCRIPTION_LANGUAGES } from "@/constants/transcription-constants";
|
||||||
import type {
|
import type {
|
||||||
TranscriptionLanguage,
|
TranscriptionLanguage,
|
||||||
@@ -20,7 +25,12 @@ import { transcriptionService } from "@/services/transcription/service";
|
|||||||
import { decodeAudioToFloat32 } from "@/lib/media/audio";
|
import { decodeAudioToFloat32 } from "@/lib/media/audio";
|
||||||
import { buildCaptionChunks } from "@/lib/transcription/caption";
|
import { buildCaptionChunks } from "@/lib/transcription/caption";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Label } from "@/components/ui/label";
|
import {
|
||||||
|
Section,
|
||||||
|
SectionContent,
|
||||||
|
SectionField,
|
||||||
|
SectionFields,
|
||||||
|
} from "@/components/editor/panels/properties/section";
|
||||||
|
|
||||||
export function Captions() {
|
export function Captions() {
|
||||||
const [selectedLanguage, setSelectedLanguage] =
|
const [selectedLanguage, setSelectedLanguage] =
|
||||||
@@ -60,29 +70,28 @@ export function Captions() {
|
|||||||
onProgress: handleProgress,
|
onProgress: handleProgress,
|
||||||
});
|
});
|
||||||
|
|
||||||
setProcessingStep("Generating captions...");
|
setProcessingStep("Generating captions...");
|
||||||
const captionChunks = buildCaptionChunks({ segments: result.segments });
|
const captionChunks = buildCaptionChunks({ segments: result.segments });
|
||||||
|
|
||||||
const captionTrackId = editor.timeline.addTrack({
|
const addTrackCommand = new AddTrackCommand("text", 0);
|
||||||
type: "text",
|
const insertCommands = captionChunks.map((caption, i) =>
|
||||||
index: 0,
|
new InsertElementCommand({
|
||||||
});
|
placement: { mode: "explicit", trackId: addTrackCommand.getTrackId() },
|
||||||
|
element: {
|
||||||
|
...DEFAULT_TEXT_ELEMENT,
|
||||||
|
name: `Caption ${i + 1}`,
|
||||||
|
content: caption.text,
|
||||||
|
duration: caption.duration,
|
||||||
|
startTime: caption.startTime,
|
||||||
|
fontSize: 65,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < captionChunks.length; i++) {
|
editor.command.execute({
|
||||||
const caption = captionChunks[i];
|
command: new BatchCommand([addTrackCommand, ...insertCommands]),
|
||||||
editor.timeline.insertElement({
|
});
|
||||||
placement: { mode: "explicit", trackId: captionTrackId },
|
|
||||||
element: {
|
|
||||||
...DEFAULT_TEXT_ELEMENT,
|
|
||||||
name: `Caption ${i + 1}`,
|
|
||||||
content: caption.text,
|
|
||||||
duration: caption.duration,
|
|
||||||
startTime: caption.startTime,
|
|
||||||
fontSize: 65,
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Transcription failed:", error);
|
console.error("Transcription failed:", error);
|
||||||
setError(
|
setError(
|
||||||
@@ -108,43 +117,49 @@ export function Captions() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelView title="Captions" ref={containerRef}>
|
<PanelView title="Captions" contentClassName="px-0 flex flex-col h-full" ref={containerRef}>
|
||||||
<div className="flex flex-col gap-3">
|
<Section showTopBorder={false} className="flex-1">
|
||||||
<Label>Language</Label>
|
<SectionContent className="flex flex-col gap-4 h-full">
|
||||||
<Select
|
<SectionFields>
|
||||||
value={selectedLanguage}
|
<SectionField label="Language">
|
||||||
onValueChange={(value) => handleLanguageChange({ value })}
|
<Select
|
||||||
>
|
value={selectedLanguage}
|
||||||
<SelectTrigger>
|
onValueChange={(value) => handleLanguageChange({ value })}
|
||||||
<SelectValue placeholder="Select a language" />
|
>
|
||||||
</SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectContent>
|
<SelectValue placeholder="Select a language" />
|
||||||
<SelectItem value="auto">Auto detect</SelectItem>
|
</SelectTrigger>
|
||||||
{TRANSCRIPTION_LANGUAGES.map((language) => (
|
<SelectContent>
|
||||||
<SelectItem key={language.code} value={language.code}>
|
<SelectItem value="auto">Auto detect</SelectItem>
|
||||||
{language.name}
|
{TRANSCRIPTION_LANGUAGES.map((language) => (
|
||||||
</SelectItem>
|
<SelectItem key={language.code} value={language.code}>
|
||||||
))}
|
{language.name}
|
||||||
</SelectContent>
|
</SelectItem>
|
||||||
</Select>
|
))}
|
||||||
</div>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<div className="flex flex-col gap-4">
|
</SectionField>
|
||||||
{error && (
|
</SectionFields>
|
||||||
<div className="bg-destructive/10 border-destructive/20 rounded-md border p-3">
|
|
||||||
<p className="text-destructive text-sm">{error}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-destructive/10 border-destructive/20 rounded-md border p-3">
|
||||||
|
<p className="text-destructive text-sm">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SectionContent>
|
||||||
|
</Section>
|
||||||
|
<Section showBottomBorder={false} showTopBorder={false}>
|
||||||
|
<SectionContent>
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={handleGenerateTranscript}
|
onClick={handleGenerateTranscript}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
>
|
>
|
||||||
{isProcessing && <Spinner className="mr-1" />}
|
{isProcessing && <Spinner className="mr-1" />}
|
||||||
{isProcessing ? processingStep : "Generate transcript"}
|
{isProcessing ? processingStep : "Generate transcript"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</SectionContent>
|
||||||
|
</Section>
|
||||||
</PanelView>
|
</PanelView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user