From 9fc60e8d19a1e3c5e71548e24e13a2bf1d54519b Mon Sep 17 00:00:00 2001 From: poturaevpetr Date: Wed, 18 Mar 2026 00:04:01 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B8=20=D1=80=D0=B0=D1=81=D0=BF=D0=BE=D0=B7=D0=BD?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apiApp/database/Audio.py | 4 +- apiApp/routers/ai_conclusion_router.py | 30 +++++++++ apiApp/routers/audio_management_router.py | 2 + apiApp/routers/external_audio.py | 81 +++++++++++++++++++++++ apiApp/services/audio_service.py | 11 ++- 5 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 apiApp/routers/external_audio.py diff --git a/apiApp/database/Audio.py b/apiApp/database/Audio.py index 0a07b93..aef21c0 100644 --- a/apiApp/database/Audio.py +++ b/apiApp/database/Audio.py @@ -14,6 +14,7 @@ class Audio(Base): file_path = Column(Text) duration = Column(Float) file_size = Column(Integer) + sourse = Column(Text, default="internal") ai_conclusion = relationship("AiConclusion", back_populates="audio", cascade="all, delete-orphan") @@ -24,5 +25,6 @@ class Audio(Base): "index_date": self.index_date.isoformat() if self.index_date else None, "file_path": self.file_path, "duration": self.duration, - "file_size": self.file_size + "file_size": self.file_size, + "sourse": self.sourse } \ No newline at end of file diff --git a/apiApp/routers/ai_conclusion_router.py b/apiApp/routers/ai_conclusion_router.py index 1f6313a..e591428 100644 --- a/apiApp/routers/ai_conclusion_router.py +++ b/apiApp/routers/ai_conclusion_router.py @@ -16,6 +16,31 @@ from apiApp.config import WEBHOOK_ENDPOINT, WEBHOOK_API_KEY logger = logging.getLogger(__name__) ai_conclusion_router = APIRouter() +def _send_callback(callback_url: str, audio: Audio, conclusion_data: Dict[str, Any]) -> None: + """Отправляем результат клиенту по callback_url (не храним в БД).""" + try: + callback_url = (callback_url or "").strip() + if not callback_url: + return + if not callback_url.startswith(("http://", "https://")): + logger.warning(f"⚠️ Некорректный callback_url для {audio.filename}: {callback_url}") + return + + payload = { + "audio_id": str(audio.id), + "filename": audio.filename, + "result": conclusion_data + } + + resp = requests.post(callback_url, json=payload, timeout=30) + if 200 <= resp.status_code < 300: + logger.info(f"✅ Callback успешно отправлен для {audio.filename}") + else: + logger.warning(f"⚠️ Callback вернул статус {resp.status_code} для {audio.filename}") + logger.warning(f"Response: {resp.text}") + except Exception as e: + logger.error(f"❌ Ошибка при отправке callback для {audio.filename}: {e}") + class AiConclusionRequest(BaseModel): """Модель запроса для сохранения AI заключения""" @@ -25,6 +50,7 @@ class AiConclusionRequest(BaseModel): analysis: Dict[str, Any] segments: Optional[List[Dict[str, Any]]] = [] processing_time_seconds: Optional[float] = 0 + callback_url: Optional[str] = None class AiConclusionResponse(BaseModel): @@ -93,6 +119,10 @@ async def save_ai_conclusion(request: AiConclusionRequest, db: Session = Depends db.commit() logger.info(f"✅ Заключение сохранено для {request.filename}") + # Для внешних файлов — отправляем результат клиенту из FileAudioAPI + if (audio.sourse or "").lower() == "external" and request.callback_url: + _send_callback(request.callback_url, audio, conclusion_data) + # Отправляем webhook в Calls_WEB_Client_main для анализа try: logger.info(f"📤 Отправка webhook в Calls_WEB_Client_main для {request.filename}") diff --git a/apiApp/routers/audio_management_router.py b/apiApp/routers/audio_management_router.py index a9dfef4..43c8a65 100644 --- a/apiApp/routers/audio_management_router.py +++ b/apiApp/routers/audio_management_router.py @@ -34,6 +34,8 @@ def query_audio_without_conclusion(db, limit=None): query = db.query(Audio).filter( ~subquery + ).filter( + Audio.sourse == "internal" ).order_by(Audio.index_date.asc()) if limit: diff --git a/apiApp/routers/external_audio.py b/apiApp/routers/external_audio.py new file mode 100644 index 0000000..5c25cd3 --- /dev/null +++ b/apiApp/routers/external_audio.py @@ -0,0 +1,81 @@ +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File as FastAPIFile, status +from apiApp.database import get_db +from fastapi.responses import FileResponse +from sqlalchemy.orm import Session +import os, uuid +from apiApp.config import UPLOAD_FOLDER, ALLOWED_AUDIO_EXTENSIONS, MAX_UPLOAD_SIZE +import aiofiles + +from apiApp.schemas import ( + AudioCreate, + AudioResponse, + AudioListResponse, + MessageResponse +) +from apiApp.services import AudioCRUD + +router = APIRouter( + prefix="/external_audio", + tags=["Внешние аудиофайлы"] +) + +@router.post("/upload") +async def upload_external_audio( + file: UploadFile = FastAPIFile(...), + db: Session = Depends(get_db) +): + """ + Загрузка внешнего аудиофайла на сервер + """ + + # Проверка расширения файла + file_ext = os.path.splitext(file.filename)[1].lower() + if file_ext not in ALLOWED_AUDIO_EXTENSIONS: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=f"File extension not allowed. Allowed: {', '.join(ALLOWED_AUDIO_EXTENSIONS)}" + ) + content = await file.read() + # Проверка размера файла + if len(content) > MAX_UPLOAD_SIZE: + raise HTTPException( + status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, + detail=f"File too large. Maximum size: {MAX_UPLOAD_SIZE / (1024*1024)}MB" + ) + + # Сохранение файла + file_path = UPLOAD_FOLDER / f"{uuid.uuid4()}{file_ext}" + try: + async with aiofiles.open(file_path, 'wb') as f: + await f.write(content) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error saving file: {str(e)}" + ) + + # Создание записи в БД + try: + audio_data = AudioCreate(filename=file.filename) + audio = AudioCRUD.create( + db=db, + audio_data=audio_data, + file_path=str(file_path), + file_size=len(content), + sourse="external" + ) + return audio + except Exception as e: + # Удаление файла при ошибке записи в БД + if os.path.exists(file_path): + os.remove(file_path) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error creating database record: {str(e)}" + ) + return {"message": "External audio uploaded successfully"} + +def send_to_recognition(file_path: str): + """ + Отправка аудиофайла на распознавание + """ \ No newline at end of file diff --git a/apiApp/services/audio_service.py b/apiApp/services/audio_service.py index 8fce463..132af26 100644 --- a/apiApp/services/audio_service.py +++ b/apiApp/services/audio_service.py @@ -26,13 +26,20 @@ class AudioCRUD: return db.query(Audio).filter(Audio.filename == filename).first() @staticmethod - def create(db: Session, audio_data: AudioCreate, file_path: str, file_size: int = None) -> Audio: + def create( + db: Session, + audio_data: AudioCreate, + file_path: str, + file_size: int = None, + sourse: str = "internal" + ) -> Audio: """Создать новую запись аудиофайла""" db_audio = Audio( filename=audio_data.filename, file_path=file_path, index_date=datetime.datetime.utcnow(), - file_size=file_size + file_size=file_size, + sourse=sourse ) db.add(db_audio) db.commit()