audio.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import logging
  2. from pathlib import Path
  3. from fastapi import APIRouter, HTTPException
  4. import os
  5. import uuid
  6. import shutil
  7. from fastapi import UploadFile, File
  8. from starlette.concurrency import run_in_threadpool
  9. import service.ai_task as ai_task
  10. import service.pyav as pyav
  11. from setting import UPLOAD_DIR, OUTPUT_DIR
  12. logger = logging.getLogger(__name__)
  13. # 创建路由对象,可以统一设置前缀 (prefix) 和 标签 (tags)
  14. router = APIRouter(
  15. prefix="/api1/audio",
  16. tags=["audio"]
  17. )
  18. @router.post("/asr")
  19. async def upload_audio(file: UploadFile = File(...)):
  20. # 1. 统一生成一次 task_id,确保前后一致
  21. task_id = str(uuid.uuid4())[:8]
  22. ext = file.filename.split('.')[-1]
  23. save_path = os.path.join(UPLOAD_DIR, f"{task_id}.{ext}")
  24. # 2. 解决 IO 阻塞方案 A: 使用 run_in_threadpool (推荐)
  25. # 这样会将同步的写入操作丢进单独的线程,不阻塞主事件循环
  26. def save_file():
  27. with open(save_path, "wb") as buffer:
  28. shutil.copyfileobj(file.file, buffer)
  29. await run_in_threadpool(save_file)
  30. # 3. 构造路径并存入队列
  31. srt_path = os.path.join(OUTPUT_DIR, f"{task_id}.srt")
  32. video_path = os.path.join(OUTPUT_DIR, f"{task_id}.mp4")
  33. # 传递已经确定好的 task_id
  34. await ai_task.put_task(task_id, save_path, srt_path, video_path)
  35. return {
  36. "status": "queued",
  37. "task_id": task_id,
  38. "message": "文件已上传并加入 GPU 处理队列",
  39. "srt_preview_path": f"{OUTPUT_DIR}/{task_id}.srt"
  40. }
  41. @router.get("/tasks")
  42. async def get_queue_status():
  43. return {"queue_size": ai_task.get_tasks()}
  44. @router.get("/result/{task_id}")
  45. async def get_asr_result(task_id: str):
  46. file_name = check_file_prefix(UPLOAD_DIR, task_id)
  47. if not file_name:
  48. raise HTTPException(status_code=404, detail="音频文件不存在")
  49. audio_path = f"{UPLOAD_DIR}/{file_name}"
  50. txt_path = f"{OUTPUT_DIR}/{task_id}.txt"
  51. if not os.path.exists(txt_path):
  52. raise HTTPException(status_code=404, detail="音频文本文件不存在")
  53. srt_path = f"{OUTPUT_DIR}/{task_id}.srt"
  54. if not os.path.exists(srt_path):
  55. raise HTTPException(status_code=404, detail="字幕文件不存在")
  56. video_path = f"{OUTPUT_DIR}/{task_id}.mp4"
  57. if not os.path.exists(video_path):
  58. raise HTTPException(status_code=404, detail="视频文件不存在")
  59. with open(txt_path, "r", encoding="utf-8") as f:
  60. text = f.read()
  61. info = pyav.get_media_info(audio_path)
  62. srt = pyav.parse_srt_to_list(srt_path)
  63. return {
  64. "task_id": task_id,
  65. "duration": info['duration'],
  66. "text": text,
  67. "srt": srt,
  68. "audio_url": f"/api1/file/audio/{file_name}",
  69. "video_url": f"/api1/file/video/{task_id}.mp4"
  70. }
  71. def check_file_prefix(directory, prefix):
  72. # 1. 转化为 Path 对象
  73. path = Path(directory)
  74. # 2. 匹配所有以 prefix 开头的文件
  75. # 如果要匹配特定后缀,可以使用 f"{prefix}*.jpg"
  76. matched_files = list(path.glob(f"{prefix}*"))
  77. count = len(matched_files)
  78. if count == 1:
  79. file_path = matched_files[0]
  80. return file_path.name
  81. else:
  82. return None