添加视频上传界面
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 数字人口播视频生成系统
|
**项目**:ViGent 数字人口播视频生成系统
|
||||||
**服务器**:Dell R730 (2× RTX 3090 24GB)
|
**服务器**:Dell R730 (2× RTX 3090 24GB)
|
||||||
**更新时间**:2026-01-16
|
**更新时间**:2026-01-19
|
||||||
**整体进度**:100%(MuseTalk 口型同步完整修复,端到端验证通过)
|
**整体进度**: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)
|
- [部署指南](file:///d:/CodingProjects/Antigravity/ViGent/Docs/DEPLOY_MANUAL.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -68,6 +68,11 @@
|
|||||||
- [x] 视频合成 MP4 生成验证
|
- [x] 视频合成 MP4 生成验证
|
||||||
- [x] 端到端流程完整测试
|
- [x] 端到端流程完整测试
|
||||||
|
|
||||||
|
### 阶段八:前端功能增强 (Day 5)
|
||||||
|
- [x] Web 视频上传功能
|
||||||
|
- [x] 上传进度显示
|
||||||
|
- [x] 自动刷新素材列表
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛤️ 后续规划
|
## 🛤️ 后续规划
|
||||||
@@ -163,5 +168,10 @@ Day 4: 口型同步完整修复 ✅ 完成
|
|||||||
- audio_processor.py 音视频长度修复
|
- audio_processor.py 音视频长度修复
|
||||||
- inference.py 错误日志增强
|
- inference.py 错误日志增强
|
||||||
- MP4 视频合成验证通过
|
- MP4 视频合成验证通过
|
||||||
|
|
||||||
|
Day 5: 前端功能增强 ✅ 完成
|
||||||
|
- Web 视频上传功能
|
||||||
|
- 上传进度显示
|
||||||
|
- 自动刷新素材列表
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ export default function Home() {
|
|||||||
const [generatedVideo, setGeneratedVideo] = useState<string | null>(null);
|
const [generatedVideo, setGeneratedVideo] = useState<string | null>(null);
|
||||||
const [fetchError, setFetchError] = useState<string | null>(null);
|
const [fetchError, setFetchError] = useState<string | null>(null);
|
||||||
const [debugData, setDebugData] = useState<string>("");
|
const [debugData, setDebugData] = useState<string>("");
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
|
const [uploadError, setUploadError] = useState<string | null>(null);
|
||||||
|
|
||||||
// 可选音色
|
// 可选音色
|
||||||
const voices = [
|
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 () => {
|
const handleGenerate = async () => {
|
||||||
if (!selectedMaterial || !text.trim()) {
|
if (!selectedMaterial || !text.trim()) {
|
||||||
@@ -162,14 +217,62 @@ export default function Home() {
|
|||||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||||
📹 选择素材视频
|
📹 选择素材视频
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<div className="flex gap-2">
|
||||||
onClick={fetchMaterials}
|
{/* 隐藏的文件输入 */}
|
||||||
className="px-3 py-1 text-xs bg-white/10 hover:bg-white/20 rounded text-gray-300"
|
<input
|
||||||
>
|
type="file"
|
||||||
🔄 刷新
|
id="video-upload"
|
||||||
</button>
|
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"
|
||||||
|
>
|
||||||
|
🔄 刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</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 ? (
|
{fetchError ? (
|
||||||
<div className="p-4 bg-red-500/20 text-red-200 rounded-xl text-sm mb-4">
|
<div className="p-4 bg-red-500/20 text-red-200 rounded-xl text-sm mb-4">
|
||||||
获取素材失败: {fetchError}
|
获取素材失败: {fetchError}
|
||||||
@@ -178,16 +281,11 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
) : materials.length === 0 ? (
|
) : materials.length === 0 ? (
|
||||||
<div className="text-center py-8 text-gray-400">
|
<div className="text-center py-8 text-gray-400">
|
||||||
|
<div className="text-5xl mb-4">📁</div>
|
||||||
<p>暂无素材视频</p>
|
<p>暂无素材视频</p>
|
||||||
<p className="text-sm mt-2">
|
<p className="text-sm mt-2">
|
||||||
请将视频放入 backend/uploads/materials/ 目录
|
点击上方「📤 上传视频」按钮添加素材
|
||||||
</p>
|
</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>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user