uppd
This commit is contained in:
parent
b8211b5220
commit
25782333e2
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -126,6 +127,11 @@ class VideoProcessor:
|
|||||||
def __init__(self, logger=None):
|
def __init__(self, logger=None):
|
||||||
self.logger = logger or Logger()
|
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",
|
def process_videos(self, video_data, output_dir, output_format="mp4",
|
||||||
quality="high", progress_callback=None,
|
quality="high", progress_callback=None,
|
||||||
fps=None, preset="medium", audio_bitrate="192k",
|
fps=None, preset="medium", audio_bitrate="192k",
|
||||||
@ -138,6 +144,7 @@ class VideoProcessor:
|
|||||||
check = cancel_check if callable(cancel_check) else lambda: False
|
check = cancel_check if callable(cancel_check) else lambda: False
|
||||||
|
|
||||||
use_batch = len(video_data) > BATCH_THRESHOLD
|
use_batch = len(video_data) > BATCH_THRESHOLD
|
||||||
|
self._check_disk_space_or_raise(output_dir, video_data, quality, use_batch)
|
||||||
if use_batch:
|
if use_batch:
|
||||||
self.logger.log("INFO", f"Режим пакетной обработки (файлов > {BATCH_THRESHOLD}), экономия памяти")
|
self.logger.log("INFO", f"Режим пакетной обработки (файлов > {BATCH_THRESHOLD}), экономия памяти")
|
||||||
return self._process_videos_batch(
|
return self._process_videos_batch(
|
||||||
@ -254,7 +261,9 @@ class VideoProcessor:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
ensure_directory(output_dir)
|
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):
|
for i, data in enumerate(video_data):
|
||||||
if cancel_check():
|
if cancel_check():
|
||||||
raise CancelledError()
|
raise CancelledError()
|
||||||
@ -390,9 +399,9 @@ class VideoProcessor:
|
|||||||
except CancelledError:
|
except CancelledError:
|
||||||
self._remove_if_exists(output_path)
|
self._remove_if_exists(output_path)
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception as e:
|
||||||
self._remove_if_exists(output_path)
|
self._remove_if_exists(output_path)
|
||||||
raise
|
raise RuntimeError(self._friendly_write_error(e, output_dir)) from e
|
||||||
if resized is not None:
|
if resized is not None:
|
||||||
try:
|
try:
|
||||||
resized.close()
|
resized.close()
|
||||||
@ -416,6 +425,73 @@ class VideoProcessor:
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
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):
|
def get_bitrate(self, quality):
|
||||||
"""Определение битрейта в зависимости от качества"""
|
"""Определение битрейта в зависимости от качества"""
|
||||||
quality_settings = {
|
quality_settings = {
|
||||||
|
|||||||
@ -763,6 +763,17 @@ Anime Video Editor — объединение нескольких видео в
|
|||||||
cancel_event = threading.Event()
|
cancel_event = threading.Event()
|
||||||
self._cancel_event = cancel_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():
|
def worker():
|
||||||
try:
|
try:
|
||||||
processor = VideoProcessor(logger=logger)
|
processor = VideoProcessor(logger=logger)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user