Files
timelaps/camlaps/models.py
2026-04-19 18:31:47 +03:00

111 lines
5.5 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.

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):
"""
модель камеры хранящая основные настройки камеры, такие как имя, код, путь в storage, RTSP URL, ожидаемая ширина, высота, активность
"""
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):
"""
модель задачи создания timelapse, хранящая основные настройки задачи, такие как камера, даты выборки, частота выборки, FPS, включать ночные кадры, время начала дня, время конца дня
"""
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='Включать ночные кадры')
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='Обработано кадров')
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': 'Для режима без ночи конец дня должен быть позже начала дня.'})