添加视频上传界面
This commit is contained in:
34
Docs/DevLogs/Day5.md
Normal file
34
Docs/DevLogs/Day5.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Day 5: 前端视频上传功能
|
||||
|
||||
---
|
||||
|
||||
## 🆕 Web 视频上传功能 (15:32)
|
||||
|
||||
**需求**:用户需要通过 Web 界面上传原视频,而非手动放入目录
|
||||
|
||||
**实现**:
|
||||
- 后端 `POST /api/materials/` 已有完整上传接口
|
||||
- 前端 `page.tsx` 新增上传 UI 组件
|
||||
|
||||
### 修改的文件
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `frontend/src/app/page.tsx` | 添加上传按钮、进度条、handleUpload 函数 |
|
||||
|
||||
### 功能特性
|
||||
|
||||
1. **上传按钮**:紫色渐变样式,位于素材区块标题栏
|
||||
2. **文件验证**:仅接受 MP4/MOV/AVI 格式
|
||||
3. **进度显示**:使用 XMLHttpRequest 实时追踪上传进度
|
||||
4. **自动刷新**:上传成功后自动刷新素材列表
|
||||
5. **错误处理**:显示文件类型错误、网络错误等提示
|
||||
|
||||
---
|
||||
|
||||
## ✅ Day 5 完成事项
|
||||
|
||||
- [x] 添加视频上传 UI 组件
|
||||
- [x] 实现上传进度显示
|
||||
- [x] 上传成功后自动刷新素材列表
|
||||
- [ ] 手动测试验证
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
**项目**:ViGent 数字人口播视频生成系统
|
||||
**服务器**:Dell R730 (2× RTX 3090 24GB)
|
||||
**更新时间**:2026-01-16
|
||||
**整体进度**:100%(MuseTalk 口型同步完整修复,端到端验证通过)
|
||||
**更新时间**:2026-01-19
|
||||
**整体进度**:100%(Day 5 前端视频上传功能完成)
|
||||
|
||||
## 📖 快速导航
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| [时间线](#-时间线) | 开发历程 |
|
||||
|
||||
**相关文档**:
|
||||
- [Day 日志](file:///d:/CodingProjects/Antigravity/ViGent/Docs/DevLogs/) (Day1-4)
|
||||
- [Day 日志](file:///d:/CodingProjects/Antigravity/ViGent/Docs/DevLogs/) (Day1-5)
|
||||
- [部署指南](file:///d:/CodingProjects/Antigravity/ViGent/Docs/DEPLOY_MANUAL.md)
|
||||
|
||||
---
|
||||
@@ -68,6 +68,11 @@
|
||||
- [x] 视频合成 MP4 生成验证
|
||||
- [x] 端到端流程完整测试
|
||||
|
||||
### 阶段八:前端功能增强 (Day 5)
|
||||
- [x] Web 视频上传功能
|
||||
- [x] 上传进度显示
|
||||
- [x] 自动刷新素材列表
|
||||
|
||||
---
|
||||
|
||||
## 🛤️ 后续规划
|
||||
@@ -163,5 +168,10 @@ Day 4: 口型同步完整修复 ✅ 完成
|
||||
- audio_processor.py 音视频长度修复
|
||||
- inference.py 错误日志增强
|
||||
- MP4 视频合成验证通过
|
||||
|
||||
Day 5: 前端功能增强 ✅ 完成
|
||||
- Web 视频上传功能
|
||||
- 上传进度显示
|
||||
- 自动刷新素材列表
|
||||
```
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ export default function Home() {
|
||||
const [generatedVideo, setGeneratedVideo] = useState<string | null>(null);
|
||||
const [fetchError, setFetchError] = useState<string | null>(null);
|
||||
const [debugData, setDebugData] = useState<string>("");
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [uploadError, setUploadError] = useState<string | null>(null);
|
||||
|
||||
// 可选音色
|
||||
const voices = [
|
||||
@@ -83,6 +86,58 @@ export default function Home() {
|
||||
}
|
||||
};
|
||||
|
||||
// 上传视频
|
||||
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// 验证文件类型
|
||||
const validTypes = ['.mp4', '.mov', '.avi'];
|
||||
const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.'));
|
||||
if (!validTypes.includes(ext)) {
|
||||
setUploadError('仅支持 MP4、MOV、AVI 格式');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
setUploadProgress(0);
|
||||
setUploadError(null);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// 使用 XMLHttpRequest 以获取上传进度
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
const progress = Math.round((event.loaded / event.total) * 100);
|
||||
setUploadProgress(progress);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
setIsUploading(false);
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
fetchMaterials(); // 刷新素材列表
|
||||
setUploadProgress(100);
|
||||
} else {
|
||||
setUploadError(`上传失败: ${xhr.statusText}`);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
setIsUploading(false);
|
||||
setUploadError('网络错误,上传失败');
|
||||
};
|
||||
|
||||
xhr.open('POST', `${API_BASE}/api/materials/`);
|
||||
xhr.send(formData);
|
||||
|
||||
// 清空 input 以便可以再次选择同一文件
|
||||
e.target.value = '';
|
||||
};
|
||||
|
||||
// 生成视频
|
||||
const handleGenerate = async () => {
|
||||
if (!selectedMaterial || !text.trim()) {
|
||||
@@ -162,6 +217,24 @@ export default function Home() {
|
||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||
📹 选择素材视频
|
||||
</h2>
|
||||
<div className="flex gap-2">
|
||||
{/* 隐藏的文件输入 */}
|
||||
<input
|
||||
type="file"
|
||||
id="video-upload"
|
||||
accept=".mp4,.mov,.avi"
|
||||
onChange={handleUpload}
|
||||
className="hidden"
|
||||
/>
|
||||
<label
|
||||
htmlFor="video-upload"
|
||||
className={`px-3 py-1 text-xs rounded cursor-pointer transition-all ${isUploading
|
||||
? "bg-gray-600 cursor-not-allowed text-gray-400"
|
||||
: "bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white"
|
||||
}`}
|
||||
>
|
||||
📤 上传视频
|
||||
</label>
|
||||
<button
|
||||
onClick={fetchMaterials}
|
||||
className="px-3 py-1 text-xs bg-white/10 hover:bg-white/20 rounded text-gray-300"
|
||||
@@ -169,6 +242,36 @@ export default function Home() {
|
||||
🔄 刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 上传进度条 */}
|
||||
{isUploading && (
|
||||
<div className="mb-4 p-4 bg-purple-500/10 rounded-xl border border-purple-500/30">
|
||||
<div className="flex justify-between text-sm text-purple-300 mb-2">
|
||||
<span>📤 上传中...</span>
|
||||
<span>{uploadProgress}%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-black/30 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-purple-500 to-pink-500 transition-all duration-300"
|
||||
style={{ width: `${uploadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 上传错误提示 */}
|
||||
{uploadError && (
|
||||
<div className="mb-4 p-4 bg-red-500/20 text-red-200 rounded-xl text-sm flex justify-between items-center">
|
||||
<span>❌ {uploadError}</span>
|
||||
<button
|
||||
onClick={() => setUploadError(null)}
|
||||
className="text-red-300 hover:text-white"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{fetchError ? (
|
||||
<div className="p-4 bg-red-500/20 text-red-200 rounded-xl text-sm mb-4">
|
||||
@@ -178,16 +281,11 @@ export default function Home() {
|
||||
</div>
|
||||
) : materials.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
<div className="text-5xl mb-4">📁</div>
|
||||
<p>暂无素材视频</p>
|
||||
<p className="text-sm mt-2">
|
||||
请将视频放入 backend/uploads/materials/ 目录
|
||||
点击上方「📤 上传视频」按钮添加素材
|
||||
</p>
|
||||
<div className="mt-4 p-4 bg-black/40 rounded text-left text-xs font-mono text-gray-500 overflow-auto whitespace-pre-wrap break-all">
|
||||
<p className="font-bold text-purple-400">Debug Info:</p>
|
||||
<p>Items: {materials.length}</p>
|
||||
<p className="mt-2 text-gray-400 border-t border-gray-700 pt-2">Raw Response:</p>
|
||||
<p>{debugData}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
|
||||
Reference in New Issue
Block a user