import os
import time
import math
import json
import asyncio
import subprocess
import uuid
import yt_dlp
from datetime import datetime, timedelta
from threading import Thread
from flask import Flask
from pyrogram import Client, filters
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery

--- Hugging Face को जगाए रखने के लिए Flask Server ---

app = Flask(name)
@app .route('/')
def health_check():
return "Bot is running perfectly!"

def run_flask():
app.run(host="0.0.0.0", port=7860)

--- Telegram Bot की डिटेल्स ---

BOT_TOKEN = "8728797060:AAH3L0eqApxEKuVvYjKT0JGpgQ3BPlrjbgI"
API_ID = 35985614
API_HASH = "6a0df17414daf6935f1f0a71b8af1ee9"

bot = Client("LiveBot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)

--- USER SESSION (Private Channel Download के लिए) ---

USER_SESSION = "BQIlGM4AazlRr5P1dhaKtIXeQTq0O5LnNaFB9uBcdjcjZQ95iBGIm3L_NTaA0ncK91ZKIr_4loGx8gw84Z_RL9gQDSZ80QOIq5RrwtlKvsfF-NrJElCGJ5NAL8GMtDceRIQpQO78DfI4PM75h7zylKi3jnDQik2bAUzJ7ylG1VI_hgfKxYZlkK2FXkGaACNgJSzUzc-4_THAlSDLNhpQ6Dv_WlFKQpGR47KdZTMxR81ExV5WjW4-AG1A0tr_B0PUvRVyZrpMLb5AzPMqFmByxeQS3QicQzXGKYlJj9Pm5uYRCcFS5WCqhC2lwISMqklhjIivc7LHko5EjyWELHhAhz2RfmPDQAAAAAHK_kURAA" # 👉 यहाँ अपना Pyrogram String Session डालें
user_app = Client("UserAcc", api_id=API_ID, api_hash=API_HASH, session_string=USER_SESSION)

--- ग्लोबल वेरिएबल्स ---

ADMIN_ID = 7700628753
GLOBAL_PASSWORD = "jitu@123"
PASSWORD_LIMIT = 999999 # डिफ़ॉल्ट लिमिट (Unlimted)
PASSWORD_USED_BY = {} # जिन लोगों ने पासवर्ड इस्तेमाल किया है उनका डेटा
GLOBAL_LIMIT = 1
user_data = {}
admin_state = None

--- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---

sent_message_map = {}
recent_admin_messages =[]
GLOBAL_SCHEDULE_TIMES = [] # एडमिन के सेट किये गए प्री-डिफाइंड शेड्यूल्स

--- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---

DATA_FILE = "bot_config.json"

def save_data():
try:
# सिर्फ ऑथेंटिकेटेड यूज़र्स का डेटा सेव करेंगे ताकि फाइल भारी न हो
auth_users = {str(uid): info['name'] for uid, info in user_data.items() if info.get('authenticated')}
with open(DATA_FILE, "w") as f:
json.dump({
"GLOBAL_PASSWORD": GLOBAL_PASSWORD,
"PASSWORD_LIMIT": PASSWORD_LIMIT,
"GLOBAL_LIMIT": GLOBAL_LIMIT,
"PASSWORD_USED_BY": {str(k): v for k, v in PASSWORD_USED_BY.items()},
"authenticated_users": auth_users,
"GLOBAL_SCHEDULE_TIMES": GLOBAL_SCHEDULE_TIMES
}, f)
except Exception as e: pass

def load_data():
global GLOBAL_PASSWORD, PASSWORD_LIMIT, GLOBAL_LIMIT, PASSWORD_USED_BY, user_data
if os.path.exists(DATA_FILE):
try:
with open(DATA_FILE, "r") as f:
data = json.load(f)
GLOBAL_PASSWORD = data.get("GLOBAL_PASSWORD", "jitu@123")
PASSWORD_LIMIT = data.get("PASSWORD_LIMIT", 999999)
GLOBAL_LIMIT = data.get("GLOBAL_LIMIT", 1)
PASSWORD_USED_BY = {int(k): v for k, v in data.get("PASSWORD_USED_BY", {}).items()}
GLOBAL_SCHEDULE_TIMES = data.get("GLOBAL_SCHEDULE_TIMES", [])

            saved_users = data.get("authenticated_users", {})
            for uid, name in saved_users.items():
                uid = int(uid)
                if uid not in user_data:
                    user_data[uid] = {
                        'name': name,
                        'authenticated': True,
                        'step': 'PLATFORM',
                        'platform': None,
                        'rtmp_url': None,
                        'loop_hours': 0,
                        'orientation': 'landscape', 
                        'custom_image_path': None,
                        'custom_audio_path': None,
                        'video_url': None,
                        'schedule_time': None,
                        'processes':[], 
                        'stream_limit': GLOBAL_LIMIT, 
                        'file_path': None,
                        'manual_stop': False,
                        'join_date': get_ist_time(),
                        'last_online': get_ist_time(),
                        'total_streams': 0,
                        'last_stream': "Never"
                    }
    except Exception as e: pass

def get_ist_time():
return datetime.utcnow() + timedelta(hours=5, minutes=30)

def format_date(dt):
if isinstance(dt, str): return dt
return dt.strftime("%d-%b-%Y %I:%M %p")

def get_video_duration(file_path):
try:
result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", file_path],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
return float(result.stdout.strip())
except:
return 0

async def notify_admin(client, text):
try:
await client.send_message(ADMIN_ID, text)
except:
pass

def init_user(user_id, message=None):
if user_id not in user_data:
name = message.from_user.first_name if message else "Unknown"
user_data[user_id] = {
'name': name,
'authenticated': False if user_id != ADMIN_ID else True,
'step': 'PASSWORD' if user_id != ADMIN_ID else 'PLATFORM',
'platform': None,
'rtmp_url': None,
'loop_hours': 0,
'orientation': 'landscape',
'custom_image_path': None, # नया: कस्टम फोटो सेव करने के लिए
'custom_audio_path': None, # ऑडियो रिप्लेस
'watermark_text': None, # <--- नया: मूविंग वाटरमार्क के लिए
'video_url': None, # डायरेक्ट लिंक
'schedule_time': None, # शेड्यूलिंग
'processes':[],
'scheduled_tasks': {}, # <--- नया: शेड्यूल्ड टास्क को ट्रैक करने के लिए
'stream_limit': GLOBAL_LIMIT,
'file_path': None,
'manual_stop': False,
'join_date': get_ist_time(),
'last_online': get_ist_time(),
'total_streams': 0,
'last_stream': "Never"
}
if user_id != ADMIN_ID:
asyncio.create_task(notify_admin(bot, f"🔔 **New User Joined!**\n👤 Name: {name}\n🆔 ID: {user_id}\n📅 Date: {format_date(get_ist_time())}"))
else:
user_data[user_id]['last_online'] = get_ist_time()
if message:
user_data[user_id]['name'] = message.from_user.first_name

def platform_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("🔴 YouTube Live", callback_data="plat_youtube")],[InlineKeyboardButton("🟢 Rumble Live", callback_data="plat_rumble")],[InlineKeyboardButton("🔵 Custom/Other", callback_data="plat_custom")]])

def loop_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ 1 Time (No Loop)", callback_data="loop_0")],[InlineKeyboardButton("⏳ 30 Mins", callback_data="loop_0.5"), InlineKeyboardButton("⏳ 1 Hour", callback_data="loop_1")],[InlineKeyboardButton("🔁 3 Hours", callback_data="loop_3"), InlineKeyboardButton("🔁 6 Hours", callback_data="loop_6")],[InlineKeyboardButton("🔁 12 Hours", callback_data="loop_12"), InlineKeyboardButton("🔁 24 Hours", callback_data="loop_24")]
])

def orientation_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("🖥 Landscape (16:9)", callback_data="ori_landscape")],[InlineKeyboardButton("📱 Portrait / Shorts (9:16)", callback_data="ori_portrait")]])

def image_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("✅ Yes (Add Custom Photo)", callback_data="img_yes")],[InlineKeyboardButton("❌ No (Normal Black Bg)", callback_data="img_no")]])

def audio_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("🎵 Replace Audio (Send File)", callback_data="aud_yes")],[InlineKeyboardButton("🔊 Keep Original Audio", callback_data="aud_no")]])

def watermark_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("✅ Yes (Moving Watermark)", callback_data="wm_yes")],[InlineKeyboardButton("❌ No Watermark", callback_data="wm_no")]])

def schedule_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ Start Now", callback_data="sch_now")],[InlineKeyboardButton("⏳ Schedule Time", callback_data="sch_later")]])

def admin_keyboard():
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("📅 Manage Schedule Times", callback_data="admin_sch_times")],
[InlineKeyboardButton("📢 Broadcast / Send Msg", callback_data="admin_msg")],
[InlineKeyboardButton("🗑 Delete Last 5 Broadcasts", callback_data="admin_del")]
])

--- Live Progress Bar ---

async def progress_bar(current, total, msg, start_time, last_update):
now = time.time()
if now - last_update[0] > 3 or current == total:
last_update[0] = now
diff = now - start_time
percentage = current * 100 / total
speed = current / diff if diff > 0 else 0
eta = round((total - current) / speed) if speed > 0 else 0

    current_mb = round(current / (1024 * 1024), 2)
    total_mb = round(total / (1024 * 1024), 2)
    speed_mb = round(speed / (1024 * 1024), 2)
    
    progress_str = "[{0}{1}]".format(''.join(["█" for _ in range(math.floor(percentage / 5))]), ''.join(["░" for _ in range(20 - math.floor(percentage / 5))]))
    eta_str = f"{eta} sec" if eta < 60 else f"{eta//60} min {eta%60} sec"
    
    text = f"📥 **Downloading Video...**\n\n{progress_str} **{round(percentage, 2)}%**\n📦 **Size:** `{current_mb} MB / {total_mb} MB`\n🚀 **Speed:** `{speed_mb} MB/s`\n⏳ **ETA:** `{eta_str}`"
    try: await msg.edit_text(text)
    except: pass

@bot .on_message(filters.command("admin"))
async def admin_cmd(client, message):
if message.chat.id == ADMIN_ID:
global admin_state
admin_state = None
await message.reply("👑 Welcome to Admin Panel\nChoose an option below:", reply_markup=admin_keyboard())

@bot .on_message(filters.command("start"))
async def start_cmd(client, message):
user_id = message.chat.id
init_user(user_id, message)
if user_data[user_id]['authenticated']:
user_data[user_id]['step'] = 'PLATFORM'
await message.reply("**✅ Logged in!**\n👇 Select platform:", reply_markup=platform_keyboard())
else:
user_data[user_id]['step'] = 'PASSWORD'
await message.reply("👋 **Welcome!**\n🔐 Enter Password to continue:")

@bot .on_message(filters.command("new"))
async def new_cmd(client, message):
user_id = message.chat.id
init_user(user_id, message)

if not user_data[user_id]['authenticated']:
    user_data[user_id]['step'] = 'PASSWORD'
    return await message.reply("🔐 **Session Expired! Please enter the new password:**")
    
user_data[user_id]['step'] = 'PLATFORM'
# नई स्ट्रीम शुरू करते समय पुराना डेटा मिटा दें
user_data[user_id]['custom_image_path'] = None 
user_data[user_id]['custom_audio_path'] = None
user_data[user_id]['watermark_text'] = None
user_data[user_id]['video_url'] = None
user_data[user_id]['file_path'] = None
await message.reply("🔄 **New Stream Setup!**\n👇 **Select platform:**", reply_markup=platform_keyboard())

@bot .on_message(filters.command("stop"))
async def stop_cmd(client, message):
user_id = message.chat.id
if user_id not in user_data: return

active_procs = []
for p in user_data[user_id].get('processes',[]):
    proc_obj = p['process'] if isinstance(p, dict) else p
    if proc_obj.poll() is None:
        active_procs.append(proc_obj)

if not active_procs:
    return await message.reply("⚠️ **No active stream found.**")
    
user_data[user_id]['manual_stop'] = True 
for p in active_procs:
    p.terminate()
    
user_data[user_id]['processes'] =[]
await message.reply(f"🛑 **Stopping {len(active_procs)} Active Stream(s)... Generating Report!**")

@bot .on_callback_query(filters.regex("^admin_"))
async def admin_callbacks(client, callback_query: CallbackQuery):
if callback_query.message.chat.id != ADMIN_ID:
return
data = callback_query.data
global admin_state
global recent_admin_messages

if data == "admin_users":
    total_users = len(user_data)
    text = f"👥 **Total Users:** `{total_users}`\n\n"
    for uid, info in user_data.items():
        if uid == ADMIN_ID: continue
        text += f"👤 **Name:** {info['name']}\n🆔 **ID:** `{uid}`\n📅 **Joined:** {format_date(info['join_date'])}\n🕒 **Last Online:** {format_date(info['last_online'])}\n🎬 **Streams Done:** {info['total_streams']}\n⏳ **Last Stream:** {format_date(info['last_stream'])}\n"
        text += "➖➖➖➖➖➖➖➖\n"
    if len(text) > 4000:
        for x in range(0, len(text), 4000):
            await client.send_message(ADMIN_ID, text[x:x+4000])
    else:
        await callback_query.message.reply(text)

elif data == "admin_live":
    live_text = ""
    total_live = 0
    for uid, info in user_data.items():
        active_streams =[]
        for p in info.get('processes', []):
            if isinstance(p, dict) and p['process'].poll() is None:
                active_streams.append(p)
            elif not isinstance(p, dict) and p.poll() is None: # Old format safety
                active_streams.append({'process': p, 'start_time': get_ist_time(), 'end_time': "Unknown", 'loop_hours': 0, 'is_image': False})

        if active_streams:
            total_live += len(active_streams)
            live_text += f"👤 **Name:** {info['name']} (ID: `{uid}`)\n🌐 **Platform:** `{info.get('platform', 'Unknown')}`\n"
            
            for i, strm in enumerate(active_streams, 1):
                mode_str = f"{strm['loop_hours']}h Loop" if strm['loop_hours'] > 0 else "1 Time"
                if strm.get('is_image'): mode_str += " (🖼 Image)"
                else: mode_str += " (🎥 Video)"
                
                st_str = strm['start_time'].strftime("%I:%M %p")
                et_str = strm['end_time'].strftime("%I:%M %p") if isinstance(strm['end_time'], datetime) else str(strm['end_time'])
                
                live_text += f"  ➤ **Stream {i}:** Mode: `{mode_str}`\n"
                live_text += f"      🟢 Start: `{st_str}` | 🔴 End: `{et_str}`\n"
            live_text += "➖➖➖➖➖➖\n"
    
    if not live_text:
        await callback_query.message.reply("❌ **No one is streaming right now.**")
    else:
        await callback_query.message.reply(f"🔴 **Live Streams ({total_live} total):**\n\n" + live_text)

elif data == "admin_pass":
    markup = InlineKeyboardMarkup([
        [InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass_change")],[InlineKeyboardButton("📊 Pass Analysis", callback_data="admin_pass_analysis")]
    ])
    await callback_query.message.edit_text("🔐 **Password Management**\nChoose an option below:", reply_markup=markup)

elif data == "admin_pass_change":
    admin_state = "AWAIT_NEW_PASSWORD"
    await callback_query.message.reply("🔑 **Please send the NEW PASSWORD and LIMIT.**\n`उदाहरण: 5550 5` (इसका मतलब सिर्फ 5 लोग पासवर्ड यूज़ कर पाएंगे)")

elif data == "admin_pass_analysis":
    limit_text = str(PASSWORD_LIMIT) if PASSWORD_LIMIT != 999999 else "Unlimited"
    if not PASSWORD_USED_BY:
        await callback_query.message.reply(f"📊 **Pass Analysis:**\n🔑 **Current Password:** `{GLOBAL_PASSWORD}`\n👥 **Limit:** `0 / {limit_text}`\n\nअभी तक किसी ने भी मौजूदा पासवर्ड का इस्तेमाल नहीं किया है।")
    else:
        text = f"📊 **Pass Analysis:**\n🔑 **Current Password:** `{GLOBAL_PASSWORD}`\n👥 **Limit:** `{len(PASSWORD_USED_BY)} / {limit_text}`\n\n"
        for uid, uname in PASSWORD_USED_BY.items():
            text += f"👤 **Name:** {uname}\n🆔 **ID:** `{uid}`\n➖➖➖➖➖➖\n"
        await callback_query.message.reply(text)

elif data == "admin_limit":
    admin_state = "AWAIT_LIMIT"
    await callback_query.message.reply("⚙️ **Set Stream Limit Mode:**\n\n👉 **सबका लिमिट बदलने के लिए:** सिर्फ नंबर भेजें (e.g. `2`)\n👉 **किसी एक यूज़र का लिमिट बदलने के लिए:** ID और नंबर भेजें\n`उदाहरण: 384884848 2`")

elif data == "admin_msg":
    admin_state = "AWAIT_BROADCAST"
    await callback_query.message.reply("📢 **Broadcast / Personal Message Mode!**\n\n"
                                       "👉 **सबको भेजने के लिए:** कोई भी Text, Photo या Video सीधा भेजें।\n"
                                       "👉 **किसी एक को भेजने के लिए:** ID स्पेस मेसेज लिखें।\n"
                                       "`उदाहरण: 8383838487 Hello kese ho`")

elif data == "admin_sch_times":
    markup = []
    for st in GLOBAL_SCHEDULE_TIMES:
        markup.append([InlineKeyboardButton(f"❌ Delete {st}", callback_data=f"admindelscht_{st}")])
    markup.append([InlineKeyboardButton("➕ Add New Time", callback_data="admin_sch_add")])
    markup.append([InlineKeyboardButton("🔙 Back", callback_data="admin_back")])
    await callback_query.message.edit_text("📅 **Manage Pre-defined Schedule Times:**\nये समय यूज़र्स को बटन के रूप में दिखेंगे।", reply_markup=InlineKeyboardMarkup(markup))

elif data == "admin_back":
    await callback_query.message.edit_text("👑 **Welcome to Admin Panel**\nChoose an option below:", reply_markup=admin_keyboard())

elif data == "admin_sch_add":
    admin_state = "AWAIT_SCH_TIME"
    await callback_query.message.reply("📅 **कृपया नया शेड्यूल्ड समय भेजें:**\n\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`")

elif data.startswith("admindelscht_"):
    time_to_del = data.replace("admindelscht_", "")
    if time_to_del in GLOBAL_SCHEDULE_TIMES:
        GLOBAL_SCHEDULE_TIMES.remove(time_to_del)
        save_data()
    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"))

elif data == "admin_del":
    if not recent_admin_messages:
        return await callback_query.message.reply("❌ **No recent messages/broadcasts found to delete.**")
        
    deleted_ops = 0
    total_msgs = 0
    
    for _ in range(5):
        if not recent_admin_messages:
            break
        targets = recent_admin_messages.pop()
        deleted_ops += 1
        for uid, mid in targets:
            try:
                await client.delete_messages(uid, mid)
                total_msgs += 1
            except: pass
            
    await callback_query.message.reply(f"✅ **Deleted last {deleted_ops} broadcast(s)/message(s) from {total_msgs} users' chats!**")

@bot .on_callback_query(filters.regex("^plat_"))
async def select_platform(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)

if not user_data[user_id].get('authenticated'):
    return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")

active_procs = [p for p in user_data[user_id].get('processes', []) if (p['process'].poll() if isinstance(p, dict) else p.poll()) is None]
user_data[user_id]['processes'] = active_procs 
limit = user_data[user_id].get('stream_limit', GLOBAL_LIMIT)

if len(active_procs) >= limit:
    return await callback_query.message.edit_text(f"⚠️ **Limit Reached!**\nआप एक साथ सिर्फ {limit} स्ट्रीम चला सकते हैं। कृपया पहली स्ट्रीम खत्म होने का इंतज़ार करें।")

data = callback_query.data
if data == "plat_youtube":
    user_data[user_id]['platform'] = 'YouTube'
    text = "🔴 **YouTube Selected!**\n🔑 **Send YouTube Stream Key:**"
elif data == "plat_rumble":
    user_data[user_id]['platform'] = 'Rumble'
    text = "🟢 **Rumble Selected!**\n🔑 **Send Rumble Stream Key (STATIC KEY):**"
elif data == "plat_custom":
    user_data[user_id]['platform'] = 'Custom'
    text = "🔵 **Custom RTMP Selected!**\n🔗 **Send FULL RTMP URL + Key:**"
    
user_data[user_id]['step'] = 'STREAM_KEY' if data != "plat_custom" else 'CUSTOM_URL'
await callback_query.message.edit_text(text)

@bot .on_callback_query(filters.regex("^loop_"))
async def select_loop(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)

if not user_data[user_id].get('authenticated'):
    return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")

# Changed int() to float() to support 0.5 hours (30 Mins)
hours = float(callback_query.data.split("_")[1])
user_data[user_id]['loop_hours'] = hours
user_data[user_id]['step'] = 'ORIENTATION'

await callback_query.message.edit_text("📐 **Select Stream Orientation:**\nवीडियो कैसे चलाना है?", reply_markup=orientation_keyboard())

@bot .on_callback_query(filters.regex("^ori_"))
async def select_orientation(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)

if not user_data[user_id].get('authenticated'):
    return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")

data = callback_query.data
if data == "ori_landscape":
    user_data[user_id]['orientation'] = 'landscape'
    user_data[user_id]['step'] = 'VIDEO_OR_URL'
    text = "🖥 **Landscape (16:9) Selected!**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**"
    await callback_query.message.edit_text(text)
else:
    user_data[user_id]['orientation'] = 'portrait'
    user_data[user_id]['step'] = 'WAIT_IMAGE_CHOICE'
    text = "📱 **Portrait/Shorts (9:16) Selected!**\n\n🖼 **क्या आप वीडियो के ऊपर/नीचे खाली जगह पर कोई Custom Photo (Background) लगाना चाहते हैं?**"
    await callback_query.message.edit_text(text, reply_markup=image_choice_keyboard())

@bot .on_callback_query(filters.regex("^img_"))
async def select_image_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)

if not user_data[user_id].get('authenticated'):
    return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")

data = callback_query.data
if data == "img_yes":
    user_data[user_id]['step'] = 'AWAIT_IMAGE'
    text = "🖼 **Send your Custom Background Image (Photo)!**\n(यह फोटो आपके वीडियो के पीछे (Background) में दिखेगी)"
else:
    user_data[user_id]['step'] = 'VIDEO_OR_URL'
    text = "⬛️ **No Image Selected (Black Background).**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**"

await callback_query.message.edit_text(text)

@bot .on_callback_query(filters.regex("^aud_"))
async def select_audio_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
data = callback_query.data
if data == "aud_yes":
user_data[user_id]['step'] = 'AWAIT_AUDIO'
await callback_query.message.edit_text("🎵 अपना Audio (MP3) या Video File भेजें जिससे आवाज़ निकालनी है!")
else:
user_data[user_id]['step'] = 'SCHEDULE'
await callback_query.message.edit_text("⏳ स्ट्रीम अभी चालू करनी है या Schedule (शेड्यूल) करनी है?", reply_markup=schedule_choice_keyboard())

@bot .on_callback_query(filters.regex("^wm_"))
async def select_watermark_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
if callback_query.data == "wm_yes":
user_data[user_id]['step'] = 'AWAIT_WATERMARK_TEXT'
await callback_query.message.edit_text("✍️ **कृपया Watermark का Text भेजें:**\n(जैसे: Class PDF Telegram Pr Milengi)")
else:
user_data[user_id]['step'] = 'AUDIO_CHOICE'
await callback_query.message.edit_text("🎵 YouTube के लिए ऑडियो जरूरी है।\nक्या आप अपना खुद का Audio (MP3) लगाना चाहते हैं?", reply_markup=audio_choice_keyboard())

@bot .on_callback_query(filters.regex("^sch_"))
async def select_schedule_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
if callback_query.data == "sch_now":
user_data[user_id]['step'] = 'READY'
await callback_query.message.delete()
await process_stream_start(client, callback_query.message)
else:
user_data[user_id]['step'] = 'AWAIT_DATETIME'
markup = []
for st in GLOBAL_SCHEDULE_TIMES:
# Inline button format for specific times
markup.append([InlineKeyboardButton(f"🗓 {st}", callback_data=f"set_schtime_{st}")])

    # बटन के बिना मैन्युअल एंट्री का निर्देश
    text = "📅 **Schedule Time चुनें या लिखें!**\n\n"
    if GLOBAL_SCHEDULE_TIMES:
        text += "👇 **एडमिन द्वारा दिए गए समय में से चुनें:**\n"
    text += "\n✍️ **या अपना खुद का समय लिखकर भेजें:**\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`"
    
    await callback_query.message.edit_text(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)

@bot .on_callback_query(filters.regex("^set_schtime_"))
async def select_predefined_time(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
time_str = callback_query.data.replace("set_schtime_", "")

# Create fake message to pass to AWAIT_DATETIME logic
class FakeMessage:
    def __init__(self, text, chat_id):
        self.text = text
        self.chat = type('Chat', (), {'id': chat_id})()
        self.id = callback_query.message.id

await callback_query.message.edit_text(f"⏳ Processing schedule for {time_str}...")
await handle_all_messages(client, FakeMessage(time_str, user_id))

@bot .on_message(filters.command("edit"))
async def edit_cmd(client, message):
user_id = message.chat.id
init_user(user_id, message)

markup = []
text = "🛠 **Manage Your Streams:**\nयहाँ से आप अपनी ग़लत बनी हुई या चल रही स्ट्रीम्स को Delete/Stop कर सकते हैं।\n\n"

# Running streams
active_procs = [p for p in user_data[user_id].get('processes', []) if p['process'].poll() is None]
if active_procs:
    text += "🔴 **Running Streams:**\n"
    for idx, p in enumerate(active_procs, 1):
        p_id = p.get('id', 'unknown')
        plat = p.get('platform', 'Unknown')
        text += f"{idx}. 🟢 {plat} - Started: {p['start_time'].strftime('%I:%M %p')}\n"
        markup.append([InlineKeyboardButton(f"🛑 Stop Running: {plat} Stream {idx}", callback_data=f"user_stop_{p_id}")])

# Scheduled streams
scheduled = user_data[user_id].get('scheduled_tasks', {})
if scheduled:
    text += "\n⏳ **Scheduled Streams:**\n"
    idx = 1
    for s_id, s_info in list(scheduled.items()):
        time_str = s_info['time_str']
        plat = s_info['config'].get('platform', 'Unknown')
        text += f"{idx}. 🗓 {plat} - Scheduled for: {time_str}\n"
        markup.append([InlineKeyboardButton(f"❌ Cancel Scheduled: {plat} {time_str}", callback_data=f"user_cancelsch_{s_id}")])
        idx += 1

if not active_procs and not scheduled:
    text = "⚠️ **आपकी कोई भी स्ट्रीम अभी चल नहीं रही है और ना ही शेड्यूल है।**"

await message.reply(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)

@bot .on_message(~filters.command(["start", "stop", "new", "admin", "edit"]))
async def handle_all_messages(client, message):
user_id = message.chat.id
init_user(user_id, message)
step = user_data[user_id].get('step')

# === Admin Reply to Delete ===
if message.text == "/delete" and message.reply_to_message and user_id == ADMIN_ID:
    summary_id = message.reply_to_message.id
    if summary_id in sent_message_map:
        targets = sent_message_map[summary_id]
        deleted_count = 0
        for uid, mid in targets:
            try:
                await client.delete_messages(uid, mid)
                deleted_count += 1
            except: pass
        await message.reply(f"✅ **Message successfully deleted from {deleted_count} user(s) chat!**")
        del sent_message_map[summary_id] 
    else:
        await message.reply("❌ **Could not find message data. Already deleted or too old.**")
    return

# === Admin State Handling ===
if user_id == ADMIN_ID:
    global admin_state, GLOBAL_PASSWORD, GLOBAL_LIMIT, recent_admin_messages, PASSWORD_LIMIT, PASSWORD_USED_BY
    
    if admin_state == "AWAIT_NEW_PASSWORD" and message.text:
        parts = message.text.strip().split()
        GLOBAL_PASSWORD = parts[0]
        # अगर एडमिन ने स्पेस देकर नंबर लिखा है, तो उसे लिमिट सेट करें, नहीं तो अनलिमिटेड
        PASSWORD_LIMIT = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 999999
        PASSWORD_USED_BY = {} # नया पासवर्ड आते ही पुरानी लिस्ट खाली कर दें
        admin_state = None
        
        limit_text = str(PASSWORD_LIMIT) if PASSWORD_LIMIT != 999999 else "Unlimited"
        await message.reply(f"✅ **Password Changed to:** `{GLOBAL_PASSWORD}`\n👥 **Max Users Allowed:** `{limit_text}`\n\nLogging everyone out...")
        for uid in user_data:
            if uid != ADMIN_ID:
                user_data[uid]['authenticated'] = False
                user_data[uid]['step'] = 'PASSWORD'
                try:
                    await client.send_message(uid, "⚠️ **लॉगआउट अलर्ट! (Logout Alert)**\n\nएडमिन ने पासवर्ड बदल दिया है। कृपया `/start` दबाकर नया पासवर्ड डालें।")
                except: pass
        save_data() # डेटाबेस अपडेट करें
        return

    elif admin_state == "AWAIT_LIMIT" and message.text:
        text = message.text.strip()
        parts = text.split()
        if len(parts) == 1 and parts[0].isdigit():
            GLOBAL_LIMIT = int(parts[0])
            for uid in user_data:
                user_data[uid]['stream_limit'] = GLOBAL_LIMIT
            await message.reply(f"✅ **Global Stream Limit set to {GLOBAL_LIMIT} for everyone!**")
        elif len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
            target_id = int(parts[0])
            limit = int(parts[1])
            if target_id in user_data:
                user_data[target_id]['stream_limit'] = limit
                await message.reply(f"✅ **User `{target_id}` can now run {limit} streams at once!**")
                try: await client.send_message(target_id, f"🎉 **Admin Alert:** Your concurrent stream limit has been increased to **{limit}**!")
                except: pass
            else:
                await message.reply("❌ **User not found in bot database!**")
        else:
            await message.reply("❌ **Invalid format!** Send something like `2` or `123456789 2`")
        admin_state = None
        save_data() # डेटाबेस अपडेट करें
        return

    elif admin_state == "AWAIT_BROADCAST":
        text_to_check = message.text or message.caption
        is_personal = False
        target_id = None
        content = None
        
        if text_to_check:
            parts = text_to_check.split(" ", 1)
            if len(parts) > 1 and parts[0].isdigit() and len(parts[0]) > 5:
                is_personal = True
                target_id = int(parts[0])
                content = parts[1]
                
        targets =[]
        
        if is_personal:
            try:
                if message.text:
                    msg = await client.send_message(target_id, content)
                else:
                    msg = await message.copy(target_id, caption=content)
                targets.append((target_id, msg.id))
                summary = await message.reply(f"✅ **Message sent privately to `{target_id}`!**\n_Reply to this message with `/delete` to remove it from their chat._")
            except Exception as e:
                await message.reply(f"❌ **Failed to send.** Target might have blocked the bot.")
                admin_state = None
                return
        else:
            success = 0
            for uid in user_data:
                if uid != ADMIN_ID:
                    try:
                        msg = await message.copy(uid)
                        targets.append((uid, msg.id))
                        success += 1
                    except: pass
            summary = await message.reply(f"✅ **Broadcast Sent successfully to {success} users!**\n_Reply to this message with `/delete` to remove it from all users._")
        
        if targets:
            sent_message_map[summary.id] = targets
            recent_admin_messages.append(targets)
            if len(recent_admin_messages) > 50:
                recent_admin_messages.pop(0) 
        admin_state = None
        return

    elif admin_state == "AWAIT_SCH_TIME" and message.text:
        try:
            # Validation
            dt_obj = datetime.strptime(message.text.strip(), "%d/%m/%Y %I:%M %p")
            GLOBAL_SCHEDULE_TIMES.append(message.text.strip())
            save_data()
            await message.reply(f"✅ **Time Added Successfully!**\n`{message.text.strip()}`\n\nअब यह समय सभी यूज़र्स को बटन में दिखाई देगा।")
            admin_state = None
        except Exception as e:
            await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
        return

# === Normal User Handling ===
if not user_data[user_id]['authenticated'] and step != 'PASSWORD':
    user_data[user_id]['step'] = 'PASSWORD'
    return await message.reply("🔐 **Session Expired! Please enter password:**")

# यहाँ हमने नए स्टेप्स (VIDEO_OR_URL और AWAIT_AUDIO) को Add कर दिया है
if not message.text and step not in['VIDEO_OR_URL', 'AWAIT_IMAGE', 'AWAIT_AUDIO']:
    return

if step == 'PASSWORD':
    if message.text == GLOBAL_PASSWORD:
        # चेक करें कि यूज़र पहले से लिस्ट में है या नहीं
        if user_id not in PASSWORD_USED_BY:
            if len(PASSWORD_USED_BY) >= PASSWORD_LIMIT:
                return await message.reply("🚫 **Password Limit Reached!**\nयह पासवर्ड अपनी लिमिट पूरी कर चुका है। कृपया एडमिन से नया पासवर्ड लें।")
            # अगर लिमिट बची है, तो यूज़र को एनालिसिस लिस्ट में जोड़ दें
            PASSWORD_USED_BY[user_id] = user_data[user_id]['name']
            
        user_data[user_id]['authenticated'] = True
        user_data[user_id]['step'] = 'PLATFORM'
        save_data() # यूज़र लॉगिन सेव करें ताकि रीस्टार्ट होने पर लॉगिन न हटे
        await message.reply("**✅ Password Correct!**\n👇 **Select platform:**", reply_markup=platform_keyboard())
    else:
        await message.reply("❌ **Wrong Password! Try again.**")

elif step == 'STREAM_KEY':
    key = message.text
    platform = user_data[user_id]['platform']
    if platform == 'YouTube':
        user_data[user_id]['rtmp_url'] = f"rtmp://a.rtmp.youtube.com/live2/{key}"
    elif platform == 'Rumble':
        user_data[user_id]['rtmp_url'] = f"rtmp://rtmp.rumble.com/live/{key}"
    user_data[user_id]['step'] = 'LOOP_CHOICE'
    await message.reply(f"✅ **Key Saved!**\n\n⏱ **How long do you want to stream?**", reply_markup=loop_keyboard())

elif step == 'CUSTOM_URL':
    user_data[user_id]['rtmp_url'] = message.text
    user_data[user_id]['step'] = 'LOOP_CHOICE'
    await message.reply("✅ **Custom URL Saved!**\n\n⏱ **How long do you want to stream?**", reply_markup=loop_keyboard())

elif step == 'AWAIT_IMAGE':
    if message.photo or message.document:
        msg = await message.reply("⏳ **Saving Image...**")
        image_path = await message.download()
        user_data[user_id]['custom_image_path'] = image_path
        user_data[user_id]['step'] = 'VIDEO_OR_URL'
        await msg.edit_text("✅ **Background Image Saved!**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**")
    else:
        await message.reply("⚠️ **Please send a valid Photo!**")

elif step == 'VIDEO_OR_URL':
    user_data[user_id]['is_image_only'] = False
    if message.video or message.document:
        msg = await message.reply("⏳ **Downloading Video...**")
        start_time = time.time()
        last_update = [start_time]
        file_path = await message.download(progress=progress_bar, progress_args=(msg, start_time, last_update))
        user_data[user_id]['file_path'] = file_path
        await msg.delete()
    elif message.photo:
        msg = await message.reply("⏳ **Downloading Image...**")
        file_path = await message.download()
        user_data[user_id]['file_path'] = file_path
        user_data[user_id]['is_image_only'] = True
        await msg.delete()
    elif message.text and "t.me/c/" in message.text:
        # Telegram Private Channel Link Logic
        if not USER_SESSION:
            return await message.reply("❌ **String Session सेट नहीं है! बॉट के कोड में USER_SESSION डालें।**")
        
        msg = await message.reply("🔍 **प्राइवेट चैनल में वीडियो ढूँढा जा रहा है... (आस-पास के मैसेज चेक कर रहा हूँ)**")
        try:
            link_parts = message.text.strip().split('/')
            chat_id = int("-100" + link_parts[4])
            msg_id = int(link_parts[5])
            
            if not user_app.is_connected:
                await user_app.start()
            
            target_msg = None
            # आगे-पीछे के 20 मैसेज चेक करेगा कि Video किसमें है
            messages = await user_app.get_messages(chat_id, range(max(1, msg_id - 10), msg_id + 11))
            for m in messages:
                if m and not m.empty and (m.video or m.document):
                    target_msg = m
                    break
                    
            if not target_msg:
                return await msg.edit_text("❌ **इस लिंक के आस-पास कोई Video नहीं मिला!**")
                
            await msg.edit_text("⏳ **Private Video Downloading...**")
            start_time = time.time()
            last_update = [start_time]
            file_path = await user_app.download_media(target_msg, progress=progress_bar, progress_args=(msg, start_time, last_update))
            
            user_data[user_id]['file_path'] = file_path
            await msg.delete()
        except Exception as e:
            return await msg.edit_text(f"❌ **Private Video Error:** {str(e)}")
            
    elif message.text and ("http://" in message.text or "https://" in message.text):
        user_data[user_id]['video_url'] = message.text.strip()
    else:
        return await message.reply("⚠️ **कृपया सही Video, Photo या Link भेजें!**")
        
    user_data[user_id]['step'] = 'WATERMARK_CHOICE'
    if user_data[user_id]['is_image_only']:
        await message.reply("✨ **क्या आप फोटो पर चलता हुआ (Moving) नाम/वाटरमार्क लगाना चाहते हैं?**\n(यह YouTube को स्पैम से बचाएगा)", reply_markup=watermark_choice_keyboard())
    else:
        await message.reply("✨ **क्या आप वीडियो पर Watermark (जैसे 'Class PDF Telegram...') लगाना चाहते हैं?**\n(यह हर 1 मिनट में दिखाई देगा)", reply_markup=watermark_choice_keyboard())

elif step == 'AWAIT_WATERMARK_TEXT':
    user_data[user_id]['watermark_text'] = message.text.strip()
    user_data[user_id]['step'] = 'AUDIO_CHOICE'
    await message.reply(f"✅ **वाटरमार्क '{message.text.strip()}' सेव हो गया!**\n\n🎵 **YouTube के लिए ऑडियो जरूरी है।**\nक्या आप अपना खुद का Audio (MP3) फाइल लगाना चाहते हैं?", reply_markup=audio_choice_keyboard())

elif step == 'AWAIT_AUDIO':
    if message.audio or message.video or message.document:
        msg = await message.reply("⏳ **Downloading Audio...**")
        audio_path = await message.download()
        user_data[user_id]['custom_audio_path'] = audio_path
        user_data[user_id]['step'] = 'SCHEDULE'
        await msg.edit_text("✅ **Audio Saved!**\n\n⏳ **स्ट्रीम अभी चालू करनी है या Schedule (शेड्यूल) करनी है?**", reply_markup=schedule_choice_keyboard())
    else:
        await message.reply("⚠️ **कृपया सही Audio या Video File भेजें!**")

elif step == 'AWAIT_DATETIME':
    try:
        time_str = message.text.strip()
        dt_obj = datetime.strptime(time_str, "%d/%m/%Y %I:%M %p")
        now_ist = get_ist_time().replace(tzinfo=None)
        if dt_obj <= now_ist:
            # If pre-defined time is passed, allow it logically but warn if past
            try: 
                await client.send_message(user_id, "⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
            except:
                await message.reply("⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
            return
        
        user_data[user_id]['step'] = 'READY'
        delay = (dt_obj - now_ist).total_seconds()
        
        # --- Snapshot Configuration ---
        # (अगर यूज़र दूसरी स्ट्रीम बनायेगा तो पहली स्ट्रीम का डेटा डिलीट न हो)
        stream_config = {
            'file_path': user_data[user_id].get('file_path'),
            'video_url': user_data[user_id].get('video_url'),
            'custom_audio_path': user_data[user_id].get('custom_audio_path'),
            'platform': user_data[user_id].get('platform', 'Custom'),
            'loop_hours': user_data[user_id].get('loop_hours', 0),
            'rtmp_url': user_data[user_id].get('rtmp_url'),
            'orientation': user_data[user_id].get('orientation', 'landscape'),
            'custom_image_path': user_data[user_id].get('custom_image_path'),
            'is_image_only': user_data[user_id].get('is_image_only', False),
            'watermark_text': user_data[user_id].get('watermark_text')
        }
        
        s_id = str(uuid.uuid4())[:8]
        task = asyncio.create_task(scheduled_stream_starter(client, message, delay, stream_config, s_id))
        
        user_data[user_id]['scheduled_tasks'][s_id] = {
            'task': task,
            'time_str': time_str,
            'config': stream_config
        }
        
        try:
            await client.send_message(user_id, f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
        except:
            await message.reply(f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
        
    except Exception as e:
        try:
            await client.send_message(user_id, "❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
        except:
            await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")

@bot .on_callback_query(filters.regex("^user_cancelsch_"))
async def cancel_sch_callback(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
s_id = callback_query.data.replace("user_cancelsch_", "")
if s_id in user_data[user_id].get('scheduled_tasks', {}):
user_data[user_id]['scheduled_tasks'][s_id]['task'].cancel()
del user_data[user_id]['scheduled_tasks'][s_id]
await callback_query.message.edit_text("✅ **Scheduled Stream Cancelled / Delete हो गई!**\nअब आप /new से नई बना सकते हैं।")
else:
await callback_query.message.edit_text("❌ यह शेड्यूल पहले ही कैंसिल हो चुका है या चल चुका है।")

@bot .on_callback_query(filters.regex("^user_stop_"))
async def stop_running_callback(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
p_id = callback_query.data.replace("user_stop_", "")

stopped = False
for p in user_data[user_id].get('processes', []):
    if p.get('id') == p_id and p['process'].poll() is None:
        user_data[user_id]['manual_stop'] = True
        p['process'].terminate()
        stopped = True
        break
        
if stopped:
    await callback_query.message.edit_text("✅ **Running Stream को सफलतापूर्वक बंद कर दिया गया है!**")
else:
    await callback_query.message.edit_text("❌ **यह स्ट्रीम पहले ही बंद हो चुकी है।**")

async def scheduled_stream_starter(client, message, delay, stream_config, s_id):
try:
await asyncio.sleep(delay)
user_id = message.chat.id
# Remove from pending queue as it starts
if s_id in user_data[user_id].get('scheduled_tasks', {}):
del user_data[user_id]['scheduled_tasks'][s_id]
await handle_video(client, message, stream_config)
except asyncio.CancelledError:
pass # Stream was cancelled by user

async def process_stream_start(client, message):
await handle_video(client, message)

def get_direct_url(url):
ydl_opts = {'format': 'best', 'quiet': True, 'noplaylist': True}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=False)
return info.get('url', url)
except:
return url

async def handle_video(client, message, stream_config=None):
user_id = message.chat.id

# अगर कॉन्फ़िग स्नैपशॉट (शेड्यूल से) आया है, तो उसका इस्तेमाल करें। नहीं तो करंट डेटा का।
config = stream_config if stream_config else user_data[user_id]

user_data[user_id]['manual_stop'] = False
try:
    msg = await client.send_message(user_id, "⏳ **Preparing Stream Environment...**")
except:
    msg = await message.reply("⏳ **Preparing Stream Environment...**")

file_path = config.get('file_path')
video_url = config.get('video_url')
custom_audio = config.get('custom_audio_path')

input_source = file_path
if video_url:
    await msg.edit_text("🔗 **Extracting direct stream link (YouTube/Facebook/Rumble etc.)...**")
    input_source = await asyncio.to_thread(get_direct_url, video_url)
    if not input_source:
        return await msg.edit_text("❌ **Failed to extract link!**")
        
platform = config.get('platform', 'Custom')
loop_hours = config.get('loop_hours', 0)
rtmp_url = config.get('rtmp_url')
orientation = config.get('orientation', 'landscape')
image_path = config.get('custom_image_path')

user_data[user_id]['total_streams'] += 1
user_data[user_id]['last_stream'] = get_ist_time()

import random  # Random time जनरेट करने के लिए

vid_duration = get_video_duration(file_path) if file_path else 3600

# -----------------------------------------------------
# ANTI-BOT RANDOMIZER: हर बार समय को थोड़ा आगे-पीछे करना
# -----------------------------------------------------
if loop_hours > 0:
    base_sec = loop_hours * 3600
    # 10% समय रैंडमली कम या ज्यादा करें (जैसे 3 घंटे में +/- 18 मिनट, 30 मिनट में +/- 3 मिनट)
    random_offset = random.randint(int(-0.1 * base_sec), int(0.1 * base_sec))
    actual_stream_seconds = base_sec + random_offset
    total_stream_time = actual_stream_seconds
else:
    total_stream_time = vid_duration
    actual_stream_seconds = vid_duration

is_image_only = config.get('is_image_only', False)

# --- FFMPEG Command Setup ---
command = ["ffmpeg", "-re"]

if is_image_only:
    # Image Only Streaming Logic (YouTube Standard 30 FPS)
    command.extend(["-loop", "1", "-framerate", "30", "-i", input_source])
    
    if custom_audio:
        command.extend(["-stream_loop", "-1", "-i", custom_audio])
        command.extend(["-map", "0:v", "-map", "1:a:0?"])
    else:
        # YouTube rejects streams without audio, so we add silent dummy audio
        command.extend(["-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100"])
        command.extend(["-map", "0:v", "-map", "1:a"])

    # -----------------------------------------------------
    # NEW: Aspect Ratio Fix for Image (Portrait vs Landscape)
    # -----------------------------------------------------
    if orientation == 'portrait':
        # Makes 16:9 photo into 9:16 (Adds black bars on top/bottom)
        vf_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black"
    else:
        # Ensures normal photos fit 16:9 screen
        vf_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black"
    
    # --- NEW: SLOW SMOOTH MOVING WATERMARK ---
    wm_text = config.get('watermark_text')
    if wm_text:
        safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
        # sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
        drawtext = f",drawtext=text='{safe_text}':fontcolor=white@0.4:fontsize=50:x=(w-tw)/2+((w-tw)/2.5)*sin(t/10):y=(h-th)/2+((h-th)/2.5)*cos(t/15)"
        vf_filter += drawtext

    command.extend(["-vf", vf_filter])
    # -----------------------------------------------------

    # Encode with strict YouTube standards (30 FPS, 2-sec Keyframe, CBR)
    command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
    # YouTube Keyframe Fix: 30fps के हिसाब से हर 2 सेकंड में कीफ्रेम (-g 60)
    command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
    # YouTube Bitrate Fix: Strict CBR (Constant Bitrate) with Dummy Padding
    command.extend(["-b:v", "4000k", "-maxrate", "4000k", "-minrate", "4000k", "-bufsize", "8000k", "-nal-hrd", "cbr"])
    command.extend(["-c:a", "aac", "-b:a", "128k"])
    
else:
    # Normal Video Logic
    if loop_hours > 0: command.extend(["-stream_loop", "-1"])
    command.extend(["-i", input_source])

    current_idx = 1; idx_aud = 0
    if custom_audio:
        command.extend(["-stream_loop", "-1", "-i", custom_audio])
        idx_aud = current_idx; current_idx += 1
        
    idx_img = -1
    if orientation == 'portrait' and image_path:
        command.extend(["-loop", "1", "-i", image_path])
        idx_img = current_idx; current_idx += 1

    if orientation == 'portrait':
        if image_path:
            vf_complex = f"[{idx_img}:v]scale=1080:1920[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[vid];[bg][vid]overlay=(W-w)/2:(H-h)/2:shortest=1[outv]"
            command.extend(["-filter_complex", vf_complex, "-map", "[outv]"])
        else:
            vf_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black"
            command.extend(["-vf", vf_filter, "-map", "0:v"])
        
        # YouTube Strict Bitrate & Keyframe (GOP) Fix for Portrait Video
        command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
        command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
        command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
    else:
        # YouTube Strict Bitrate & Keyframe (GOP) Fix for Landscape Video
        wm_text = config.get('watermark_text')
        
        if wm_text:
            safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
            # Custom Timing Logic: 1st min, 3rd min, then every 5 mins (each for 15 sec)
            timing_logic = "between(t\\,60\\,75)+between(t\\,180\\,195)+(gte(t\\,480)*lt(mod(t-480\\,300)\\,15))"
            
            vf_filter = f"drawtext=text=' Telegram\\: {safe_text} ':fontcolor=white:fontsize=38:box=1:boxcolor=#2AABEE@0.9:boxborderw=15:x=40:y=h-th-40:enable='{timing_logic}'"
            command.extend(["-vf", vf_filter, "-map", "0:v"])
        else:
            command.extend(["-map", "0:v"])
            
        command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
        command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
        command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])

    if custom_audio:
        command.extend(["-map", f"{idx_aud}:a:0?", "-c:a", "aac", "-b:a", "128k", "-shortest"])
    else:
        command.extend(["-map", "0:a?", "-c:a", "aac", "-b:a", "128k"])
        
# Apply time limit for BOTH Image and Video
if loop_hours > 0:
    seconds_str = str(int(actual_stream_seconds)) # असली रैंडम सेकंड्स
    if not is_image_only: command.extend(["-fflags", "+genpts"])
    command.extend(["-t", seconds_str])

command.extend(["-f", "flv", rtmp_url])

st_type = "🖼 Image" if is_image_only else "🎥 Video"
live_msg = await msg.edit_text(f"🟢 **Live Started on {platform}!**\n📋 **Type:** `{st_type}`\n📐 **Mode:** `{orientation.capitalize()}`\n⏳ **Time Left:** `Calculating...`\n\n🔹 To stop, send /stop")

process_obj = subprocess.Popen(command)

# Store Advanced Details for Admin Panel (Now saves actual randomized end time)
start_t = get_ist_time()
end_t = start_t + timedelta(seconds=actual_stream_seconds) if loop_hours > 0 else "Auto (Depends on Video)"

unique_p_id = str(uuid.uuid4())[:8]
user_data[user_id]['processes'].append({
    'id': unique_p_id,
    'process': process_obj,
    'start_time': start_t,
    'end_time': end_t,
    'loop_hours': loop_hours,
    'is_image': is_image_only,
    'platform': platform
})

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))

async def monitor_stream(client, chat_id, process, file_path, msg_id, loop_hours, platform, total_time, image_path, custom_audio):
start_time_ist = get_ist_time()
last_ui_update = time.time()
mode_text = f"🔁 {loop_hours} Hours Loop" if loop_hours > 0 else "▶️ 1-Time Play"

while process.poll() is None:
    await asyncio.sleep(5) 
    now_time = time.time()
    if total_time > 0 and (now_time - last_ui_update > 60):
        last_ui_update = now_time
        elapsed = (get_ist_time() - start_time_ist).total_seconds()
        rem_sec = total_time - elapsed
        
        if rem_sec > 0:
            h = int(rem_sec // 3600)
            m = int((rem_sec % 3600) // 60)
            time_left_str = f"{h}h {m}m" if h > 0 else f"{m}m"
            try:
                await client.edit_message_text(
                    chat_id, msg_id,
                    f"🟢 **Live is RUNNING on {platform}!**\n"
                    f"⏱ **Loop:** `{mode_text}`\n"
                    f"⏳ **Time Left:** `{time_left_str}`\n\n"
                    f"🔹 Send /stop to end manually."
                )
            except: pass

end_time_ist = get_ist_time()
duration = end_time_ist - start_time_ist

try: await client.delete_messages(chat_id, msg_id)
except: pass

# Server Storage साफ़ करना (Delete file to save memory)
try:
    if file_path and os.path.exists(file_path): os.remove(file_path)
    if image_path and os.path.exists(image_path): os.remove(image_path)
    if custom_audio and os.path.exists(custom_audio): os.remove(custom_audio)
except: pass

start_str = start_time_ist.strftime("%I:%M %p")
end_str = end_time_ist.strftime("%I:%M %p")
dur_str = str(duration).split('.')[0] 

is_manual = user_data.get(chat_id, {}).get('manual_stop', False)
status_text = "🛑 **Live Stream Stopped Manually!**" if is_manual else "🔴 **Live Stream Ended Automatically!**"

report_msg = (
    f"{status_text}\n\n"
    f"📊 **STREAM SUMMARY:**\n"
    f"🔸 **Platform:** `{platform}`\n"
    f"🟢 **Started at:** `{start_str}`\n"
    f"🔴 **Ended at:** `{end_str}`\n"
    f"⏱ **Total Runtime:** `{dur_str}`\n\n"
    f"🗑 __Video, Audio & Image deleted safely.__"
)

await client.send_message(chat_id, report_msg)

if name == "main":
load_data() # बॉट स्टार्ट होते ही पुरानी सेटिंग्स और यूज़र्स वापस लोड करें
Thread(target=run_flask).start()
bot.run()

Cannot merge
This branch has merge conflicts in the following files:
  • bot.py

Sign up or log in to comment