feat(main): main
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user