Download app.py from Mroo9/Tst: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/spaces/Mroo9/Tst/resolve/main/app.py
- Command line
-
hf download hf://spaces/Mroo9/Tst/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Mroo9/Tst/resolve/main/app.py
13 kB
| import os | |
| import time | |
| import subprocess | |
| import threading | |
| import json | |
| import pickle | |
| import psutil | |
| from datetime import datetime, timezone | |
| from google.oauth2.credentials import Credentials | |
| from google.auth.transport.requests import Request | |
| from googleapiclient.discovery import build | |
| from flask import Flask | |
| # ------------------------------------------------------------ | |
| # الإعدادات الأساسية | |
| # ------------------------------------------------------------ | |
| TIKTOK_USERNAME = "@a3dam_111" | |
| CUSTOM_STREAM_KEY = os.getenv("CUSTOM_STREAM_KEY", "p3dq-5cex-bc1k-vet7-bsyx") | |
| FFMPEG_LOG = "/tmp/ffmpeg.log" | |
| MONITOR_LOG = "/tmp/monitor.log" | |
| LOCK_FILE = "/tmp/app_monitor_core.lock" | |
| TOKEN_FILE = "/tmp/yt_token.pkl" | |
| BROADCAST_FILE = "/tmp/current_broadcast.json" | |
| TIKTOK_COOKIES = os.getenv("TIKTOK_COOKIES", "") | |
| TIKTOK_COOKIES_ADULT = os.getenv("TIKTOK_COOKIES_ADULT", "") | |
| def log(msg): | |
| ts = time.strftime("%Y-%m-%d %H:%M:%S") | |
| line = f"[{ts}] {msg}" | |
| print(line, flush=True) | |
| with open(MONITOR_LOG, "a", encoding="utf-8") as f: | |
| f.write(line + "\n") | |
| # ------------------------------------------------------------ | |
| # حفظ الكوكيز | |
| # ------------------------------------------------------------ | |
| def save_cookies(): | |
| if TIKTOK_COOKIES: | |
| with open("/tmp/tiktok_cookies.txt", "w") as f: | |
| f.write(TIKTOK_COOKIES) | |
| if TIKTOK_COOKIES_ADULT: | |
| with open("/tmp/tiktok_cookies_adult.txt", "w") as f: | |
| f.write(TIKTOK_COOKIES_ADULT) | |
| save_cookies() | |
| # ------------------------------------------------------------ | |
| # أدوات مساعدة | |
| # ------------------------------------------------------------ | |
| def kill_process_tree(pid): | |
| try: | |
| parent = psutil.Process(pid) | |
| for child in parent.children(recursive=True): | |
| child.kill() | |
| parent.kill() | |
| except: | |
| pass | |
| def get_tiktok_stream_url(): | |
| try: | |
| url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live" | |
| cmd = ["streamlink", "--stream-url", url, "best", "--retry-streams", "5", "--retry-open", "3"] | |
| if os.path.exists("/tmp/tiktok_cookies_adult.txt"): | |
| cmd.extend(["--cookies", "/tmp/tiktok_cookies_adult.txt"]) | |
| elif os.path.exists("/tmp/tiktok_cookies.txt"): | |
| cmd.extend(["--cookies", "/tmp/tiktok_cookies.txt"]) | |
| result = subprocess.run(cmd, capture_output=True, text=True, timeout=25) | |
| if result.returncode == 0 and result.stdout.strip(): | |
| return result.stdout.strip().split("\n")[0] | |
| except: pass | |
| try: | |
| url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live" | |
| cmd = ["yt-dlp", "--no-warnings", "--no-check-certificates", "--no-live-from-start", "--no-cache-dir", "--rm-cache-dir", "-f", "best", "--get-url", url] | |
| if os.path.exists("/tmp/tiktok_cookies_adult.txt"): | |
| cmd.extend(["--cookies", "/tmp/tiktok_cookies_adult.txt"]) | |
| elif os.path.exists("/tmp/tiktok_cookies.txt"): | |
| cmd.extend(["--cookies", "/tmp/tiktok_cookies.txt"]) | |
| result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) | |
| if result.returncode == 0 and result.stdout.strip(): | |
| return result.stdout.strip().split("\n")[0] | |
| except: pass | |
| return None | |
| def start_stream(stream_key, stream_url): | |
| if not stream_key or not stream_url: return None | |
| log("🎬 بدء البث (تحويل الفيديو H.264 + فلاتر صوتية + تخطي الحظر)...") | |
| rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{stream_key}" | |
| try: subprocess.run(["killall", "-9", "ffmpeg", "yt-dlp", "streamlink"], stderr=subprocess.DEVNULL) | |
| except: pass | |
| cmd = [ | |
| "nice", "-n", "19", "ffmpeg", "-hide_banner", "-loglevel", "warning", | |
| "-thread_queue_size", "2048", | |
| "-user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", | |
| "-headers", "Referer: https://www.tiktok.com/\r\n", | |
| "-fflags", "+genpts+discardcorrupt+igndts", # إصلاح التزامن | |
| "-flags", "+global_header", | |
| "-reconnect", "1", | |
| "-reconnect_at_eof", "1", | |
| "-reconnect_delay_max", "60", | |
| "-i", stream_url, | |
| "-c:v", "libx264", "-preset", "ultrafast", "-b:v", "2500k", | |
| "-maxrate", "2500k", "-bufsize", "5000k", | |
| "-pix_fmt", "yuv420p", | |
| "-g", "60", | |
| "-c:a", "aac", "-b:a", "192k", "-ar", "44100", | |
| "-profile:a", "aac_low", | |
| "-af", "aresample=async=1000:min_hard_comp=0.100000:first_pts=0,highpass=f=50,lowpass=f=15000,dynaudnorm=f=150:g=15:m=25:p=0.95", | |
| "-max_muxing_queue_size", "2048", | |
| "-f", "flv", rtmp_url | |
| ] | |
| with open(FFMPEG_LOG, "w") as stderr_file: | |
| proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=stderr_file, preexec_fn=os.setsid) | |
| log(f"✅ ffmpeg PID: {proc.pid}") | |
| return proc | |
| # ------------------------------------------------------------ | |
| # YouTube API | |
| # ------------------------------------------------------------ | |
| def build_youtube_client(): | |
| try: | |
| if os.path.exists(TOKEN_FILE): | |
| with open(TOKEN_FILE, "rb") as f: creds = pickle.load(f) | |
| if creds and creds.valid: return build('youtube', 'v3', credentials=creds) | |
| if creds and creds.expired and creds.refresh_token: | |
| creds.refresh(Request()) | |
| with open(TOKEN_FILE, "wb") as f: pickle.dump(creds, f) | |
| return build('youtube', 'v3', credentials=creds) | |
| creds_json = os.getenv("YT_CREDENTIALS_JSON") | |
| if not creds_json: return None | |
| data = json.loads(creds_json) | |
| creds = Credentials(token=data.get("token"), refresh_token=data.get("refresh_token"), token_uri=data.get("token_uri"), client_id=data.get("client_id"), client_secret=data.get("client_secret"), scopes=data.get("scopes", ["https://www.googleapis.com/auth/youtube.force-ssl"])) | |
| if creds.expired and creds.refresh_token: creds.refresh(Request()) | |
| with open(TOKEN_FILE, "wb") as f: pickle.dump(creds, f) | |
| return build('youtube', 'v3', credentials=creds) | |
| except Exception as e: | |
| log(f"❌ فشل بناء عميل YouTube: {e}") | |
| return None | |
| def create_new_broadcast(): | |
| try: | |
| yt = build_youtube_client() | |
| if not yt: return None, None | |
| now = datetime.now(timezone.utc) | |
| date_str = now.strftime('%d-%m-%Y %H:%M') | |
| title = f"🔥 بث تيك توك أعدام المباشر | {date_str} 🚀" | |
| description = ( | |
| "أهلاً بكم في البث المباشر المخصص لـ أعدام.\n" | |
| "استمتعوا معنا بأجواء البث وتفاعلوا في التعليقات!\n\n" | |
| "🔔 لا تنسوا الاشتراك في القناة وتفعيل زر التنبيهات ليصلكم كل جديد فور انطلاق البث.\n" | |
| "شكراً لدعمكم المتواصل!\n\n" | |
| "#أعدام #بث_مباشر #تيك_توك #TikTokLive #الاردن #بث #مباشر #a3dam_111" | |
| ) | |
| insert_body = { | |
| "snippet": { | |
| "title": title, | |
| "description": description, | |
| "scheduledStartTime": now.isoformat() | |
| }, | |
| "status": {"privacyStatus": "public"}, | |
| "contentDetails": {"enableAutoStart": True, "enableAutoStop": False, "latencyPreference": "normal"} | |
| } | |
| new_broadcast = yt.liveBroadcasts().insert(part="snippet,status,contentDetails", body=insert_body).execute() | |
| bid = new_broadcast["id"] | |
| log(f"✅ بث جديد: {bid} | {title}") | |
| stream_insert = yt.liveStreams().insert( | |
| part="snippet,cdn,contentDetails", | |
| body={"snippet": {"title": "TikTok Stream Key"}, "cdn": {"ingestionType": "rtmp", "resolution": "1080p", "frameRate": "30fps"}, "contentDetails": {"isReusable": True}} | |
| ).execute() | |
| stream_id = stream_insert["id"] | |
| stream_key = stream_insert["cdn"]["ingestionInfo"]["streamName"] | |
| yt.liveBroadcasts().bind(id=bid, part="id", streamId=stream_id).execute() | |
| with open(BROADCAST_FILE, "w") as f: | |
| json.dump({"broadcast_id": bid, "stream_key": stream_key}, f) | |
| return stream_key, bid | |
| except Exception as e: | |
| log(f"❌ فشل إنشاء بث جديد: {e}") | |
| return None, None | |
| # ✅ دالة جديدة: تنظيف البث السابق لنفس المساحة فقط | |
| def cleanup_own_previous_broadcast(): | |
| """تنهي بثاً سابقاً لهذه المساحة فقط (إن وُجد وما زال مفتوحاً)""" | |
| if not os.path.exists(BROADCAST_FILE): | |
| return | |
| try: | |
| with open(BROADCAST_FILE, "r") as f: | |
| data = json.load(f) | |
| bid = data.get("broadcast_id") | |
| if not bid: | |
| return | |
| yt = build_youtube_client() | |
| if not yt: | |
| return | |
| # جلب حالة البث | |
| resp = yt.liveBroadcasts().list(id=bid, part="status").execute() | |
| items = resp.get("items", []) | |
| if not items: | |
| os.remove(BROADCAST_FILE) | |
| return | |
| status = items[0]["status"]["lifeCycleStatus"] | |
| if status in ["live", "ready", "created", "testing"]: | |
| log(f"🧹 إنهاء بث سابق لهذه المساحة: {bid}") | |
| yt.liveBroadcasts().transition(broadcastStatus="complete", id=bid, part="status").execute() | |
| # نحذف الملف على أية حال لأننا سننشئ بثاً جديداً | |
| os.remove(BROADCAST_FILE) | |
| except Exception as e: | |
| log(f"⚠️ تعذر تنظيف البث السابق: {e}") | |
| if os.path.exists(BROADCAST_FILE): | |
| os.remove(BROADCAST_FILE) | |
| def finalize_current_broadcast(broadcast_id): | |
| if not broadcast_id: return | |
| try: | |
| yt = build_youtube_client() | |
| if not yt: return | |
| yt.liveBroadcasts().transition(broadcastStatus="complete", id=broadcast_id, part="status").execute() | |
| log(f"🛑 تم إنهاء البث: {broadcast_id}") | |
| except Exception as e: | |
| log(f"⚠️ فشل إنهاء البث: {e}") | |
| if os.path.exists(BROADCAST_FILE): | |
| os.remove(BROADCAST_FILE) | |
| # ------------------------------------------------------------ | |
| # المحرك الرئيسي | |
| # ------------------------------------------------------------ | |
| def monitor_loop(): | |
| try: subprocess.run(["killall", "-9", "ffmpeg", "yt-dlp", "streamlink"], stderr=subprocess.DEVNULL) | |
| except: pass | |
| time.sleep(2) | |
| if os.path.exists(LOCK_FILE): return | |
| with open(LOCK_FILE, "w") as f: f.write(str(os.getpid())) | |
| # ✅ تنظيف بث هذه المساحة فقط (وليس كل البثوث) | |
| cleanup_own_previous_broadcast() | |
| ffmpeg_proc = None | |
| was_streaming = False | |
| consecutive_fails = 0 | |
| current_stream_key = None | |
| current_broadcast_id = None | |
| while True: | |
| try: | |
| stream_url = get_tiktok_stream_url() | |
| if stream_url: | |
| consecutive_fails = 0 | |
| if was_streaming and ffmpeg_proc and ffmpeg_proc.poll() is None: | |
| pass | |
| elif was_streaming and ffmpeg_proc and ffmpeg_proc.poll() is not None: | |
| kill_process_tree(ffmpeg_proc.pid) | |
| ffmpeg_proc = start_stream(current_stream_key, stream_url) | |
| else: | |
| if not current_broadcast_id: | |
| # لا ننهي كل البثوث، فقط ننشئ بثًا جديدًا (المساحة الأخرى مستقلة) | |
| stream_key, bid = create_new_broadcast() | |
| if not stream_key: | |
| stream_key = CUSTOM_STREAM_KEY | |
| current_broadcast_id = bid | |
| current_stream_key = stream_key | |
| ffmpeg_proc = start_stream(current_stream_key, stream_url) | |
| was_streaming = True | |
| else: | |
| if was_streaming: | |
| consecutive_fails += 1 | |
| if consecutive_fails >= 6: | |
| if ffmpeg_proc: kill_process_tree(ffmpeg_proc.pid) | |
| ffmpeg_proc = None | |
| was_streaming = False | |
| if current_broadcast_id: | |
| finalize_current_broadcast(current_broadcast_id) | |
| current_broadcast_id = None | |
| current_stream_key = None | |
| time.sleep(30) | |
| except Exception as e: | |
| log(f"❌ خطأ في حلقة المراقبة: {e}") | |
| time.sleep(30) | |
| app = Flask(__name__) | |
| def index(): return "OK" | |
| if __name__ == "__main__": | |
| t = threading.Thread(target=monitor_loop, daemon=True) | |
| t.start() | |
| app.run(host="0.0.0.0", port=7860, debug=False) |