import gradio as gr import os, sqlite3, subprocess, threading, time, signal, uuid, shutil, psutil from datetime import datetime DB = "data.db" UPLOAD_DIR = "uploads" os.makedirs(UPLOAD_DIR, exist_ok=True) proc = None started_at = None current_job = None logs = [] def get_db(): c = sqlite3.connect(DB) c.execute("""CREATE TABLE IF NOT EXISTS videos( id TEXT PRIMARY KEY, name TEXT, path TEXT, size INTEGER, duration REAL, added TEXT)""") c.execute("""CREATE TABLE IF NOT EXISTS history( id INTEGER PRIMARY KEY AUTOINCREMENT, started TEXT, stopped TEXT, video TEXT, resolution TEXT, audio TEXT, mode TEXT, status TEXT)""") c.commit() return c def duration(path): try: r = subprocess.run( ["ffprobe","-v","error","-show_entries","format=duration", "-of","default=noprint_wrappers=1:nokey=1",path], capture_output=True, text=True, timeout=30) return float(r.stdout.strip()) except: return 0 def fmt_time(seconds): if not seconds: return "-" seconds = int(seconds) return f"{seconds//3600:02d}:{(seconds%3600)//60:02d}:{seconds%60:02d}" def add_log(msg): logs.append(str(msg)) if len(logs) > 150: del logs[:-150] def list_videos(): c = get_db() rows = c.execute( "SELECT name,size,duration,added FROM videos ORDER BY added DESC" ).fetchall() c.close() return [[n, f"{s/1024/1024:.1f} MB", fmt_time(d), a] for n,s,d,a in rows] def upload(files): if not files: return list_videos(), "❌ कोई वीडियो नहीं चुना।" c = get_db() count = 0 for src in files: if not src: continue uid = str(uuid.uuid4()) dest = os.path.join(UPLOAD_DIR, uid + "_" + os.path.basename(src)) shutil.copy2(src, dest) c.execute( "INSERT INTO videos VALUES(?,?,?,?,?,?)", (uid, os.path.basename(src), dest, os.path.getsize(dest), duration(dest), datetime.now().strftime("%Y-%m-%d %H:%M:%S")) ) count += 1 c.commit() c.close() return list_videos(), f"✅ {count} वीडियो library में जोड़ दिए।" def delete_video(name): if not name: return list_videos(), "❌ वीडियो का exact नाम डालो।" c = get_db() row = c.execute("SELECT id,path FROM videos WHERE name=?", (name,)).fetchone() if not row: c.close() return list_videos(), "❌ वीडियो नहीं मिला।" if proc and proc.poll() is None and current_job and current_job.get("video") == name: c.close() return list_videos(), "❌ जो वीडियो LIVE है उसे delete नहीं कर सकते।" try: os.remove(row[1]) except: pass c.execute("DELETE FROM videos WHERE id=?", (row[0],)) c.commit() c.close() return list_videos(), "🗑️ वीडियो delete हो गया।" def history_table(): c = get_db() rows = c.execute( """SELECT started,stopped,video,resolution,audio,mode,status FROM history ORDER BY id DESC LIMIT 100""" ).fetchall() c.close() return [list(r) for r in rows] def selected_path(name): c = get_db() r = c.execute("SELECT path FROM videos WHERE name=?", (name,)).fetchone() c.close() return r[0] if r else None def stop_process(): global proc, started_at, current_job if proc and proc.poll() is None: try: proc.send_signal(signal.SIGINT) proc.wait(timeout=8) except: try: proc.kill() except: pass proc = None started_at = None current_job = None def launch(video, resolution, audio, mode, stream_key, auto_restart=True): global proc, started_at, current_job path = selected_path(video) if not path: return "❌ वीडियो नहीं मिला।" if not stream_key or len(stream_key.strip()) < 5: return "❌ YouTube Stream Key डालो।" audio_bitrate = { "64k":"64k","96k":"96k","128k":"128k", "160k":"160k","192k":"192k","256k":"256k" }[audio] if resolution == "Original (low CPU)": video_args = ["-c:v","copy"] else: heights = { "144p":144,"240p":240,"360p":360, "480p":480,"720p":720,"1080p":1080 } bitrates = { "144p":"250k","240p":"400k","360p":"800k", "480p":"1200k","720p":"2500k","1080p":"4500k" } video_args = [ "-vf", f"scale=-2:{heights[resolution]}", "-c:v","libx264","-preset","veryfast", "-b:v", bitrates[resolution] ] if mode == "Loop current video": input_args = ["-re","-stream_loop","-1","-i",path] else: c = get_db() rows = c.execute("SELECT path FROM videos ORDER BY added ASC").fetchall() c.close() with open("playlist.txt","w",encoding="utf-8") as f: for (p,) in rows: f.write("file '" + os.path.abspath(p).replace("'","'\\''") + "'\n") input_args = ["-re","-stream_loop","-1","-f","concat","-safe","0","-i","playlist.txt"] rtmp = "rtmp://a.rtmp.youtube.com/live2/" + stream_key.strip() cmd = [ "ffmpeg","-hide_banner","-loglevel","warning" ] + input_args + [ "-map","0:v:0","-map","0:a:0?" ] + video_args + [ "-c:a","aac","-b:a",audio_bitrate, "-f","flv",rtmp ] add_log("▶️ FFmpeg starting...") try: proc = subprocess.Popen( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, bufsize=1 ) except FileNotFoundError: return "❌ FFmpeg नहीं मिला। packages.txt check करो।" started_at = time.time() current_job = { "video":video, "resolution":resolution, "audio":audio, "mode":mode, "auto_restart":auto_restart } def read_logs(): for line in iter(proc.stderr.readline, ""): if line: add_log(line.strip()) threading.Thread(target=read_logs, daemon=True).start() threading.Thread( target=watch_process, args=(video,resolution,audio,mode,stream_key,auto_restart), daemon=True ).start() return "🟢 LIVE / FFmpeg running" def watch_process(video,resolution,audio,mode,key,auto_restart): global proc p = proc while p and p.poll() is None: time.sleep(5) if p and p.poll() is not None and auto_restart: add_log("⚠️ FFmpeg stopped/crashed — auto restarting...") time.sleep(3) if current_job: launch(video,resolution,audio,mode,key,auto_restart=True) def start_live(video,resolution,audio,mode,key,auto_restart): global current_job if proc and proc.poll() is None: return "🟢 पहले से LIVE है।" if not video: return "❌ Library से वीडियो चुनो।" c = get_db() c.execute( """INSERT INTO history(started,video,resolution,audio,mode,status) VALUES(?,?,?,?,?,?)""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), video,resolution,audio,mode,"STARTED") ) c.commit() c.close() return launch(video,resolution,audio,mode,key,auto_restart) def stop_live(): if not proc or proc.poll() is not None: return "🔴 OFFLINE" stop_process() c = get_db() c.execute( """UPDATE history SET stopped=?,status='STOPPED' WHERE id=(SELECT MAX(id) FROM history)""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),) ) c.commit() c.close() return "⚪ LIVE stopped" def status_text(): if proc and proc.poll() is None: uptime = fmt_time(time.time() - started_at) if started_at else "-" return f"🟢 LIVE\nUptime: {uptime}\nFFmpeg PID: {proc.pid}" return "🔴 OFFLINE" def system_stats(): cpu = psutil.cpu_percent(interval=0.2) ram = psutil.virtual_memory().percent running = bool(proc and proc.poll() is None) return ( f"CPU: {cpu:.1f}% | RAM: {ram:.1f}% | " f"FFmpeg: {'Running' if running else 'Stopped'}", "\n".join(logs[-50:]) ) with gr.Blocks(title="YT 24×7 Live Manager") as ui: gr.Markdown( "# 🔴 YT 24×7 Live Manager\n" "वीडियो upload → Library → Settings → Start Live.\n\n" "**Browser बंद करने पर server-side FFmpeg चलता रहेगा। " "लेकिन free Hugging Face Space sleep/restart होने पर Live रुक सकता है।**" ) with gr.Tab("📁 Video Library"): files = gr.File( label="Multiple videos upload", file_count="multiple", type="filepath", file_types=["video"] ) upload_btn = gr.Button("⬆️ Upload") library = gr.Dataframe( headers=["Name","Size","Duration","Added"], interactive=False ) delete_name = gr.Textbox(label="Delete करने के लिए exact video name") delete_btn = gr.Button("🗑️ Delete") message = gr.Textbox(label="Message") upload_btn.click(upload, files, [library,message]) delete_btn.click(delete_video, delete_name, [library,message]) ui.load(list_videos, outputs=library) with gr.Tab("🔴 Live Control"): video_name = gr.Textbox(label="Video name (Library से exact name)") resolution = gr.Dropdown( ["Original (low CPU)","144p","240p","360p","480p","720p","1080p"], value="Original (low CPU)", label="Resolution" ) audio = gr.Dropdown( ["64k","96k","128k","160k","192k","256k"], value="128k", label="Audio bitrate" ) mode = gr.Radio( ["Loop current video","Playlist (all library videos)"], value="Loop current video", label="Mode" ) key = gr.Textbox(label="YouTube Stream Key", type="password") auto = gr.Checkbox( value=True, label="FFmpeg crash होने पर auto-restart" ) with gr.Row(): start = gr.Button("🔴 START LIVE", variant="primary") stop = gr.Button("⏹️ STOP") refresh_status = gr.Button("🔄 STATUS") state = gr.Textbox(label="Live Status") start.click( start_live, [video_name,resolution,audio,mode,key,auto], state ) stop.click(stop_live, outputs=state) refresh_status.click(status_text, outputs=state) with gr.Tab("📜 Streaming History"): history = gr.Dataframe( headers=["Started","Stopped","Video","Resolution", "Audio","Mode","Status"], interactive=False ) refresh_history = gr.Button("🔄 Refresh History") refresh_history.click(history_table, outputs=history) ui.load(history_table, outputs=history) with gr.Tab("📊 Server"): system = gr.Textbox(label="CPU / RAM / FFmpeg", interactive=False) log_box = gr.Textbox(label="FFmpeg Logs", lines=15, interactive=False) refresh_server = gr.Button("🔄 Refresh") refresh_server.click(system_stats, outputs=[system,log_box]) ui.load(system_stats, outputs=[system,log_box]) ui.queue().launch(server_name="0.0.0.0", server_port=7860)