filename = request.filename file_path = request.file_path logger.info(f"📝 Регистрация файла: {filename}") # Проверяем, что файл уже существует в общей папке if not os.path.exists(file_path): logger.error(f"❌ Файл не найден: {file_path}") raise HTTPException( status_code=404, detail=f'Файл не найден на диске: {file_path}' ) # Проверяем, что файл не был уже зарегистрирован existing_audio = db.query(Audio).filter(Audio.filename == filename).first() if existing_audio: logger.warning(f"⚠️ Файл уже зарегистрирован: {filename}") raise HTTPException( status_code=400, detail=f'Файл {filename} уже зарегистрирован в БД' ) # Получаем размер файла file_size = os.path.getsize(file_path) # Создаём запись в БД audio = Audio() audio.filename = filename audio.file_size = file_size audio.index_date = datetime.utcnow() db.add(audio) db.commit() db.refresh(audio) logger.info(f"✅ Файл зарегистрирован: {filename} (audio_id={audio.id})") return AudioRegisterResponse( id=str(audio.id), filename=audio.filename, file_size=audio.file_size, created_at=audio.index_date ) except HTTPException: raise except Exception as e: db.rollback() logger.error(f"❌ Ошибка при регистрации файла: {e}") raise HTTPException( status_code=500, detail=f'Ошибка при регистрации: {str(e)}' ) def process_audio_file(audio_id: str, db: Session): """ Фоновая обработка одного аудиофайла Отправляет запрос в GigaAM API для распознавания """ try: audio = db.query(Audio).filter(Audio.id == audio_id).first() if not audio: logger.error(f"❌ Audio {audio_id} не найден") return logger.info(f"🎵 Запуск распознавания для {audio.filename}") # Формируем запрос в GigaAM API from apiApp.config import GIGAAM_API_URL api_url = f"{GIGAAM_API_URL}/api/call/process" payload = { "filename": audio.filename } # Отправляем запрос в GigaAM API import requests response = requests.post(api_url, json=payload, timeout=10) if response.status_code == 200 or response.status_code == 202: logger.info(f"✅ Запущено распознавание для {audio.filename}") else: logger.error(f"❌ Ошибка запуска распознавания для {audio.filename}: {response.status_code} - {response.text}") except Exception as e: logger.error(f"❌ Ошибка при обработке {audio_id}: {e}") @audio_management_router.post("/audio/process-all") async def process_all_pending_audio( request: AudioProcessAllRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db) ): """ Запуск распознавания для всех Audio без AiConclusion Находит все записи Audio, у которых нет AiConclusion, и запускает распознавание для них (до указанного лимита). Args: request: {limit: 100} - максимум файлов для обработки Returns: 200 OK + { "started_count": 15, "pending_files": ["file1.wav", "file2.wav", ...], "total_pending": 50 } """ try: from sqlalchemy import exists limit = request.limit logger.info(f"🚀 Поиск Audio без AiConclusion (limit={limit})") # Находим все Audio без AiConclusion через подзапрос subquery = db.query(AiConclusion.audio_id).filter( AiConclusion.audio_id == Audio.id ) pending_audio = db.query(Audio).filter( ~exists().where(subquery.exists()) ).order_by(Audio.index_date.asc()).limit(limit).all() total_pending = db.query(Audio).filter( ~exists().where(subquery.exists()) ).count() if not pending_audio: logger.info("ℹ️ Нет файлов для распознавания") return { "started_count": 0, "pending_files": [], "total_pending": 0, "message": "Нет файлов без AiConclusion" } logger.info(f"📋 Найдено файлов для обработки: {len(pending_audio)} из {total_pending}") # Добавляем задачи в фон started_count = 0 pending_files = [] for audio in pending_audio: # Проверяем, что файл существует file_path = os.path.join(AUDIOFILES_PATH, audio.filename) if not os.path.exists(file_path): logger.warning(f"⚠️ Файл не найден на диске: {audio.filename}") continue # Добавляем в фон (асинхронно) # В FastAPI используем BackgroundTasks # Но нужно создавать новую сессию для каждого таска pending_files.append(audio.filename) started_count += 1 # Запускаем обработку в фоне # Используем lambda для захвата audio_id background_tasks.add_task( process_single_audio, str(audio.id) ) logger.info(f"✅ Запущено распознавание для {started_count} файлов") return { "started_count": started_count, "pending_files": pending_files, "total_pending": total_pending } except Exception as e: logger.error(f"❌ Ошибка при запуске пакетного распознавания: {e}") raise HTTPException( status_code=500, detail=f'Ошибка: {str(e)}' ) def process_single_audio(audio_id: str): """ Обработка одного аудиофайла в фоне Создаёт новую DB сессию для обработки """ from apiApp.database import SessionLocal db = SessionLocal() try: process_audio_file(audio_id, db) finally: db.close() @audio_management_router.get("/audio/pending") async def get_pending_audio( db: Session = Depends(get_db), limit: int = 100 ): """ Получить список Audio без AiConclusion Query Parameters: limit: Максимум файлов (default: 100) Returns: Список файлов, ожидающих распознавания """ try: from sqlalchemy import exists subquery = db.query(AiConclusion.audio_id).filter( AiConclusion.audio_id == Audio.id ) pending_audio = db.query(Audio).filter( ~exists().where(subquery.exists()) ).order_by(Audio.index_date.asc()).limit(limit).all() files_info = [] for audio in pending_audio: file_path = os.path.join(AUDIOFILES_PATH, audio.filename) exists = os.path.exists(file_path) files_info.append({ "audio_id": str(audio.id), "filename": audio.filename, "file_size": audio.file_size, "created_at": audio.index_date.isoformat() if audio.index_date else None, "exists_on_disk": exists }) total_pending = db.query(Audio).filter( ~exists().where(subquery.exists()) ).count() return { "total_pending": total_pending, "count": len(files_info), "files": files_info } except Exception as e: logger.error(f"❌ Ошибка при получении списка: {e}") raise HTTPException( status_code=500, detail=str(e) ) @audio_management_router.get("/audio/stats") async def get_audio_stats(db: Session = Depends(get_db)): """ Получить статистику по аудиофайлам Returns: Статистика по Audio записям """ try: from sqlalchemy import exists total_audio = db.query(Audio).count() subquery = db.query(AiConclusion.audio_id).filter( AiConclusion.audio_id == Audio.id ) with_conclusion = db.query(Audio).filter( exists().where(subquery.exists()) ).count() without_conclusion = total_audio - with_conclusion # Проверяем существование файлов на диске all_audio = db.query(Audio).all() existing_count = 0 for audio in all_audio: file_path = os.path.join(AUDIOFILES_PATH, audio.filename) if os.path.exists(file_path): existing_count += 1 return { "total_audio": total_audio, "with_conclusion": with_conclusion, "without_conclusion": without_conclusion, "existing_on_disk": existing_count, "missing_on_disk": total_audio - existing_count } except Exception as e: logger.error(f"❌ Ошибка при получении статистики: {e}") raise HTTPException( status_code=500, detail=str(e) ) def auto_restore_on_startup(db: Session, limit: int = 100): """ Автоматическое восстановление распознавания при старте FileAudioAPI Проверяет, есть ли файлы без AiConclusion, и запускает их распознавание Args: db: Сессия БД limit: Максимум файлов для восстановления """ try: from sqlalchemy import or_ # Проверяем, есть ли файлы без AiConclusion pending_audio = db.query(Audio).filter( or_( Audio.AiConclusion == None, Audio.AiConclusion == '' ) ).limit(limit).all() if not pending_audio: logger.info("ℹ️ Auto-restore: нет файлов для распознавания") return logger.info(f"🔄 Auto-restore: найдено {len(pending_audio)} файлов без AiConclusion") # Запускаем распознавание started_count = 0 for audio in pending_audio: file_path = os.path.join(AUDIOFILES_PATH, audio.filename) if not os.path.exists(file_path): logger.warning(f"⚠️ Файл не найден: {audio.filename}") continue # Отправляем в GigaAM API from apiApp.config import GIGAAM_API_URL api_url = f"{GIGAAM_API_URL}/api/call/process" payload = {"filename": audio.filename} try: import requests response = requests.post(api_url, json=payload, timeout=5) if response.status_code in [200, 202]: logger.info(f"✅ Запущено распознавание: {audio.filename}") started_count += 1 else: logger.warning(f"⚠️ Ошибка запуска {audio.filename}: {response.status_code}") except Exception as e: logger.error(f"❌ Ошибка при запуске {audio.filename}: {e}") logger.info(f"🎉 Auto-restore завершено: запущено {started_count} файлов") except Exception as e: logger.error(f"❌ Ошибка при auto-restore: {e}")