API для работы с файлами, индексация файлов и результатов распощнавания
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# apiApp package
|
||||
# FastAPI application for Speech Analytics
|
||||
|
||||
__version__ = "1.0.0"
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Базовые пути
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
UPLOAD_FOLDER = BASE_DIR / "uploads"
|
||||
UPLOAD_FOLDER.mkdir(exist_ok=True)
|
||||
|
||||
# Database
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./speech_analytics.db")
|
||||
|
||||
# API Settings
|
||||
API_V1_PREFIX = "/api/v1"
|
||||
MAX_UPLOAD_SIZE = 100 * 1024 * 1024 # 100MB
|
||||
ALLOWED_AUDIO_EXTENSIONS = {".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aac"}
|
||||
|
||||
# Application
|
||||
APP_TITLE = "Speech Analytics API"
|
||||
APP_VERSION = "1.0.0"
|
||||
@@ -0,0 +1,25 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from apiApp.config import DATABASE_URL
|
||||
|
||||
# Создание engine
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}
|
||||
)
|
||||
|
||||
# SessionLocal
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Base
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Зависимость для получения сессии БД
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,21 @@
|
||||
from sqlalchemy import Column, UUID, ForeignKey, DateTime, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from apiApp.database import Base
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class AiConclusion(Base):
|
||||
__tablename__ = "ai_conclusion"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
audio_id = Column(UUID(as_uuid=True), ForeignKey("audio.id"), nullable=False)
|
||||
conclusion = Column(JSON, default=lambda: {
|
||||
"transcription": [],
|
||||
"ai_transcription": [],
|
||||
"conclusion": {}
|
||||
})
|
||||
index_date = Column(DateTime, default=datetime.utcnow)
|
||||
end_date = Column(DateTime)
|
||||
|
||||
audio = relationship("Audio", back_populates="ai_conclusion")
|
||||
@@ -0,0 +1,28 @@
|
||||
from sqlalchemy import Column, String, DateTime, UUID, ForeignKey, Float, Integer
|
||||
from sqlalchemy.orm import relationship
|
||||
from apiApp.database import Base
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Audio(Base):
|
||||
__tablename__ = "audio"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
filename = Column(String(255), nullable=False)
|
||||
index_date = Column(DateTime, default=datetime.utcnow)
|
||||
file_path = Column(String(500))
|
||||
duration = Column(Float)
|
||||
file_size = Column(Integer)
|
||||
|
||||
ai_conclusion = relationship("AiConclusion", back_populates="audio", cascade="all, delete-orphan")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"filename": self.filename,
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, UUID, ForeignKey, Integer, Text
|
||||
from apiApp.database import Base
|
||||
import uuid
|
||||
|
||||
|
||||
class ConclusionVersion(Base):
|
||||
__tablename__ = "conclusion_version"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
conclusion_id = Column(UUID(as_uuid=True), ForeignKey("conclusion.id"))
|
||||
version = Column(Integer)
|
||||
content = Column(Text)
|
||||
@@ -0,0 +1,16 @@
|
||||
from sqlalchemy import Column, UUID, String, Integer
|
||||
from sqlalchemy.orm import relationship
|
||||
from apiApp.database import Base
|
||||
import uuid
|
||||
|
||||
|
||||
class Operator(Base):
|
||||
__tablename__ = "operator"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
fio = Column(String(100))
|
||||
num = Column(Integer)
|
||||
|
||||
calls = relationship("Call", back_populates="operator")
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from apiApp.config import DATABASE_URL
|
||||
|
||||
# Создание engine
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}
|
||||
)
|
||||
|
||||
# SessionLocal
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Base
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# Зависимость для получения сессии БД
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
from apiApp.database.Operator import Operator
|
||||
from apiApp.database.Audio import Audio
|
||||
from apiApp.database.AiConclusion import AiConclusion
|
||||
from apiApp.database.ConclusionVersion import ConclusionVersion
|
||||
@@ -0,0 +1,65 @@
|
||||
# Legacy database API support
|
||||
# Этот модуль обеспечивает обратную совместимость со старым кодом
|
||||
|
||||
from apiApp.crud import AudioCRUD, AiConclusionCRUD
|
||||
|
||||
class AudioDB:
|
||||
"""
|
||||
Совместимый слой для старого API
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
from apiApp.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
return AudioCRUD.get_all(db)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def get(audio_id):
|
||||
from apiApp.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
return AudioCRUD.get_by_id(db, audio_id)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def add(data):
|
||||
from apiApp.database import SessionLocal
|
||||
from apiApp.schemas import AudioCreate
|
||||
db = SessionLocal()
|
||||
try:
|
||||
audio_data = AudioCreate(**data)
|
||||
return AudioCRUD.create(db, audio_data, file_path=data.get('file_path', ''))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def put(audio_id, data):
|
||||
from apiApp.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
return AudioCRUD.update(db, audio_id, data)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def delete(audio_id):
|
||||
from apiApp.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
return AudioCRUD.delete(db, audio_id)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@staticmethod
|
||||
def update_recognition_result(audio_id, result):
|
||||
from apiApp.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
try:
|
||||
return AudioCRUD.update_recognition_result(db, audio_id, result)
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
from apiApp.database.Audio import Audio
|
||||
from apiApp.database import db
|
||||
import datetime
|
||||
|
||||
class AudioDB:
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
return Audio.query.all()
|
||||
|
||||
@staticmethod
|
||||
def get(audio_id):
|
||||
return Audio.query.get(audio_id)
|
||||
|
||||
@staticmethod
|
||||
def add(data):
|
||||
data['datetime'] = datetime.datetime.now()
|
||||
audio = Audio(**data)
|
||||
db.session.add(audio)
|
||||
db.session.commit()
|
||||
return audio
|
||||
|
||||
@staticmethod
|
||||
def put(audio_id, data):
|
||||
audio = Audio.query.get(audio_id)
|
||||
for key, value in data.items():
|
||||
setattr(audio, key, value)
|
||||
db.session.commit()
|
||||
return audio
|
||||
|
||||
@staticmethod
|
||||
def delete(audio_id):
|
||||
audio = Audio.query.get(audio_id)
|
||||
db.session.delete(audio)
|
||||
db.session.commit()
|
||||
return audio
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from apiApp.routers.audio import router as audio_router
|
||||
from apiApp.routers.recognition import router as recognition_router
|
||||
|
||||
__all__ = ["audio_router", "recognition_router"]
|
||||
@@ -0,0 +1,155 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File as FastAPIFile, status
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
import os
|
||||
import uuid
|
||||
import aiofiles
|
||||
|
||||
from apiApp.database import get_db
|
||||
from apiApp.schemas import (
|
||||
AudioCreate,
|
||||
AudioResponse,
|
||||
AudioListResponse,
|
||||
MessageResponse
|
||||
)
|
||||
from apiApp.services import AudioCRUD
|
||||
from apiApp.config import UPLOAD_FOLDER, ALLOWED_AUDIO_EXTENSIONS, MAX_UPLOAD_SIZE
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/upload", response_model=AudioResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def upload_audio_file(
|
||||
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)
|
||||
)
|
||||
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)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/audio/list", response_model=AudioListResponse)
|
||||
async def list_audio_files(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Получить список всех аудиофайлов
|
||||
"""
|
||||
audios = AudioCRUD.get_all(db)
|
||||
return AudioListResponse(
|
||||
audios=audios[skip:skip+limit],
|
||||
count=len(audios)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/audio/{audio_id}", response_model=AudioResponse)
|
||||
async def get_audio(
|
||||
audio_id: uuid.UUID,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Получить информацию о аудиофайле по ID
|
||||
"""
|
||||
audio = AudioCRUD.get_by_id(db, audio_id)
|
||||
if not audio:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio not found"
|
||||
)
|
||||
return audio
|
||||
|
||||
|
||||
@router.get("/audio/file/{audio_id}")
|
||||
async def download_audio_file(
|
||||
audio_id: uuid.UUID,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Скачать аудиофайл по ID
|
||||
"""
|
||||
audio = AudioCRUD.get_by_id(db, audio_id)
|
||||
if not audio:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio not found"
|
||||
)
|
||||
|
||||
if not audio.file_path or not os.path.exists(audio.file_path):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio file not found on disk"
|
||||
)
|
||||
|
||||
return FileResponse(
|
||||
path=audio.file_path,
|
||||
filename=audio.filename,
|
||||
media_type='audio/mpeg'
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/audio/delete/{audio_id}", response_model=AudioResponse)
|
||||
async def delete_audio(
|
||||
audio_id: uuid.UUID,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Удалить аудиофайл
|
||||
"""
|
||||
audio = AudioCRUD.delete(db, audio_id)
|
||||
if not audio:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio not found"
|
||||
)
|
||||
return audio
|
||||
@@ -0,0 +1,264 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any
|
||||
import uuid
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from apiApp.database import get_db, Audio
|
||||
from apiApp.schemas import (
|
||||
RecognitionStartResponse,
|
||||
RecognitionStatus,
|
||||
ErrorResponse
|
||||
)
|
||||
from apiApp.services import AudioCRUD, AiConclusionCRUD
|
||||
from apiApp.config import UPLOAD_FOLDER
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Глобальное хранилище статусов задач (в продакшене лучше использовать Redis)
|
||||
recognition_tasks: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
|
||||
async def process_recognition(audio_id: uuid.UUID, file_path: str, task_id: str):
|
||||
"""
|
||||
Фоновая задача для распознавания аудио
|
||||
"""
|
||||
try:
|
||||
# Обновляем статус на processing
|
||||
recognition_tasks[task_id] = {
|
||||
'audio_id': audio_id,
|
||||
'status': 'processing',
|
||||
'result': None,
|
||||
'error': None
|
||||
}
|
||||
|
||||
# Проверяем существование файла
|
||||
if not os.path.exists(file_path):
|
||||
recognition_tasks[task_id]['status'] = 'error'
|
||||
recognition_tasks[task_id]['error'] = 'File not found on disk'
|
||||
return
|
||||
|
||||
# Здесь должна быть реальная логика распознавания
|
||||
# Например, вызов внешнего API или локальной модели
|
||||
# result = await your_recognize_function(file_path)
|
||||
|
||||
# Симуляция обработки (в реальном коде убрать)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Пример результата (заменить на реальный)
|
||||
result = {
|
||||
'text': 'Распознанный текст из аудио',
|
||||
'confidence': 0.95,
|
||||
'duration': 120.5,
|
||||
'segments': [
|
||||
{
|
||||
'start': 0.0,
|
||||
'end': 5.2,
|
||||
'text': 'Привет, чем могу помочь?',
|
||||
'speaker': 'SPEAKER_00'
|
||||
},
|
||||
{
|
||||
'start': 5.5,
|
||||
'end': 10.8,
|
||||
'text': 'Мне нужна информация о услугах',
|
||||
'speaker': 'SPEAKER_01'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Обновляем статус на completed
|
||||
recognition_tasks[task_id]['status'] = 'completed'
|
||||
recognition_tasks[task_id]['result'] = result
|
||||
|
||||
# Получаем сессию БД (для фоновой задачи)
|
||||
from apiApp.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# Создаем или обновляем AI Conclusion
|
||||
conclusion = AiConclusionCRUD.get_by_audio_id(db, audio_id)
|
||||
if conclusion:
|
||||
AiConclusionCRUD.update(
|
||||
db=db,
|
||||
conclusion_id=conclusion.id,
|
||||
conclusion_data={
|
||||
"transcription": result.get('segments', []),
|
||||
"ai_transcription": [result.get('text', '')],
|
||||
"conclusion": {
|
||||
"confidence": result.get('confidence', 0.0),
|
||||
"duration": result.get('duration', 0.0)
|
||||
}
|
||||
},
|
||||
end_date=True
|
||||
)
|
||||
else:
|
||||
AiConclusionCRUD.create(
|
||||
db=db,
|
||||
audio_id=audio_id,
|
||||
conclusion={
|
||||
"transcription": result.get('segments', []),
|
||||
"ai_transcription": [result.get('text', '')],
|
||||
"conclusion": {
|
||||
"confidence": result.get('confidence', 0.0),
|
||||
"duration": result.get('duration', 0.0)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Обновляем запись аудио
|
||||
AudioCRUD.update_recognition_result(db, audio_id, result)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
recognition_tasks[task_id]['status'] = 'error'
|
||||
recognition_tasks[task_id]['error'] = str(e)
|
||||
|
||||
|
||||
@router.post("/recognize/{audio_id}", response_model=RecognitionStartResponse, status_code=status.HTTP_202_ACCEPTED)
|
||||
async def start_recognition(
|
||||
audio_id: uuid.UUID,
|
||||
background_tasks: BackgroundTasks,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Запуск распознавания аудиофайла
|
||||
"""
|
||||
# Проверяем существование аудио
|
||||
audio = AudioCRUD.get_by_id(db, audio_id)
|
||||
if not audio:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio not found in database"
|
||||
)
|
||||
|
||||
# Проверяем, нет ли уже активной задачи
|
||||
for task_id, task in recognition_tasks.items():
|
||||
if task['audio_id'] == audio_id and task['status'] == 'processing':
|
||||
return RecognitionStartResponse(
|
||||
status="info",
|
||||
message="Recognition already in progress",
|
||||
task_id=task_id,
|
||||
audio_id=audio_id
|
||||
)
|
||||
|
||||
# Проверяем наличие файла
|
||||
if not audio.file_path or not os.path.exists(audio.file_path):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio file not found on disk"
|
||||
)
|
||||
|
||||
# Создаем task_id
|
||||
task_id = str(uuid.uuid4())
|
||||
|
||||
# Добавляем фоновую задачу
|
||||
background_tasks.add_task(process_recognition, audio_id, audio.file_path, task_id)
|
||||
|
||||
return RecognitionStartResponse(
|
||||
status="success",
|
||||
message="Recognition started",
|
||||
task_id=task_id,
|
||||
audio_id=audio_id
|
||||
)
|
||||
|
||||
|
||||
@router.get("/recognize/{audio_id}", response_model=RecognitionStatus)
|
||||
async def get_recognition_status(
|
||||
audio_id: uuid.UUID,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Получение статуса распознавания по audio_id
|
||||
"""
|
||||
# Проверяем существование аудио
|
||||
audio = AudioCRUD.get_by_id(db, audio_id)
|
||||
if not audio:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio not found"
|
||||
)
|
||||
|
||||
# Ищем задачу для данного audio_id
|
||||
task_info = None
|
||||
for task_id, task in recognition_tasks.items():
|
||||
if task['audio_id'] == audio_id:
|
||||
task_info = {
|
||||
'task_id': task_id,
|
||||
**task
|
||||
}
|
||||
break
|
||||
|
||||
if not task_info:
|
||||
# Проверяем, есть ли сохраненный результат
|
||||
conclusion = AiConclusionCRUD.get_by_audio_id(db, audio_id)
|
||||
if conclusion and conclusion.end_date:
|
||||
return RecognitionStatus(
|
||||
task_id="",
|
||||
audio_id=audio_id,
|
||||
status="completed",
|
||||
result=conclusion.conclusion
|
||||
)
|
||||
|
||||
return RecognitionStatus(
|
||||
task_id="",
|
||||
audio_id=audio_id,
|
||||
status="not_started"
|
||||
)
|
||||
|
||||
return RecognitionStatus(**task_info)
|
||||
|
||||
|
||||
@router.get("/recognize/task/{task_id}", response_model=RecognitionStatus)
|
||||
async def get_recognition_task(task_id: str):
|
||||
"""
|
||||
Получение статуса задачи по task_id
|
||||
"""
|
||||
if task_id not in recognition_tasks:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
task = recognition_tasks[task_id]
|
||||
return RecognitionStatus(
|
||||
task_id=task_id,
|
||||
audio_id=task['audio_id'],
|
||||
status=task['status'],
|
||||
result=task.get('result'),
|
||||
error=task.get('error')
|
||||
)
|
||||
|
||||
|
||||
@router.get("/recognize/{audio_id}/result")
|
||||
async def get_recognition_result(
|
||||
audio_id: uuid.UUID,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Получение результата распознавания из базы данных
|
||||
"""
|
||||
audio = AudioCRUD.get_by_id(db, audio_id)
|
||||
if not audio:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audio not found"
|
||||
)
|
||||
|
||||
conclusion = AiConclusionCRUD.get_by_audio_id(db, audio_id)
|
||||
if not conclusion or not conclusion.end_date:
|
||||
return {
|
||||
"status": "not_available",
|
||||
"message": "Recognition result not available yet",
|
||||
"audio_id": str(audio_id)
|
||||
}
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"audio_id": str(audio_id),
|
||||
"result": conclusion.conclusion,
|
||||
"index_date": conclusion.index_date,
|
||||
"end_date": conclusion.end_date
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
from apiApp.schemas.audio_schemas import *
|
||||
from apiApp.schemas.ai_conclusions_schemas import *
|
||||
@@ -0,0 +1,45 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class AiConclusionBase(BaseModel):
|
||||
audio_id: uuid.UUID
|
||||
conclusion: Optional[Dict[str, Any]] = None
|
||||
|
||||
class AiConclusionCreate(AiConclusionBase):
|
||||
pass
|
||||
|
||||
|
||||
class AiConclusionResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
audio_id: uuid.UUID
|
||||
conclusion: Dict[str, Any]
|
||||
index_date: Optional[datetime] = None
|
||||
end_date: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class RecognitionStatus(BaseModel):
|
||||
task_id: str
|
||||
audio_id: uuid.UUID
|
||||
status: str # pending, processing, completed, error
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class RecognitionStartResponse(BaseModel):
|
||||
status: str
|
||||
message: str
|
||||
task_id: str
|
||||
audio_id: uuid.UUID
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
detail: str
|
||||
@@ -0,0 +1,25 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class AudioBase(BaseModel):
|
||||
filename: str = Field(..., max_length=255)
|
||||
file_path: Optional[str] = None
|
||||
duration: Optional[float] = None
|
||||
file_size: Optional[int] = None
|
||||
|
||||
class AudioCreate(AudioBase):
|
||||
pass
|
||||
|
||||
class AudioResponse(AudioBase):
|
||||
id: uuid.UUID
|
||||
index_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class AudioListResponse(BaseModel):
|
||||
audios: list[AudioResponse]
|
||||
count: int
|
||||
@@ -0,0 +1,2 @@
|
||||
from apiApp.services.audio_service import AudioCRUD
|
||||
from apiApp.services.ai_conclusion_service import AiConclusionCRUD
|
||||
@@ -0,0 +1,56 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import os
|
||||
from apiApp.database import Audio
|
||||
from apiApp.schemas import AudioCreate
|
||||
from apiApp.config import UPLOAD_FOLDER
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
|
||||
class AiConclusionCRUD:
|
||||
|
||||
@staticmethod
|
||||
def get_by_audio_id(db: Session, audio_id: uuid.UUID):
|
||||
"""Получить заключение по audio_id"""
|
||||
from apiApp.database import AiConclusion
|
||||
return db.query(AiConclusion).filter(AiConclusion.audio_id == audio_id).first()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, audio_id: uuid.UUID, conclusion: dict = None):
|
||||
"""Создать новое заключение"""
|
||||
from apiApp.database import AiConclusion
|
||||
|
||||
db_conclusion = AiConclusion(
|
||||
audio_id=audio_id,
|
||||
conclusion=conclusion or {
|
||||
"transcription": [],
|
||||
"ai_transcription": [],
|
||||
"conclusion": {}
|
||||
},
|
||||
index_date=datetime.datetime.utcnow()
|
||||
)
|
||||
db.add(db_conclusion)
|
||||
db.commit()
|
||||
db.refresh(db_conclusion)
|
||||
return db_conclusion
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, conclusion_id: uuid.UUID, conclusion_data: dict, end_date: bool = False):
|
||||
"""Обновить заключение"""
|
||||
from apiApp.database import AiConclusion
|
||||
|
||||
db_conclusion = db.query(AiConclusion).filter(AiConclusion.id == conclusion_id).first()
|
||||
if not db_conclusion:
|
||||
return None
|
||||
|
||||
if conclusion_data:
|
||||
db_conclusion.conclusion = conclusion_data
|
||||
|
||||
if end_date:
|
||||
db_conclusion.end_date = datetime.datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_conclusion)
|
||||
return db_conclusion
|
||||
@@ -0,0 +1,79 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import os
|
||||
from apiApp.database import Audio
|
||||
from apiApp.schemas import AudioCreate
|
||||
from apiApp.config import UPLOAD_FOLDER
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class AudioCRUD:
|
||||
|
||||
@staticmethod
|
||||
def get_all(db: Session) -> List[Audio]:
|
||||
"""Получить все аудиофайлы"""
|
||||
return db.query(Audio).all()
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, audio_id: uuid.UUID) -> Optional[Audio]:
|
||||
"""Получить аудиофайл по ID"""
|
||||
return db.query(Audio).filter(Audio.id == audio_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_by_filename(db: Session, filename: str) -> Optional[Audio]:
|
||||
"""Получить аудиофайл по имени файла"""
|
||||
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:
|
||||
"""Создать новую запись аудиофайла"""
|
||||
db_audio = Audio(
|
||||
filename=audio_data.filename,
|
||||
file_path=file_path,
|
||||
index_date=datetime.datetime.utcnow(),
|
||||
file_size=file_size
|
||||
)
|
||||
db.add(db_audio)
|
||||
db.commit()
|
||||
db.refresh(db_audio)
|
||||
return db_audio
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, audio_id: uuid.UUID, audio_data: dict) -> Optional[Audio]:
|
||||
"""Обновить запись аудиофайла"""
|
||||
db_audio = db.query(Audio).filter(Audio.id == audio_id).first()
|
||||
if not db_audio:
|
||||
return None
|
||||
|
||||
for key, value in audio_data.items():
|
||||
if hasattr(db_audio, key):
|
||||
setattr(db_audio, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_audio)
|
||||
return db_audio
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, audio_id: uuid.UUID) -> Optional[Audio]:
|
||||
"""Удалить аудиофайл"""
|
||||
db_audio = db.query(Audio).filter(Audio.id == audio_id).first()
|
||||
if not db_audio:
|
||||
return None
|
||||
|
||||
# Удаление файла с диска
|
||||
if db_audio.file_path and os.path.exists(db_audio.file_path):
|
||||
try:
|
||||
os.remove(db_audio.file_path)
|
||||
except Exception as e:
|
||||
print(f"Error deleting file: {e}")
|
||||
|
||||
db.delete(db_audio)
|
||||
db.commit()
|
||||
return db_audio
|
||||
|
||||
@staticmethod
|
||||
def update_recognition_result(db: Session, audio_id: uuid.UUID, result: dict) -> Optional[Audio]:
|
||||
"""Обновить результат распознавания"""
|
||||
return AudioCRUD.update(db, audio_id, {"recognition_result": result})
|
||||
|
||||
Reference in New Issue
Block a user