feat(main): main

This commit is contained in:
2026-03-10 08:39:28 -04:00
parent b1a93161c0
commit af3076342a
18 changed files with 826 additions and 38 deletions

View File

@@ -56,6 +56,24 @@ class ScheduleGenerator:
target_weekday_bit = 1 << target_date.weekday()
blocks = template.scheduleblock_set.all().order_by('start_local_time')
airings_created = 0
# Build last_played mapping for the repeat gap
from core.models import ChannelSourceRule
rules = ChannelSourceRule.objects.filter(channel=self.channel).select_related('media_source')
max_gap_hours = 0
for rule in rules:
if rule.media_source and rule.media_source.min_repeat_gap_hours:
max_gap_hours = max(max_gap_hours, rule.media_source.min_repeat_gap_hours)
last_played_times = {}
if max_gap_hours > 0:
past_dt = datetime.combine(target_date, datetime.min.time(), tzinfo=local_tz).astimezone(timezone.utc) - timedelta(hours=max_gap_hours)
past_airings = Airing.objects.filter(
channel=self.channel,
starts_at__gte=past_dt
).order_by('starts_at')
for a in past_airings:
last_played_times[a.media_item_id] = a.starts_at
for block in blocks:
if not (block.day_of_week_mask & target_weekday_bit):
@@ -98,7 +116,7 @@ class ScheduleGenerator:
continue
airings_created += self._fill_block(
template, block, actual_start_dt, end_dt, available_items
template, block, actual_start_dt, end_dt, available_items, last_played_times
)
return airings_created
@@ -220,17 +238,41 @@ class ScheduleGenerator:
start_dt: datetime,
end_dt: datetime,
items: list,
last_played_times: dict[int, datetime] = None,
) -> int:
"""Fill start_dt→end_dt with sequential Airings, cycling through items."""
cursor = start_dt
idx = 0
created = 0
batch = uuid.uuid4()
if last_played_times is None:
last_played_times = {}
while cursor < end_dt:
item = items[idx % len(items)]
idx += 1
# Look ahead to find the first item that respects its cooldown rules
valid_item = None
items_checked = 0
while items_checked < len(items):
candidate = items[idx % len(items)]
idx += 1
items_checked += 1
# Check cooldown gap
gap_hours = candidate.media_source.min_repeat_gap_hours if candidate.media_source else None
if gap_hours:
last_played = last_played_times.get(candidate.id)
if last_played:
if (cursor - last_played).total_seconds() < gap_hours * 3600:
continue # skip, hasn't been long enough
valid_item = candidate
break
if not valid_item:
# If everything in the pool is currently cooling down, fallback to ignoring cooldowns
valid_item = items[(idx - 1) % len(items)]
item = valid_item
duration = timedelta(seconds=max(item.runtime_seconds or 1800, 1))
# Don't let a single item overshoot the end by more than its own length
@@ -249,6 +291,8 @@ class ScheduleGenerator:
source_reason="template",
generation_batch_uuid=batch,
)
last_played_times[item.id] = cursor
cursor += duration
created += 1