import spaces import gradio as gr import tempfile import os import subprocess import shutil import asyncio from pathlib import Path import uuid import logging # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Конфигурация MAX_PROCESS_TIMEOUT = 120 TEMP_BASE_DIR = Path("/tmp/video_processing") TEMP_BASE_DIR.mkdir(exist_ok=True, parents=True) async def run_ffmpeg_async(cmd, timeout=120): """Асинхронный запуск ffmpeg с таймаутом""" try: process = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) try: stdout, stderr = await asyncio.wait_for( process.communicate(), timeout=timeout ) if process.returncode != 0: error_msg = stderr.decode() if stderr else "Unknown error" logger.error(f"FFmpeg error: {error_msg}") raise Exception(f"FFmpeg failed: {error_msg}") return stdout, stderr except asyncio.TimeoutError: try: process.kill() except: pass raise Exception(f"FFmpeg timeout after {timeout} seconds") except Exception as e: logger.error(f"FFmpeg execution error: {e}") raise async def process_video_with_resources(start_video, job_id): """Обработка видео с изоляцией ресурсов""" # Создаем уникальную рабочую директорию для каждого задания work_dir = TEMP_BASE_DIR / job_id work_dir.mkdir(exist_ok=True) # Пути к файлам input_path = work_dir / "input.mp4" audio_path = work_dir / "with_audio.mp4" blurred_path = work_dir / "blurred.mp4" try: # Шаг 1: Сохраняем входное видео if isinstance(start_video, str): shutil.copy(start_video, str(input_path)) else: # Для объектов Gradio Video with open(input_path, "wb") as f: # Gradio Video возвращает временный файл if hasattr(start_video, "name"): with open(start_video.name, "rb") as src: f.write(src.read()) else: # Fallback import io if isinstance(start_video, io.IOBase): f.write(start_video.read()) # Шаг 2: Добавляем аудио cmd_add_audio = [ 'ffmpeg', '-f', 'lavfi', '-i', 'anullsrc=channel_layout=stereo:sample_rate=44100', '-i', str(input_path), '-c:v', 'copy', '-c:a', 'aac', '-shortest', '-threads', '2', '-loglevel', 'error', '-y', str(audio_path) ] await run_ffmpeg_async(cmd_add_audio, timeout=MAX_PROCESS_TIMEOUT) # Шаг 3: Применяем размытие cmd_blur = [ 'ffmpeg', '-i', str(audio_path), '-vf', 'gblur=sigma=25', '-c:a', 'copy', '-threads', '2', '-loglevel', 'error', '-y', str(blurred_path) ] await run_ffmpeg_async(cmd_blur, timeout=MAX_PROCESS_TIMEOUT) # Возвращаем пути к результатам return str(audio_path), str(blurred_path) except Exception as e: logger.error(f"Processing error for job {job_id}: {e}") # Удаляем рабочую директорию в случае ошибки try: shutil.rmtree(work_dir) except: pass raise finally: # Удаляем только входной файл, результаты остаются try: if input_path.exists(): input_path.unlink() except: pass async def finalise_video(start_video): """Основная функция обработки видео""" job_id = str(uuid.uuid4()) logger.info(f"Starting video processing job: {job_id}") try: # Обрабатываем видео output_path1, output_path2 = await process_video_with_resources( start_video, job_id ) logger.info(f"Completed video processing job: {job_id}") return output_path1, output_path2 except Exception as e: logger.error(f"Finalise video error: {e}") raise gr.Error(f"Ошибка обработки видео: {str(e)}") def check_ffmpeg(): """Проверка доступности ffmpeg""" try: result = subprocess.run( ['ffmpeg', '-version'], capture_output=True, text=True, timeout=5 ) return result.returncode == 0 except Exception: return False def cleanup_old_files(): """Фоновая очистка старых файлов""" try: import time current_time = time.time() for item in TEMP_BASE_DIR.iterdir(): try: if item.is_dir(): # Удаляем директории старше 1 часа stat = item.stat() dir_age = current_time - stat.st_ctime if dir_age > 600: shutil.rmtree(item, ignore_errors=True) logger.info(f"Cleaned up old directory: {item}") except Exception as e: logger.warning(f"Failed to clean up {item}: {e}") except Exception as e: logger.error(f"Cleanup error: {e}") # Создаем интерфейс Gradio # ИСПРАВЛЕНИЕ: убрали theme из конструктора Blocks with gr.Blocks() as demo: gr.Markdown("# Видео Финализатор") with gr.Row(): with gr.Column(scale=1): input_video = gr.Video( label="Входное видео", interactive=True ) process_btn = gr.Button( "Обработать видео", variant="primary" ) with gr.Column(scale=2): with gr.Row(): output1 = gr.Video( label="Видео с аудио", autoplay=True, interactive=False ) output2 = gr.Video( label="Размытое видео", autoplay=True, interactive=False ) # Обработчик без ограничений параллелизма process_btn.click( fn=finalise_video, inputs=[input_video], outputs=[output1, output2] ) if __name__ == "__main__": # Проверяем ffmpeg if not check_ffmpeg(): logger.error("FFmpeg не найден! Установите ffmpeg.") print("Ошибка: FFmpeg не установлен.") # Периодическая очистка старых файлов import threading import time def cleanup_thread(): while True: cleanup_old_files() time.sleep(600) # Каждый час cleanup_thread = threading.Thread(target=cleanup_thread, daemon=True) cleanup_thread.start() # ИСПРАВЛЕНИЕ: правильный запуск для Gradio 4.0+ # Убраны несовместимые параметры: enable_queue, max_threads demo.queue(max_size=100).launch( server_name="0.0.0.0", server_port=7860 )