video-flow-b/components/pages/video-to-video.tsx
2025-07-03 01:46:58 +08:00

233 lines
8.4 KiB
TypeScript

"use client";
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { ArrowLeft, Upload, Loader2 } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { ModeEnum, ResolutionEnum } from "@/api/enums";
import { createScriptEpisode, CreateScriptEpisodeRequest, updateScriptEpisode, UpdateScriptEpisodeRequest } from "@/api/script_episode";
import { convertVideoToScene } from "@/api/video_flow";
import { getUploadToken, uploadToQiniu } from "@/api/common";
export function VideoToVideo() {
const router = useRouter();
const [videoUrl, setVideoUrl] = useState('');
const [selectedMode, setSelectedMode] = useState<ModeEnum>(ModeEnum.AUTOMATIC);
const [selectedResolution, setSelectedResolution] = useState<ResolutionEnum>(ResolutionEnum.HD_720P);
const [isUploading, setIsUploading] = useState(false);
const [isCreating, setIsCreating] = useState(false);
const handleBack = () => {
router.push('/create');
};
const handleUploadVideo = async () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'video/*';
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
try {
setIsUploading(true);
// Get upload token
const { token } = await getUploadToken();
// Upload to Qiniu
const uploadedVideoUrl = await uploadToQiniu(file, token);
setVideoUrl(uploadedVideoUrl);
} catch (error) {
console.error('Upload error:', error);
alert('Upload failed, please try again');
} finally {
setIsUploading(false);
}
}
};
input.click();
};
const handleCreate = async () => {
if (!videoUrl) {
alert('请先上传视频');
return;
}
try {
setIsCreating(true);
// Create episode
const episodeData: CreateScriptEpisodeRequest = {
title: "Video Episode",
script_id: 0, // This should come from a project
status: 1,
summary: "Video conversion"
};
const episodeResponse = await createScriptEpisode(episodeData);
if (episodeResponse.code !== 0) {
alert(`创建剧集失败: ${episodeResponse.message}`);
return;
}
const episodeId = episodeResponse.data.id;
// Convert video to scenes
const convertResponse = await convertVideoToScene(videoUrl, episodeId, 0);
if (convertResponse.code === 0) {
// Update episode with generated data
const updateEpisodeData: UpdateScriptEpisodeRequest = {
id: episodeId,
atmosphere: convertResponse.data.atmosphere,
summary: convertResponse.data.summary,
scene: convertResponse.data.scene,
characters: convertResponse.data.characters,
};
await updateScriptEpisode(updateEpisodeData);
// Navigate to workflow
router.push(`/create/work-flow?episodeId=${episodeId}`);
} else {
alert(`转换失败: ${convertResponse.message}`);
}
} catch (error) {
console.error('创建过程出错:', error);
alert("创建项目时发生错误,请稍后重试");
} finally {
setIsCreating(false);
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 p-6">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="flex items-center gap-4 mb-8">
<Button
variant="ghost"
size="sm"
onClick={handleBack}
className="text-white/70 hover:text-white"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold text-white">Video to Video</h1>
</div>
{/* Main Content */}
<Card className="p-6 bg-white/5 border-white/10">
<div className="space-y-6">
{/* Video Upload */}
<div>
<label className="block text-sm font-medium text-white/70 mb-2">
Upload Video
</label>
<div className="border-2 border-dashed border-white/20 rounded-lg p-8 text-center">
{videoUrl ? (
<div className="space-y-4">
<video
src={videoUrl}
controls
className="max-w-full h-64 mx-auto rounded-lg"
/>
<Button
variant="outline"
onClick={handleUploadVideo}
disabled={isUploading}
className="border-white/20 text-white hover:bg-white/10"
>
Replace Video
</Button>
</div>
) : (
<div className="space-y-4">
<Upload className="h-12 w-12 text-white/40 mx-auto" />
<div>
<p className="text-white/70 mb-2">Click to upload a video file</p>
<Button
onClick={handleUploadVideo}
disabled={isUploading}
className="bg-purple-600 hover:bg-purple-700"
>
{isUploading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Uploading...
</>
) : (
<>
<Upload className="h-4 w-4 mr-2" />
Choose Video
</>
)}
</Button>
</div>
</div>
)}
</div>
</div>
{/* Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-white/70 mb-2">
Mode
</label>
<Select value={selectedMode.toString()} onValueChange={(value) => setSelectedMode(Number(value) as ModeEnum)}>
<SelectTrigger className="bg-white/5 border-white/20 text-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={ModeEnum.AUTOMATIC.toString()}>Automatic</SelectItem>
<SelectItem value={ModeEnum.MANUAL.toString()}>Manual</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-medium text-white/70 mb-2">
Resolution
</label>
<Select value={selectedResolution.toString()} onValueChange={(value) => setSelectedResolution(Number(value) as ResolutionEnum)}>
<SelectTrigger className="bg-white/5 border-white/20 text-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={ResolutionEnum.HD_720P.toString()}>720P HD</SelectItem>
<SelectItem value={ResolutionEnum.FULL_HD_1080P.toString()}>1080P Full HD</SelectItem>
<SelectItem value={ResolutionEnum.UHD_2K.toString()}>2K UHD</SelectItem>
<SelectItem value={ResolutionEnum.UHD_4K.toString()}>4K UHD</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* Create Button */}
<div className="flex justify-end">
<Button
onClick={handleCreate}
disabled={isCreating || !videoUrl}
className="bg-purple-600 hover:bg-purple-700"
>
{isCreating ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Creating...
</>
) : (
'Create Video'
)}
</Button>
</div>
</div>
</Card>
</div>
</div>
);
}