From f92eb075466102a68b01c2948326b8eda85145ed Mon Sep 17 00:00:00 2001 From: DJP Date: Wed, 10 Dec 2025 22:18:33 -0500 Subject: [PATCH] Add comprehensive subtitle styling UI with font, colors, and position controls --- frontend/app/video/subtitles/page.tsx | 144 ++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 6 deletions(-) diff --git a/frontend/app/video/subtitles/page.tsx b/frontend/app/video/subtitles/page.tsx index 7d8344f..87edd28 100644 --- a/frontend/app/video/subtitles/page.tsx +++ b/frontend/app/video/subtitles/page.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { toast } from 'react-hot-toast'; -import { Captions, Download, Sparkles } from 'lucide-react'; +import { Captions, Download, Sparkles, ChevronDown, ChevronUp } from 'lucide-react'; import FileUpload from '@/components/FileUpload'; import JobProgress from '@/components/JobProgress'; import { modulesApi, assetsApi } from '@/lib/api'; @@ -35,6 +35,25 @@ const targetLanguages = [ { value: 'ZH', label: 'Chinese' }, ]; +const fonts = [ + 'Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana', + 'Georgia', 'Comic Sans MS', 'Impact', 'Tahoma', 'Trebuchet MS', + 'Lucida Sans', 'Lucida Console', 'Palatino Linotype', 'Book Antiqua', + 'Century Gothic', 'Franklin Gothic Medium', 'Garamond', 'Segoe UI', + 'Calibri', 'Cambria', 'Candara', 'Constantia', 'Consolas', 'Corbel' +]; + +const colors = [ + { value: 'white', label: 'White' }, + { value: 'yellow', label: 'Yellow' }, + { value: 'black', label: 'Black' }, + { value: 'red', label: 'Red' }, + { value: 'blue', label: 'Blue' }, + { value: 'green', label: 'Green' }, + { value: 'orange', label: 'Orange' }, + { value: 'purple', label: 'Purple' }, +]; + export default function SubtitlesPage() { const { addJob, updateJob } = useStore(); const [file, setFile] = useState(null); @@ -42,6 +61,17 @@ export default function SubtitlesPage() { const [sourceLanguage, setSourceLanguage] = useState(''); const [targetLanguage, setTargetLanguage] = useState(''); const [burnSubtitles, setBurnSubtitles] = useState(false); + + // Styling options + const [font, setFont] = useState('Arial'); + const [fontSize, setFontSize] = useState(24); + const [textColor, setTextColor] = useState('white'); + const [outlineColor, setOutlineColor] = useState('black'); + const [outlineWidth, setOutlineWidth] = useState(2); + const [position, setPosition] = useState('bottom'); + + const [showAdvanced, setShowAdvanced] = useState(false); + const [jobId, setJobId] = useState(null); const [results, setResults] = useState(null); const [loading, setLoading] = useState(false); @@ -64,7 +94,7 @@ export default function SubtitlesPage() { if (shouldOverwrite) { try { - const response = await assetsApi.upload(uploadedFile, undefined, false, true); // overwrite=true + const response = await assetsApi.upload(uploadedFile, undefined, false, true); setAssetId(response.data.id); toast.success('Video overwritten!'); } catch (retryErr: any) { @@ -99,6 +129,12 @@ export default function SubtitlesPage() { source_language: sourceLanguage || undefined, target_language: targetLanguage || undefined, burn_subtitles: burnSubtitles, + font, + font_size: fontSize, + text_color: textColor, + outline_color: outlineColor, + outline_width: outlineWidth, + position, }); const job = response.data; @@ -165,7 +201,7 @@ export default function SubtitlesPage() {

Subtitle Generator

-

Auto-generate and translate subtitles

+

Auto-generate and translate subtitles with custom styling

@@ -200,7 +236,7 @@ export default function SubtitlesPage() { ) => setTargetLanguage(e.target.value)} + onChange={(e) => setTargetLanguage(e.target.value)} className="select-field" > {targetLanguages.map((lang) => ( @@ -228,13 +264,109 @@ export default function SubtitlesPage() { + {/* Subtitle Styling */} +
+

Subtitle Styling

+ +
+
+ + +
+
+ + setFontSize(parseInt(e.target.value))} + min="8" + max="72" + className="input-field" + /> +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + setOutlineWidth(parseFloat(e.target.value))} + min="0" + max="4" + step="0.1" + className="input-field" + /> +
+
+ + +
+
+
+ {/* Burn Subtitles */}
) => setBurnSubtitles(e.target.checked)} + onChange={(e) => setBurnSubtitles(e.target.checked)} className="w-4 h-4 rounded border-gray-600 bg-forge-dark text-forge-yellow focus:ring-forge-yellow" />