imetting/backend/app/services/audio_upload_task_service.py

155 lines
5.3 KiB
Python

"""
音频上传后台处理服务
将上传后的重操作放到后台线程执行,避免请求长时间阻塞:
1. 音频预处理
2. 更新音频文件记录
3. 启动转录
4. 启动自动总结监控
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Optional
from app.core.config import BACKGROUND_TASK_CONFIG, BASE_DIR
from app.services.async_transcription_service import AsyncTranscriptionService
from app.services.audio_preprocess_service import audio_preprocess_service
from app.services.audio_service import handle_audio_upload
from app.services.background_task_runner import KeyedBackgroundTaskRunner
upload_task_runner = KeyedBackgroundTaskRunner(
max_workers=max(1, int(BACKGROUND_TASK_CONFIG.get("upload_workers", 2))),
thread_name_prefix="imeeting-audio-upload",
)
class AudioUploadTaskService:
def __init__(self):
self.transcription_service = AsyncTranscriptionService()
def enqueue_upload_processing(
self,
*,
meeting_id: int,
original_file_path: str,
current_user: dict,
auto_summarize: bool,
prompt_id: Optional[int] = None,
model_code: Optional[str] = None,
) -> str:
task_id = self.transcription_service.create_local_processing_task(
meeting_id=meeting_id,
status="processing",
progress=5,
)
upload_task_runner.submit(
f"audio-upload:{task_id}",
self._process_uploaded_audio,
task_id,
meeting_id,
original_file_path,
current_user,
auto_summarize,
prompt_id,
model_code,
)
return task_id
def _process_uploaded_audio(
self,
task_id: str,
meeting_id: int,
original_file_path: str,
current_user: dict,
auto_summarize: bool,
prompt_id: Optional[int],
model_code: Optional[str],
) -> None:
source_absolute_path = BASE_DIR / original_file_path.lstrip("/")
processed_absolute_path: Optional[Path] = None
handoff_to_audio_service = False
try:
self.transcription_service.update_local_processing_task(task_id, "processing", 15, None)
preprocess_result = audio_preprocess_service.preprocess(source_absolute_path)
processed_absolute_path = preprocess_result.file_path
audio_duration = preprocess_result.metadata.duration_seconds
file_path = "/" + str(processed_absolute_path.relative_to(BASE_DIR))
print(
f"[AudioUploadTaskService] 音频预处理完成: source={source_absolute_path.name}, "
f"target={processed_absolute_path.name}, duration={audio_duration}s, "
f"applied={preprocess_result.applied}"
)
self.transcription_service.update_local_processing_task(task_id, "processing", 40, None)
handoff_to_audio_service = True
result = handle_audio_upload(
file_path=file_path,
file_name=preprocess_result.file_name,
file_size=preprocess_result.file_size,
meeting_id=meeting_id,
current_user=current_user,
auto_summarize=auto_summarize,
background_tasks=None,
prompt_id=prompt_id,
model_code=model_code,
duration=audio_duration,
transcription_task_id=task_id,
)
if not result["success"]:
raise RuntimeError(self._extract_response_message(result["response"]))
if preprocess_result.applied and processed_absolute_path != source_absolute_path and source_absolute_path.exists():
try:
os.remove(source_absolute_path)
except OSError:
pass
except Exception as exc:
error_message = str(exc)
print(f"[AudioUploadTaskService] 音频后台处理失败, task_id={task_id}, meeting_id={meeting_id}: {error_message}")
self.transcription_service.update_local_processing_task(task_id, "failed", 0, error_message)
if handoff_to_audio_service:
return
cleanup_targets = []
if processed_absolute_path:
cleanup_targets.append(processed_absolute_path)
if source_absolute_path.exists():
cleanup_targets.append(source_absolute_path)
deduped_targets: list[Path] = []
for target in cleanup_targets:
if target not in deduped_targets:
deduped_targets.append(target)
for target in deduped_targets:
if target.exists():
try:
os.remove(target)
except OSError:
pass
@staticmethod
def _extract_response_message(response) -> str:
body = getattr(response, "body", None)
if not body:
return "音频处理失败"
try:
payload = json.loads(body.decode("utf-8"))
return payload.get("message") or "音频处理失败"
except Exception:
return "音频处理失败"
audio_upload_task_service = AudioUploadTaskService()