4e06f4abe2
- 后端: 空镜素材迁移到 config/materials.json,duration从文件名_{N}s_自动解析
- 后端: 新增 POST /api/v1/materials/match 接口,后端做关键词匹配
- 前端: VideoGeneration 空镜匹配改为调用后端接口
- 前端: 人物出镜素材改为本地文件选择器直接选取,不走素材库
- 前端: 视频生成流程简化,移除Vidu对口型和七牛云上传
- Rust: 视频合成支持从随机起始时间截取人物素材片段
- Rust: 修复ffprobe参数错误(添加-show_entries format=duration)
190 lines
5.5 KiB
Python
190 lines
5.5 KiB
Python
"""
|
|
文件上传 API
|
|
============
|
|
|
|
提供通用文件上传功能,直接上传到七牛云对象存储。
|
|
"""
|
|
|
|
import logging
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
import io
|
|
|
|
from fastapi import APIRouter, File, HTTPException, UploadFile
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.schemas.common import ApiResponse, success_response
|
|
from app.services.qiniu_service import get_qiniu_service
|
|
|
|
router = APIRouter(prefix="/upload", tags=["Upload"])
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class UploadResponse(BaseModel):
|
|
"""上传响应"""
|
|
|
|
url: str = Field(..., description="七牛云文件 URL")
|
|
key: str = Field(..., description="七牛云文件 key")
|
|
size: int = Field(..., description="文件大小(字节)")
|
|
|
|
|
|
@router.post("/video", response_model=ApiResponse[UploadResponse])
|
|
async def upload_video(
|
|
file: UploadFile = File(..., description="视频文件"),
|
|
):
|
|
"""
|
|
上传视频到七牛云
|
|
|
|
支持格式:mp4, mov, avi, webm
|
|
返回七牛云永久访问 URL。
|
|
"""
|
|
try:
|
|
# 验证文件格式
|
|
allowed_types = {
|
|
"video/mp4",
|
|
"video/quicktime",
|
|
"video/x-msvideo",
|
|
"video/webm",
|
|
}
|
|
content_type = file.content_type or ""
|
|
|
|
# 如果 content_type 为空,尝试从文件名推断
|
|
if not content_type:
|
|
ext = Path(file.filename or "").suffix.lower()
|
|
ext_to_mime = {
|
|
".mp4": "video/mp4",
|
|
".mov": "video/quicktime",
|
|
".avi": "video/x-msvideo",
|
|
".webm": "video/webm",
|
|
}
|
|
content_type = ext_to_mime.get(ext, "")
|
|
|
|
if content_type not in allowed_types:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"不支持的文件格式: {content_type},请上传 mp4/mov/avi/webm 视频",
|
|
)
|
|
|
|
# 读取文件内容
|
|
content = await file.read()
|
|
if not content:
|
|
raise HTTPException(status_code=400, detail="文件内容为空")
|
|
|
|
# 生成唯一文件名
|
|
ext = Path(file.filename or "video.mp4").suffix or ".mp4"
|
|
unique_name = f"{uuid.uuid4().hex[:16]}{ext}"
|
|
|
|
# 上传到七牛云
|
|
qiniu = get_qiniu_service()
|
|
bucket, domain = qiniu._get_bucket_and_domain("video")
|
|
key = qiniu.generate_key("video", unique_name)
|
|
stream = io.BytesIO(content)
|
|
result = qiniu.upload_stream(
|
|
stream=stream,
|
|
key=key,
|
|
mime_type=content_type or "video/mp4",
|
|
bucket=bucket,
|
|
domain=domain,
|
|
)
|
|
|
|
url = result.get("url")
|
|
key = result.get("key")
|
|
|
|
if not url:
|
|
raise HTTPException(status_code=500, detail="上传到七牛云失败:未返回 URL")
|
|
|
|
logger.info(f"[Upload] 视频上传成功: {url[:80]}..., size={len(content)}")
|
|
|
|
return success_response(
|
|
data=UploadResponse(
|
|
url=url,
|
|
key=key or unique_name,
|
|
size=len(content),
|
|
)
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"[Upload] 视频上传失败: {e}")
|
|
raise HTTPException(status_code=500, detail=f"上传失败: {e}")
|
|
|
|
|
|
@router.post("/image", response_model=ApiResponse[UploadResponse])
|
|
async def upload_image(
|
|
file: UploadFile = File(..., description="图片文件"),
|
|
):
|
|
"""
|
|
上传图片到七牛云
|
|
|
|
支持格式:jpg, png, gif, webp
|
|
"""
|
|
try:
|
|
allowed_types = {
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/gif",
|
|
"image/webp",
|
|
}
|
|
content_type = file.content_type or ""
|
|
|
|
if not content_type:
|
|
ext = Path(file.filename or "").suffix.lower()
|
|
ext_to_mime = {
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".png": "image/png",
|
|
".gif": "image/gif",
|
|
".webp": "image/webp",
|
|
}
|
|
content_type = ext_to_mime.get(ext, "")
|
|
|
|
if content_type not in allowed_types:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"不支持的图片格式: {content_type}",
|
|
)
|
|
|
|
content = await file.read()
|
|
if not content:
|
|
raise HTTPException(status_code=400, detail="文件内容为空")
|
|
|
|
ext = Path(file.filename or "image.jpg").suffix or ".jpg"
|
|
unique_name = f"{uuid.uuid4().hex[:16]}{ext}"
|
|
|
|
qiniu = get_qiniu_service()
|
|
bucket, domain = qiniu._get_bucket_and_domain("image")
|
|
key = qiniu.generate_key("image", unique_name)
|
|
stream = io.BytesIO(content)
|
|
result = qiniu.upload_stream(
|
|
stream=stream,
|
|
key=key,
|
|
mime_type=content_type or "image/jpeg",
|
|
bucket=bucket,
|
|
domain=domain,
|
|
)
|
|
|
|
url = result.get("url")
|
|
key = result.get("key")
|
|
|
|
if not url:
|
|
raise HTTPException(status_code=500, detail="上传到七牛云失败:未返回 URL")
|
|
|
|
logger.info(f"[Upload] 图片上传成功: {url[:80]}..., size={len(content)}")
|
|
|
|
return success_response(
|
|
data=UploadResponse(
|
|
url=url,
|
|
key=key or unique_name,
|
|
size=len(content),
|
|
)
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"[Upload] 图片上传失败: {e}")
|
|
raise HTTPException(status_code=500, detail=f"上传失败: {e}")
|