uppd
This commit is contained in:
parent
b8211b5220
commit
25782333e2
@ -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 = {
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user