增加了文件上传和url的支持
This commit is contained in:
440
api.py
440
api.py
@@ -1,8 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any, Callable, Dict, List, Optional
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -10,7 +10,7 @@ import uvicorn
|
|||||||
from fastapi import BackgroundTasks, FastAPI, File, Form, HTTPException, UploadFile
|
from fastapi import BackgroundTasks, FastAPI, File, Form, HTTPException, UploadFile
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from models import Task, TaskCreateRequest, TaskStatus
|
from models import Task, TaskStatus
|
||||||
from processor import ProcessManager
|
from processor import ProcessManager
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,9 @@ from processor import ProcessManager
|
|||||||
class TaskManager:
|
class TaskManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tasks: Dict[str, Task] = {}
|
self.tasks: Dict[str, Task] = {}
|
||||||
self.process_manager = ProcessManager(config_path="config/machines.json", task_manager=self)
|
self.process_manager = ProcessManager(
|
||||||
|
config_path="config/machines.json", task_manager=self
|
||||||
|
)
|
||||||
self.callbacks: Dict[str, List[Callable[[Task], Any]]] = {}
|
self.callbacks: Dict[str, List[Callable[[Task], Any]]] = {}
|
||||||
self._task_lock = asyncio.Lock()
|
self._task_lock = asyncio.Lock()
|
||||||
self._callback_lock = asyncio.Lock()
|
self._callback_lock = asyncio.Lock()
|
||||||
@@ -39,12 +41,19 @@ class TaskManager:
|
|||||||
logger.info("任务管理器已停止")
|
logger.info("任务管理器已停止")
|
||||||
|
|
||||||
async def add_task(
|
async def add_task(
|
||||||
self, audio_file_path: str, video_file_path: str, output_dir: str, description: str, callback_url: Optional[str] = None
|
self,
|
||||||
|
audio_file_path: str,
|
||||||
|
video_file_path: str,
|
||||||
|
output_dir: str,
|
||||||
|
description: str,
|
||||||
|
callback_url: Optional[str] = None,
|
||||||
) -> Task:
|
) -> Task:
|
||||||
"""添加新任务"""
|
"""添加新任务"""
|
||||||
async with self._task_lock:
|
async with self._task_lock:
|
||||||
task_id = f"task_{len(self.tasks) + 1}"
|
task_id = f"task_{len(self.tasks) + 1}"
|
||||||
output_file_path = os.path.join(output_dir, f"{task_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
|
output_file_path = os.path.join(
|
||||||
|
output_dir, f"{task_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
|
||||||
|
)
|
||||||
task = Task(
|
task = Task(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
audio_file_path=audio_file_path,
|
audio_file_path=audio_file_path,
|
||||||
@@ -60,7 +69,9 @@ class TaskManager:
|
|||||||
|
|
||||||
# 如果有回调URL,添加回调
|
# 如果有回调URL,添加回调
|
||||||
if callback_url:
|
if callback_url:
|
||||||
await self.add_callback(task_id, self._create_callback_handler(callback_url))
|
await self.add_callback(
|
||||||
|
task_id, self._create_callback_handler(callback_url)
|
||||||
|
)
|
||||||
|
|
||||||
return task
|
return task
|
||||||
|
|
||||||
@@ -104,12 +115,22 @@ class TaskManager:
|
|||||||
# 更新任务状态为已入队
|
# 更新任务状态为已入队
|
||||||
return await self.update_task_status(task_id, TaskStatus.QUEUED)
|
return await self.update_task_status(task_id, TaskStatus.QUEUED)
|
||||||
else:
|
else:
|
||||||
return await self.update_task_status(task_id, TaskStatus.FAILED, error_message="Failed to queue video processing task")
|
return await self.update_task_status(
|
||||||
|
task_id,
|
||||||
|
TaskStatus.FAILED,
|
||||||
|
error_message="Failed to queue video processing task",
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return await self.update_task_status(task_id, TaskStatus.FAILED, error_message=str(e))
|
return await self.update_task_status(
|
||||||
|
task_id, TaskStatus.FAILED, error_message=str(e)
|
||||||
|
)
|
||||||
|
|
||||||
async def update_task_status(
|
async def update_task_status(
|
||||||
self, task_id: str, status: TaskStatus, machine_name: Optional[str] = None, error_message: Optional[str] = None
|
self,
|
||||||
|
task_id: str,
|
||||||
|
status: TaskStatus,
|
||||||
|
machine_name: Optional[str] = None,
|
||||||
|
error_message: Optional[str] = None,
|
||||||
) -> Task:
|
) -> Task:
|
||||||
"""更新任务状态"""
|
"""更新任务状态"""
|
||||||
async with self._task_lock:
|
async with self._task_lock:
|
||||||
@@ -129,7 +150,9 @@ class TaskManager:
|
|||||||
|
|
||||||
# 触发回调
|
# 触发回调
|
||||||
if task_id in self.callbacks:
|
if task_id in self.callbacks:
|
||||||
logger.info(f"触发任务回调: {task_id}, 回调数量: {len(self.callbacks[task_id])}")
|
logger.info(
|
||||||
|
f"触发任务回调: {task_id}, 回调数量: {len(self.callbacks[task_id])}"
|
||||||
|
)
|
||||||
for callback in self.callbacks[task_id]:
|
for callback in self.callbacks[task_id]:
|
||||||
try:
|
try:
|
||||||
await callback(task)
|
await callback(task)
|
||||||
@@ -154,7 +177,9 @@ class TaskManager:
|
|||||||
self.callbacks[task_id] = []
|
self.callbacks[task_id] = []
|
||||||
self.callbacks[task_id].append(callback)
|
self.callbacks[task_id].append(callback)
|
||||||
|
|
||||||
async def remove_callback(self, task_id: str, callback: Callable[[Task], Any]) -> None:
|
async def remove_callback(
|
||||||
|
self, task_id: str, callback: Callable[[Task], Any]
|
||||||
|
) -> None:
|
||||||
"""移除任务回调"""
|
"""移除任务回调"""
|
||||||
async with self._callback_lock:
|
async with self._callback_lock:
|
||||||
if task_id in self.callbacks:
|
if task_id in self.callbacks:
|
||||||
@@ -181,62 +206,31 @@ async def shutdown_event():
|
|||||||
# API路由
|
# API路由
|
||||||
@app.post("/tasks/", response_model=Task)
|
@app.post("/tasks/", response_model=Task)
|
||||||
async def create_task(
|
async def create_task(
|
||||||
audio: UploadFile = File(...),
|
audio: Optional[UploadFile] = File(None),
|
||||||
video: UploadFile = File(...),
|
video: Optional[UploadFile] = File(None),
|
||||||
|
audio_url: Optional[str] = Form(None),
|
||||||
|
video_url: Optional[str] = Form(None),
|
||||||
description: str = Form(...),
|
description: str = Form(...),
|
||||||
callback_url: Optional[str] = Form(None),
|
callback_url: Optional[str] = Form(None),
|
||||||
background_tasks: BackgroundTasks = BackgroundTasks(),
|
background_tasks: BackgroundTasks = BackgroundTasks(),
|
||||||
):
|
):
|
||||||
"""创建新的视频处理任务"""
|
"""创建新的视频处理任务,支持文件上传或URL下载"""
|
||||||
try:
|
try:
|
||||||
# 创建临时目录
|
# 使用公共函数准备任务文件
|
||||||
temp_dir = os.path.join(os.getcwd(), "temp", datetime.now().strftime("%Y%m%d_%H%M%S"))
|
task, temp_dir = await prepare_task_files(
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
audio, video, audio_url, video_url, description, callback_url
|
||||||
try:
|
)
|
||||||
# 确保文件名不为None
|
|
||||||
if not audio.filename or not video.filename:
|
|
||||||
raise ValueError("文件名不能为空")
|
|
||||||
|
|
||||||
# 保存上传的文件
|
# 异步处理任务
|
||||||
audio_path = os.path.join(temp_dir, audio.filename)
|
background_tasks.add_task(
|
||||||
video_path = os.path.join(temp_dir, video.filename)
|
process_video_task,
|
||||||
|
task.task_id,
|
||||||
|
task.audio_file_path,
|
||||||
|
task.video_file_path,
|
||||||
|
task.output_file_path,
|
||||||
|
)
|
||||||
|
|
||||||
# 保存音频文件
|
return task
|
||||||
logger.info(f"开始保存音频文件: {audio_path}")
|
|
||||||
async with aiofiles.open(audio_path, "wb") as f:
|
|
||||||
content = await audio.read()
|
|
||||||
await f.write(content)
|
|
||||||
logger.info(f"音频文件保存完成: {audio_path}")
|
|
||||||
|
|
||||||
# 保存视频文件
|
|
||||||
logger.info(f"开始保存视频文件: {video_path}")
|
|
||||||
async with aiofiles.open(video_path, "wb") as f:
|
|
||||||
content = await video.read()
|
|
||||||
await f.write(content)
|
|
||||||
logger.info(f"视频文件保存完成: {video_path}")
|
|
||||||
|
|
||||||
# 验证文件是否保存成功
|
|
||||||
if not os.path.exists(audio_path) or not os.path.exists(video_path):
|
|
||||||
raise ValueError("文件保存失败")
|
|
||||||
|
|
||||||
# 创建输出目录
|
|
||||||
output_dir = os.path.join("output", datetime.now().strftime("%Y%m%d"))
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# 创建任务
|
|
||||||
task = await task_manager.add_task(audio_path, video_path, output_dir, description, callback_url)
|
|
||||||
|
|
||||||
# 异步处理任务
|
|
||||||
background_tasks.add_task(process_video_task, task.task_id, audio_path, video_path, task.output_file_path)
|
|
||||||
|
|
||||||
return task
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# 如果发生错误,立即清理临时文件
|
|
||||||
# if os.path.exists(temp_dir):
|
|
||||||
# shutil.rmtree(temp_dir)
|
|
||||||
logger.error(f"创建任务失败: {str(e)}")
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"创建任务失败: {str(e)}")
|
logger.error(f"创建任务失败: {str(e)}")
|
||||||
@@ -245,116 +239,88 @@ async def create_task(
|
|||||||
|
|
||||||
@app.post("/tasks/sync")
|
@app.post("/tasks/sync")
|
||||||
async def create_task_sync(
|
async def create_task_sync(
|
||||||
audio: UploadFile = File(...),
|
audio: Optional[UploadFile] = File(None),
|
||||||
video: UploadFile = File(...),
|
video: Optional[UploadFile] = File(None),
|
||||||
|
audio_url: Optional[str] = Form(None),
|
||||||
|
video_url: Optional[str] = Form(None),
|
||||||
description: str = Form(...),
|
description: str = Form(...),
|
||||||
callback_url: Optional[str] = Form(None),
|
callback_url: Optional[str] = Form(None),
|
||||||
background_tasks: BackgroundTasks = BackgroundTasks(),
|
|
||||||
):
|
):
|
||||||
"""创建新的视频处理任务并返回处理结果"""
|
"""创建新的视频处理任务并返回处理结果,支持文件上传或URL下载"""
|
||||||
try:
|
try:
|
||||||
logger.info("创建新的视频处理任务并返回处理结果")
|
logger.info("创建新的视频处理任务并返回处理结果")
|
||||||
# 创建临时目录
|
|
||||||
temp_dir = os.path.join(os.getcwd(), "temp", datetime.now().strftime("%Y%m%d_%H%M%S"))
|
# 使用公共函数准备任务文件
|
||||||
logger.info(f"临时目录: {temp_dir}")
|
task, temp_dir = await prepare_task_files(
|
||||||
os.makedirs(temp_dir, exist_ok=True)
|
audio, video, audio_url, video_url, description, callback_url
|
||||||
|
)
|
||||||
|
logger.info(f"任务创建成功: {task.task_id}")
|
||||||
|
logger.info(f"音频文件路径: {task.audio_file_path}")
|
||||||
|
logger.info(f"视频文件路径: {task.video_file_path}")
|
||||||
|
|
||||||
|
# 创建一个事件来等待任务完成
|
||||||
|
task_completed = asyncio.Event()
|
||||||
|
logger.info(f"创建任务完成事件: {task.task_id}")
|
||||||
|
|
||||||
|
async def task_completion_callback(completed_task: Task):
|
||||||
|
logger.info(
|
||||||
|
f"任务完成回调被触发: {completed_task.task_id}, 状态: {completed_task.status}"
|
||||||
|
)
|
||||||
|
if completed_task.task_id == task.task_id and (
|
||||||
|
completed_task.status == TaskStatus.COMPLETED
|
||||||
|
or completed_task.status == TaskStatus.FAILED
|
||||||
|
):
|
||||||
|
logger.info(f"设置任务完成事件: {task.task_id}")
|
||||||
|
task_completed.set()
|
||||||
|
|
||||||
|
# 添加回调
|
||||||
|
await task_manager.add_callback(task.task_id, task_completion_callback)
|
||||||
|
logger.info(f"已添加任务完成回调: {task.task_id}")
|
||||||
|
|
||||||
|
# 开始处理任务
|
||||||
|
await task_manager.process_task_sync(task.task_id)
|
||||||
|
logger.info(f"开始处理任务: {task.task_id}")
|
||||||
|
|
||||||
|
# 等待任务完成
|
||||||
try:
|
try:
|
||||||
# 确保文件名不为None
|
logger.info(f"等待任务完成: {task.task_id}")
|
||||||
if not audio.filename or not video.filename:
|
await asyncio.wait_for(task_completed.wait(), timeout=1800) # 30分钟超时
|
||||||
raise ValueError("文件名不能为空")
|
##################sdsadsadsads
|
||||||
logger.info(f"文件名: {audio.filename}, {video.filename}")
|
logger.info(f"任务完成事件已触发: {task.task_id}")
|
||||||
# 保存上传的文件
|
except asyncio.TimeoutError:
|
||||||
audio_path = os.path.join(temp_dir, audio.filename)
|
logger.error(f"任务处理超时: {task.task_id}")
|
||||||
video_path = os.path.join(temp_dir, video.filename)
|
raise HTTPException(status_code=500, detail="任务处理超时")
|
||||||
|
|
||||||
# 保存音频文件
|
# 获取最终任务状态
|
||||||
logger.info(f"开始保存音频文件: {audio_path}")
|
result = await task_manager.get_task(task.task_id)
|
||||||
async with aiofiles.open(audio_path, "wb") as f:
|
if not result:
|
||||||
content = await audio.read()
|
raise HTTPException(status_code=404, detail="任务不存在")
|
||||||
await f.write(content)
|
|
||||||
logger.info(f"音频文件保存完成: {audio_path}")
|
|
||||||
|
|
||||||
# 保存视频文件
|
if result.status == TaskStatus.COMPLETED:
|
||||||
logger.info(f"开始保存视频文件: {video_path}")
|
# 获取输出文件路径
|
||||||
async with aiofiles.open(video_path, "wb") as f:
|
output_file = result.output_file_path
|
||||||
content = await video.read()
|
if not os.path.exists(output_file):
|
||||||
await f.write(content)
|
logger.error(f"输出文件不存在: {output_file}")
|
||||||
logger.info(f"视频文件保存完成: {video_path}")
|
raise HTTPException(status_code=404, detail="输出文件不存在")
|
||||||
|
|
||||||
# 验证文件是否保存成功
|
# 获取文件名
|
||||||
if not os.path.exists(audio_path) or not os.path.exists(video_path):
|
filename = os.path.basename(output_file)
|
||||||
raise ValueError("文件保存失败")
|
|
||||||
|
|
||||||
# 创建输出目录
|
# 创建文件流
|
||||||
output_dir = os.path.join("output", datetime.now().strftime("%Y%m%d"))
|
async def file_iterator():
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
async with aiofiles.open(output_file, "rb") as f:
|
||||||
|
while chunk := await f.read(8192): # 8KB chunks
|
||||||
|
yield chunk
|
||||||
|
|
||||||
# 创建任务
|
return StreamingResponse(
|
||||||
task = await task_manager.add_task(audio_path, video_path, output_dir, description, callback_url)
|
file_iterator(),
|
||||||
|
media_type="video/mp4",
|
||||||
# 创建一个事件来等待任务完成
|
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||||
task_completed = asyncio.Event()
|
)
|
||||||
logger.info(f"创建任务完成事件: {task.task_id}")
|
else:
|
||||||
|
raise HTTPException(
|
||||||
async def task_completion_callback(completed_task: Task):
|
status_code=500, detail=result.error_message or "处理失败"
|
||||||
logger.info(f"任务完成回调被触发: {completed_task.task_id}, 状态: {completed_task.status}")
|
)
|
||||||
if completed_task.task_id == task.task_id and (
|
|
||||||
completed_task.status == TaskStatus.COMPLETED or completed_task.status == TaskStatus.FAILED
|
|
||||||
):
|
|
||||||
logger.info(f"设置任务完成事件: {task.task_id}")
|
|
||||||
task_completed.set()
|
|
||||||
|
|
||||||
# 添加回调
|
|
||||||
await task_manager.add_callback(task.task_id, task_completion_callback)
|
|
||||||
logger.info(f"已添加任务完成回调: {task.task_id}")
|
|
||||||
|
|
||||||
# 开始处理任务
|
|
||||||
await task_manager.process_task_sync(task.task_id)
|
|
||||||
logger.info(f"开始处理任务: {task.task_id}")
|
|
||||||
|
|
||||||
# 等待任务完成
|
|
||||||
try:
|
|
||||||
logger.info(f"等待任务完成: {task.task_id}")
|
|
||||||
await asyncio.wait_for(task_completed.wait(), timeout=1800) # 30分钟超时
|
|
||||||
##################sdsadsadsads
|
|
||||||
logger.info(f"任务完成事件已触发: {task.task_id}")
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
logger.error(f"任务处理超时: {task.task_id}")
|
|
||||||
raise HTTPException(status_code=500, detail="任务处理超时")
|
|
||||||
|
|
||||||
# 获取最终任务状态
|
|
||||||
result = await task_manager.get_task(task.task_id)
|
|
||||||
if not result:
|
|
||||||
raise HTTPException(status_code=404, detail="任务不存在")
|
|
||||||
|
|
||||||
if result.status == TaskStatus.COMPLETED:
|
|
||||||
# 获取输出文件路径
|
|
||||||
output_file = result.output_file_path
|
|
||||||
if not os.path.exists(output_file):
|
|
||||||
logger.error(f"输出文件不存在: {output_file}")
|
|
||||||
raise HTTPException(status_code=404, detail="输出文件不存在")
|
|
||||||
|
|
||||||
# 获取文件名
|
|
||||||
filename = os.path.basename(output_file)
|
|
||||||
|
|
||||||
# 创建文件流
|
|
||||||
async def file_iterator():
|
|
||||||
async with aiofiles.open(output_file, "rb") as f:
|
|
||||||
while chunk := await f.read(8192): # 8KB chunks
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
return StreamingResponse(
|
|
||||||
file_iterator(), media_type="video/mp4", headers={"Content-Disposition": f'attachment; filename="{filename}"'}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=500, detail=result.error_message or "处理失败")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# 如果发生错误,立即清理临时文件
|
|
||||||
# if os.path.exists(temp_dir):
|
|
||||||
# shutil.rmtree(temp_dir)
|
|
||||||
logger.error(f"创建任务失败: {str(e)}", exc_info=True)
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"创建任务失败: {str(e)}", exc_info=True)
|
logger.error(f"创建任务失败: {str(e)}", exc_info=True)
|
||||||
@@ -377,7 +343,9 @@ async def get_all_tasks():
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/tasks/{task_id}/callback")
|
@app.post("/tasks/{task_id}/callback")
|
||||||
async def add_task_callback(task_id: str, callback_url: str, background_tasks: BackgroundTasks):
|
async def add_task_callback(
|
||||||
|
task_id: str, callback_url: str, background_tasks: BackgroundTasks
|
||||||
|
):
|
||||||
"""添加任务完成回调"""
|
"""添加任务完成回调"""
|
||||||
if not await task_manager.get_task(task_id):
|
if not await task_manager.get_task(task_id):
|
||||||
raise HTTPException(status_code=404, detail="Task not found")
|
raise HTTPException(status_code=404, detail="Task not found")
|
||||||
@@ -394,7 +362,9 @@ async def add_task_callback(task_id: str, callback_url: str, background_tasks: B
|
|||||||
|
|
||||||
|
|
||||||
# 后台任务处理函数
|
# 后台任务处理函数
|
||||||
async def process_video_task(task_id: str, audio_file_path: str, video_file_path: str, out_file_path: str):
|
async def process_video_task(
|
||||||
|
task_id: str, audio_file_path: str, video_file_path: str, out_file_path: str
|
||||||
|
):
|
||||||
"""处理视频任务的后台函数"""
|
"""处理视频任务的后台函数"""
|
||||||
try:
|
try:
|
||||||
# 更新任务状态为处理中
|
# 更新任务状态为处理中
|
||||||
@@ -404,21 +374,161 @@ async def process_video_task(task_id: str, audio_file_path: str, video_file_path
|
|||||||
file_id = f"{task_id}_{int(datetime.now().timestamp())}"
|
file_id = f"{task_id}_{int(datetime.now().timestamp())}"
|
||||||
|
|
||||||
# 处理视频
|
# 处理视频
|
||||||
success = await task_manager.process_manager.process_video(task_id, audio_file_path, video_file_path, out_file_path)
|
success = await task_manager.process_manager.process_video(
|
||||||
|
task_id, audio_file_path, video_file_path, out_file_path
|
||||||
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
await task_manager.update_task_status(task_id, TaskStatus.COMPLETED)
|
await task_manager.update_task_status(task_id, TaskStatus.COMPLETED)
|
||||||
else:
|
else:
|
||||||
await task_manager.update_task_status(task_id, TaskStatus.FAILED, error_message="Video processing failed")
|
await task_manager.update_task_status(
|
||||||
|
task_id, TaskStatus.FAILED, error_message="Video processing failed"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await task_manager.update_task_status(task_id, TaskStatus.FAILED, error_message=str(e))
|
await task_manager.update_task_status(
|
||||||
|
task_id, TaskStatus.FAILED, error_message=str(e)
|
||||||
|
)
|
||||||
# 清理临时文件
|
# 清理临时文件
|
||||||
# if os.path.exists(temp_dir):
|
# if os.path.exists(temp_dir):
|
||||||
# shutil.rmtree(temp_dir)
|
# shutil.rmtree(temp_dir)
|
||||||
# logger.info(f"已清理临时目录: {temp_dir}")
|
# logger.info(f"已清理临时目录: {temp_dir}")
|
||||||
|
|
||||||
|
|
||||||
|
# 添加文件下载辅助函数
|
||||||
|
async def download_file_from_url(url: str, destination_path: str) -> bool:
|
||||||
|
"""从URL下载文件到指定路径"""
|
||||||
|
try:
|
||||||
|
logger.info(f"开始下载文件: {url} -> {destination_path}")
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
url, timeout=aiohttp.ClientTimeout(total=300)
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
# 获取Content-Length用于进度追踪
|
||||||
|
content_length = response.headers.get("Content-Length")
|
||||||
|
if content_length:
|
||||||
|
logger.info(f"文件大小: {int(content_length)} bytes")
|
||||||
|
|
||||||
|
async with aiofiles.open(destination_path, "wb") as f:
|
||||||
|
downloaded = 0
|
||||||
|
async for chunk in response.content.iter_chunked(8192):
|
||||||
|
await f.write(chunk)
|
||||||
|
downloaded += len(chunk)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"文件下载完成: {destination_path} ({downloaded} bytes)"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"下载失败,HTTP状态码: {response.status}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"下载文件时发生错误: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def process_media_input(
|
||||||
|
media_file: Optional[UploadFile],
|
||||||
|
media_url: Optional[str],
|
||||||
|
media_type: str,
|
||||||
|
temp_dir: str,
|
||||||
|
) -> str:
|
||||||
|
"""处理媒体输入(文件或URL),返回本地文件路径"""
|
||||||
|
if media_file and media_url:
|
||||||
|
raise ValueError(f"{media_type}不能同时提供文件和URL")
|
||||||
|
|
||||||
|
if not media_file and not media_url:
|
||||||
|
raise ValueError(f"必须提供{media_type}文件或URL")
|
||||||
|
|
||||||
|
if media_file:
|
||||||
|
# 处理上传文件
|
||||||
|
if not media_file.filename:
|
||||||
|
raise ValueError(f"{media_type}文件名不能为空")
|
||||||
|
|
||||||
|
file_path = os.path.join(temp_dir, media_file.filename)
|
||||||
|
logger.info(f"开始保存{media_type}文件: {file_path}")
|
||||||
|
async with aiofiles.open(file_path, "wb") as f:
|
||||||
|
content = await media_file.read()
|
||||||
|
await f.write(content)
|
||||||
|
logger.info(f"{media_type}文件保存完成: {file_path}")
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 处理URL下载
|
||||||
|
parsed_url = urllib.parse.urlparse(media_url)
|
||||||
|
filename = os.path.basename(parsed_url.path)
|
||||||
|
if not filename or "." not in filename:
|
||||||
|
# 如果URL没有文件名或没有扩展名,根据媒体类型生成
|
||||||
|
if media_type == "音频":
|
||||||
|
extension = ".mp3" # 默认音频扩展名
|
||||||
|
elif media_type == "视频":
|
||||||
|
extension = ".mp4" # 默认视频扩展名
|
||||||
|
else:
|
||||||
|
extension = ""
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
filename = f"{media_type}_{int(datetime.now().timestamp())}{extension}"
|
||||||
|
elif "." not in filename:
|
||||||
|
filename = f"{filename}{extension}"
|
||||||
|
|
||||||
|
file_path = os.path.join(temp_dir, filename)
|
||||||
|
success = await download_file_from_url(media_url, file_path)
|
||||||
|
if not success:
|
||||||
|
raise ValueError(f"下载{media_type}文件失败: {media_url}")
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
|
async def prepare_task_files(
|
||||||
|
audio: Optional[UploadFile],
|
||||||
|
video: Optional[UploadFile],
|
||||||
|
audio_url: Optional[str],
|
||||||
|
video_url: Optional[str],
|
||||||
|
description: str,
|
||||||
|
callback_url: Optional[str],
|
||||||
|
) -> tuple[Task, str]:
|
||||||
|
"""
|
||||||
|
准备任务文件和创建任务的公共逻辑
|
||||||
|
返回创建的任务对象和临时目录路径
|
||||||
|
"""
|
||||||
|
# 创建临时目录
|
||||||
|
temp_dir = os.path.join(
|
||||||
|
os.getcwd(), "temp", datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
)
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 处理音频输入(文件或URL)
|
||||||
|
audio_path = await process_media_input(audio, audio_url, "音频", temp_dir)
|
||||||
|
|
||||||
|
# 处理视频输入(文件或URL)
|
||||||
|
video_path = await process_media_input(video, video_url, "视频", temp_dir)
|
||||||
|
|
||||||
|
# 验证文件是否存在
|
||||||
|
if not os.path.exists(audio_path) or not os.path.exists(video_path):
|
||||||
|
raise ValueError("文件处理失败")
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
output_dir = os.path.join("output", datetime.now().strftime("%Y%m%d"))
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 创建任务
|
||||||
|
task = await task_manager.add_task(
|
||||||
|
audio_path, video_path, output_dir, description, callback_url
|
||||||
|
)
|
||||||
|
|
||||||
|
return task, temp_dir
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 如果发生错误,立即清理临时文件
|
||||||
|
# if os.path.exists(temp_dir):
|
||||||
|
# shutil.rmtree(temp_dir)
|
||||||
|
logger.error(f"准备任务文件失败: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run("api:app", host="0.0.0.0", port=8101, reload=True, workers=1, log_level="info") # 开发模式下启用热重载 # 单进程模式
|
uvicorn.run(
|
||||||
|
"api:app", host="0.0.0.0", port=8101, reload=True, workers=1, log_level="info"
|
||||||
|
) # 开发模式下启用热重载 # 单进程模式
|
||||||
|
|||||||
Reference in New Issue
Block a user