This commit is contained in:
stirelshka8 2026-03-24 18:34:32 +03:00
parent b8211b5220
commit 25782333e2
2 changed files with 90 additions and 3 deletions

View File

@ -1,4 +1,5 @@
import os
import shutil
import subprocess
import sys
import tempfile
@ -126,6 +127,11 @@ class VideoProcessor:
def __init__(self, logger=None):
self.logger = logger or Logger()
def check_disk_space_for_job(self, output_dir, video_data, quality):
"""Публичная проверка места на диске до старта обработки."""
use_batch = len(video_data) > BATCH_THRESHOLD
self._check_disk_space_or_raise(output_dir, video_data, quality, use_batch)
def process_videos(self, video_data, output_dir, output_format="mp4",
quality="high", progress_callback=None,
fps=None, preset="medium", audio_bitrate="192k",
@ -138,6 +144,7 @@ class VideoProcessor:
check = cancel_check if callable(cancel_check) else lambda: False
use_batch = len(video_data) > BATCH_THRESHOLD
self._check_disk_space_or_raise(output_dir, video_data, quality, use_batch)
if use_batch:
self.logger.log("INFO", f"Режим пакетной обработки (файлов > {BATCH_THRESHOLD}), экономия памяти")
return self._process_videos_batch(
@ -254,7 +261,9 @@ class VideoProcessor:
try:
ensure_directory(output_dir)
with tempfile.TemporaryDirectory(prefix="ave_") as tmpdir:
# Временные части создаём рядом с выходным каталогом:
# это снижает риск переполнения системного диска (обычно C:\Temp).
with tempfile.TemporaryDirectory(prefix="ave_", dir=output_dir) as tmpdir:
for i, data in enumerate(video_data):
if cancel_check():
raise CancelledError()
@ -390,9 +399,9 @@ class VideoProcessor:
except CancelledError:
self._remove_if_exists(output_path)
raise
except Exception:
except Exception as e:
self._remove_if_exists(output_path)
raise
raise RuntimeError(self._friendly_write_error(e, output_dir)) from e
if resized is not None:
try:
resized.close()
@ -416,6 +425,73 @@ class VideoProcessor:
except OSError:
pass
@staticmethod
def _friendly_write_error(error, output_dir):
msg = str(error or "")
low = msg.lower()
if "no space left on device" in low or "errno 32" in low or "broken pipe" in low:
return (
"Недостаточно свободного места на диске для сохранения видео. "
f"Освободите место в папке назначения ({output_dir}) и повторите попытку."
)
return msg
def _check_disk_space_or_raise(self, output_dir, video_data, quality, use_batch):
try:
free_bytes = shutil.disk_usage(output_dir).free
except Exception:
# Если не получилось определить свободное место, не блокируем обработку.
return
required_bytes = self._estimate_required_space_bytes(video_data, quality, use_batch)
if free_bytes >= required_bytes:
self.logger.log(
"INFO",
f"Проверка места: требуется ~{self._format_size(required_bytes)}, доступно {self._format_size(free_bytes)}"
)
return
raise RuntimeError(
"Недостаточно свободного места для обработки. "
f"Нужно примерно {self._format_size(required_bytes)}, доступно {self._format_size(free_bytes)}. "
"Выберите другой диск или освободите место."
)
@staticmethod
def _estimate_required_space_bytes(video_data, quality, use_batch):
input_total = 0
for data in video_data:
path = data.get("path")
if path and os.path.isfile(path):
try:
input_total += os.path.getsize(path)
except OSError:
pass
# Грубая оценка размера результата с запасом в зависимости от качества.
quality_factor = {
"low": 0.7,
"medium": 1.0,
"high": 1.4,
}.get(quality, 1.0)
output_estimate = int(input_total * quality_factor)
# В пакетном режиме временные части могут занимать объём, близкий к итогу.
temp_estimate = output_estimate if use_batch else int(output_estimate * 0.2)
# Дополнительный запас на контейнер/служебные файлы и колебания битрейта.
safety_margin = int((output_estimate + temp_estimate) * 0.25)
return output_estimate + temp_estimate + safety_margin
@staticmethod
def _format_size(num_bytes):
units = ["B", "KB", "MB", "GB", "TB"]
value = float(max(0, num_bytes))
for unit in units:
if value < 1024 or unit == units[-1]:
return f"{value:.1f} {unit}"
value /= 1024.0
def get_bitrate(self, quality):
"""Определение битрейта в зависимости от качества"""
quality_settings = {

View File

@ -763,6 +763,17 @@ Anime Video Editor — объединение нескольких видео в
cancel_event = threading.Event()
self._cancel_event = cancel_event
# Проверяем место сразу по нажатию "Выполнить", до запуска фоновой обработки.
try:
VideoProcessor(logger=logger).check_disk_space_for_job(
output_dir=output_dir,
video_data=video_data,
quality=quality,
)
except Exception as e:
messagebox.showerror("Недостаточно места", str(e))
return
def worker():
try:
processor = VideoProcessor(logger=logger)