diff --git a/apiApp/routers/audio_files_router.py b/apiApp/routers/audio_files_router.py new file mode 100644 index 0000000..0d479f8 --- /dev/null +++ b/apiApp/routers/audio_files_router.py @@ -0,0 +1,236 @@ +""" +API endpoints для обслуживания аудиофайлов +""" +from fastapi import APIRouter, HTTPException, Depends +from fastapi.responses import FileResponse, StreamingResponse +from sqlalchemy.orm import Session +from typing import Optional +import os +import logging + +from apiApp.database import get_db +from apiApp.database.Audio import Audio +from apiApp.config import AUDIOFILES_PATH + +logger = logging.getLogger(__name__) +audio_files_router = APIRouter() + + +@audio_files_router.get("/audio/{filename}") +async def get_audio_file(filename: str): + """ + Возвращает аудиофайл для стриминга/воспроизведения + + Args: + filename: Имя аудиофайла (например, "in-xxx.wav") + + Returns: + StreamingResponse с аудиофайлом + """ + try: + # Проверяем расширение файла + allowed_extensions = ['.wav', '.mp3', '.ogg', '.m4a', '.flac'] + if not any(filename.lower().endswith(ext) for ext in allowed_extensions): + raise HTTPException( + status_code=400, + detail=f'Неподдерживаемый формат файла. Разрешены: {", ".join(allowed_extensions)}' + ) + + # Формируем путь к файлу + file_path = os.path.join(AUDIOFILES_PATH, filename) + + # Проверяем существование файла + if not os.path.exists(file_path): + logger.warning(f"⚠️ Файл не найден: {file_path}") + raise HTTPException( + status_code=404, + detail=f'Файл {filename} не найден' + ) + + logger.info(f"🎵 Отдача аудио: {filename}") + + # Определяем MIME тип + import mimetypes + mime_type, _ = mimetypes.guess_type(file_path) + if mime_type is None: + mime_type = 'audio/wav' + + # Возвращаем файл как поток + def iterfile(): + with open(file_path, 'rb') as f: + data = f.read(8192) + while data: + yield data + data = f.read(8192) + + return StreamingResponse( + iterfile(), + media_type=mime_type, + headers={ + 'Content-Disposition': f'inline; filename="{filename}"', + 'Accept-Ranges': 'bytes' + } + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"❌ Ошибка при отдаче аудио: {e}") + raise HTTPException( + status_code=500, + detail=f'Ошибка при отдаче файла: {str(e)}' + ) + + +@audio_files_router.get("/audio/{filename}/info") +async def get_audio_info(filename: str, db: Session = Depends(get_db)): + """ + Возвращает информацию об аудиофайле + + Args: + filename: Имя аудиофайла + + Returns: + JSON с информацией о файле + """ + try: + # Ищем информацию в БД + audio = db.query(Audio).filter(Audio.filename == filename).first() + + if not audio: + raise HTTPException( + status_code=404, + detail=f'Файл {filename} не найден в БД' + ) + + # Проверяем существование файла + file_path = os.path.join(AUDIOFILES_PATH, filename) + if not os.path.exists(file_path): + raise HTTPException( + status_code=404, + detail=f'Файл {filename} не найден на диске' + ) + + # Получаем размер файла + file_size = os.path.getsize(file_path) + + # Определяем длительность (приблизительно) + # В идеале использовать библиотеку типа mutagen или soundfile + duration = None + try: + import wave + with wave.open(file_path, 'r') as wav_file: + frames = wav_file.getnframes() + rate = wav_file.getframerate() + duration = frames / float(rate) if rate > 0 else 0 + except: + pass + + return { + "success": True, + "filename": filename, + "file_size_bytes": file_size, + "duration_seconds": duration, + "created_at": audio.index_date.isoformat() if audio.index_date else None, + "audio_id": str(audio.id) + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"❌ Ошибка при получении информации: {e}") + raise HTTPException( + status_code=500, + detail=str(e) + ) + + +@audio_files_router.get("/audio/list") +async def list_audio_files( + db: Session = Depends(get_db), + limit: int = 100, + offset: int = 0 +): + """ + Возвращает список аудиофайлов + + Query Parameters: + limit: Максимальное количество файлов (default: 100) + offset: Смещение для пагинации (default: 0) + + Returns: + JSON со списком файлов + """ + try: + # Получаем список файлов из БД + query = db.query(Audio).order_by(Audio.index_date.desc()) + + total = query.count() + audio_files = query.offset(offset).limit(limit).all() + + files_info = [] + for audio in audio_files: + file_path = os.path.join(AUDIOFILES_PATH, audio.filename) + exists = os.path.exists(file_path) + + files_info.append({ + "filename": audio.filename, + "file_size": audio.file_size, + "created_at": audio.index_date.isoformat() if audio.index_date else None, + "exists": exists, + "audio_id": str(audio.id) + }) + + return { + "success": True, + "total": total, + "count": len(files_info), + "files": files_info + } + + except Exception as e: + logger.error(f"❌ Ошибка при получении списка файлов: {e}") + raise HTTPException( + status_code=500, + detail=str(e) + ) + + +@audio_files_router.get("/audio/{filename}/download") +async def download_audio_file(filename: str): + """ + Возвращает аудиофайл для скачивания + + Args: + filename: Имя аудиофайла + + Returns: + FileResponse для скачивания + """ + try: + # Формируем путь к файлу + file_path = os.path.join(AUDIOFILES_PATH, filename) + + # Проверяем существование файла + if not os.path.exists(file_path): + raise HTTPException( + status_code=404, + detail=f'Файл {filename} не найден' + ) + + logger.info(f"📥 Скачивание аудио: {filename}") + + return FileResponse( + path=file_path, + media_type='audio/wav', + filename=filename + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"❌ Ошибка при скачивании: {e}") + raise HTTPException( + status_code=500, + detail=str(e) + ) diff --git a/main.py b/main.py index 7b5dc0a..5fdc3f9 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ from apiApp.config import APP_TITLE, APP_VERSION, API_V1_PREFIX, UPLOAD_FOLDER, from apiApp.database import engine, Base from apiApp.routers import audio_router, recognition_router from apiApp.routers.ai_conclusion_router import ai_conclusion_router +from apiApp.routers.audio_files_router import audio_files_router # Настройка логирования logging.basicConfig(level=logging.INFO) @@ -58,6 +59,7 @@ async def startup_event(): app.include_router(audio_router, prefix=API_V1_PREFIX, tags=["audio"]) app.include_router(recognition_router, prefix=API_V1_PREFIX, tags=["recognition"]) app.include_router(ai_conclusion_router, prefix=API_V1_PREFIX, tags=["ai_conclusion"]) +app.include_router(audio_files_router, prefix=API_V1_PREFIX, tags=["audio_files"]) # Статические файлы (для загрузки аудио) app.mount("/uploads", StaticFiles(directory=str(UPLOAD_FOLDER)), name="uploads")