Files changed (1) hide show
  1. bot.py +213 -29
bot.py CHANGED
@@ -4,6 +4,7 @@ import math
4
  import json
5
  import asyncio
6
  import subprocess
 
7
  import yt_dlp
8
  from datetime import datetime, timedelta
9
  from threading import Thread
@@ -43,6 +44,7 @@ admin_state = None
43
  # --- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---
44
  sent_message_map = {}
45
  recent_admin_messages =[]
 
46
 
47
  # --- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---
48
  DATA_FILE = "bot_config.json"
@@ -57,7 +59,8 @@ def save_data():
57
  "PASSWORD_LIMIT": PASSWORD_LIMIT,
58
  "GLOBAL_LIMIT": GLOBAL_LIMIT,
59
  "PASSWORD_USED_BY": {str(k): v for k, v in PASSWORD_USED_BY.items()},
60
- "authenticated_users": auth_users
 
61
  }, f)
62
  except Exception as e: pass
63
 
@@ -71,6 +74,7 @@ def load_data():
71
  PASSWORD_LIMIT = data.get("PASSWORD_LIMIT", 999999)
72
  GLOBAL_LIMIT = data.get("GLOBAL_LIMIT", 1)
73
  PASSWORD_USED_BY = {int(k): v for k, v in data.get("PASSWORD_USED_BY", {}).items()}
 
74
 
75
  saved_users = data.get("authenticated_users", {})
76
  for uid, name in saved_users.items():
@@ -140,6 +144,7 @@ def init_user(user_id, message=None):
140
  'video_url': None, # डायरेक्ट लिंक
141
  'schedule_time': None, # शेड्यूलिंग
142
  'processes':[],
 
143
  'stream_limit': GLOBAL_LIMIT,
144
  'file_path': None,
145
  'manual_stop': False,
@@ -178,7 +183,13 @@ def schedule_choice_keyboard():
178
  return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ Start Now", callback_data="sch_now")],[InlineKeyboardButton("⏳ Schedule Time", callback_data="sch_later")]])
179
 
180
  def admin_keyboard():
181
- return InlineKeyboardMarkup([[InlineKeyboardButton("👥 Users List", callback_data="admin_users"), InlineKeyboardButton("🔴 Live Status", callback_data="admin_live")],[InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass"), InlineKeyboardButton("⚙️ Set Limit", callback_data="admin_limit")],[InlineKeyboardButton("📢 Broadcast / Send Msg", callback_data="admin_msg")],[InlineKeyboardButton("🗑 Delete Last 5 Broadcasts", callback_data="admin_del")]])
 
 
 
 
 
 
182
 
183
  # --- Live Progress Bar ---
184
  async def progress_bar(current, total, msg, start_time, last_update):
@@ -342,6 +353,28 @@ async def admin_callbacks(client, callback_query: CallbackQuery):
342
  "👉 **किसी एक को भेजने के लिए:** ID स्पेस मेसेज लिखें।\n"
343
  "`उदाहरण: 8383838487 Hello kese ho`")
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  elif data == "admin_del":
346
  if not recent_admin_messages:
347
  return await callback_query.message.reply("❌ **No recent messages/broadcasts found to delete.**")
@@ -471,14 +504,74 @@ async def select_schedule_choice(client, callback_query: CallbackQuery):
471
  user_id = callback_query.message.chat.id
472
  if callback_query.data == "sch_now":
473
  user_data[user_id]['step'] = 'READY'
474
- # 🚀 यह मैसेज डिलीट हो जाएगा ताकि स्ट्रीम खत्म होने के बाद स्क्रीनशॉट की तरह फालतू न दिखे
475
  await callback_query.message.delete()
476
  await process_stream_start(client, callback_query.message)
477
  else:
478
  user_data[user_id]['step'] = 'AWAIT_DATETIME'
479
- await callback_query.message.edit_text("📅 **Schedule Time भेजो!**\n\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `26/05/2026 12:40 PM`")
 
 
 
 
 
 
 
 
 
 
 
480
 
481
- @bot.on_message(~filters.command(["start", "stop", "new", "admin"]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  async def handle_all_messages(client, message):
483
  user_id = message.chat.id
484
  init_user(user_id, message)
@@ -595,6 +688,18 @@ async def handle_all_messages(client, message):
595
  admin_state = None
596
  return
597
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  # === Normal User Handling ===
599
  if not user_data[user_id]['authenticated'] and step != 'PASSWORD':
600
  user_data[user_id]['step'] = 'PASSWORD'
@@ -723,24 +828,94 @@ async def handle_all_messages(client, message):
723
 
724
  elif step == 'AWAIT_DATETIME':
725
  try:
726
- dt_obj = datetime.strptime(message.text.strip(), "%d/%m/%Y %I:%M %p")
 
727
  now_ist = get_ist_time().replace(tzinfo=None)
728
  if dt_obj <= now_ist:
729
- return await message.reply("⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया फिर से भेजें।\nExample: `26/05/2026 12:40 PM`")
 
 
 
 
 
730
 
731
- user_data[user_id]['schedule_time'] = dt_obj
732
  user_data[user_id]['step'] = 'READY'
733
-
734
  delay = (dt_obj - now_ist).total_seconds()
735
- await message.reply(f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{message.text.strip()}` पर चालू हो जाएगा।")
736
 
737
- asyncio.create_task(scheduled_stream_starter(client, message, delay))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  except Exception as e:
739
- await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `26/05/2026 12:40 PM`")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
 
741
- async def scheduled_stream_starter(client, message, delay):
742
- await asyncio.sleep(delay)
743
- await process_stream_start(client, message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
 
745
  async def process_stream_start(client, message):
746
  await handle_video(client, message)
@@ -754,15 +929,21 @@ def get_direct_url(url):
754
  except:
755
  return url
756
 
757
- async def handle_video(client, message):
758
  user_id = message.chat.id
759
 
 
 
 
760
  user_data[user_id]['manual_stop'] = False
761
- msg = await client.send_message(user_id, "⏳ **Preparing Stream Environment...**")
 
 
 
762
 
763
- file_path = user_data[user_id].get('file_path')
764
- video_url = user_data[user_id].get('video_url')
765
- custom_audio = user_data[user_id].get('custom_audio_path')
766
 
767
  input_source = file_path
768
  if video_url:
@@ -771,11 +952,11 @@ async def handle_video(client, message):
771
  if not input_source:
772
  return await msg.edit_text("❌ **Failed to extract link!**")
773
 
774
- platform = user_data[user_id].get('platform', 'Custom')
775
- loop_hours = user_data[user_id].get('loop_hours', 0)
776
- rtmp_url = user_data[user_id]['rtmp_url']
777
- orientation = user_data[user_id].get('orientation', 'landscape')
778
- image_path = user_data[user_id].get('custom_image_path')
779
 
780
  user_data[user_id]['total_streams'] += 1
781
  user_data[user_id]['last_stream'] = get_ist_time()
@@ -797,7 +978,7 @@ async def handle_video(client, message):
797
  total_stream_time = vid_duration
798
  actual_stream_seconds = vid_duration
799
 
800
- is_image_only = user_data[user_id].get('is_image_only', False)
801
 
802
  # --- FFMPEG Command Setup ---
803
  command = ["ffmpeg", "-re"]
@@ -825,7 +1006,7 @@ async def handle_video(client, message):
825
  vf_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black"
826
 
827
  # --- NEW: SLOW SMOOTH MOVING WATERMARK ---
828
- wm_text = user_data[user_id].get('watermark_text')
829
  if wm_text:
830
  safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
831
  # sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
@@ -872,7 +1053,7 @@ async def handle_video(client, message):
872
  command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
873
  else:
874
  # YouTube Strict Bitrate & Keyframe (GOP) Fix for Landscape Video
875
- wm_text = user_data[user_id].get('watermark_text')
876
 
877
  if wm_text:
878
  safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
@@ -910,12 +1091,15 @@ async def handle_video(client, message):
910
  start_t = get_ist_time()
911
  end_t = start_t + timedelta(seconds=actual_stream_seconds) if loop_hours > 0 else "Auto (Depends on Video)"
912
 
 
913
  user_data[user_id]['processes'].append({
 
914
  'process': process_obj,
915
  'start_time': start_t,
916
  'end_time': end_t,
917
  'loop_hours': loop_hours,
918
- 'is_image': is_image_only
 
919
  })
920
 
921
  asyncio.create_task(monitor_stream(client, message.chat.id, process_obj, file_path, live_msg.id, loop_hours, platform, total_stream_time, image_path, custom_audio))
 
4
  import json
5
  import asyncio
6
  import subprocess
7
+ import uuid
8
  import yt_dlp
9
  from datetime import datetime, timedelta
10
  from threading import Thread
 
44
  # --- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---
45
  sent_message_map = {}
46
  recent_admin_messages =[]
47
+ GLOBAL_SCHEDULE_TIMES = [] # एडमिन के सेट किये गए प्री-डिफाइंड शेड्यूल्स
48
 
49
  # --- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---
50
  DATA_FILE = "bot_config.json"
 
59
  "PASSWORD_LIMIT": PASSWORD_LIMIT,
60
  "GLOBAL_LIMIT": GLOBAL_LIMIT,
61
  "PASSWORD_USED_BY": {str(k): v for k, v in PASSWORD_USED_BY.items()},
62
+ "authenticated_users": auth_users,
63
+ "GLOBAL_SCHEDULE_TIMES": GLOBAL_SCHEDULE_TIMES
64
  }, f)
65
  except Exception as e: pass
66
 
 
74
  PASSWORD_LIMIT = data.get("PASSWORD_LIMIT", 999999)
75
  GLOBAL_LIMIT = data.get("GLOBAL_LIMIT", 1)
76
  PASSWORD_USED_BY = {int(k): v for k, v in data.get("PASSWORD_USED_BY", {}).items()}
77
+ GLOBAL_SCHEDULE_TIMES = data.get("GLOBAL_SCHEDULE_TIMES", [])
78
 
79
  saved_users = data.get("authenticated_users", {})
80
  for uid, name in saved_users.items():
 
144
  'video_url': None, # डायरेक्ट लिंक
145
  'schedule_time': None, # शेड्यूलिंग
146
  'processes':[],
147
+ 'scheduled_tasks': {}, # <--- नया: शेड्यूल्ड टास्क को ट्रैक करने के लिए
148
  'stream_limit': GLOBAL_LIMIT,
149
  'file_path': None,
150
  'manual_stop': False,
 
183
  return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ Start Now", callback_data="sch_now")],[InlineKeyboardButton("⏳ Schedule Time", callback_data="sch_later")]])
184
 
185
  def admin_keyboard():
186
+ return InlineKeyboardMarkup([
187
+ [InlineKeyboardButton("👥 Users List", callback_data="admin_users"), InlineKeyboardButton("🔴 Live Status", callback_data="admin_live")],
188
+ [InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass"), InlineKeyboardButton("⚙️ Set Limit", callback_data="admin_limit")],
189
+ [InlineKeyboardButton("📅 Manage Schedule Times", callback_data="admin_sch_times")],
190
+ [InlineKeyboardButton("📢 Broadcast / Send Msg", callback_data="admin_msg")],
191
+ [InlineKeyboardButton("🗑 Delete Last 5 Broadcasts", callback_data="admin_del")]
192
+ ])
193
 
194
  # --- Live Progress Bar ---
195
  async def progress_bar(current, total, msg, start_time, last_update):
 
353
  "👉 **किसी एक को भेजने के लिए:** ID स्पेस मेसेज लिखें।\n"
354
  "`उदाहरण: 8383838487 Hello kese ho`")
355
 
356
+ elif data == "admin_sch_times":
357
+ markup = []
358
+ for st in GLOBAL_SCHEDULE_TIMES:
359
+ markup.append([InlineKeyboardButton(f"❌ Delete {st}", callback_data=f"admindelscht_{st}")])
360
+ markup.append([InlineKeyboardButton("➕ Add New Time", callback_data="admin_sch_add")])
361
+ markup.append([InlineKeyboardButton("🔙 Back", callback_data="admin_back")])
362
+ await callback_query.message.edit_text("📅 **Manage Pre-defined Schedule Times:**\nये समय यूज़र्स को बटन के रूप में दिखेंगे।", reply_markup=InlineKeyboardMarkup(markup))
363
+
364
+ elif data == "admin_back":
365
+ await callback_query.message.edit_text("👑 **Welcome to Admin Panel**\nChoose an option below:", reply_markup=admin_keyboard())
366
+
367
+ elif data == "admin_sch_add":
368
+ admin_state = "AWAIT_SCH_TIME"
369
+ await callback_query.message.reply("📅 **कृपया नया शेड्यूल्ड समय भेजें:**\n\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`")
370
+
371
+ elif data.startswith("admindelscht_"):
372
+ time_to_del = data.replace("admindelscht_", "")
373
+ if time_to_del in GLOBAL_SCHEDULE_TIMES:
374
+ GLOBAL_SCHEDULE_TIMES.remove(time_to_del)
375
+ save_data()
376
+ await admin_callbacks(client, callback_query._client.callback_query(id=callback_query.id, from_user=callback_query.from_user, message=callback_query.message, data="admin_sch_times"))
377
+
378
  elif data == "admin_del":
379
  if not recent_admin_messages:
380
  return await callback_query.message.reply("❌ **No recent messages/broadcasts found to delete.**")
 
504
  user_id = callback_query.message.chat.id
505
  if callback_query.data == "sch_now":
506
  user_data[user_id]['step'] = 'READY'
 
507
  await callback_query.message.delete()
508
  await process_stream_start(client, callback_query.message)
509
  else:
510
  user_data[user_id]['step'] = 'AWAIT_DATETIME'
511
+ markup = []
512
+ for st in GLOBAL_SCHEDULE_TIMES:
513
+ # Inline button format for specific times
514
+ markup.append([InlineKeyboardButton(f"🗓 {st}", callback_data=f"set_schtime_{st}")])
515
+
516
+ # बटन के बिना मैन्युअल एंट्री का निर्देश
517
+ text = "📅 **Schedule Time चुनें या लिखें!**\n\n"
518
+ if GLOBAL_SCHEDULE_TIMES:
519
+ text += "👇 **एडमिन द्वारा दिए गए समय में से चुनें:**\n"
520
+ text += "\n✍️ **या अपना खुद का समय लिखकर भेजें:**\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`"
521
+
522
+ await callback_query.message.edit_text(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)
523
 
524
+ @bot.on_callback_query(filters.regex("^set_schtime_"))
525
+ async def select_predefined_time(client, callback_query: CallbackQuery):
526
+ user_id = callback_query.message.chat.id
527
+ time_str = callback_query.data.replace("set_schtime_", "")
528
+
529
+ # Create fake message to pass to AWAIT_DATETIME logic
530
+ class FakeMessage:
531
+ def __init__(self, text, chat_id):
532
+ self.text = text
533
+ self.chat = type('Chat', (), {'id': chat_id})()
534
+ self.id = callback_query.message.id
535
+
536
+ await callback_query.message.edit_text(f"⏳ Processing schedule for {time_str}...")
537
+ await handle_all_messages(client, FakeMessage(time_str, user_id))
538
+
539
+ @bot.on_message(filters.command("edit"))
540
+ async def edit_cmd(client, message):
541
+ user_id = message.chat.id
542
+ init_user(user_id, message)
543
+
544
+ markup = []
545
+ text = "🛠 **Manage Your Streams:**\nयहाँ से आप अपनी ग़लत बनी हुई या चल रही स्ट्रीम्स को Delete/Stop कर सकते हैं।\n\n"
546
+
547
+ # Running streams
548
+ active_procs = [p for p in user_data[user_id].get('processes', []) if p['process'].poll() is None]
549
+ if active_procs:
550
+ text += "🔴 **Running Streams:**\n"
551
+ for idx, p in enumerate(active_procs, 1):
552
+ p_id = p.get('id', 'unknown')
553
+ plat = p.get('platform', 'Unknown')
554
+ text += f"{idx}. 🟢 {plat} - Started: {p['start_time'].strftime('%I:%M %p')}\n"
555
+ markup.append([InlineKeyboardButton(f"🛑 Stop Running: {plat} Stream {idx}", callback_data=f"user_stop_{p_id}")])
556
+
557
+ # Scheduled streams
558
+ scheduled = user_data[user_id].get('scheduled_tasks', {})
559
+ if scheduled:
560
+ text += "\n⏳ **Scheduled Streams:**\n"
561
+ idx = 1
562
+ for s_id, s_info in list(scheduled.items()):
563
+ time_str = s_info['time_str']
564
+ plat = s_info['config'].get('platform', 'Unknown')
565
+ text += f"{idx}. 🗓 {plat} - Scheduled for: {time_str}\n"
566
+ markup.append([InlineKeyboardButton(f"❌ Cancel Scheduled: {plat} {time_str}", callback_data=f"user_cancelsch_{s_id}")])
567
+ idx += 1
568
+
569
+ if not active_procs and not scheduled:
570
+ text = "⚠️ **आपकी कोई भी स्ट्रीम अभी चल नहीं रही है और ना ही शेड्यूल है।**"
571
+
572
+ await message.reply(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)
573
+
574
+ @bot.on_message(~filters.command(["start", "stop", "new", "admin", "edit"]))
575
  async def handle_all_messages(client, message):
576
  user_id = message.chat.id
577
  init_user(user_id, message)
 
688
  admin_state = None
689
  return
690
 
691
+ elif admin_state == "AWAIT_SCH_TIME" and message.text:
692
+ try:
693
+ # Validation
694
+ dt_obj = datetime.strptime(message.text.strip(), "%d/%m/%Y %I:%M %p")
695
+ GLOBAL_SCHEDULE_TIMES.append(message.text.strip())
696
+ save_data()
697
+ await message.reply(f"✅ **Time Added Successfully!**\n`{message.text.strip()}`\n\nअब यह समय सभी यूज़र्स को बटन में दिखाई देगा।")
698
+ admin_state = None
699
+ except Exception as e:
700
+ await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
701
+ return
702
+
703
  # === Normal User Handling ===
704
  if not user_data[user_id]['authenticated'] and step != 'PASSWORD':
705
  user_data[user_id]['step'] = 'PASSWORD'
 
828
 
829
  elif step == 'AWAIT_DATETIME':
830
  try:
831
+ time_str = message.text.strip()
832
+ dt_obj = datetime.strptime(time_str, "%d/%m/%Y %I:%M %p")
833
  now_ist = get_ist_time().replace(tzinfo=None)
834
  if dt_obj <= now_ist:
835
+ # If pre-defined time is passed, allow it logically but warn if past
836
+ try:
837
+ await client.send_message(user_id, "⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
838
+ except:
839
+ await message.reply("⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
840
+ return
841
 
 
842
  user_data[user_id]['step'] = 'READY'
 
843
  delay = (dt_obj - now_ist).total_seconds()
 
844
 
845
+ # --- Snapshot Configuration ---
846
+ # (अगर यूज़र दूसरी स्ट्रीम बनायेगा तो पहली स्ट्रीम का डेटा डिलीट न हो)
847
+ stream_config = {
848
+ 'file_path': user_data[user_id].get('file_path'),
849
+ 'video_url': user_data[user_id].get('video_url'),
850
+ 'custom_audio_path': user_data[user_id].get('custom_audio_path'),
851
+ 'platform': user_data[user_id].get('platform', 'Custom'),
852
+ 'loop_hours': user_data[user_id].get('loop_hours', 0),
853
+ 'rtmp_url': user_data[user_id].get('rtmp_url'),
854
+ 'orientation': user_data[user_id].get('orientation', 'landscape'),
855
+ 'custom_image_path': user_data[user_id].get('custom_image_path'),
856
+ 'is_image_only': user_data[user_id].get('is_image_only', False),
857
+ 'watermark_text': user_data[user_id].get('watermark_text')
858
+ }
859
+
860
+ s_id = str(uuid.uuid4())[:8]
861
+ task = asyncio.create_task(scheduled_stream_starter(client, message, delay, stream_config, s_id))
862
+
863
+ user_data[user_id]['scheduled_tasks'][s_id] = {
864
+ 'task': task,
865
+ 'time_str': time_str,
866
+ 'config': stream_config
867
+ }
868
+
869
+ try:
870
+ await client.send_message(user_id, f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
871
+ except:
872
+ await message.reply(f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
873
+
874
  except Exception as e:
875
+ try:
876
+ await client.send_message(user_id, "❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
877
+ except:
878
+ await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
879
+
880
+ @bot.on_callback_query(filters.regex("^user_cancelsch_"))
881
+ async def cancel_sch_callback(client, callback_query: CallbackQuery):
882
+ user_id = callback_query.message.chat.id
883
+ s_id = callback_query.data.replace("user_cancelsch_", "")
884
+ if s_id in user_data[user_id].get('scheduled_tasks', {}):
885
+ user_data[user_id]['scheduled_tasks'][s_id]['task'].cancel()
886
+ del user_data[user_id]['scheduled_tasks'][s_id]
887
+ await callback_query.message.edit_text("✅ **Scheduled Stream Cancelled / Delete हो गई!**\nअब आप `/new` से नई बना सकते हैं।")
888
+ else:
889
+ await callback_query.message.edit_text("❌ **यह शेड्यूल पहले ही कैंसिल हो चुका है या चल चुका है।**")
890
 
891
+ @bot.on_callback_query(filters.regex("^user_stop_"))
892
+ async def stop_running_callback(client, callback_query: CallbackQuery):
893
+ user_id = callback_query.message.chat.id
894
+ p_id = callback_query.data.replace("user_stop_", "")
895
+
896
+ stopped = False
897
+ for p in user_data[user_id].get('processes', []):
898
+ if p.get('id') == p_id and p['process'].poll() is None:
899
+ user_data[user_id]['manual_stop'] = True
900
+ p['process'].terminate()
901
+ stopped = True
902
+ break
903
+
904
+ if stopped:
905
+ await callback_query.message.edit_text("✅ **Running Stream को सफलतापूर्वक बंद कर दिया गया है!**")
906
+ else:
907
+ await callback_query.message.edit_text("❌ **यह स्ट्रीम पहले ही बंद हो चुकी है।**")
908
+
909
+ async def scheduled_stream_starter(client, message, delay, stream_config, s_id):
910
+ try:
911
+ await asyncio.sleep(delay)
912
+ user_id = message.chat.id
913
+ # Remove from pending queue as it starts
914
+ if s_id in user_data[user_id].get('scheduled_tasks', {}):
915
+ del user_data[user_id]['scheduled_tasks'][s_id]
916
+ await handle_video(client, message, stream_config)
917
+ except asyncio.CancelledError:
918
+ pass # Stream was cancelled by user
919
 
920
  async def process_stream_start(client, message):
921
  await handle_video(client, message)
 
929
  except:
930
  return url
931
 
932
+ async def handle_video(client, message, stream_config=None):
933
  user_id = message.chat.id
934
 
935
+ # अगर कॉन्फ़िग स्नैपशॉट (शेड्यूल से) आया है, तो उसका इस्तेमाल करें। नहीं तो करंट डेटा का।
936
+ config = stream_config if stream_config else user_data[user_id]
937
+
938
  user_data[user_id]['manual_stop'] = False
939
+ try:
940
+ msg = await client.send_message(user_id, "⏳ **Preparing Stream Environment...**")
941
+ except:
942
+ msg = await message.reply("⏳ **Preparing Stream Environment...**")
943
 
944
+ file_path = config.get('file_path')
945
+ video_url = config.get('video_url')
946
+ custom_audio = config.get('custom_audio_path')
947
 
948
  input_source = file_path
949
  if video_url:
 
952
  if not input_source:
953
  return await msg.edit_text("❌ **Failed to extract link!**")
954
 
955
+ platform = config.get('platform', 'Custom')
956
+ loop_hours = config.get('loop_hours', 0)
957
+ rtmp_url = config.get('rtmp_url')
958
+ orientation = config.get('orientation', 'landscape')
959
+ image_path = config.get('custom_image_path')
960
 
961
  user_data[user_id]['total_streams'] += 1
962
  user_data[user_id]['last_stream'] = get_ist_time()
 
978
  total_stream_time = vid_duration
979
  actual_stream_seconds = vid_duration
980
 
981
+ is_image_only = config.get('is_image_only', False)
982
 
983
  # --- FFMPEG Command Setup ---
984
  command = ["ffmpeg", "-re"]
 
1006
  vf_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black"
1007
 
1008
  # --- NEW: SLOW SMOOTH MOVING WATERMARK ---
1009
+ wm_text = config.get('watermark_text')
1010
  if wm_text:
1011
  safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
1012
  # sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
 
1053
  command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
1054
  else:
1055
  # YouTube Strict Bitrate & Keyframe (GOP) Fix for Landscape Video
1056
+ wm_text = config.get('watermark_text')
1057
 
1058
  if wm_text:
1059
  safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
 
1091
  start_t = get_ist_time()
1092
  end_t = start_t + timedelta(seconds=actual_stream_seconds) if loop_hours > 0 else "Auto (Depends on Video)"
1093
 
1094
+ unique_p_id = str(uuid.uuid4())[:8]
1095
  user_data[user_id]['processes'].append({
1096
+ 'id': unique_p_id,
1097
  'process': process_obj,
1098
  'start_time': start_t,
1099
  'end_time': end_t,
1100
  'loop_hours': loop_hours,
1101
+ 'is_image': is_image_only,
1102
+ 'platform': platform
1103
  })
1104
 
1105
  asyncio.create_task(monitor_stream(client, message.chat.id, process_obj, file_path, live_msg.id, loop_hours, platform, total_stream_time, image_path, custom_audio))