bep40 commited on
Commit
2c56b85
·
verified ·
1 Parent(s): 4098f30

Upload dvr.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. dvr.py +40 -6
dvr.py CHANGED
@@ -60,10 +60,12 @@ def _start_buffer(channel_id, stream_url):
60
  "-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5",
61
  "-i", stream_url,
62
  "-c", "copy", # copy, không transcode -> nhẹ CPU
 
63
  "-f", "hls",
64
  "-hls_time", str(SEG_SEC),
65
  "-hls_list_size", str(LIST_SIZE), # giữ ~5 phút
66
- "-hls_flags", "delete_segments+omit_endlist+independent_segments",
 
67
  "-hls_segment_filename", seg,
68
  playlist,
69
  ]
@@ -102,16 +104,23 @@ def buffer_playlist(channel_id: str):
102
  if not os.path.exists(pl):
103
  return JSONResponse({"error": "buffer not ready"}, status_code=404)
104
  txt = open(pl, "r", encoding="utf-8", errors="ignore").read()
105
- # rewrite tên segment -> route phục vụ .ts
106
  out = []
107
  for line in txt.split("\n"):
108
  s = line.strip()
109
  if s and not s.startswith("#") and s.endswith(".ts"):
 
110
  out.append(f"/api/dvr/buffer/seg/{channel_id}/{s}")
111
  else:
112
  out.append(line)
 
113
  return Response("\n".join(out), media_type="application/vnd.apple.mpegurl",
114
- headers={"Access-Control-Allow-Origin": "*", "Cache-Control": "no-cache"})
 
 
 
 
 
115
 
116
  @router.get("/api/dvr/buffer/seg/{channel_id}/{seg}")
117
  def buffer_seg(channel_id: str, seg: str):
@@ -202,15 +211,40 @@ _SCHEDULES = _load_sched()
202
 
203
  @router.post("/api/dvr/record/schedule")
204
  def record_schedule(payload: dict = Body(...)):
205
- """body: {channel, start_iso (giờ VN), duration_sec, prebuffer?, upload_repo?}"""
206
  ch = str(payload.get("channel", "")).lower().strip()
207
  if not ch:
208
  return JSONResponse({"error": "missing channel"}, status_code=400)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  job = {
210
  "id": str(int(time.time() * 1000)),
211
  "channel": ch,
212
- "start_iso": payload.get("start_iso") or datetime.now(VN_TZ).isoformat(),
213
- "duration_sec": int(payload.get("duration_sec", 600)),
 
214
  "prebuffer": bool(payload.get("prebuffer", True)),
215
  "upload_repo": payload.get("upload_repo"),
216
  "status": "pending", "file": None,
 
60
  "-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5",
61
  "-i", stream_url,
62
  "-c", "copy", # copy, không transcode -> nhẹ CPU
63
+ "-copyts", "-start_at_zero",
64
  "-f", "hls",
65
  "-hls_time", str(SEG_SEC),
66
  "-hls_list_size", str(LIST_SIZE), # giữ ~5 phút
67
+ "-hls_flags", "delete_segments+omit_endlist+independent_segments+program_date_time",
68
+ "-hls_segment_type", "mpegts",
69
  "-hls_segment_filename", seg,
70
  playlist,
71
  ]
 
104
  if not os.path.exists(pl):
105
  return JSONResponse({"error": "buffer not ready"}, status_code=404)
106
  txt = open(pl, "r", encoding="utf-8", errors="ignore").read()
107
+ # rewrite segment names -> absolute URLs for HLS.js
108
  out = []
109
  for line in txt.split("\n"):
110
  s = line.strip()
111
  if s and not s.startswith("#") and s.endswith(".ts"):
112
+ # Dùng absolute URL để HLS.js resolve đúng
113
  out.append(f"/api/dvr/buffer/seg/{channel_id}/{s}")
114
  else:
115
  out.append(line)
116
+ # Thêm cache-bust để tránh browser cache cũ
117
  return Response("\n".join(out), media_type="application/vnd.apple.mpegurl",
118
+ headers={
119
+ "Access-Control-Allow-Origin": "*",
120
+ "Cache-Control": "no-cache, no-store, must-revalidate",
121
+ "Pragma": "no-cache",
122
+ "Expires": "0",
123
+ })
124
 
125
  @router.get("/api/dvr/buffer/seg/{channel_id}/{seg}")
126
  def buffer_seg(channel_id: str, seg: str):
 
211
 
212
  @router.post("/api/dvr/record/schedule")
213
  def record_schedule(payload: dict = Body(...)):
214
+ """body: {channel, start_iso (giờ VN), end_iso (giờ VN), duration_sec, prebuffer?, upload_repo?}"""
215
  ch = str(payload.get("channel", "")).lower().strip()
216
  if not ch:
217
  return JSONResponse({"error": "missing channel"}, status_code=400)
218
+ start_iso = payload.get("start_iso")
219
+ end_iso = payload.get("end_iso")
220
+ if not start_iso:
221
+ return JSONResponse({"error": "missing start_iso"}, status_code=400)
222
+ # Parse và validate timezone
223
+ try:
224
+ start_dt = datetime.fromisoformat(start_iso)
225
+ if start_dt.tzinfo is None:
226
+ start_dt = start_dt.replace(tzinfo=VN_TZ)
227
+ except Exception:
228
+ return JSONResponse({"error": "invalid start_iso"}, status_code=400)
229
+ # Tính duration từ end_iso nếu có, nếu không dùng duration_sec
230
+ if end_iso:
231
+ try:
232
+ end_dt = datetime.fromisoformat(end_iso)
233
+ if end_dt.tzinfo is None:
234
+ end_dt = end_dt.replace(tzinfo=VN_TZ)
235
+ duration_from_end = int((end_dt - start_dt).total_seconds())
236
+ duration = max(5, min(3600, duration_from_end))
237
+ except Exception:
238
+ duration = int(payload.get("duration_sec", 600))
239
+ else:
240
+ duration = int(payload.get("duration_sec", 600))
241
+ duration = max(5, min(3600, duration))
242
  job = {
243
  "id": str(int(time.time() * 1000)),
244
  "channel": ch,
245
+ "start_iso": start_dt.isoformat(),
246
+ "end_iso": end_iso or (start_dt + timedelta(seconds=duration)).isoformat(),
247
+ "duration_sec": duration,
248
  "prebuffer": bool(payload.get("prebuffer", True)),
249
  "upload_repo": payload.get("upload_repo"),
250
  "status": "pending", "file": None,