diff --git a/package.json b/package.json index 57bf4fcc..0f699ff5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.2", + "@xenova/transformers": "^2.17.2", "clsx": "^2.1.1", "focus-trap-react": "^12.0.1", "jszip": "^3.10.1", diff --git a/src/components/SubtitlesControl.tsx b/src/components/SubtitlesControl.tsx new file mode 100644 index 00000000..085b393b --- /dev/null +++ b/src/components/SubtitlesControl.tsx @@ -0,0 +1,151 @@ +import { EditRecipe, Subtitle } from "@/lib/types"; +import { useSubtitles } from "@/hooks/useSubtitles"; +import { Wand2, X, Plus, Clock } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface SubtitlesControlProps { + recipe: EditRecipe; + onChange: (patch: Partial) => void; + file: File | null; + duration: number; +} + +export default function SubtitlesControl({ recipe, onChange, file, duration }: SubtitlesControlProps) { + const { isGenerating, progressText, progressPercent, error, generateSubtitles, cancelGeneration } = useSubtitles(); + + const handleGenerate = () => { + if (!file) return; + generateSubtitles(file, (newSubtitles) => { + // Append or replace? Let's replace for now, or append if user wants to. + // Auto generator should probably replace to avoid duplicates if they run it twice. + onChange({ subtitles: newSubtitles }); + }); + }; + + const updateSubtitle = (id: string, patch: Partial) => { + onChange({ + subtitles: recipe.subtitles.map(s => s.id === id ? { ...s, ...patch } : s) + }); + }; + + const removeSubtitle = (id: string) => { + onChange({ + subtitles: recipe.subtitles.filter(s => s.id !== id) + }); + }; + + const addSubtitle = () => { + const newSub: Subtitle = { + id: `sub-${Date.now()}`, + text: "New subtitle", + startTime: 0, + endTime: 2, + x: -1, + y: 90, + fontSize: 48, + color: "#ffffff", + fontWeight: "bold", + }; + onChange({ subtitles: [...(recipe.subtitles || []), newSub] }); + }; + + return ( +
+
+ +
+ + {isGenerating && ( +
+
+ {progressText} + {progressPercent > 0 && {Math.round(progressPercent)}%} +
+ {progressPercent > 0 && ( +
+
+
+ )} +
+ )} + + {error && ( +
+ {error} +
+ )} + +
+ {recipe.subtitles?.map((sub, index) => ( +
+ +
+ {index + 1} + + updateSubtitle(sub.id, { startTime: parseFloat(e.target.value) || 0 })} + className="w-14 bg-transparent border-b border-transparent hover:border-[var(--border)] focus:border-film-500 outline-none px-1" + /> + to + updateSubtitle(sub.id, { endTime: parseFloat(e.target.value) || 0 })} + className="w-14 bg-transparent border-b border-transparent hover:border-[var(--border)] focus:border-film-500 outline-none px-1" + /> +
+