Add comprehensive subtitle styling UI with font, colors, and position controls

This commit is contained in:
DJP 2025-12-10 22:18:33 -05:00
parent dade418940
commit f92eb07546

View file

@ -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<File | null>(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<string | null>(null);
const [results, setResults] = useState<any>(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() {
</div>
<div>
<h1 className="text-2xl font-bold text-white">Subtitle Generator</h1>
<p className="text-gray-500">Auto-generate and translate subtitles</p>
<p className="text-gray-500">Auto-generate and translate subtitles with custom styling</p>
</div>
</div>
@ -200,7 +236,7 @@ export default function SubtitlesPage() {
</label>
<select
value={sourceLanguage}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setSourceLanguage(e.target.value)}
onChange={(e) => setSourceLanguage(e.target.value)}
className="select-field"
>
{languages.map((lang) => (
@ -216,7 +252,7 @@ export default function SubtitlesPage() {
</label>
<select
value={targetLanguage}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setTargetLanguage(e.target.value)}
onChange={(e) => setTargetLanguage(e.target.value)}
className="select-field"
>
{targetLanguages.map((lang) => (
@ -228,13 +264,109 @@ export default function SubtitlesPage() {
</div>
</div>
{/* Subtitle Styling */}
<div className="bg-forge-dark rounded-xl border border-gray-800 p-4 space-y-4">
<h3 className="text-white font-medium">Subtitle Styling</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Font
</label>
<select
value={font}
onChange={(e) => setFont(e.target.value)}
className="select-field"
>
{fonts.map((f) => (
<option key={f} value={f}>{f}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Font Size
</label>
<input
type="number"
value={fontSize}
onChange={(e) => setFontSize(parseInt(e.target.value))}
min="8"
max="72"
className="input-field"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Text Color
</label>
<select
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
className="select-field"
>
{colors.map((c) => (
<option key={c.value} value={c.value}>{c.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Outline Color
</label>
<select
value={outlineColor}
onChange={(e) => setOutlineColor(e.target.value)}
className="select-field"
>
{colors.map((c) => (
<option key={c.value} value={c.value}>{c.label}</option>
))}
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Outline Width
</label>
<input
type="number"
value={outlineWidth}
onChange={(e) => setOutlineWidth(parseFloat(e.target.value))}
min="0"
max="4"
step="0.1"
className="input-field"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Position
</label>
<select
value={position}
onChange={(e) => setPosition(e.target.value)}
className="select-field"
>
<option value="bottom">Bottom</option>
<option value="top">Top</option>
</select>
</div>
</div>
</div>
{/* Burn Subtitles */}
<div className="flex items-center gap-3">
<input
type="checkbox"
id="burnSubtitles"
checked={burnSubtitles}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => 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"
/>
<label htmlFor="burnSubtitles" className="text-gray-300">