import os, time, threading, subprocess, requests import streamlit as st # ---------- إعدادات ---------- COMMENTS_URL = "https://reoo88-signserver.hf.space/comments" COMMENTS_FILE = "/tmp/comments.txt" FONT_PATH = "/usr/share/fonts/opentype/fonts-hosny-amiri/Amiri-Regular.ttf" MAX_LINES = 6 MAX_LINE_LEN = 50 RECONNECT_MAX = 5 # تهيئة ملف التعليقات if not os.path.exists(COMMENTS_FILE): with open(COMMENTS_FILE, "w", encoding="utf-8") as f: f.write(" ") text_lock = threading.Lock() stop_event = threading.Event() # ---------- تحديث التعليقات ---------- def fetch_comments_once(): try: r = requests.get(COMMENTS_URL, timeout=5) if r.status_code != 200: return lines = [ln.strip() for ln in r.text.strip().split("\n") if ln.strip()][-MAX_LINES:] lines = [(ln[:MAX_LINE_LEN] + "...") if len(ln) > MAX_LINE_LEN else ln for ln in lines] new_txt = "\n".join(lines) if lines else " " tmp = COMMENTS_FILE + ".tmp" with text_lock: with open(tmp, "w", encoding="utf-8") as f: f.write(new_txt) os.replace(tmp, COMMENTS_FILE) print(f"✅ تم تحديث التعليقات ({len(lines)} سطر)") except Exception as e: print(f"❌ خطأ بالتعليقات: {e}") def comments_updater(): while not stop_event.is_set(): fetch_comments_once() for _ in range(30): if stop_event.is_set(): return time.sleep(0.1) # ---------- بناء أوامر ffmpeg/streamlink ---------- def build_ff_cmd(yt_key: str) -> list: drawtext = ( f"drawtext=" f"fontfile='{FONT_PATH}':" f"textfile='{COMMENTS_FILE}':reload=1:" f"text_shaping=1:" f"fontcolor=white:fontsize=28:line_spacing=10:" f"x=15:y=h-th-50:" f"box=1:boxcolor=black@0.55:boxborderw=12:" f"shadowcolor=black:shadowx=2:shadowy=2" ) return [ "ffmpeg", "-hide_banner", "-loglevel", "warning", "-thread_queue_size", "8192", "-re", "-i", "pipe:0", "-vf", f"scale=720:1280,{drawtext}", "-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency", "-profile:v", "main", "-pix_fmt", "yuv420p", "-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k", "-r", "30", "-g", "60", "-keyint_min", "60", "-sc_threshold", "0", "-c:a", "aac", "-ar", "44100", "-b:a", "128k", "-ac", "2", "-f", "flv", "-flvflags", "no_duration_filesize+aac_seq_header_detect", "-rtmp_live", "live", f"rtmp://a.rtmp.youtube.com/live2/{yt_key}", ] def build_sl_cmd(username: str) -> list: return [ "streamlink", f"https://www.tiktok.com/@{username}/live", "best", "--hls-live-edge", "3", "--retry-streams", "3", "--retry-max", "5", "--stdout", ] # ---------- بدء البث ---------- def start_stream(yt_key, username): stop_event.clear() threading.Thread(target=comments_updater, daemon=True).start() time.sleep(2) for attempt in range(1, RECONNECT_MAX + 1): if stop_event.is_set(): break print(f"\n========== محاولة بث #{attempt}/{RECONNECT_MAX} ==========") sl_process = ffmpeg_process = None try: sl_process = subprocess.Popen( build_sl_cmd(username), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ) ffmpeg_process = subprocess.Popen( build_ff_cmd(yt_key), stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, bufsize=10**7, ) def log_ffmpeg(): for line in iter(ffmpeg_process.stderr.readline, b""): txt = line.decode(errors="ignore").rstrip() if txt: print(f"[ffmpeg] {txt}") threading.Thread(target=log_ffmpeg, daemon=True).start() bytes_streamed, start_ts = 0, time.time() while not stop_event.is_set(): if ffmpeg_process.poll() is not None: break data = sl_process.stdout.read(8192) if not data: break try: ffmpeg_process.stdin.write(data) bytes_streamed += len(data) except BrokenPipeError: break duration = time.time() - start_ts print(f"[STATS] بُثّ {bytes_streamed/(1024*1024):.1f} MB خلال {duration:.0f}s") if duration < 5: print("❌ يوتيوب أغلق الاتصال فوراً — تحقّق من Stream Key.") break except Exception as e: print(f"[ERROR] {e}") finally: for p in (ffmpeg_process, sl_process): if not p: continue try: if p.stdin: try: p.stdin.close() except Exception: pass if p.poll() is None: p.terminate() try: p.wait(timeout=5) except subprocess.TimeoutExpired: p.kill() except Exception: pass if not stop_event.is_set() and attempt < RECONNECT_MAX: time.sleep(5) print("[INFO] انتهت جلسة البث.") # ---------- واجهة Streamlit ---------- st.set_page_config(page_title="بث تيك توك + تعليقات", page_icon="📹") st.title("📹 بث تيك توك مع تعليقات عربية مباشرة") with st.form("stream_form"): user = st.text_input("معرف تيك توك (بدون @)", "majd.alban") key = st.text_input("YouTube Stream Key", type="password") if st.form_submit_button("🚀 ابدأ البث", type="primary"): if user and key: threading.Thread(target=start_stream, args=(key, user), daemon=True).start() st.success("✅ بدأ البث!") else: st.error("الرجاء إدخال المعرف ومفتاح البث.")