1+ import time
2+
13import nebula
24from nebula .enum import ObjectStatus , RunMode
3- from nebula .helpers .scheduling import (
4- get_item_runs ,
5- get_pending_assets ,
6- parse_rundown_date ,
7- )
5+ from nebula .helpers .scheduling import get_pending_assets , parse_rundown_date
86
97from .models import RundownRequestModel , RundownResponseModel , RundownRow
108
@@ -14,9 +12,9 @@ async def get_rundown(request: RundownRequestModel) -> RundownResponseModel:
1412 if not (channel := nebula .settings .get_playout_channel (request .id_channel )):
1513 raise nebula .BadRequestException (f"No such channel: { request .id_channel } " )
1614
15+ request_start_time = time .monotonic ()
1716 start_time = parse_rundown_date (request .date , channel )
1817 end_time = start_time + (3600 * 24 )
19- item_runs = await get_item_runs (request .id_channel , start_time , end_time )
2018 pending_assets = await get_pending_assets (channel .send_action )
2119 pskey = f"playout_status/{ request .id_channel } "
2220
@@ -27,19 +25,45 @@ async def get_rundown(request: RundownRequestModel) -> RundownResponseModel:
2725 e.id_magic AS id_bin,
2826 i.id AS id_item,
2927 i.meta AS imeta,
30- a.meta AS ameta
31- FROM
32- events AS e
33- LEFT JOIN
34- items AS i
35- ON
36- e.id_magic = i.id_bin
37- LEFT JOIN
38- assets AS a
39- ON
40- i.id_asset = a.id
28+ a.meta AS ameta,
29+ ar.latest_start AS as_start,
30+ ar.latest_stop AS as_stop
31+ FROM events AS e
32+
33+ LEFT JOIN items AS i
34+ ON e.id_magic = i.id_bin
35+
36+ LEFT JOIN assets AS a
37+ ON i.id_asset = a.id
38+
39+ LEFT JOIN (
40+ SELECT
41+ id_item,
42+ start AS latest_start,
43+ stop AS latest_stop
44+ FROM (
45+ SELECT
46+ id_item,
47+ start,
48+ stop,
49+ ROW_NUMBER() OVER
50+ (PARTITION BY id_item ORDER BY start DESC) AS rn
51+ FROM asrun
52+ WHERE
53+ id_channel = $1
54+ AND start >= $2 - 604800
55+ AND start < $3 + 604800
56+ ) AS ranked
57+ WHERE rn = 1
58+ ) AS ar
59+
60+ ON i.id = ar.id_item
61+
4162 WHERE
42- e.id_channel = $1 AND e.start >= $2 AND e.start < $3
63+ e.id_channel = $1
64+ AND e.start >= $2
65+ AND e.start < $3
66+
4367 ORDER BY
4468 e.start ASC,
4569 i.position ASC,
@@ -102,10 +126,12 @@ async def get_rundown(request: RundownRequestModel) -> RundownResponseModel:
102126 # TODO: append empty row?
103127 continue
104128
105- airstatus = 0
106- if (runs := item_runs .get (id_item )) is not None :
107- as_start , as_stop = runs
108- ts_broadcast = as_start
129+ airstatus : ObjectStatus | None = None
130+
131+ if (as_start := record ["as_start" ]) is not None :
132+ if as_start > ts_broadcast :
133+ ts_broadcast = as_start
134+ as_stop = record ["as_stop" ]
109135 airstatus = ObjectStatus .AIRED if as_stop else ObjectStatus .ONAIR
110136
111137 # TODO
@@ -114,19 +140,27 @@ async def get_rundown(request: RundownRequestModel) -> RundownResponseModel:
114140
115141 # Row status
116142
117- istatus = 0
143+ istatus : ObjectStatus
118144 if not ameta :
145+ # virtual item. consider it online
119146 istatus = ObjectStatus .ONLINE
120- elif airstatus :
121- istatus = airstatus
122147 elif ameta .get ("status" ) == ObjectStatus .OFFLINE :
148+ # media is not on the production storage
123149 istatus = ObjectStatus .OFFLINE
124150 elif pskey not in ameta or ameta [pskey ]["status" ] == ObjectStatus .OFFLINE :
151+ # media is not on the playout storage
125152 istatus = ObjectStatus .REMOTE
126- elif ameta [pskey ]["status" ] == ObjectStatus .ONLINE :
127- istatus = ObjectStatus .ONLINE
128153 elif ameta [pskey ]["status" ] == ObjectStatus .CORRUPTED :
154+ # media is on the playout storage but corrupted
129155 istatus = ObjectStatus .CORRUPTED
156+ elif ameta [pskey ]["status" ] == ObjectStatus .ONLINE :
157+ if airstatus is not None :
158+ # media is on the playout storage and aired
159+ istatus = airstatus
160+ last_air = as_start
161+ else :
162+ # media is on the playout storage but not aired
163+ istatus = ObjectStatus .ONLINE
130164 else :
131165 istatus = ObjectStatus .UNKNOWN
132166
@@ -192,8 +226,12 @@ async def get_rundown(request: RundownRequestModel) -> RundownResponseModel:
192226 if not last_event .duration :
193227 last_event .broadcast_time = ts_broadcast
194228 ts_scheduled += duration
195- ts_broadcast += duration
229+ if row .item_role not in ["placeholder" , "lead_in" , "lead_out" ]:
230+ ts_broadcast += duration
196231 last_event .duration += duration
197232 last_event .is_empty = False
198233
199- return RundownResponseModel (rows = rows )
234+ elapsed = time .monotonic () - request_start_time
235+ msg = f"Rundown generated in { elapsed :.3f} seconds"
236+
237+ return RundownResponseModel (rows = rows , detail = msg )
0 commit comments