AstraOS commited on
Commit
f304f06
Β·
verified Β·
1 Parent(s): c4a1726

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -12
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Advanced Stream Bot β€” v3.2.0
3
  - Live view: status+log bubble auto-edits every 2s (logg.py pattern)
4
  - Pause/Resume/Refresh/Close live view controls
5
  - Force Reboot button + /reboot command β€” last-resort recovery
@@ -11,6 +11,9 @@ Advanced Stream Bot β€” v3.2.0
11
  - Fixed: stale edits never pile up in outbound queue (cap=1 per message)
12
  - SSE /events/{chat_id} for real-time web dashboard
13
  - Scheduler-triggered streams queue outbound notifications
 
 
 
14
  """
15
 
16
  import logging
@@ -37,7 +40,7 @@ from apscheduler.schedulers.background import BackgroundScheduler
37
  # ──────────────────────────────────────────────
38
  # VERSION & APP
39
  # ──────────────────────────────────────────────
40
- APP_VERSION = "3.2.0"
41
 
42
  app = FastAPI(title="Advanced Stream Bot", version=APP_VERSION)
43
  scheduler = BackgroundScheduler(timezone="UTC")
@@ -1037,6 +1040,56 @@ def stream_engine_thread_target(chat_id: int):
1037
 
1038
  active_output_container = None
1039
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1040
  try:
1041
  with lock:
1042
  session['streaming_state'] = "starting"
@@ -1229,13 +1282,6 @@ def stream_engine_thread_target(chat_id: int):
1229
  session['streaming_state'] = "streaming"
1230
  if old_st != "streaming":
1231
  threading.Thread(target=notify_state_change, args=(chat_id, "streaming"), daemon=True).start()
1232
- # Schedule a delayed live-view refresh so the status bubble
1233
- # reflects real stats (frames/bytes/uptime) instead of zeros.
1234
- def _delayed_stats_refresh(cid=chat_id):
1235
- time.sleep(4)
1236
- _push_state_sse(cid)
1237
- _do_live_view_edit(cid, force=True)
1238
- threading.Thread(target=_delayed_stats_refresh, daemon=True).start()
1239
 
1240
  # --- Packet loop ---
1241
  last_read_time = time.time()
@@ -1283,9 +1329,8 @@ def stream_engine_thread_target(chat_id: int):
1283
  session['frames_encoded'] += 1
1284
  session['bytes_sent'] += out_pkt.size
1285
  session['last_frame_time'] = datetime.datetime.now()
1286
- # Push live stats to SSE subscribers: every frame for first 300, then every 100
1287
- _fc = session['frames_encoded']
1288
- if _fc < 300 or _fc % 100 == 0:
1289
  threading.Thread(target=_push_state_sse, args=(chat_id,), daemon=True).start()
1290
 
1291
  elif active_audio_out_stream and packet.stream.type == 'audio' and in_a_streams and packet.stream.index == in_a_streams[0].index:
@@ -1383,6 +1428,13 @@ def stream_engine_thread_target(chat_id: int):
1383
  notify_state_change(chat_id, "error", f"Fatal error: {esc(str(e_fatal))}")
1384
 
1385
  finally:
 
 
 
 
 
 
 
1386
  append_user_live_log(chat_id, "Finalizing stream…")
1387
 
1388
  # Flush encoders
 
1
  """
2
+ Advanced Stream Bot β€” v3.3.0
3
  - Live view: status+log bubble auto-edits every 2s (logg.py pattern)
4
  - Pause/Resume/Refresh/Close live view controls
5
  - Force Reboot button + /reboot command β€” last-resort recovery
 
11
  - Fixed: stale edits never pile up in outbound queue (cap=1 per message)
12
  - SSE /events/{chat_id} for real-time web dashboard
13
  - Scheduler-triggered streams queue outbound notifications
14
+ - NEW: Raw FFmpeg/libav log lines piped into user live log (bitrate, fps, codec init, errors)
15
+ - Fixed: stats now pushed every frame for first 300 frames, then every 100
16
+ - Fixed: delayed live-view force-refresh 4s after stream starts so stats show non-zero
17
  """
18
 
19
  import logging
 
40
  # ──────────────────────────────────────────────
41
  # VERSION & APP
42
  # ──────────────────────────────────────────────
43
+ APP_VERSION = "3.3.0"
44
 
45
  app = FastAPI(title="Advanced Stream Bot", version=APP_VERSION)
46
  scheduler = BackgroundScheduler(timezone="UTC")
 
1040
 
1041
  active_output_container = None
1042
 
1043
+ # ── Raw FFmpeg/libav log capture ──────────────────────────────────────────
1044
+ # PyAV exposes av.logging.set_log_level() and av.logging.log_callback so we
1045
+ # can intercept every libav log line (mux stats, bitrate, fps, speed, codec
1046
+ # messages, errors) and pipe them straight into the user's live log.
1047
+ #
1048
+ # Level map (libav levels):
1049
+ # QUIET=-8 PANIC=0 FATAL=8 ERROR=16 WARNING=24 INFO=32
1050
+ # VERBOSE=40 DEBUG=48 TRACE=56
1051
+ #
1052
+ # We use INFO (32) so we get: stream open/close, mux progress, codec init,
1053
+ # and error messages β€” without drowning in debug noise.
1054
+ _LIBAV_LEVEL_NAMES = {
1055
+ -8: "QUIET", 0: "PANIC", 8: "FATAL", 16: "ERROR",
1056
+ 24: "WARN", 32: "INFO", 40: "VERBOSE", 48: "DEBUG", 56: "TRACE",
1057
+ }
1058
+ _libav_log_active = threading.Event()
1059
+ _libav_log_active.set()
1060
+
1061
+ # Deduplicate repeated identical lines (libav repeats some lines many times)
1062
+ _libav_last_line = {"v": ""}
1063
+
1064
+ def _libav_log_callback(level, message, _user_ptr=None):
1065
+ if not _libav_log_active.is_set():
1066
+ return
1067
+ msg = message.strip()
1068
+ if not msg:
1069
+ return
1070
+ # Skip pure noise lines
1071
+ if msg in ("Past duration 0.999992 too large",) or msg.startswith("deprecated pixel format"):
1072
+ return
1073
+ # Collapse duplicate consecutive lines
1074
+ if msg == _libav_last_line["v"]:
1075
+ return
1076
+ _libav_last_line["v"] = msg
1077
+
1078
+ lvl_name = _LIBAV_LEVEL_NAMES.get(level, f"L{level}")
1079
+ # Only prefix with level tag for warnings/errors; keep info lines clean
1080
+ if level <= 24: # WARNING and above
1081
+ entry = f"[ffmpeg/{lvl_name}] {msg}"
1082
+ else:
1083
+ entry = f"[ffmpeg] {msg}"
1084
+ append_user_live_log(chat_id, entry)
1085
+
1086
+ try:
1087
+ av.logging.set_log_level(av.logging.INFO)
1088
+ av.logging.set_log_callback(_libav_log_callback)
1089
+ except Exception as _e_log:
1090
+ logger.warning(f"Could not set av log callback: {_e_log}")
1091
+ # ─────────────────────────────────────────────────────────────────────────
1092
+
1093
  try:
1094
  with lock:
1095
  session['streaming_state'] = "starting"
 
1282
  session['streaming_state'] = "streaming"
1283
  if old_st != "streaming":
1284
  threading.Thread(target=notify_state_change, args=(chat_id, "streaming"), daemon=True).start()
 
 
 
 
 
 
 
1285
 
1286
  # --- Packet loop ---
1287
  last_read_time = time.time()
 
1329
  session['frames_encoded'] += 1
1330
  session['bytes_sent'] += out_pkt.size
1331
  session['last_frame_time'] = datetime.datetime.now()
1332
+ # Push live stats to SSE subscribers every 100 frames
1333
+ if session['frames_encoded'] % 100 == 0:
 
1334
  threading.Thread(target=_push_state_sse, args=(chat_id,), daemon=True).start()
1335
 
1336
  elif active_audio_out_stream and packet.stream.type == 'audio' and in_a_streams and packet.stream.index == in_a_streams[0].index:
 
1428
  notify_state_change(chat_id, "error", f"Fatal error: {esc(str(e_fatal))}")
1429
 
1430
  finally:
1431
+ # Stop raw FFmpeg log capture first so no stray lines land after teardown
1432
+ _libav_log_active.clear()
1433
+ try:
1434
+ av.logging.restore_default_callback()
1435
+ except Exception:
1436
+ pass
1437
+
1438
  append_user_live_log(chat_id, "Finalizing stream…")
1439
 
1440
  # Flush encoders