| 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: |
| |
| if isinstance(start_video, str): |
| shutil.copy(start_video, str(input_path)) |
| else: |
| |
| with open(input_path, "wb") as f: |
| |
| if hasattr(start_video, "name"): |
| with open(start_video.name, "rb") as src: |
| f.write(src.read()) |
| else: |
| |
| import io |
| if isinstance(start_video, io.IOBase): |
| f.write(start_video.read()) |
| |
| |
| 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) |
| |
| |
| 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(): |
| |
| 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}") |
|
|
| |
| |
| 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__": |
| |
| 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() |
| |
| |
| |
| demo.queue(max_size=100).launch( |
| server_name="0.0.0.0", |
| server_port=7860 |
| ) |