界面优化

This commit is contained in:
Kevin Wong
2026-01-22 11:14:42 +08:00
parent c7e2b4d363
commit ad7ff7a385
5 changed files with 324 additions and 41 deletions

View File

@@ -1,19 +1,33 @@
from fastapi import APIRouter, UploadFile, File, HTTPException
from app.core.config import settings
import shutil
import uuid
import re
import time
from pathlib import Path
router = APIRouter()
def sanitize_filename(filename: str) -> str:
"""清理文件名,移除不安全字符"""
# 移除路径分隔符和特殊字符
safe_name = re.sub(r'[<>:"/\\|?*]', '_', filename)
# 限制长度
if len(safe_name) > 100:
ext = Path(safe_name).suffix
safe_name = safe_name[:100 - len(ext)] + ext
return safe_name
@router.post("/")
async def upload_material(file: UploadFile = File(...)):
if not file.filename.lower().endswith(('.mp4', '.mov', '.avi')):
raise HTTPException(400, "Invalid format")
file_id = str(uuid.uuid4())
ext = Path(file.filename).suffix
save_path = settings.UPLOAD_DIR / "materials" / f"{file_id}{ext}"
# 使用时间戳+原始文件名(保留原始名称,避免冲突)
timestamp = int(time.time())
safe_name = sanitize_filename(file.filename)
save_path = settings.UPLOAD_DIR / "materials" / f"{timestamp}_{safe_name}"
# Save file
with open(save_path, "wb") as buffer:
@@ -21,11 +35,14 @@ async def upload_material(file: UploadFile = File(...)):
# Calculate size
size_mb = save_path.stat().st_size / (1024 * 1024)
# 提取显示名称(去掉时间戳前缀)
display_name = safe_name
return {
"id": file_id,
"name": file.filename,
"path": f"uploads/materials/{file_id}{ext}",
"id": save_path.stem,
"name": display_name,
"path": f"uploads/materials/{save_path.name}",
"size_mb": size_mb,
"type": "video"
}
@@ -38,9 +55,16 @@ async def list_materials():
for f in materials_dir.glob("*"):
try:
stat = f.stat()
# 提取显示名称:去掉时间戳前缀 (格式: {timestamp}_{原始文件名})
display_name = f.name
if '_' in f.name:
parts = f.name.split('_', 1)
if parts[0].isdigit():
display_name = parts[1] # 原始文件名
files.append({
"id": f.stem,
"name": f.name,
"name": display_name,
"path": f"uploads/materials/{f.name}",
"size_mb": stat.st_size / (1024 * 1024),
"type": "video",
@@ -51,3 +75,26 @@ async def list_materials():
# Sort by creation time desc
files.sort(key=lambda x: x.get("created_at", 0), reverse=True)
return {"materials": files}
@router.delete("/{material_id}")
async def delete_material(material_id: str):
"""删除素材文件"""
materials_dir = settings.UPLOAD_DIR / "materials"
# 查找匹配的文件ID 是文件名不含扩展名)
found = None
for f in materials_dir.glob("*"):
if f.stem == material_id:
found = f
break
if not found:
raise HTTPException(404, "Material not found")
try:
found.unlink()
return {"success": True, "message": "素材已删除"}
except Exception as e:
raise HTTPException(500, f"删除失败: {str(e)}")

View File

@@ -141,3 +141,58 @@ async def lipsync_health():
"""获取 LipSync 服务健康状态"""
lipsync = _get_lipsync_service()
return await lipsync.check_health()
@router.get("/generated")
async def list_generated_videos():
"""从文件系统读取生成的视频列表(持久化)"""
output_dir = settings.OUTPUT_DIR
videos = []
if output_dir.exists():
for f in output_dir.glob("*_output.mp4"):
try:
stat = f.stat()
videos.append({
"id": f.stem,
"name": f.name,
"path": f"/outputs/{f.name}",
"size_mb": stat.st_size / (1024 * 1024),
"created_at": stat.st_ctime
})
except Exception:
continue
# Sort by creation time desc (newest first)
videos.sort(key=lambda x: x.get("created_at", 0), reverse=True)
return {"videos": videos}
@router.delete("/generated/{video_id}")
async def delete_generated_video(video_id: str):
"""删除生成的视频"""
output_dir = settings.OUTPUT_DIR
# 查找匹配的文件
found = None
for f in output_dir.glob("*.mp4"):
if f.stem == video_id:
found = f
break
if not found:
raise HTTPException(404, "Video not found")
try:
found.unlink()
# 同时删除相关的临时文件(如果存在)
task_id = video_id.replace("_output", "")
for suffix in ["_audio.mp3", "_lipsync.mp4"]:
temp_file = output_dir / f"{task_id}{suffix}"
if temp_file.exists():
temp_file.unlink()
return {"success": True, "message": "视频已删除"}
except Exception as e:
raise HTTPException(500, f"删除失败: {str(e)}")