fuck yeah!
This commit is contained in:
parent
ccb53d9091
commit
da10f5e132
44 changed files with 3260 additions and 448 deletions
227
models/timelapse_model.py
Normal file
227
models/timelapse_model.py
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
"""
|
||||
Модель для управления процессом создания таймлапса
|
||||
Single Responsibility: только данные и состояние процесса таймлапса
|
||||
"""
|
||||
|
||||
from PySide6.QtCore import QObject, Signal, QThread
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TimelapseStatus(Enum):
|
||||
"""Статус процесса создания таймлапса"""
|
||||
IDLE = "idle"
|
||||
PREPARING = "preparing"
|
||||
DOWNLOADING = "downloading"
|
||||
PROCESSING = "processing"
|
||||
CREATING_VIDEO = "creating_video"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TimelapseConfig:
|
||||
"""Конфигурация таймлапса"""
|
||||
source_id: int
|
||||
start_date: datetime
|
||||
end_date: datetime
|
||||
output_path: Path
|
||||
fps: int = 10
|
||||
quality: int = 90 # качество видео (0-100)
|
||||
include_metadata: bool = True
|
||||
output_format: str = "mp4" # mp4, gif, webm
|
||||
|
||||
|
||||
@dataclass
|
||||
class TimelapseProgress:
|
||||
"""Прогресс создания таймлапса"""
|
||||
status: TimelapseStatus
|
||||
current: int = 0
|
||||
total: int = 0
|
||||
message: str = ""
|
||||
current_date: Optional[datetime] = None
|
||||
downloaded_files: List[Path] = field(default_factory=list)
|
||||
|
||||
|
||||
class TimelapseModel(QObject):
|
||||
"""
|
||||
Модель для управления созданием таймлапса
|
||||
Хранит состояние и предоставляет интерфейс для контроллера
|
||||
"""
|
||||
|
||||
# Сигналы для оповещения о изменениях
|
||||
progress_updated = Signal(TimelapseProgress)
|
||||
status_changed = Signal(TimelapseStatus)
|
||||
log_message = Signal(str)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._current_progress = TimelapseProgress(status=TimelapseStatus.IDLE)
|
||||
self._config: Optional[TimelapseConfig] = None
|
||||
self._is_cancelled = False
|
||||
|
||||
def configure(self, config: TimelapseConfig) -> bool:
|
||||
"""
|
||||
Настраивает таймлапс
|
||||
|
||||
Args:
|
||||
config: Конфигурация таймлапса
|
||||
|
||||
Returns:
|
||||
True если конфигурация валидна
|
||||
"""
|
||||
# Валидация параметров
|
||||
if config.start_date >= config.end_date:
|
||||
self.log_message.emit("Ошибка: Дата начала должна быть раньше даты окончания")
|
||||
return False
|
||||
|
||||
if config.fps < 1 or config.fps > 60:
|
||||
self.log_message.emit("Ошибка: FPS должен быть в диапазоне 1-60")
|
||||
return False
|
||||
|
||||
if not config.output_path.parent.exists():
|
||||
self.log_message.emit(f"Ошибка: Папка {config.output_path.parent} не существует")
|
||||
return False
|
||||
|
||||
self._config = config
|
||||
self._is_cancelled = False
|
||||
|
||||
# Рассчитываем общее количество кадров
|
||||
delta = config.end_date - config.start_date
|
||||
total_frames = delta.days + 1
|
||||
self._current_progress = TimelapseProgress(
|
||||
status=TimelapseStatus.PREPARING,
|
||||
total=total_frames,
|
||||
message=f"Подготовка к созданию таймлапса из {total_frames} кадров"
|
||||
)
|
||||
|
||||
self.log_message.emit(f"Настроен таймлапс: {total_frames} кадров, {config.fps} FPS")
|
||||
return True
|
||||
|
||||
def get_total_frames(self) -> int:
|
||||
"""Возвращает общее количество кадров"""
|
||||
if self._config:
|
||||
delta = self._config.end_date - self._config.start_date
|
||||
return delta.days + 1
|
||||
return 0
|
||||
|
||||
def update_progress(self, current_frame: int, total_frames: int,
|
||||
message: str = "", current_date: datetime = None):
|
||||
"""
|
||||
Обновляет прогресс создания таймлапса
|
||||
|
||||
Args:
|
||||
current_frame: Текущий кадр
|
||||
total_frames: Всего кадров
|
||||
message: Сообщение о прогрессе
|
||||
current_date: Текущая обрабатываемая дата
|
||||
"""
|
||||
self._current_progress.current = current_frame
|
||||
self._current_progress.total = total_frames
|
||||
self._current_progress.message = message or self._current_progress.message
|
||||
self._current_progress.current_date = current_date or self._current_progress.current_date
|
||||
|
||||
# Вычисляем процент
|
||||
if total_frames > 0:
|
||||
percent = (current_frame / total_frames) * 100
|
||||
status_text = f"{percent:.1f}%"
|
||||
|
||||
self.progress_updated.emit(self._current_progress)
|
||||
|
||||
def set_status(self, status: TimelapseStatus, message: str = ""):
|
||||
"""
|
||||
Устанавливает статус процесса
|
||||
|
||||
Args:
|
||||
status: Новый статус
|
||||
message: Сообщение (опционально)
|
||||
"""
|
||||
self._current_progress.status = status
|
||||
if message:
|
||||
self._current_progress.message = message
|
||||
self.status_changed.emit(status)
|
||||
self.progress_updated.emit(self._current_progress)
|
||||
self.log_message.emit(message or f"Статус: {status.value}")
|
||||
|
||||
def add_downloaded_file(self, filepath: Path):
|
||||
"""Добавляет скачанный файл в список"""
|
||||
self._current_progress.downloaded_files.append(filepath)
|
||||
|
||||
def get_downloaded_files(self) -> List[Path]:
|
||||
"""Возвращает список скачанных файлов"""
|
||||
return self._current_progress.downloaded_files.copy()
|
||||
|
||||
def cancel(self):
|
||||
"""Отменяет создание таймлапса"""
|
||||
self._is_cancelled = True
|
||||
self.set_status(TimelapseStatus.CANCELLED, "Создание таймлапса отменено пользователем")
|
||||
|
||||
def is_cancelled(self) -> bool:
|
||||
"""Проверяет, отменен ли процесс"""
|
||||
return self._is_cancelled
|
||||
|
||||
def get_config(self) -> Optional[TimelapseConfig]:
|
||||
"""Возвращает конфигурацию таймлапса"""
|
||||
return self._config
|
||||
|
||||
def reset(self):
|
||||
"""Сбрасывает состояние модели"""
|
||||
self._config = None
|
||||
self._is_cancelled = False
|
||||
self._current_progress = TimelapseProgress(status=TimelapseStatus.IDLE)
|
||||
self.progress_updated.emit(self._current_progress)
|
||||
|
||||
def generate_date_sequence(self) -> List[datetime]:
|
||||
"""
|
||||
Генерирует последовательность дат для таймлапса
|
||||
|
||||
Returns:
|
||||
Список дат для каждого кадра
|
||||
"""
|
||||
if not self._config:
|
||||
return []
|
||||
|
||||
dates = []
|
||||
current = self._config.start_date.replace(hour=12, minute=0, second=0)
|
||||
|
||||
while current <= self._config.end_date:
|
||||
dates.append(current)
|
||||
current += timedelta(days=1)
|
||||
|
||||
return dates
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
"""
|
||||
Оценивает примерный размер видео в байтах
|
||||
|
||||
Returns:
|
||||
Оценочный размер или 0 если невозможно оценить
|
||||
"""
|
||||
if not self._config:
|
||||
return 0
|
||||
|
||||
# Приблизительная оценка: ~500KB на кадр для HD видео
|
||||
total_frames = self.get_total_frames()
|
||||
estimated_bytes = total_frames * 500 * 1024
|
||||
|
||||
# Корректируем в зависимости от FPS и качества
|
||||
estimated_bytes = estimated_bytes * (self._config.fps / 30) * (self._config.quality / 100)
|
||||
|
||||
return int(estimated_bytes)
|
||||
|
||||
def get_formatted_estimated_size(self) -> str:
|
||||
"""Возвращает отформатированную оценку размера"""
|
||||
size_bytes = self.get_estimated_size()
|
||||
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes} B"
|
||||
elif size_bytes < 1024 * 1024:
|
||||
return f"{size_bytes / 1024:.1f} KB"
|
||||
elif size_bytes < 1024 * 1024 * 1024:
|
||||
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
||||
else:
|
||||
return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"
|
||||
Loading…
Add table
Add a link
Reference in a new issue