Files
timelaps/camlaps/models.py
ack ae15bee2d1
All checks were successful
Deploy timelaps / deploy (push) Successful in 5s
добавил удаление таймлапсов
2026-04-19 23:21:07 +03:00

116 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Модели приложения camlaps: камеры и задания таймлапса."""
from datetime import time
from django.core.validators import MaxValueValidator, MinValueValidator
from django.core.exceptions import ValidationError
from django.db import models
class Camera(models.Model):
"""Камера: имя, идентификатор, путь к данным, опционально RTSP и ожидаемое разрешение."""
name = models.CharField(max_length=120, verbose_name='Наименование')
slug = models.SlugField(max_length=80, unique=True, verbose_name='Код камеры')
storage_path = models.CharField(
max_length=255,
verbose_name='Путь в storage',
help_text='Относительный путь внутри /app/storage, например: Camera3',
)
rtsp_url = models.URLField(blank=True, verbose_name='RTSP URL (опционально)')
expected_width = models.PositiveIntegerField(null=True, blank=True, verbose_name='Ожидаемая ширина')
expected_height = models.PositiveIntegerField(null=True, blank=True, verbose_name='Ожидаемая высота')
is_active = models.BooleanField(default=True, verbose_name='Активна')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = 'Камера'
verbose_name_plural = 'Камеры'
ordering = ['name']
def __str__(self):
return self.name
class TimelapseJob(models.Model):
"""Задание сборки таймлапса: параметры выборки кадров, статус, прогресс и результаты."""
class Status(models.TextChoices):
PLANNED = 'planned', 'Запланировано'
RUNNING = 'running', 'В работе'
SUCCESS = 'success', 'Успешно'
ERROR = 'error', 'Ошибка'
SAMPLING_PRESET_MINUTES = (15, 30, 45, 60, 90, 120, 180, 240, 360, 720, 1440)
class SamplingPreset(models.IntegerChoices):
EVERY_15_MIN = 0, '1 кадр / 15 минут'
EVERY_30_MIN = 1, '1 кадр / 30 минут'
EVERY_45_MIN = 2, '1 кадр / 45 минут'
EVERY_HOUR = 3, '1 кадр / час'
EVERY_90_MIN = 4, '1 кадр / 1.5 часа'
EVERY_2_HOURS = 5, '1 кадр / 2 часа'
EVERY_3_HOURS = 6, '1 кадр / 3 часа'
EVERY_4_HOURS = 7, '1 кадр / 4 часа'
EVERY_6_HOURS = 8, '1 кадр / 6 часов'
EVERY_12_HOURS = 9, '1 кадр / 12 часов'
EVERY_DAY = 10, '1 кадр / сутки'
camera = models.ForeignKey(Camera, on_delete=models.PROTECT, related_name='jobs', verbose_name='Камера')
date_from = models.DateField(verbose_name='Дата начала выборки')
date_to = models.DateField(verbose_name='Дата окончания выборки')
sampling_preset = models.PositiveSmallIntegerField(
choices=SamplingPreset.choices,
default=SamplingPreset.EVERY_HOUR,
verbose_name='Частота выборки',
)
fps = models.PositiveSmallIntegerField(
default=25,
validators=[MinValueValidator(1), MaxValueValidator(120)],
verbose_name='FPS итогового видео',
)
include_night = models.BooleanField(default=True, verbose_name='Включать ночные кадры')
anchor_time = models.TimeField(default=time(12, 0), verbose_name='Время якоря кадра')
day_start_time = models.TimeField(default=time(6, 0), verbose_name='Начало дня')
day_end_time = models.TimeField(default=time(22, 0), verbose_name='Конец дня')
status = models.CharField(max_length=16, choices=Status.choices, default=Status.PLANNED, db_index=True)
progress_percent = models.PositiveSmallIntegerField(
default=0,
validators=[MinValueValidator(0), MaxValueValidator(100)],
verbose_name='Прогресс, %',
)
frames_total = models.PositiveIntegerField(null=True, blank=True, verbose_name='Всего кадров')
frames_processed = models.PositiveIntegerField(default=0, verbose_name='Обработано кадров')
days_total = models.PositiveIntegerField(default=0, verbose_name='Всего дней в диапазоне')
days_with_frames = models.PositiveIntegerField(default=0, verbose_name='Дней с кадрами')
days_skipped = models.PositiveIntegerField(default=0, verbose_name='Пропущено дней')
output_rel_path = models.CharField(max_length=255, blank=True, verbose_name='Путь к видео в storage')
error_message = models.TextField(blank=True, verbose_name='Текст ошибки')
created_at = models.DateTimeField(auto_now_add=True)
started_at = models.DateTimeField(null=True, blank=True)
finished_at = models.DateTimeField(null=True, blank=True)
class Meta:
verbose_name = 'Задание таймлапса'
verbose_name_plural = 'Задания таймлапса'
ordering = ['-created_at']
def __str__(self):
return f'{self.camera.name}: {self.date_from}{self.date_to}'
@property
def sampling_interval_minutes(self):
return self.SAMPLING_PRESET_MINUTES[int(self.sampling_preset)]
def clean(self):
if self.date_to < self.date_from:
raise ValidationError({'date_to': 'Дата окончания должна быть не раньше даты начала.'})
if not self.include_night and self.day_start_time >= self.day_end_time:
raise ValidationError({'day_end_time': 'Для режима без ночи конец дня должен быть позже начала дня.'})
if not self.include_night and not (self.day_start_time <= self.anchor_time <= self.day_end_time):
raise ValidationError({'anchor_time': 'Якорное время должно попадать в дневной интервал.'})