Update app.py
Browse files
app.py
CHANGED
|
@@ -2,14 +2,26 @@ import os
|
|
| 2 |
import time
|
| 3 |
import subprocess
|
| 4 |
import threading
|
|
|
|
|
|
|
| 5 |
import psutil
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
|
|
|
|
|
|
|
|
|
| 7 |
TIKTOK_USERNAME = "@osei_iraq"
|
| 8 |
-
|
| 9 |
FFMPEG_LOG = "/tmp/ffmpeg.log"
|
| 10 |
MONITOR_LOG = "/tmp/monitor.log"
|
| 11 |
LOCK_FILE = "/tmp/app_monitor_core.lock"
|
|
|
|
|
|
|
| 12 |
|
|
|
|
| 13 |
TIKTOK_COOKIES = os.getenv("TIKTOK_COOKIES", "")
|
| 14 |
TIKTOK_COOKIES_ADULT = os.getenv("TIKTOK_COOKIES_ADULT", "")
|
| 15 |
|
|
@@ -20,6 +32,9 @@ def log(msg):
|
|
| 20 |
with open(MONITOR_LOG, "a", encoding="utf-8") as f:
|
| 21 |
f.write(line + "\n")
|
| 22 |
|
|
|
|
|
|
|
|
|
|
| 23 |
def save_cookies():
|
| 24 |
if TIKTOK_COOKIES:
|
| 25 |
with open("/tmp/tiktok_cookies.txt", "w") as f:
|
|
@@ -30,6 +45,9 @@ def save_cookies():
|
|
| 30 |
|
| 31 |
save_cookies()
|
| 32 |
|
|
|
|
|
|
|
|
|
|
| 33 |
def kill_process_tree(pid):
|
| 34 |
try:
|
| 35 |
parent = psutil.Process(pid)
|
|
@@ -76,7 +94,7 @@ def get_tiktok_stream_url():
|
|
| 76 |
def start_stream(stream_key, stream_url):
|
| 77 |
if not stream_key or not stream_url:
|
| 78 |
return None
|
| 79 |
-
log("🎬 بدء البث (فيديو نسخ + صوت احترافي
|
| 80 |
rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{stream_key}"
|
| 81 |
|
| 82 |
try:
|
|
@@ -92,9 +110,7 @@ def start_stream(stream_key, stream_url):
|
|
| 92 |
"-fflags", "+genpts",
|
| 93 |
"-flags", "+global_header",
|
| 94 |
"-i", stream_url,
|
| 95 |
-
|
| 96 |
"-c:v", "copy",
|
| 97 |
-
|
| 98 |
"-c:a", "aac",
|
| 99 |
"-b:a", "192k",
|
| 100 |
"-ar", "44100",
|
|
@@ -102,11 +118,9 @@ def start_stream(stream_key, stream_url):
|
|
| 102 |
"-cutoff", "18000",
|
| 103 |
"-af", "highpass=f=60,lowpass=f=17000,aresample=async=1:first_pts=0",
|
| 104 |
"-async", "1",
|
| 105 |
-
|
| 106 |
"-start_at_zero",
|
| 107 |
"-copyts",
|
| 108 |
"-vsync", "2",
|
| 109 |
-
|
| 110 |
"-bsf:v", "h264_mp4toannexb",
|
| 111 |
"-reconnect", "1",
|
| 112 |
"-reconnect_at_eof", "1",
|
|
@@ -122,6 +136,150 @@ def start_stream(stream_key, stream_url):
|
|
| 122 |
log(f"✅ ffmpeg PID: {proc.pid}")
|
| 123 |
return proc
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
def monitor_loop():
|
| 126 |
try:
|
| 127 |
subprocess.run(["killall", "-9", "ffmpeg", "yt-dlp", "streamlink"], stderr=subprocess.DEVNULL)
|
|
@@ -141,13 +299,19 @@ def monitor_loop():
|
|
| 141 |
with open(LOCK_FILE, "w") as f:
|
| 142 |
f.write(str(os.getpid()))
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
is_streaming = False
|
| 145 |
ffmpeg_proc = None
|
| 146 |
was_streaming = False
|
| 147 |
consecutive_fails = 0
|
| 148 |
ffmpeg_restart_count = 0
|
|
|
|
|
|
|
| 149 |
|
| 150 |
-
log("🚀 محرك النقل الشفاف (
|
| 151 |
|
| 152 |
while True:
|
| 153 |
try:
|
|
@@ -163,25 +327,51 @@ def monitor_loop():
|
|
| 163 |
log("📋 آخر سجلات ffmpeg:\n" + "".join(f.readlines()[-10:]))
|
| 164 |
ffmpeg_restart_count += 1
|
| 165 |
if ffmpeg_restart_count >= 10:
|
| 166 |
-
log("
|
| 167 |
kill_process_tree(ffmpeg_proc.pid)
|
| 168 |
ffmpeg_proc = None
|
| 169 |
is_streaming = False
|
| 170 |
was_streaming = False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
time.sleep(300)
|
| 172 |
ffmpeg_restart_count = 0
|
| 173 |
continue
|
| 174 |
time.sleep(10)
|
| 175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
log("🟢 بدء بث جديد...")
|
| 177 |
if ffmpeg_proc:
|
| 178 |
kill_process_tree(ffmpeg_proc.pid)
|
| 179 |
-
ffmpeg_proc = start_stream(
|
| 180 |
if ffmpeg_proc:
|
| 181 |
is_streaming = True
|
| 182 |
was_streaming = True
|
| 183 |
consecutive_fails = 0
|
| 184 |
ffmpeg_restart_count = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
else:
|
| 186 |
consecutive_fails += 1
|
| 187 |
if was_streaming and consecutive_fails > 5:
|
|
@@ -191,6 +381,10 @@ def monitor_loop():
|
|
| 191 |
ffmpeg_proc = None
|
| 192 |
is_streaming = False
|
| 193 |
was_streaming = False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
consecutive_fails = 0
|
| 195 |
|
| 196 |
time.sleep(30)
|
|
@@ -200,7 +394,7 @@ def monitor_loop():
|
|
| 200 |
time.sleep(30)
|
| 201 |
|
| 202 |
if __name__ == "__main__":
|
| 203 |
-
log("✅ بدء تشغيل المحرك
|
| 204 |
t = threading.Thread(target=monitor_loop, daemon=True)
|
| 205 |
t.start()
|
| 206 |
while True:
|
|
|
|
| 2 |
import time
|
| 3 |
import subprocess
|
| 4 |
import threading
|
| 5 |
+
import json
|
| 6 |
+
import pickle
|
| 7 |
import psutil
|
| 8 |
+
from google.oauth2.credentials import Credentials
|
| 9 |
+
from google.auth.transport.requests import Request
|
| 10 |
+
from googleapiclient.discovery import build
|
| 11 |
+
from googleapiclient.errors import HttpError
|
| 12 |
|
| 13 |
+
# ------------------------------------------------------------
|
| 14 |
+
# الإعدادات الأساسية
|
| 15 |
+
# ------------------------------------------------------------
|
| 16 |
TIKTOK_USERNAME = "@osei_iraq"
|
| 17 |
+
CUSTOM_STREAM_KEY = os.getenv("CUSTOM_STREAM_KEY", "p3dq-5cex-bc1k-vet7-bsyx") # مفتاح احتياطي
|
| 18 |
FFMPEG_LOG = "/tmp/ffmpeg.log"
|
| 19 |
MONITOR_LOG = "/tmp/monitor.log"
|
| 20 |
LOCK_FILE = "/tmp/app_monitor_core.lock"
|
| 21 |
+
BROADCAST_FILE = "/tmp/current_broadcast.json"
|
| 22 |
+
TOKEN_FILE = "/tmp/yt_token.pkl"
|
| 23 |
|
| 24 |
+
# الكوكيز من المتغيرات البيئية
|
| 25 |
TIKTOK_COOKIES = os.getenv("TIKTOK_COOKIES", "")
|
| 26 |
TIKTOK_COOKIES_ADULT = os.getenv("TIKTOK_COOKIES_ADULT", "")
|
| 27 |
|
|
|
|
| 32 |
with open(MONITOR_LOG, "a", encoding="utf-8") as f:
|
| 33 |
f.write(line + "\n")
|
| 34 |
|
| 35 |
+
# ------------------------------------------------------------
|
| 36 |
+
# إعداد الكوكيز
|
| 37 |
+
# ------------------------------------------------------------
|
| 38 |
def save_cookies():
|
| 39 |
if TIKTOK_COOKIES:
|
| 40 |
with open("/tmp/tiktok_cookies.txt", "w") as f:
|
|
|
|
| 45 |
|
| 46 |
save_cookies()
|
| 47 |
|
| 48 |
+
# ------------------------------------------------------------
|
| 49 |
+
# أدوات مساعدة
|
| 50 |
+
# ------------------------------------------------------------
|
| 51 |
def kill_process_tree(pid):
|
| 52 |
try:
|
| 53 |
parent = psutil.Process(pid)
|
|
|
|
| 94 |
def start_stream(stream_key, stream_url):
|
| 95 |
if not stream_key or not stream_url:
|
| 96 |
return None
|
| 97 |
+
log("🎬 بدء البث (فيديو نسخ + صوت احترافي)...")
|
| 98 |
rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{stream_key}"
|
| 99 |
|
| 100 |
try:
|
|
|
|
| 110 |
"-fflags", "+genpts",
|
| 111 |
"-flags", "+global_header",
|
| 112 |
"-i", stream_url,
|
|
|
|
| 113 |
"-c:v", "copy",
|
|
|
|
| 114 |
"-c:a", "aac",
|
| 115 |
"-b:a", "192k",
|
| 116 |
"-ar", "44100",
|
|
|
|
| 118 |
"-cutoff", "18000",
|
| 119 |
"-af", "highpass=f=60,lowpass=f=17000,aresample=async=1:first_pts=0",
|
| 120 |
"-async", "1",
|
|
|
|
| 121 |
"-start_at_zero",
|
| 122 |
"-copyts",
|
| 123 |
"-vsync", "2",
|
|
|
|
| 124 |
"-bsf:v", "h264_mp4toannexb",
|
| 125 |
"-reconnect", "1",
|
| 126 |
"-reconnect_at_eof", "1",
|
|
|
|
| 136 |
log(f"✅ ffmpeg PID: {proc.pid}")
|
| 137 |
return proc
|
| 138 |
|
| 139 |
+
# ------------------------------------------------------------
|
| 140 |
+
# YouTube API دوال (مأخوذة من الكود المرجعي ومعدلة)
|
| 141 |
+
# ------------------------------------------------------------
|
| 142 |
+
def build_youtube_client():
|
| 143 |
+
"""إنشاء عميل YouTube API من التوكن المحفوظ أو من JSON"""
|
| 144 |
+
try:
|
| 145 |
+
# محاولة تحميل توكن موجود
|
| 146 |
+
if os.path.exists(TOKEN_FILE):
|
| 147 |
+
with open(TOKEN_FILE, "rb") as f:
|
| 148 |
+
creds = pickle.load(f)
|
| 149 |
+
if creds and creds.valid:
|
| 150 |
+
return build('youtube', 'v3', credentials=creds)
|
| 151 |
+
if creds and creds.expired and creds.refresh_token:
|
| 152 |
+
creds.refresh(Request())
|
| 153 |
+
with open(TOKEN_FILE, "wb") as f:
|
| 154 |
+
pickle.dump(creds, f)
|
| 155 |
+
return build('youtube', 'v3', credentials=creds)
|
| 156 |
+
|
| 157 |
+
# إنشاء من JSON الموجود في المتغير البيئي
|
| 158 |
+
creds_json = os.getenv("YT_CREDENTIALS_JSON")
|
| 159 |
+
if not creds_json:
|
| 160 |
+
log("❌ YT_CREDENTIALS_JSON غير موجود")
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
data = json.loads(creds_json)
|
| 164 |
+
creds = Credentials(
|
| 165 |
+
token=data.get("token"),
|
| 166 |
+
refresh_token=data.get("refresh_token"),
|
| 167 |
+
token_uri=data.get("token_uri"),
|
| 168 |
+
client_id=data.get("client_id"),
|
| 169 |
+
client_secret=data.get("client_secret"),
|
| 170 |
+
scopes=data.get("scopes", ["https://www.googleapis.com/auth/youtube.force-ssl"])
|
| 171 |
+
)
|
| 172 |
+
if creds.expired and creds.refresh_token:
|
| 173 |
+
creds.refresh(Request())
|
| 174 |
+
with open(TOKEN_FILE, "wb") as f:
|
| 175 |
+
pickle.dump(creds, f)
|
| 176 |
+
return build('youtube', 'v3', credentials=creds)
|
| 177 |
+
except Exception as e:
|
| 178 |
+
log(f"❌ فشل بناء عميل YouTube: {e}")
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
def create_live_stream(yt):
|
| 182 |
+
"""إنشاء liveStream وإرجاع stream_id و stream_key"""
|
| 183 |
+
try:
|
| 184 |
+
stream_insert = yt.liveStreams().insert(
|
| 185 |
+
part="snippet,cdn,contentDetails",
|
| 186 |
+
body={
|
| 187 |
+
"snippet": {"title": "TikTok Stream Key"},
|
| 188 |
+
"cdn": {
|
| 189 |
+
"ingestionType": "rtmp",
|
| 190 |
+
"resolution": "1080p",
|
| 191 |
+
"frameRate": "30fps"
|
| 192 |
+
},
|
| 193 |
+
"contentDetails": {"isReusable": True}
|
| 194 |
+
}
|
| 195 |
+
).execute()
|
| 196 |
+
stream_id = stream_insert["id"]
|
| 197 |
+
stream_key = stream_insert["cdn"]["ingestionInfo"]["streamName"]
|
| 198 |
+
log(f"✅ liveStream جاهز: {stream_id} | key: ...{stream_key[-4:]}")
|
| 199 |
+
return stream_id, stream_key
|
| 200 |
+
except Exception as e:
|
| 201 |
+
log(f"❌ فشل إنشاء liveStream: {e}")
|
| 202 |
+
return None, None
|
| 203 |
+
|
| 204 |
+
def bind_stream_to_broadcast(yt, broadcast_id, stream_id):
|
| 205 |
+
try:
|
| 206 |
+
yt.liveBroadcasts().bind(id=broadcast_id, part="id", streamId=stream_id).execute()
|
| 207 |
+
log(f"✅ تم ربط البث {broadcast_id} مع {stream_id}")
|
| 208 |
+
return True
|
| 209 |
+
except Exception as e:
|
| 210 |
+
log(f"❌ فشل ربط البث: {e}")
|
| 211 |
+
return False
|
| 212 |
+
|
| 213 |
+
def finalize_all_broadcasts():
|
| 214 |
+
"""إنهاء جميع البثوث النشطة لبداية نظيفة"""
|
| 215 |
+
try:
|
| 216 |
+
yt = build_youtube_client()
|
| 217 |
+
if not yt:
|
| 218 |
+
return
|
| 219 |
+
broadcasts = yt.liveBroadcasts().list(mine=True, part="id,status").execute()
|
| 220 |
+
for item in broadcasts.get("items", []):
|
| 221 |
+
bid = item["id"]
|
| 222 |
+
status = item.get("status", {}).get("lifeCycleStatus", "")
|
| 223 |
+
if status in ["ready", "created", "live", "testing"]:
|
| 224 |
+
try:
|
| 225 |
+
yt.liveBroadcasts().transition(broadcastStatus="complete", id=bid, part="status").execute()
|
| 226 |
+
log(f"🧹 أنهيت البث: {bid} (كان {status})")
|
| 227 |
+
except Exception as e:
|
| 228 |
+
log(f"⚠️ تعذر إنهاء البث {bid}: {e}")
|
| 229 |
+
except Exception as e:
|
| 230 |
+
log(f"❌ خطأ في التنظيف: {e}")
|
| 231 |
+
|
| 232 |
+
def create_new_broadcast():
|
| 233 |
+
"""إنشاء بث جديد كامل وإرجاع (stream_key, broadcast_id)"""
|
| 234 |
+
try:
|
| 235 |
+
yt = build_youtube_client()
|
| 236 |
+
if not yt:
|
| 237 |
+
return None, None
|
| 238 |
+
|
| 239 |
+
now = time.strftime("%Y-%m-%d %H:%M:%S")
|
| 240 |
+
title = f"بث تيك توك مباشر | {now}"
|
| 241 |
+
insert_body = {
|
| 242 |
+
"snippet": {"title": title},
|
| 243 |
+
"status": {"privacyStatus": "public"},
|
| 244 |
+
"contentDetails": {"enableAutoStart": True, "enableAutoStop": True, "latencyPreference": "normal"}
|
| 245 |
+
}
|
| 246 |
+
new_broadcast = yt.liveBroadcasts().insert(part="snippet,status,contentDetails", body=insert_body).execute()
|
| 247 |
+
bid = new_broadcast["id"]
|
| 248 |
+
log(f"✅ بث جديد: {bid} | {title}")
|
| 249 |
+
|
| 250 |
+
stream_id, stream_key = create_live_stream(yt)
|
| 251 |
+
if not stream_key:
|
| 252 |
+
return None, None
|
| 253 |
+
if not bind_stream_to_broadcast(yt, bid, stream_id):
|
| 254 |
+
return None, None
|
| 255 |
+
|
| 256 |
+
# حفظ معلومات البث
|
| 257 |
+
with open(BROADCAST_FILE, "w") as f:
|
| 258 |
+
json.dump({"broadcast_id": bid, "stream_key": stream_key}, f)
|
| 259 |
+
|
| 260 |
+
return stream_key, bid
|
| 261 |
+
except Exception as e:
|
| 262 |
+
log(f"❌ فشل إنشاء بث جديد: {e}")
|
| 263 |
+
return None, None
|
| 264 |
+
|
| 265 |
+
def finalize_current_broadcast(broadcast_id):
|
| 266 |
+
"""إنهاء بث محدد"""
|
| 267 |
+
if not broadcast_id:
|
| 268 |
+
return
|
| 269 |
+
try:
|
| 270 |
+
yt = build_youtube_client()
|
| 271 |
+
if not yt:
|
| 272 |
+
return
|
| 273 |
+
yt.liveBroadcasts().transition(broadcastStatus="complete", id=broadcast_id, part="status").execute()
|
| 274 |
+
log(f"✅ تم إنهاء البث {broadcast_id}")
|
| 275 |
+
except Exception as e:
|
| 276 |
+
log(f"⚠️ خطأ في إنهاء البث {broadcast_id}: {e}")
|
| 277 |
+
if os.path.exists(BROADCAST_FILE):
|
| 278 |
+
os.remove(BROADCAST_FILE)
|
| 279 |
+
|
| 280 |
+
# ------------------------------------------------------------
|
| 281 |
+
# المحرك الرئيسي (معدل لاستخدام API مع احتياطي المفتاح الثابت)
|
| 282 |
+
# ------------------------------------------------------------
|
| 283 |
def monitor_loop():
|
| 284 |
try:
|
| 285 |
subprocess.run(["killall", "-9", "ffmpeg", "yt-dlp", "streamlink"], stderr=subprocess.DEVNULL)
|
|
|
|
| 299 |
with open(LOCK_FILE, "w") as f:
|
| 300 |
f.write(str(os.getpid()))
|
| 301 |
|
| 302 |
+
# تنظيف البثوث القديمة عبر API
|
| 303 |
+
log("🧹 تنظيف البثوث السابقة...")
|
| 304 |
+
finalize_all_broadcasts()
|
| 305 |
+
|
| 306 |
is_streaming = False
|
| 307 |
ffmpeg_proc = None
|
| 308 |
was_streaming = False
|
| 309 |
consecutive_fails = 0
|
| 310 |
ffmpeg_restart_count = 0
|
| 311 |
+
current_stream_key = None
|
| 312 |
+
current_broadcast_id = None
|
| 313 |
|
| 314 |
+
log("🚀 محرك النقل الشفاف (YouTube API + احتياطي) بدأ")
|
| 315 |
|
| 316 |
while True:
|
| 317 |
try:
|
|
|
|
| 327 |
log("📋 آخر سجلات ffmpeg:\n" + "".join(f.readlines()[-10:]))
|
| 328 |
ffmpeg_restart_count += 1
|
| 329 |
if ffmpeg_restart_count >= 10:
|
| 330 |
+
log("�� 10 محاولات فاشلة – توقف 5 دقائق")
|
| 331 |
kill_process_tree(ffmpeg_proc.pid)
|
| 332 |
ffmpeg_proc = None
|
| 333 |
is_streaming = False
|
| 334 |
was_streaming = False
|
| 335 |
+
if current_broadcast_id:
|
| 336 |
+
finalize_current_broadcast(current_broadcast_id)
|
| 337 |
+
current_broadcast_id = None
|
| 338 |
+
current_stream_key = None
|
| 339 |
time.sleep(300)
|
| 340 |
ffmpeg_restart_count = 0
|
| 341 |
continue
|
| 342 |
time.sleep(10)
|
| 343 |
|
| 344 |
+
# الحصول على مفتاح بث جديد (API أولاً، ثم احتياطي)
|
| 345 |
+
stream_key = None
|
| 346 |
+
bid = None
|
| 347 |
+
# محاولة إنشاء بث عبر API
|
| 348 |
+
stream_key, bid = create_new_broadcast()
|
| 349 |
+
if not stream_key:
|
| 350 |
+
log("⚠️ فشل إنشاء بث بالـAPI، استخدام المفتاح الاحتياطي...")
|
| 351 |
+
stream_key = CUSTOM_STREAM_KEY
|
| 352 |
+
if not stream_key:
|
| 353 |
+
log("❌ لا يوجد مفتاح احتياطي")
|
| 354 |
+
time.sleep(30)
|
| 355 |
+
continue
|
| 356 |
+
else:
|
| 357 |
+
current_broadcast_id = bid
|
| 358 |
+
|
| 359 |
log("🟢 بدء بث جديد...")
|
| 360 |
if ffmpeg_proc:
|
| 361 |
kill_process_tree(ffmpeg_proc.pid)
|
| 362 |
+
ffmpeg_proc = start_stream(stream_key, stream_url)
|
| 363 |
if ffmpeg_proc:
|
| 364 |
is_streaming = True
|
| 365 |
was_streaming = True
|
| 366 |
consecutive_fails = 0
|
| 367 |
ffmpeg_restart_count = 0
|
| 368 |
+
current_stream_key = stream_key
|
| 369 |
+
log(f"🔑 المفتاح الحالي: ...{stream_key[-4:]}")
|
| 370 |
+
else:
|
| 371 |
+
if current_broadcast_id:
|
| 372 |
+
finalize_current_broadcast(current_broadcast_id)
|
| 373 |
+
current_broadcast_id = None
|
| 374 |
+
current_stream_key = None
|
| 375 |
else:
|
| 376 |
consecutive_fails += 1
|
| 377 |
if was_streaming and consecutive_fails > 5:
|
|
|
|
| 381 |
ffmpeg_proc = None
|
| 382 |
is_streaming = False
|
| 383 |
was_streaming = False
|
| 384 |
+
if current_broadcast_id:
|
| 385 |
+
finalize_current_broadcast(current_broadcast_id)
|
| 386 |
+
current_broadcast_id = None
|
| 387 |
+
current_stream_key = None
|
| 388 |
consecutive_fails = 0
|
| 389 |
|
| 390 |
time.sleep(30)
|
|
|
|
| 394 |
time.sleep(30)
|
| 395 |
|
| 396 |
if __name__ == "__main__":
|
| 397 |
+
log("✅ بدء تشغيل المحرك مع YouTube API")
|
| 398 |
t = threading.Thread(target=monitor_loop, daemon=True)
|
| 399 |
t.start()
|
| 400 |
while True:
|