GiorgioV commited on
Commit
cdfac50
·
verified ·
1 Parent(s): 6e0a7ae

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -107
app.py CHANGED
@@ -5,11 +5,8 @@ import os
5
  import subprocess
6
  import shutil
7
  import asyncio
8
- import aiofiles
9
  from pathlib import Path
10
  import uuid
11
- import json
12
- from datetime import datetime
13
  import logging
14
 
15
  # Настройка логирования
@@ -17,18 +14,13 @@ logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger(__name__)
18
 
19
  # Конфигурация
20
- MAX_PROCESS_TIMEOUT = 120
21
  TEMP_BASE_DIR = Path("/tmp/video_processing")
22
  TEMP_BASE_DIR.mkdir(exist_ok=True, parents=True)
23
 
24
- # Глобальный кэш для отслеживания активных процессов
25
- active_processes = {}
26
- process_lock = asyncio.Lock()
27
-
28
- async def run_ffmpeg_async(cmd, timeout=300):
29
  """Асинхронный запуск ffmpeg с таймаутом"""
30
  try:
31
- # Используем asyncio для неблокирующего выполнения
32
  process = await asyncio.create_subprocess_exec(
33
  *cmd,
34
  stdout=asyncio.subprocess.PIPE,
@@ -49,35 +41,16 @@ async def run_ffmpeg_async(cmd, timeout=300):
49
  return stdout, stderr
50
 
51
  except asyncio.TimeoutError:
52
- process.kill()
53
- await process.wait()
 
 
54
  raise Exception(f"FFmpeg timeout after {timeout} seconds")
55
 
56
  except Exception as e:
57
  logger.error(f"FFmpeg execution error: {e}")
58
  raise
59
 
60
- def cleanup_directory(directory, max_age_hours=1):
61
- """Очистка старых временных файлов"""
62
- try:
63
- now = datetime.now()
64
- for item in directory.iterdir():
65
- try:
66
- # Получаем время создания файла
67
- stat = item.stat()
68
- file_age = now - datetime.fromtimestamp(stat.st_ctime)
69
-
70
- if file_age.total_seconds() > max_age_hours * 3600:
71
- if item.is_file():
72
- item.unlink()
73
- else:
74
- shutil.rmtree(item)
75
- logger.info(f"Cleaned up old directory: {item}")
76
- except Exception as e:
77
- logger.warning(f"Failed to clean up {item}: {e}")
78
- except Exception as e:
79
- logger.error(f"Cleanup error: {e}")
80
-
81
  async def process_video_with_resources(start_video, job_id):
82
  """Обработка видео с изоляцией ресурсов"""
83
  # Создаем уникальную рабочую директорию для каждого задания
@@ -88,22 +61,25 @@ async def process_video_with_resources(start_video, job_id):
88
  input_path = work_dir / "input.mp4"
89
  audio_path = work_dir / "with_audio.mp4"
90
  blurred_path = work_dir / "blurred.mp4"
91
- final_path1 = work_dir / "final1.mp4"
92
- final_path2 = work_dir / "final2.mp4"
93
 
94
  try:
95
  # Шаг 1: Сохраняем входное видео
96
  if isinstance(start_video, str):
97
  shutil.copy(start_video, str(input_path))
98
  else:
99
- # Для объектов Gradio
100
  with open(input_path, "wb") as f:
101
- if hasattr(start_video, "read"):
102
- f.write(start_video.read())
 
 
103
  else:
104
- start_video.save(f)
 
 
 
105
 
106
- # Шаг 2: Добавляем аудио (параллельно с ограниченными ресурсами)
107
  cmd_add_audio = [
108
  'ffmpeg',
109
  '-f', 'lavfi',
@@ -112,13 +88,13 @@ async def process_video_with_resources(start_video, job_id):
112
  '-c:v', 'copy',
113
  '-c:a', 'aac',
114
  '-shortest',
115
- '-threads', '1', # Ограничиваем до 1 потока для лучшей масштабируемости
116
- '-loglevel', 'quiet', # Минимальное логирование
117
  '-y',
118
  str(audio_path)
119
  ]
120
 
121
- await run_ffmpeg_async(cmd_add_audio, timeout=120)
122
 
123
  # Шаг 3: Применяем размытие
124
  cmd_blur = [
@@ -126,62 +102,53 @@ async def process_video_with_resources(start_video, job_id):
126
  '-i', str(audio_path),
127
  '-vf', 'gblur=sigma=25',
128
  '-c:a', 'copy',
129
- '-threads', '1',
130
- '-loglevel', 'quiet',
131
  '-y',
132
  str(blurred_path)
133
  ]
134
 
135
- await run_ffmpeg_async(cmd_blur, timeout=120)
136
-
137
- # Шаг 4: Копируем результаты в финальные файлы
138
- shutil.copy(audio_path, final_path1)
139
- shutil.copy(blurred_path, final_path2)
140
 
141
- return str(final_path1), str(final_path2)
 
142
 
143
  except Exception as e:
144
  logger.error(f"Processing error for job {job_id}: {e}")
 
 
 
 
 
 
 
145
  raise
146
  finally:
147
- # Очищаем промежуточные файлы, оставляя только финальные
148
  try:
149
- for file in [input_path, audio_path, blurred_path]:
150
- if file.exists():
151
- file.unlink()
152
- except Exception as e:
153
- logger.warning(f"Failed to clean intermediate files: {e}")
154
 
 
155
  async def finalise_video(start_video):
156
  """Основная функция обработки видео"""
157
- # Генерируем уникальный ID задания
158
  job_id = str(uuid.uuid4())
159
-
160
- async with process_lock:
161
- active_processes[job_id] = datetime.now()
162
 
163
  try:
164
- # Периодическая очистка старых файлов
165
- cleanup_directory(TEMP_BASE_DIR, max_age_hours=1)
166
-
167
  # Обрабатываем видео
168
  output_path1, output_path2 = await process_video_with_resources(
169
  start_video, job_id
170
  )
171
 
 
172
  return output_path1, output_path2
173
 
174
  except Exception as e:
175
  logger.error(f"Finalise video error: {e}")
176
  raise gr.Error(f"Ошибка обработки видео: {str(e)}")
177
- finally:
178
- # Удаляем задание из активных
179
- async with process_lock:
180
- if job_id in active_processes:
181
- del active_processes[job_id]
182
-
183
- # Логируем статистику
184
- logger.info(f"Active processes: {len(active_processes)}")
185
 
186
  def check_ffmpeg():
187
  """Проверка доступности ffmpeg"""
@@ -196,21 +163,43 @@ def check_ffmpeg():
196
  except Exception:
197
  return False
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  # Создаем интерфейс Gradio
200
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
201
- gr.Markdown("""
202
- # Финализатор
203
- """)
204
 
205
  with gr.Row():
206
  with gr.Column(scale=1):
207
  input_video = gr.Video(
208
- label="📹 Входное видео",
209
  interactive=True
210
  )
211
 
212
  process_btn = gr.Button(
213
- "Финализировать",
214
  variant="primary",
215
  size="lg"
216
  )
@@ -218,61 +207,47 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
218
  with gr.Column(scale=2):
219
  with gr.Row():
220
  output1 = gr.Video(
221
- label="Video",
222
  autoplay=True,
223
  interactive=False
224
  )
225
  output2 = gr.Video(
226
- label="Video",
227
  autoplay=True,
228
  interactive=False
229
  )
230
 
231
- # Обработчик с прогрессом
232
- process_event = process_btn.click(
233
- fn=finalise_video,
234
- inputs=[input_video],
235
- outputs=[output1, output2],
236
- # Не устанавливаем concurrency_limit - не ограничиваем параллелизм
237
- )
238
-
239
- # Добавляем обработку Enter
240
- input_video.submit(
241
  fn=finalise_video,
242
  inputs=[input_video],
243
  outputs=[output1, output2]
244
  )
245
-
246
- # Примеры
247
- gr.Examples(
248
- examples=[
249
- ["https://example.com/sample1.mp4"],
250
- ["https://example.com/sample2.mp4"],
251
- ],
252
- inputs=[input_video],
253
- outputs=[output1, output2],
254
- fn=finalise_video,
255
- cache_examples=False # Не кэшируем, чтобы обрабатывать каждый раз
256
- )
257
 
258
  if __name__ == "__main__":
259
  # Проверяем ffmpeg
260
  if not check_ffmpeg():
261
  logger.error("FFmpeg не найден! Установите ffmpeg.")
262
- raise RuntimeError("FFmpeg не установлен")
 
 
 
 
 
 
 
263
 
264
  # Запускаем с максимальной производительностью
 
265
  demo.queue(
266
  max_size=100, # Большая очередь
267
- default_concurrency_limit=None
268
  ).launch(
269
  server_name="0.0.0.0",
270
  server_port=7860,
271
  share=False,
272
- # Настройки для высокой производительности
273
- max_threads=40,
274
  enable_queue=True,
275
  show_error=True,
276
- debug=False,
277
- ssl_verify=False
278
  )
 
5
  import subprocess
6
  import shutil
7
  import asyncio
 
8
  from pathlib import Path
9
  import uuid
 
 
10
  import logging
11
 
12
  # Настройка логирования
 
14
  logger = logging.getLogger(__name__)
15
 
16
  # Конфигурация
17
+ MAX_PROCESS_TIMEOUT = 120
18
  TEMP_BASE_DIR = Path("/tmp/video_processing")
19
  TEMP_BASE_DIR.mkdir(exist_ok=True, parents=True)
20
 
21
+ async def run_ffmpeg_async(cmd, timeout=120):
 
 
 
 
22
  """Асинхронный запуск ffmpeg с таймаутом"""
23
  try:
 
24
  process = await asyncio.create_subprocess_exec(
25
  *cmd,
26
  stdout=asyncio.subprocess.PIPE,
 
41
  return stdout, stderr
42
 
43
  except asyncio.TimeoutError:
44
+ try:
45
+ process.kill()
46
+ except:
47
+ pass
48
  raise Exception(f"FFmpeg timeout after {timeout} seconds")
49
 
50
  except Exception as e:
51
  logger.error(f"FFmpeg execution error: {e}")
52
  raise
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  async def process_video_with_resources(start_video, job_id):
55
  """Обработка видео с изоляцией ресурсов"""
56
  # Создаем уникальную рабочую директорию для каждого задания
 
61
  input_path = work_dir / "input.mp4"
62
  audio_path = work_dir / "with_audio.mp4"
63
  blurred_path = work_dir / "blurred.mp4"
 
 
64
 
65
  try:
66
  # Шаг 1: Сохраняем входное видео
67
  if isinstance(start_video, str):
68
  shutil.copy(start_video, str(input_path))
69
  else:
70
+ # Для объектов Gradio Video
71
  with open(input_path, "wb") as f:
72
+ # Gradio Video возвращает временный файл
73
+ if hasattr(start_video, "name"):
74
+ with open(start_video.name, "rb") as src:
75
+ f.write(src.read())
76
  else:
77
+ # Fallback
78
+ import io
79
+ if isinstance(start_video, io.IOBase):
80
+ f.write(start_video.read())
81
 
82
+ # Шаг 2: Добавляем аудио
83
  cmd_add_audio = [
84
  'ffmpeg',
85
  '-f', 'lavfi',
 
88
  '-c:v', 'copy',
89
  '-c:a', 'aac',
90
  '-shortest',
91
+ '-threads', '2', # Используем 2 потока для баланса производительности
92
+ '-loglevel', 'error',
93
  '-y',
94
  str(audio_path)
95
  ]
96
 
97
+ await run_ffmpeg_async(cmd_add_audio, timeout=MAX_PROCESS_TIMEOUT)
98
 
99
  # Шаг 3: Применяем размытие
100
  cmd_blur = [
 
102
  '-i', str(audio_path),
103
  '-vf', 'gblur=sigma=25',
104
  '-c:a', 'copy',
105
+ '-threads', '2',
106
+ '-loglevel', 'error',
107
  '-y',
108
  str(blurred_path)
109
  ]
110
 
111
+ await run_ffmpeg_async(cmd_blur, timeout=MAX_PROCESS_TIMEOUT)
 
 
 
 
112
 
113
+ # Возвращаем пути к результатам
114
+ return str(audio_path), str(blurred_path)
115
 
116
  except Exception as e:
117
  logger.error(f"Processing error for job {job_id}: {e}")
118
+
119
+ # Удаляем рабочую директорию в случае ошибки
120
+ try:
121
+ shutil.rmtree(work_dir)
122
+ except:
123
+ pass
124
+
125
  raise
126
  finally:
127
+ # Удаляем только входной файл, результаты остаются
128
  try:
129
+ if input_path.exists():
130
+ input_path.unlink()
131
+ except:
132
+ pass
 
133
 
134
+ # Убрал декоратор @spaces.GPU - у Space нет GPU
135
  async def finalise_video(start_video):
136
  """Основная функция обработки видео"""
 
137
  job_id = str(uuid.uuid4())
138
+ logger.info(f"Starting video processing job: {job_id}")
 
 
139
 
140
  try:
 
 
 
141
  # Обрабатываем видео
142
  output_path1, output_path2 = await process_video_with_resources(
143
  start_video, job_id
144
  )
145
 
146
+ logger.info(f"Completed video processing job: {job_id}")
147
  return output_path1, output_path2
148
 
149
  except Exception as e:
150
  logger.error(f"Finalise video error: {e}")
151
  raise gr.Error(f"Ошибка обработки видео: {str(e)}")
 
 
 
 
 
 
 
 
152
 
153
  def check_ffmpeg():
154
  """Проверка доступности ffmpeg"""
 
163
  except Exception:
164
  return False
165
 
166
+ def cleanup_old_files():
167
+ """Фоновая очистка старых файлов"""
168
+ try:
169
+ now = asyncio.get_event_loop().time()
170
+ for item in TEMP_BASE_DIR.iterdir():
171
+ try:
172
+ if item.is_dir():
173
+ # Удаляем директории старше 1 часа
174
+ stat = item.stat()
175
+ dir_age = now - stat.st_ctime
176
+ if dir_age > 3600: # 1 час
177
+ shutil.rmtree(item, ignore_errors=True)
178
+ logger.info(f"Cleaned up old directory: {item}")
179
+ except Exception as e:
180
+ logger.warning(f"Failed to clean up {item}: {e}")
181
+ except Exception as e:
182
+ logger.error(f"Cleanup error: {e}")
183
+
184
+ async def periodic_cleanup():
185
+ """Периодическая очистка старых файлов"""
186
+ while True:
187
+ await asyncio.sleep(3600) # Каждый час
188
+ cleanup_old_files()
189
+
190
  # Создаем интерфейс Gradio
191
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
192
+ gr.Markdown("# Видео Финализатор")
 
 
193
 
194
  with gr.Row():
195
  with gr.Column(scale=1):
196
  input_video = gr.Video(
197
+ label="Входное видео",
198
  interactive=True
199
  )
200
 
201
  process_btn = gr.Button(
202
+ "Обработать видео",
203
  variant="primary",
204
  size="lg"
205
  )
 
207
  with gr.Column(scale=2):
208
  with gr.Row():
209
  output1 = gr.Video(
210
+ label="Видео с аудио",
211
  autoplay=True,
212
  interactive=False
213
  )
214
  output2 = gr.Video(
215
+ label="Размытое видео",
216
  autoplay=True,
217
  interactive=False
218
  )
219
 
220
+ # Обработчик без ограничений параллелизма
221
+ process_btn.click(
 
 
 
 
 
 
 
 
222
  fn=finalise_video,
223
  inputs=[input_video],
224
  outputs=[output1, output2]
225
  )
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
  if __name__ == "__main__":
228
  # Проверяем ffmpeg
229
  if not check_ffmpeg():
230
  logger.error("FFmpeg не найден! Установите ffmpeg.")
231
+ print("Ошибка: FFmpeg не установлен.")
232
+ print("Для установки в Hugging Face Spaces добавьте в README.md:")
233
+ print("```")
234
+ print("app_port: 7860")
235
+ print("sdk: gradio")
236
+ print("sdk_version: 4.0.0")
237
+ print("```")
238
+ print("И убедитесь, что ffmpeg установлен в системе.")
239
 
240
  # Запускаем с максимальной производительностью
241
+ # Нет ограничений на параллелизм - будьте осторожны!
242
  demo.queue(
243
  max_size=100, # Большая очередь
244
+ default_concurrency_limit=None # Без ограничений
245
  ).launch(
246
  server_name="0.0.0.0",
247
  server_port=7860,
248
  share=False,
249
+ max_threads=100, # Много потоков для обработки
 
250
  enable_queue=True,
251
  show_error=True,
252
+ debug=False
 
253
  )