AstraOS commited on
Commit
3850296
Β·
verified Β·
1 Parent(s): 3e31a65

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -34
app.py CHANGED
@@ -49,7 +49,7 @@ live_log_thread = None
49
 
50
  # Live logging globals
51
  live_log_lines = [] # Rolling list (max 50 log lines)
52
- live_log_display = "" # Global variable updated every second by live_log_updater()
53
  error_notification = "" # Global variable to hold error details if any
54
 
55
  # -------------------------------------------------------------------
@@ -77,6 +77,17 @@ list_handler = ListHandler()
77
  list_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S"))
78
  logger.addHandler(list_handler)
79
 
 
 
 
 
 
 
 
 
 
 
 
80
  # -------------------------------------------------------------------
81
  # Utility Functions & UI Helpers
82
  # -------------------------------------------------------------------
@@ -155,17 +166,6 @@ def validate_inputs():
155
  return False, f"Missing fields: {', '.join(missing)}"
156
  return True, ""
157
 
158
- # -------------------------------------------------------------------
159
- # Helper to Compose the Streaming Message (HTML)
160
- # -------------------------------------------------------------------
161
- def compose_streaming_message():
162
- global live_log_display, error_notification
163
- msg = ""
164
- if error_notification:
165
- msg += "<b>ERROR:</b> " + error_notification + "\n\n"
166
- msg += "πŸš€ <b>Streaming in progress!</b>\n\nLive Logs:\n" + live_log_display + "\n\nUse the inline keyboard to control the stream."
167
- return msg
168
-
169
  # -------------------------------------------------------------------
170
  # Error Notification Helper
171
  # -------------------------------------------------------------------
@@ -181,7 +181,7 @@ def live_log_updater():
181
  global live_log_display, streaming_state
182
  try:
183
  while streaming_state in ["streaming", "paused"]:
184
- # Update live_log_display with the last 15 log lines (HTML formatted)
185
  live_log_display = "<pre>" + "\n".join(live_log_lines[-15:]) + "</pre>"
186
  time.sleep(1)
187
  except Exception as e:
@@ -210,23 +210,23 @@ def logs_history(chat_id):
210
  # -------------------------------------------------------------------
211
  def handle_start(chat_id):
212
  global current_step, user_inputs, conversation_fields, advanced_mode
213
- # Use simple mode unless advanced_mode is set via /setting
214
  user_inputs = {}
215
  if not advanced_mode:
216
  conversation_fields = ["input_url", "output_url"]
217
  else:
218
  conversation_fields = ["input_url", "quality_settings", "video_codec", "audio_codec", "output_url"]
219
  current_step = conversation_fields[0]
220
- text = ("πŸ‘‹ <b>Welcome to the Stream Bot!</b>\n\n"
221
  "Let's set up your stream.\n"
222
- f"Please enter the <b>{current_step.replace('_', ' ')}</b>"
223
- f"{' (no default)' if current_step not in default_settings else f' (default: {default_settings[current_step]})'}:")
224
  logging.info(f"/start command from chat {chat_id} (advanced_mode={advanced_mode})")
225
  return {
226
  "method": "sendMessage",
227
  "chat_id": chat_id,
228
  "text": text,
229
- "parse_mode": "HTML"
230
  }
231
 
232
  def handle_setting(chat_id):
@@ -235,14 +235,14 @@ def handle_setting(chat_id):
235
  conversation_fields = ["input_url", "quality_settings", "video_codec", "audio_codec", "output_url"]
236
  user_inputs = {}
237
  current_step = conversation_fields[0]
238
- text = ("βš™οΈ <b>Advanced Mode Activated!</b>\n\n"
239
- "Please enter the <b>input url</b>:")
240
  logging.info(f"/setting command from chat {chat_id} - advanced mode enabled")
241
  return {
242
  "method": "sendMessage",
243
  "chat_id": chat_id,
244
  "text": text,
245
- "parse_mode": "HTML"
246
  }
247
 
248
  def handle_help(chat_id):
@@ -263,18 +263,14 @@ def handle_conversation(chat_id, text):
263
  else:
264
  user_inputs[current_step] = text.strip()
265
  logging.info(f"Received {current_step}: {text.strip()}")
 
266
  idx = conversation_fields.index(current_step)
267
  if idx < len(conversation_fields) - 1:
268
  current_step = conversation_fields[idx + 1]
269
- prompt = f"Please enter the <b>{current_step.replace('_', ' ')}</b>"
270
  if current_step in default_settings:
271
- prompt += f" (default: {default_settings[current_step]})"
272
- return {
273
- "method": "sendMessage",
274
- "chat_id": chat_id,
275
- "text": prompt,
276
- "parse_mode": "HTML"
277
- }
278
  else:
279
  current_step = None
280
  valid, msg = validate_inputs()
@@ -287,9 +283,9 @@ def handle_conversation(chat_id, text):
287
  return {
288
  "method": "sendMessage",
289
  "chat_id": chat_id,
290
- "text": "All inputs received. Press <b>πŸš€ Start Streaming</b> to begin.",
291
  "reply_markup": get_inline_keyboard_for_start(),
292
- "parse_mode": "HTML"
293
  }
294
  else:
295
  return send_guide_message(chat_id, "Unrecognized input. Type /help for available commands.")
@@ -303,13 +299,17 @@ def stream_to_youtube(input_url, quality_settings, video_codec, audio_codec, out
303
  try:
304
  streaming_state = "streaming"
305
  reset_statistics()
 
306
  input_stream = av.open(input_url)
307
  output_stream = av.open(output_url, mode='w', format='flv')
 
 
308
  video_stream = output_stream.add_stream(video_codec, rate=30)
309
  video_stream.width = input_stream.streams.video[0].width
310
  video_stream.height = input_stream.streams.video[0].height
311
  video_stream.pix_fmt = input_stream.streams.video[0].format.name
312
  video_stream.codec_context.options.update({'g': '30'})
 
313
  if quality_settings.lower() == "high":
314
  video_stream.bit_rate = 3000000
315
  video_stream.bit_rate_tolerance = 1000000
@@ -319,18 +319,25 @@ def stream_to_youtube(input_url, quality_settings, video_codec, audio_codec, out
319
  elif quality_settings.lower() == "low":
320
  video_stream.bit_rate = 800000
321
  video_stream.bit_rate_tolerance = 200000
 
 
322
  audio_stream_in = input_stream.streams.audio[0]
323
  out_audio_stream = output_stream.add_stream(audio_codec, rate=audio_stream_in.rate)
324
  out_audio_stream.layout = "stereo"
 
325
  video_stream.codec_context.time_base = fractions.Fraction(1, video_stream.rate)
 
326
  logging.info("Streaming started successfully.")
327
- # Start live log updater thread if not already running
 
328
  global live_log_thread
329
  if live_log_thread is None or not live_log_thread.is_alive():
330
  live_log_thread = threading.Thread(target=live_log_updater)
331
  live_log_thread.daemon = True
332
  live_log_thread.start()
333
  logging.info("Live log updater thread started.")
 
 
334
  while streaming_state in ["streaming", "paused"]:
335
  for packet in input_stream.demux():
336
  if streaming_state == "stopped":
@@ -354,12 +361,17 @@ def stream_to_youtube(input_url, quality_settings, video_codec, audio_codec, out
354
  output_stream.mux(out_packet)
355
  if hasattr(out_packet, "size"):
356
  bytes_sent += out_packet.size
 
 
357
  for out_packet in video_stream.encode():
358
  output_stream.mux(out_packet)
359
  for out_packet in out_audio_stream.encode():
360
  output_stream.mux(out_packet)
 
361
  if streaming_state == "paused":
362
  time.sleep(1)
 
 
363
  try:
364
  video_stream.close()
365
  out_audio_stream.close()
@@ -367,6 +379,7 @@ def stream_to_youtube(input_url, quality_settings, video_codec, audio_codec, out
367
  input_stream.close()
368
  except Exception as cleanup_error:
369
  logging.error(f"Error during cleanup: {cleanup_error}")
 
370
  logging.info("Streaming complete, resources cleaned up.")
371
  streaming_state = "idle"
372
  except Exception as e:
@@ -380,6 +393,7 @@ def start_streaming(chat_id):
380
  valid, msg = validate_inputs()
381
  if not valid:
382
  return send_guide_message(chat_id, f"Validation error: {msg}")
 
383
  stream_chat_id = chat_id
384
  try:
385
  stream_thread = threading.Thread(
@@ -396,6 +410,7 @@ def start_streaming(chat_id):
396
  stream_thread.daemon = True
397
  stream_thread.start()
398
  logging.info("Streaming thread started.")
 
399
  return {
400
  "method": "sendMessage",
401
  "chat_id": chat_id,
@@ -420,7 +435,7 @@ def pause_stream(chat_id):
420
  return {
421
  "method": "sendMessage",
422
  "chat_id": chat_id,
423
- "text": "⏸ <b>Streaming paused.</b>\n\n" + compose_streaming_message(),
424
  "parse_mode": "HTML"
425
  }
426
  return send_guide_message(chat_id, "Streaming is not active.")
@@ -433,7 +448,7 @@ def resume_stream(chat_id):
433
  return {
434
  "method": "sendMessage",
435
  "chat_id": chat_id,
436
- "text": "▢️ <b>Streaming resumed.</b>\n\n" + compose_streaming_message(),
437
  "parse_mode": "HTML"
438
  }
439
  return send_guide_message(chat_id, "Streaming is not paused.")
@@ -473,10 +488,12 @@ def stream_status(chat_id):
473
  async def telegram_webhook(request: Request):
474
  update = await request.json()
475
  logging.debug(f"Received update: {update}")
 
476
  # Process messages from users
477
  if "message" in update:
478
  chat_id = update["message"]["chat"]["id"]
479
  text = update["message"].get("text", "").strip()
 
480
  if text.startswith("/setting"):
481
  return handle_setting(chat_id)
482
  elif text.startswith("/start"):
@@ -495,11 +512,13 @@ async def telegram_webhook(request: Request):
495
  return stream_status(chat_id)
496
  else:
497
  return handle_conversation(chat_id, text)
 
498
  # Process inline keyboard callback queries
499
  elif "callback_query" in update:
500
  callback_data = update["callback_query"]["data"]
501
  chat_id = update["callback_query"]["message"]["chat"]["id"]
502
  message_id = update["callback_query"]["message"]["message_id"]
 
503
  if callback_data == "pause":
504
  response = pause_stream(chat_id)
505
  elif callback_data == "resume":
@@ -512,12 +531,16 @@ async def telegram_webhook(request: Request):
512
  response = start_streaming(chat_id)
513
  else:
514
  response = send_guide_message(chat_id, "❓ Unknown callback command.")
 
 
515
  if callback_data in ["pause", "resume", "abort", "status", "start_stream"]:
516
  response["method"] = "editMessageText"
517
  response["message_id"] = message_id
518
  response["text"] = compose_streaming_message()
519
  response["parse_mode"] = "HTML"
 
520
  if streaming_state in ["streaming", "paused"]:
521
  response["reply_markup"] = get_inline_keyboard_for_stream()
522
  return response
 
523
  return {"status": "ok"}
 
49
 
50
  # Live logging globals
51
  live_log_lines = [] # Rolling list (max 50 log lines)
52
+ live_log_display = "" # Global variable updated every second by live_log_updater
53
  error_notification = "" # Global variable to hold error details if any
54
 
55
  # -------------------------------------------------------------------
 
77
  list_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S"))
78
  logger.addHandler(list_handler)
79
 
80
+ # -------------------------------------------------------------------
81
+ # Helper Function: Compose Streaming Message
82
+ # -------------------------------------------------------------------
83
+ def compose_streaming_message():
84
+ global live_log_display, error_notification
85
+ msg = ""
86
+ if error_notification:
87
+ msg += "<b>ERROR:</b> " + error_notification + "\n\n"
88
+ msg += "πŸš€ <b>Streaming in progress!</b>\n\nLive Logs:\n" + live_log_display + "\n\nUse the inline keyboard to control the stream."
89
+ return msg
90
+
91
  # -------------------------------------------------------------------
92
  # Utility Functions & UI Helpers
93
  # -------------------------------------------------------------------
 
166
  return False, f"Missing fields: {', '.join(missing)}"
167
  return True, ""
168
 
 
 
 
 
 
 
 
 
 
 
 
169
  # -------------------------------------------------------------------
170
  # Error Notification Helper
171
  # -------------------------------------------------------------------
 
181
  global live_log_display, streaming_state
182
  try:
183
  while streaming_state in ["streaming", "paused"]:
184
+ # Update the global live_log_display with the last 15 log lines in HTML format
185
  live_log_display = "<pre>" + "\n".join(live_log_lines[-15:]) + "</pre>"
186
  time.sleep(1)
187
  except Exception as e:
 
210
  # -------------------------------------------------------------------
211
  def handle_start(chat_id):
212
  global current_step, user_inputs, conversation_fields, advanced_mode
213
+ # By default, use simple mode (unless advanced_mode was set via /setting)
214
  user_inputs = {}
215
  if not advanced_mode:
216
  conversation_fields = ["input_url", "output_url"]
217
  else:
218
  conversation_fields = ["input_url", "quality_settings", "video_codec", "audio_codec", "output_url"]
219
  current_step = conversation_fields[0]
220
+ text = ("πŸ‘‹ *Welcome to the Stream Bot!*\n\n"
221
  "Let's set up your stream.\n"
222
+ f"Please enter the *{current_step.replace('_', ' ')}*"
223
+ f"{' (no default)' if current_step not in default_settings else f' _(default: {default_settings[current_step]})_'}:")
224
  logging.info(f"/start command from chat {chat_id} (advanced_mode={advanced_mode})")
225
  return {
226
  "method": "sendMessage",
227
  "chat_id": chat_id,
228
  "text": text,
229
+ "parse_mode": "Markdown"
230
  }
231
 
232
  def handle_setting(chat_id):
 
235
  conversation_fields = ["input_url", "quality_settings", "video_codec", "audio_codec", "output_url"]
236
  user_inputs = {}
237
  current_step = conversation_fields[0]
238
+ text = ("βš™οΈ *Advanced Mode Activated!*\n\n"
239
+ "Please enter the *input url*:")
240
  logging.info(f"/setting command from chat {chat_id} - advanced mode enabled")
241
  return {
242
  "method": "sendMessage",
243
  "chat_id": chat_id,
244
  "text": text,
245
+ "parse_mode": "Markdown"
246
  }
247
 
248
  def handle_help(chat_id):
 
263
  else:
264
  user_inputs[current_step] = text.strip()
265
  logging.info(f"Received {current_step}: {text.strip()}")
266
+
267
  idx = conversation_fields.index(current_step)
268
  if idx < len(conversation_fields) - 1:
269
  current_step = conversation_fields[idx + 1]
270
+ prompt = f"Please enter the *{current_step.replace('_', ' ')}*"
271
  if current_step in default_settings:
272
+ prompt += f" _(default: {default_settings[current_step]})_"
273
+ return send_guide_message(chat_id, prompt)
 
 
 
 
 
274
  else:
275
  current_step = None
276
  valid, msg = validate_inputs()
 
283
  return {
284
  "method": "sendMessage",
285
  "chat_id": chat_id,
286
+ "text": "All inputs received. Press *πŸš€ Start Streaming* to begin.",
287
  "reply_markup": get_inline_keyboard_for_start(),
288
+ "parse_mode": "Markdown"
289
  }
290
  else:
291
  return send_guide_message(chat_id, "Unrecognized input. Type /help for available commands.")
 
299
  try:
300
  streaming_state = "streaming"
301
  reset_statistics()
302
+
303
  input_stream = av.open(input_url)
304
  output_stream = av.open(output_url, mode='w', format='flv')
305
+
306
+ # Configure video stream
307
  video_stream = output_stream.add_stream(video_codec, rate=30)
308
  video_stream.width = input_stream.streams.video[0].width
309
  video_stream.height = input_stream.streams.video[0].height
310
  video_stream.pix_fmt = input_stream.streams.video[0].format.name
311
  video_stream.codec_context.options.update({'g': '30'})
312
+
313
  if quality_settings.lower() == "high":
314
  video_stream.bit_rate = 3000000
315
  video_stream.bit_rate_tolerance = 1000000
 
319
  elif quality_settings.lower() == "low":
320
  video_stream.bit_rate = 800000
321
  video_stream.bit_rate_tolerance = 200000
322
+
323
+ # Configure audio stream
324
  audio_stream_in = input_stream.streams.audio[0]
325
  out_audio_stream = output_stream.add_stream(audio_codec, rate=audio_stream_in.rate)
326
  out_audio_stream.layout = "stereo"
327
+
328
  video_stream.codec_context.time_base = fractions.Fraction(1, video_stream.rate)
329
+
330
  logging.info("Streaming started successfully.")
331
+
332
+ # Start the live log updater in a background thread if not already running.
333
  global live_log_thread
334
  if live_log_thread is None or not live_log_thread.is_alive():
335
  live_log_thread = threading.Thread(target=live_log_updater)
336
  live_log_thread.daemon = True
337
  live_log_thread.start()
338
  logging.info("Live log updater thread started.")
339
+
340
+ # Stream loop: process packets until state changes
341
  while streaming_state in ["streaming", "paused"]:
342
  for packet in input_stream.demux():
343
  if streaming_state == "stopped":
 
361
  output_stream.mux(out_packet)
362
  if hasattr(out_packet, "size"):
363
  bytes_sent += out_packet.size
364
+
365
+ # Flush remaining packets
366
  for out_packet in video_stream.encode():
367
  output_stream.mux(out_packet)
368
  for out_packet in out_audio_stream.encode():
369
  output_stream.mux(out_packet)
370
+
371
  if streaming_state == "paused":
372
  time.sleep(1)
373
+
374
+ # Clean up resources
375
  try:
376
  video_stream.close()
377
  out_audio_stream.close()
 
379
  input_stream.close()
380
  except Exception as cleanup_error:
381
  logging.error(f"Error during cleanup: {cleanup_error}")
382
+
383
  logging.info("Streaming complete, resources cleaned up.")
384
  streaming_state = "idle"
385
  except Exception as e:
 
393
  valid, msg = validate_inputs()
394
  if not valid:
395
  return send_guide_message(chat_id, f"Validation error: {msg}")
396
+
397
  stream_chat_id = chat_id
398
  try:
399
  stream_thread = threading.Thread(
 
410
  stream_thread.daemon = True
411
  stream_thread.start()
412
  logging.info("Streaming thread started.")
413
+ # Immediately return a message that includes the live log display via compose_streaming_message()
414
  return {
415
  "method": "sendMessage",
416
  "chat_id": chat_id,
 
435
  return {
436
  "method": "sendMessage",
437
  "chat_id": chat_id,
438
+ "text": "⏸ <b>Streaming paused.</b>",
439
  "parse_mode": "HTML"
440
  }
441
  return send_guide_message(chat_id, "Streaming is not active.")
 
448
  return {
449
  "method": "sendMessage",
450
  "chat_id": chat_id,
451
+ "text": "▢️ <b>Streaming resumed.</b>",
452
  "parse_mode": "HTML"
453
  }
454
  return send_guide_message(chat_id, "Streaming is not paused.")
 
488
  async def telegram_webhook(request: Request):
489
  update = await request.json()
490
  logging.debug(f"Received update: {update}")
491
+
492
  # Process messages from users
493
  if "message" in update:
494
  chat_id = update["message"]["chat"]["id"]
495
  text = update["message"].get("text", "").strip()
496
+
497
  if text.startswith("/setting"):
498
  return handle_setting(chat_id)
499
  elif text.startswith("/start"):
 
512
  return stream_status(chat_id)
513
  else:
514
  return handle_conversation(chat_id, text)
515
+
516
  # Process inline keyboard callback queries
517
  elif "callback_query" in update:
518
  callback_data = update["callback_query"]["data"]
519
  chat_id = update["callback_query"]["message"]["chat"]["id"]
520
  message_id = update["callback_query"]["message"]["message_id"]
521
+
522
  if callback_data == "pause":
523
  response = pause_stream(chat_id)
524
  elif callback_data == "resume":
 
531
  response = start_streaming(chat_id)
532
  else:
533
  response = send_guide_message(chat_id, "❓ Unknown callback command.")
534
+
535
+ # For streaming control callbacks, update the message with the latest live logs
536
  if callback_data in ["pause", "resume", "abort", "status", "start_stream"]:
537
  response["method"] = "editMessageText"
538
  response["message_id"] = message_id
539
  response["text"] = compose_streaming_message()
540
  response["parse_mode"] = "HTML"
541
+ # Always include the inline keyboard if streaming is active.
542
  if streaming_state in ["streaming", "paused"]:
543
  response["reply_markup"] = get_inline_keyboard_for_stream()
544
  return response
545
+
546
  return {"status": "ok"}