subtitle-merge/src/App.tsx

182 lines
5.7 KiB
TypeScript

import { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { FileSelector } from './components/FileSelector';
import './styles/index.css';
interface FileState {
path: string;
name: string;
}
function App() {
const [videoFile, setVideoFile] = useState<FileState | null>(null);
const [subtitleFile, setSubtitleFile] = useState<FileState | null>(null);
const [progress, setProgress] = useState<number>(0);
const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [addBlackBar, setAddBlackBar] = useState(true);
const [resizeTo720p, setResizeTo720p] = useState(true);
useEffect(() => {
invoke('check_dependencies')
.catch(err => {
setError(err as string);
console.error('Dependency check failed:', err);
});
}, []);
const handleVideoSelect = (path: string) => {
const name = path.split('/').pop() || '';
setVideoFile({ path, name });
};
const handleSubtitleSelect = (path: string) => {
const name = path.split('/').pop() || '';
setSubtitleFile({ path, name });
};
useEffect(() => {
let intervalId: number | undefined;
if (isProcessing) {
// Poll for progress every 100ms while processing
intervalId = window.setInterval(async () => {
try {
const currentProgress = await invoke<number>('get_progress');
console.log('Progress update:', currentProgress);
setProgress(currentProgress);
} catch (error) {
console.error('Failed to get progress:', error);
}
}, 100);
}
return () => {
if (intervalId !== undefined) {
window.clearInterval(intervalId);
}
};
}, [isProcessing]);
const handleMerge = async () => {
if (!videoFile || !subtitleFile) return;
setIsProcessing(true);
setProgress(0);
try {
console.log('Starting merge with args:', {
videoPath: videoFile.path,
subtitlePath: subtitleFile.path,
addBlackBar,
resizeTo720p
});
const result = await invoke<string>('merge_video_subtitle', {
args: {
video_path: videoFile.path,
subtitle_path: subtitleFile.path,
add_black_bar: addBlackBar,
resize_to_720p: resizeTo720p
}
});
console.log('Merge completed:', result);
setProgress(100);
} catch (error) {
console.error('Error merging files:', error);
setError(error as string);
} finally {
setIsProcessing(false);
}
};
return (
<div className="min-h-screen bg-gray-100 py-8 px-4">
<div className="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
<h1 className="text-2xl font-bold text-center mb-8">Video Subtitle Merger <span className="text-blue-500">2.0</span></h1>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex-1 mr-4">
<p className="text-sm text-gray-600 mb-2">Video file:</p>
<p className="text-gray-800">{videoFile?.name || 'No file selected'}</p>
</div>
<FileSelector
label="Select Video"
onFileSelect={handleVideoSelect}
accept={['mp4', 'avi', 'mov', 'mkv']}
/>
</div>
<div className="flex items-center justify-between">
<div className="flex-1 mr-4">
<p className="text-sm text-gray-600 mb-2">Subtitle file:</p>
<p className="text-gray-800">{subtitleFile?.name || 'No file selected'}</p>
</div>
<FileSelector
label="Select Subtitle"
onFileSelect={handleSubtitleSelect}
accept={['srt', 'ass']}
/>
</div>
<div className="space-y-3">
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={addBlackBar}
onChange={(e) => setAddBlackBar(e.target.checked)}
className="input-checkbox"
/>
<span>Add black bar for subtitles</span>
</label>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={resizeTo720p}
onChange={(e) => setResizeTo720p(e.target.checked)}
className="input-checkbox"
/>
<span>Resize to 720p resolution</span>
</label>
</div>
<div className="space-y-2">
<div className="h-2.5 bg-gray-200 rounded-full">
<div
className="h-2.5 bg-blue-600 rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
<p className="text-center text-sm text-gray-600">
{isProcessing ? `${Math.round(progress)}%` : ''}
</p>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4">
<strong className="font-bold">Error: </strong>
<span className="block sm:inline">{error}</span>
</div>
)}
<button
onClick={handleMerge}
disabled={!videoFile || !subtitleFile || isProcessing}
className={`w-full btn ${
!videoFile || !subtitleFile || isProcessing
? 'bg-gray-400 cursor-not-allowed'
: 'btn-primary'
}`}
>
{isProcessing ? 'Processing...' : 'Merge Video and Subtitle'}
</button>
</div>
</div>
</div>
);
}
export default App;