Finaliser / app.py
GiorgioV's picture
Update app.py
b3a1b8a verified
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
)