Dateigröße pro Aufnahme entkoppelt - feste Größe nach Abschluss
Build Dev Docker Image / build-amd64 (push) Successful in 10s
Build Dev Docker Image / build-arm64 (push) Successful in 16s
Build Dev Docker Image / publish-dev-manifest (push) Successful in 9s

Die Dateigröße wird jetzt pro Aufnahme korrekt berechnet statt das
gesamte Verzeichnis zu summieren. Bei abgeschlossenen Aufnahmen wird
die Größe einmalig gespeichert und nicht mehr durch laufende Aufnahmen
verändert.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Asmus
2026-09-16 11:46:18 +02:00
co-authored by Claude Opus 4.6
parent 1ad87c7186
commit 851ff086ac
6 changed files with 600 additions and 124 deletions
+121 -38
View File
@@ -1,5 +1,6 @@
import datetime
from apscheduler.schedulers.background import BackgroundScheduler
from sqlalchemy import or_
from models import Job, Recording, get_session
DAY_MAP = {
@@ -75,6 +76,111 @@ class StreamScheduler:
today = job.schedule_date or today
return f"{job.id}|{job.schedule_once}|{today}|{job.schedule_start}"
def _schedule_state(self, job: dict, now: datetime.datetime | None = None) -> dict:
now = now or datetime.datetime.now()
today_num = now.isoweekday()
today_date = now.strftime("%Y-%m-%d")
now_time = now.strftime("%H:%M")
occ_key = (
f"{job['id']}|{job['schedule_once']}|"
f"{today_date if not job['schedule_once'] else job['schedule_date']}|"
f"{job['schedule_start']}"
)
is_today = False
multi_day = bool(job.get("schedule_end_date") and job["schedule_once"])
if job["schedule_once"]:
if job["schedule_date"] == today_date:
is_today = True
else:
if _matches_day(today_num, job["schedule_days"] or "*"):
is_today = True
in_window = False
is_cross_midnight = False
if is_today:
if multi_day:
now_min = _time_to_minutes(now_time)
start_min = _time_to_minutes(job["schedule_start"])
in_window = now_min >= start_min
elif job["schedule_stop"]:
start_min = _time_to_minutes(job["schedule_start"])
stop_min = _time_to_minutes(job["schedule_stop"])
if stop_min <= start_min:
is_cross_midnight = True
now_min = _time_to_minutes(now_time)
in_window = now_min >= start_min
else:
in_window = _in_time_window(now_time, job["schedule_start"], job["schedule_stop"])
else:
now_min = _time_to_minutes(now_time)
start_min = _time_to_minutes(job["schedule_start"])
in_window = now_min >= start_min
return {
"occ_key": occ_key,
"in_window": in_window,
"multi_day": multi_day,
"is_cross_midnight": is_cross_midnight,
}
def suppress_current_occurrence(self, job_id: int) -> bool:
session = get_session()
try:
j = session.query(Job).filter_by(id=job_id).first()
if not j or not j.schedule_enabled or not j.schedule_start:
return False
job = {
"id": j.id,
"schedule_once": j.schedule_once,
"schedule_date": j.schedule_date,
"schedule_days": j.schedule_days,
"schedule_start": j.schedule_start,
"schedule_stop": j.schedule_stop,
"schedule_end_date": j.schedule_end_date,
}
finally:
session.close()
state = self._schedule_state(job)
if not state["in_window"]:
return False
self._started_keys.add(state["occ_key"])
return True
def _occurrence_start_utc(self, job: dict, now: datetime.datetime) -> datetime.datetime | None:
date_text = job["schedule_date"] if job["schedule_once"] else now.strftime("%Y-%m-%d")
try:
local_start = datetime.datetime.strptime(
f"{date_text} {job['schedule_start']}",
"%Y-%m-%d %H:%M",
)
except (TypeError, ValueError):
return None
local_utc_offset = datetime.datetime.now() - datetime.datetime.utcnow()
return local_start - local_utc_offset
def _has_recording_for_occurrence(self, job: dict, now: datetime.datetime) -> bool:
occurrence_start = self._occurrence_start_utc(job, now)
if occurrence_start is None:
return False
session = get_session()
try:
return session.query(Recording).filter(
Recording.job_id == job["id"],
or_(
Recording.started_at >= occurrence_start,
Recording.stopped_at >= occurrence_start,
),
).first() is not None
finally:
session.close()
def _is_job_recording(self, job_id: int) -> bool:
session = get_session()
try:
@@ -99,9 +205,6 @@ class StreamScheduler:
def _tick(self):
now = datetime.datetime.now()
today_num = now.isoweekday()
today_date = now.strftime("%Y-%m-%d")
now_time = now.strftime("%H:%M")
session = get_session()
try:
@@ -132,44 +235,19 @@ class StreamScheduler:
if not job["schedule_start"]:
continue
is_today = False
multi_day = bool(job.get("schedule_end_date") and job["schedule_once"])
if job["schedule_once"]:
if job["schedule_date"] == today_date:
is_today = True
else:
if _matches_day(today_num, job["schedule_days"] or "*"):
is_today = True
in_window = False
is_cross_midnight = False
if is_today:
if multi_day:
now_min = _time_to_minutes(now_time)
start_min = _time_to_minutes(job["schedule_start"])
in_window = now_min >= start_min
elif job["schedule_stop"]:
start_min = _time_to_minutes(job["schedule_start"])
stop_min = _time_to_minutes(job["schedule_stop"])
if stop_min <= start_min:
is_cross_midnight = True
now_min = _time_to_minutes(now_time)
in_window = now_min >= start_min
else:
in_window = _in_time_window(now_time, job["schedule_start"], job["schedule_stop"])
else:
now_min = _time_to_minutes(now_time)
start_min = _time_to_minutes(job["schedule_start"])
in_window = now_min >= start_min
occ_key = f"{job['id']}|{job['schedule_once']}|{today_date if not job['schedule_once'] else job['schedule_date']}|{job['schedule_start']}"
state = self._schedule_state(job, now)
in_window = state["in_window"]
occ_key = state["occ_key"]
if in_window:
if occ_key not in self._started_keys and not self._is_job_recording(job["id"]):
if (
occ_key not in self._started_keys
and not self._is_job_recording(job["id"])
and not self._has_recording_for_occurrence(job, now)
):
duration = job["max_duration"]
if not duration and job["schedule_stop"]:
if multi_day:
if state["multi_day"]:
start_dt = datetime.datetime.strptime(
f"{job['schedule_date']} {job['schedule_start']}", "%Y-%m-%d %H:%M")
end_dt = datetime.datetime.strptime(
@@ -204,7 +282,12 @@ class StreamScheduler:
)
self._started_keys.add(occ_key)
else:
if not multi_day and not is_cross_midnight and job["schedule_stop"] and self._is_job_recording(job["id"]):
if (
not state["multi_day"]
and not state["is_cross_midnight"]
and job["schedule_stop"]
and self._is_job_recording(job["id"])
):
rec_id = self._get_active_scheduled_recording_for_job(job["id"])
if rec_id:
self.recorder.stop_recording(rec_id)