Add comprehensive subtitle styling UI with font, colors, and position controls
This commit is contained in:
parent
dade418940
commit
f92eb07546
1 changed files with 138 additions and 6 deletions
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue