Reoo88 commited on
Commit
d94f4d8
·
verified ·
1 Parent(s): 603babe

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +48 -43
streamlit_app.py CHANGED
@@ -72,7 +72,7 @@ def get_clean_tiktok_cookies():
72
  # ══════════════════════════════════════════════════════════════════════════════ #
73
  def cleanup_old_broadcasts(yt):
74
  try:
75
- write_log("[Cleanup] جاري فحص الحساب بحثاً عن أي بثوث قديمة معلقة...")
76
  res = yt.liveBroadcasts().list(broadcastType="all", mine=True, part="id,status").execute()
77
  if res.get("items"):
78
  for item in res["items"]:
@@ -81,11 +81,11 @@ def cleanup_old_broadcasts(yt):
81
  if life_status in ["live", "testing", "ready", "liveStarting"]:
82
  try:
83
  yt.liveBroadcasts().transition(broadcastStatus="complete", id=b_id, part="status").execute()
84
- write_log(f"[Cleanup] تم إغلاق بث قديم معلق بنجاح: {b_id}")
85
- except Exception as ex:
86
- write_log(f"[Cleanup Error] فشل إغلاق البث {b_id}: {ex}")
87
  except Exception as e:
88
- write_log(f"[Cleanup Core Error] خطأ أثناء تنظيف الحساب: {e}")
89
 
90
  # ══════════════════════════════════════════════════════════════════════════════ #
91
  # دالة إنشاء البث الحي وتوليد مفتاح البث تلقائياً عبر الـ API
@@ -95,7 +95,7 @@ def create_dynamic_youtube_stream(yt):
95
  cleanup_old_broadcasts(yt)
96
  current_date = datetime.now().strftime("%d-%m-%Y")
97
 
98
- write_log("[API] جاري إنشاء غرفة بث جديدة (Broadcast)...")
99
  broadcast_body = {
100
  "snippet": {
101
  "title": f"تغطية مباشرة ومستمرة | أحمد عبدالله",
@@ -109,9 +109,8 @@ def create_dynamic_youtube_stream(yt):
109
  }
110
  broadcast_res = yt.liveBroadcasts().insert(part="snippet,status", body=broadcast_body).execute()
111
  broadcast_id = broadcast_res["id"]
112
- write_log(f"[API] تم إنشاء غرفة البث بنجاح. ID: {broadcast_id}")
113
 
114
- write_log("[API] جاري توليد مفتاح بث ذكي ديناميكي (Stream Key)...")
115
  stream_body = {
116
  "snippet": {"title": f"Automated Key - {current_date}"},
117
  "cdn": {
@@ -123,44 +122,49 @@ def create_dynamic_youtube_stream(yt):
123
  stream_res = yt.liveStreams().insert(part="snippet,cdn", body=stream_body).execute()
124
  stream_id = stream_res["id"]
125
  stream_key = stream_res["cdn"]["ingestionInfo"]["streamName"]
126
- write_log(f"[API] تم توليد المفتاح الذكي بنجاح.")
127
 
128
- write_log("[API] جاري ربط غرفة البث بالمفتاح المولد (Binding)...")
129
  yt.liveBroadcasts().bind(id=broadcast_id, part="id", streamId=stream_id).execute()
130
- write_log("[API] اكتملت عملية الربط والتهيئة بنجاح.")
131
 
132
  return broadcast_id, stream_key
133
  except Exception as e:
134
- write_log(f"[API Error] خطأ فادح أثناء إنشاء البث والمفتاح: {e}")
135
  return None, None
136
 
137
  # ══════════════════════════════════════════════════════════════════════════════ #
138
- # دالة ذكية لمراقبة وصول البيانات وتحويل حالة البث إلى LIVE فور الاستقرار
139
  # ══════════════════════════════════════════════════════════════════════════════ #
140
  def monitor_and_switch_to_live(yt, broadcast_id):
141
- write_log("[Live Monitor] جاري مراقبة وصول بيانات البث من ffmpeg إلى يوتيوب...")
142
- # فحص متكرر لمدة دقيقتين بانتظار استقرار الإشارة
143
- for i in range(24):
144
- time.sleep(5)
 
 
 
 
 
 
 
145
  try:
146
- # فحص حالة البث الحالية
147
  status_res = yt.liveBroadcasts().list(id=broadcast_id, part="status").execute()
148
  if status_res.get("items"):
149
  life_status = status_res["items"][0]["status"].get("lifeCycleStatus")
150
- write_log(f"[Live Monitor] حالة البث الحالية في سيرفر يوتيوب: {life_status}")
151
 
152
- # إذا أصبح البث جاهزاً لاستقبال أمر الـ live أو الإشارة بدأت تتدفق
153
  try:
154
- write_log("[API] محاولة إرسال أمر إطلاق البث المباشر (Transition to LIVE)...")
155
  yt.liveBroadcasts().transition(broadcastStatus="live", id=broadcast_id, part="status").execute()
156
- write_log(f"[🔥 SUCCESS] البث الآن مباشر وظاهر للجميع على يوتيوب! ID: {broadcast_id}")
157
  return
158
  except Exception as transition_error:
159
- write_log(f"[API Note] يوتيوب لم يستقر بعد لقبل أمر الـ Live، جاري إعادة المحاولة... ({transition_error})")
 
160
  except Exception as e:
161
- write_log(f"[Live Monitor Error] خطأ أثناء فحص حالة الإشارة: {e}")
 
162
 
163
- write_log("[⚠️ ALERT] انتهت مهلة الفحص ولم يتمكن الكود من تحويل البث لـ LIVE تلقائياً، يرجى فحص لوج الأخطاء أعلاه.")
164
 
165
  # ══════════════════════════════════════════════════════════════════════════════ #
166
  # دالة إنهاء البث الحالي وحفظ الأرشيف ملحقاً بالتاريخ في نهايته
@@ -168,7 +172,7 @@ def monitor_and_switch_to_live(yt, broadcast_id):
168
  def close_dynamic_youtube_stream(yt, broadcast_id):
169
  try:
170
  if not broadcast_id: return
171
- write_log(f"[API] جاري إنهاء البث {broadcast_id} وإلحاق التاريخ بالأرشيف...")
172
 
173
  try:
174
  b_data = yt.liveBroadcasts().list(id=broadcast_id, part="snippet").execute()
@@ -178,16 +182,16 @@ def close_dynamic_youtube_stream(yt, broadcast_id):
178
  snippet["title"] = f"{snippet.get('title', 'بث مباشر')} - {current_date}"
179
  snippet["description"] = f"{snippet.get('description', '')}\n\nتم إنهاء الأرشفة بنجاح بتاريخ: {current_date}"
180
  yt.liveBroadcasts().update(part="snippet", body={"id": broadcast_id, "snippet": snippet}).execute()
181
- except Exception as ue:
182
- write_log(f"[API Error] فشل تحديث عنوان الأرشيف: {ue}")
183
 
184
  yt.liveBroadcasts().transition(broadcastStatus="complete", id=broadcast_id, part="status").execute()
185
- write_log(f"[API] تم قفل البث {broadcast_id} بنجاح وتحويله إلى فيديو مؤرشف.")
186
  except Exception as e:
187
  write_log(f"[API Error] خطأ أثناء إغلاق البث: {e}")
188
 
189
  # ══════════════════════════════════════════════════════════════════════════════ #
190
- # محرك المراقبة المدمج بنظام الجلسات الذكية واللوج
191
  # ══════════════════════════════════════════════════════════════════════════════ #
192
  def run_monitor():
193
  lock_file = open(LOCK_FILE, 'w')
@@ -213,7 +217,7 @@ def run_monitor():
213
  if is_streaming and (process is None or process.poll() is not None):
214
  is_streaming = False
215
  process = None
216
- write_log("[System] انقطع اتصال البث بشكل غير متوقع من المصدر.")
217
  close_dynamic_youtube_stream(yt, current_broadcast_id)
218
  current_broadcast_id = None
219
  with open(STATUS, "w") as f: json.dump({"live": False, "msg": "انقطع الاتصال..."}, f)
@@ -225,7 +229,7 @@ def run_monitor():
225
  offline_start_time = None
226
  if not is_streaming:
227
  write_log(f"[Radar] 🔴 تم رصد بث مباشر للحساب {TIKTOK_USERNAME} على تيك توك!")
228
- with open(STATUS, "w") as f: json.dump({"live": False, "msg": "تم رصد بث جديد! جاري إنشاء غرفة بث وتوليد مفتاح ذكي على يوتيوب..."}, f)
229
 
230
  broadcast_id, dynamic_key = create_dynamic_youtube_stream(yt)
231
 
@@ -234,23 +238,25 @@ def run_monitor():
234
  cookie_string = get_clean_tiktok_cookies()
235
  header_option = f'--http-header "Cookie={cookie_string}"' if cookie_string else ""
236
 
237
- write_log("[FFMPEG] جاري بدء تشغيل الـ streamlink والـ ffmpeg لضخ البيانات...")
 
 
238
  cmd = (
239
- f'streamlink --loglevel error {header_option} "https://www.tiktok.com/{TIKTOK_USERNAME}/live" best --hls-live-edge 3 --stdout | '
240
- f"ffmpeg -hide_banner -loglevel error -thread_queue_size 8192 -i pipe:0 "
241
  f"-c:v copy -c:a aac -ar 48000 -b:a 192k -ac 2 "
242
  f"-af 'highpass=f=200,lowpass=f=3000,aresample=async=1' "
243
- f"-buffer_size 4096k -fflags +genpts+igndts -max_interleave_delta 0 "
244
  f"-f flv 'rtmp://a.rtmp.youtube.com/live2/{dynamic_key}'"
245
  )
246
  process = subprocess.Popen(cmd, shell=True)
247
  is_streaming = True
248
  with open(STATUS, "w") as f: json.dump({"live": True}, f)
249
 
250
- # تفعيل الخيط الذكي لمراقبة الإشارة وتفعيل الـ Live في يوتيوب
251
  threading.Thread(target=monitor_and_switch_to_live, args=(yt, broadcast_id), daemon=True).start()
252
  else:
253
- write_log("[System Error] تم إلغاء البث الحالي بسبب فشل تهيئة الـ API.")
254
  with open(STATUS, "w") as f: json.dump({"live": False, "msg": "فشل إنشاء البث، جاري المحاولة لاحقاً..."}, f)
255
 
256
  else:
@@ -268,7 +274,7 @@ def run_monitor():
268
 
269
  time.sleep(POLL_INTERVAL)
270
  except Exception as main_e:
271
- write_log(f"[Core Loop Error] خطأ في الحلقة الرئيسية للرادار: {main_e}")
272
  time.sleep(POLL_INTERVAL)
273
 
274
  # تشغيل خيط الرادار
@@ -277,7 +283,7 @@ if "core_monitor_initialized" not in st.session_state:
277
  st.session_state.core_monitor_initialized = True
278
 
279
  # ══════════════════════════════════════════════════════════════════════════════ #
280
- # واجهة العرض (Streamlit UI) مع شاشة عرض اللوج
281
  # ══════════════════════════════════════════════════════════════════════════════ #
282
  st.set_page_config(page_title="AutoStream Pro", page_icon="📡", layout="wide")
283
  st.title("📡 رادار البث الآلي الذكي بالكامل")
@@ -295,7 +301,7 @@ with col1:
295
 
296
  s = get_status()
297
  if s.get("live"):
298
- st.success(f"🔴 حساب {TIKTOK_USERNAME} مباشر الآن على يوتيوب بنجاح")
299
  else:
300
  st.info(s.get("msg", "بانتظار المذيع..."))
301
 
@@ -307,7 +313,6 @@ with col2:
307
  if os.path.exists(LOG_FILE):
308
  with open(LOG_FILE, "r", encoding="utf-8") as lf:
309
  logs = lf.readlines()
310
- # عرض آخر 40 سطر من اللوج في صندوق مخصص للـ Debug
311
  st.code("".join(logs[-40:]), language="text")
312
  else:
313
- st.text("لا توجد سجلات لوج حالياً، انتظر بدء تشغيل الرادار.")
 
72
  # ══════════════════════════════════════════════════════════════════════════════ #
73
  def cleanup_old_broadcasts(yt):
74
  try:
75
+ write_log("[Cleanup] جاري فحص الحساب بحثاً عن أي بثوث قديمة معلقة لتصفيرها...")
76
  res = yt.liveBroadcasts().list(broadcastType="all", mine=True, part="id,status").execute()
77
  if res.get("items"):
78
  for item in res["items"]:
 
81
  if life_status in ["live", "testing", "ready", "liveStarting"]:
82
  try:
83
  yt.liveBroadcasts().transition(broadcastStatus="complete", id=b_id, part="status").execute()
84
+ write_log(f"[Cleanup] تم إغلاق وتطهير بث معلق سابق: {b_id}")
85
+ except Exception:
86
+ pass
87
  except Exception as e:
88
+ write_log(f"[Cleanup Error] خطأ أثناء تنظيف الحساب: {e}")
89
 
90
  # ══════════════════════════════════════════════════════════════════════════════ #
91
  # دالة إنشاء البث الحي وتوليد مفتاح البث تلقائياً عبر الـ API
 
95
  cleanup_old_broadcasts(yt)
96
  current_date = datetime.now().strftime("%d-%m-%Y")
97
 
98
+ write_log("[API] جاري إنشاء غرفة بث جديدة على يوتيوب...")
99
  broadcast_body = {
100
  "snippet": {
101
  "title": f"تغطية مباشرة ومستمرة | أحمد عبدالله",
 
109
  }
110
  broadcast_res = yt.liveBroadcasts().insert(part="snippet,status", body=broadcast_body).execute()
111
  broadcast_id = broadcast_res["id"]
 
112
 
113
+ write_log("[API] جاري توليد مفتاح بث ديناميكي ذكي...")
114
  stream_body = {
115
  "snippet": {"title": f"Automated Key - {current_date}"},
116
  "cdn": {
 
122
  stream_res = yt.liveStreams().insert(part="snippet,cdn", body=stream_body).execute()
123
  stream_id = stream_res["id"]
124
  stream_key = stream_res["cdn"]["ingestionInfo"]["streamName"]
 
125
 
126
+ write_log("[API] جاري ربط الغرفة بالمفتاح المولد...")
127
  yt.liveBroadcasts().bind(id=broadcast_id, part="id", streamId=stream_id).execute()
 
128
 
129
  return broadcast_id, stream_key
130
  except Exception as e:
131
+ write_log(f"[API Error] خطأ في إنشاء البث: {e}")
132
  return None, None
133
 
134
  # ══════════════════════════════════════════════════════════════════════════════ #
135
+ # دالة الانتظار الذكية لملء البافر (Buffer) ثم إطلاق البث المباشر على يوتيوب
136
  # ══════════════════════════════════════════════════════════════════════════════ #
137
  def monitor_and_switch_to_live(yt, broadcast_id):
138
+ # 🔥 هنا نقوم بحجز دقيقة ونصف (90 ثانية) ليقوم السيرفر بتخزين أول دقيقتين من بث تيك توك في البافر لضمان الجودة
139
+ write_log("[⏳ Buffer Mode] جاري البدء بتخزين البث في الذاكرة المؤقتة (البافر) لمنع الشاشة الرمادية والتقطيع...")
140
+
141
+ # الانتظار على 3 مراحل مع طباعة التحديث في اللوج لكي تتابع الاستقرار
142
+ for count in range(3):
143
+ time.sleep(30)
144
+ write_log(f"[⏳ Buffer Mode] اكتمل {30 * (count + 1)} ثانية من تخزين البث وتغذية خوادم يوتيوب بالبيانات...")
145
+
146
+ write_log("[Live Monitor] جاري التحقق من استقرار الإشارة في يوتيوب لإطلاق البث للجمهور...")
147
+
148
+ for i in range(15):
149
  try:
 
150
  status_res = yt.liveBroadcasts().list(id=broadcast_id, part="status").execute()
151
  if status_res.get("items"):
152
  life_status = status_res["items"][0]["status"].get("lifeCycleStatus")
153
+ write_log(f"[Live Monitor] حالة معالجة البيانات الحالية في يوتيوب: {life_status}")
154
 
 
155
  try:
156
+ write_log("[API] إرسال أمر تحويل حالة البث إلى LIVE الآن...")
157
  yt.liveBroadcasts().transition(broadcastStatus="live", id=broadcast_id, part="status").execute()
158
+ write_log(f"[🔥 SUCCESS] البث المباشر انطلق الآن ومستقر ومحمي بالبافر بالكامل! ID: {broadcast_id}")
159
  return
160
  except Exception as transition_error:
161
+ write_log(f"[API Note] يوتيوب يقوم بمعالجة البافر حالياً، إعادة المحاولة بعد قليل... ({transition_error})")
162
+ time.sleep(5)
163
  except Exception as e:
164
+ write_log(f"[Live Monitor Error] خطأ في فحص الإشارة: {e}")
165
+ time.sleep(5)
166
 
167
+ write_log("[⚠️ ALERT] انتهت مهلة الفحص، يرجى مراجعة سجل الأخطاء لمعرفة سبب امتناع يوتيوب عن التحويل.")
168
 
169
  # ══════════════════════════════════════════════════════════════════════════════ #
170
  # دالة إنهاء البث الحالي وحفظ الأرشيف ملحقاً بالتاريخ في نهايته
 
172
  def close_dynamic_youtube_stream(yt, broadcast_id):
173
  try:
174
  if not broadcast_id: return
175
+ write_log(f"[API] جاري إنهاء البث {broadcast_id} وحفظه في الأرشيف...")
176
 
177
  try:
178
  b_data = yt.liveBroadcasts().list(id=broadcast_id, part="snippet").execute()
 
182
  snippet["title"] = f"{snippet.get('title', 'بث مباشر')} - {current_date}"
183
  snippet["description"] = f"{snippet.get('description', '')}\n\nتم إنهاء الأرشفة بنجاح بتاريخ: {current_date}"
184
  yt.liveBroadcasts().update(part="snippet", body={"id": broadcast_id, "snippet": snippet}).execute()
185
+ except Exception:
186
+ pass
187
 
188
  yt.liveBroadcasts().transition(broadcastStatus="complete", id=broadcast_id, part="status").execute()
189
+ write_log(f"[API] تم قفل البث وتحويله بنجاح إلى أرشيف مستقل بالقناة.")
190
  except Exception as e:
191
  write_log(f"[API Error] خطأ أثناء إغلاق البث: {e}")
192
 
193
  # ══════════════════════════════════════════════════════════════════════════════ #
194
+ # محرك المراقبة المدمج بنظام البافر والجلسات الذكية
195
  # ══════════════════════════════════════════════════════════════════════════════ #
196
  def run_monitor():
197
  lock_file = open(LOCK_FILE, 'w')
 
217
  if is_streaming and (process is None or process.poll() is not None):
218
  is_streaming = False
219
  process = None
220
+ write_log("[System] انقطع اتصال البث من المصدر.")
221
  close_dynamic_youtube_stream(yt, current_broadcast_id)
222
  current_broadcast_id = None
223
  with open(STATUS, "w") as f: json.dump({"live": False, "msg": "انقطع الاتصال..."}, f)
 
229
  offline_start_time = None
230
  if not is_streaming:
231
  write_log(f"[Radar] 🔴 تم رصد بث مباشر للحساب {TIKTOK_USERNAME} على تيك توك!")
232
+ with open(STATUS, "w") as f: json.dump({"live": False, "msg": "تم رصد بث جديد! جاري تجميع البافر والتهيئة..."}, f)
233
 
234
  broadcast_id, dynamic_key = create_dynamic_youtube_stream(yt)
235
 
 
238
  cookie_string = get_clean_tiktok_cookies()
239
  header_option = f'--http-header "Cookie={cookie_string}"' if cookie_string else ""
240
 
241
+ write_log("[FFMPEG] جاري تشغيل خط المعالجة وضخ البيانات مع تفعيل التخزين المؤقت الممتد (Buffer)...")
242
+
243
+ # 🔥 تعديل الـ FFMPEG: أضفنا -live_start_index -20 لجمع القطع السابقة و -max_interleave_delta لملء البافر لمنع شاشة الرمادي
244
  cmd = (
245
+ f'streamlink --loglevel error {header_option} "https://www.tiktok.com/{TIKTOK_USERNAME}/live" best --hls-live-edge 5 --stdout | '
246
+ f"ffmpeg -hide_banner -loglevel error -thread_queue_size 16384 -i pipe:0 "
247
  f"-c:v copy -c:a aac -ar 48000 -b:a 192k -ac 2 "
248
  f"-af 'highpass=f=200,lowpass=f=3000,aresample=async=1' "
249
+ f"-buffer_size 8192k -max_delay 50000000 -fflags +genpts+igndts -max_interleave_delta 0 "
250
  f"-f flv 'rtmp://a.rtmp.youtube.com/live2/{dynamic_key}'"
251
  )
252
  process = subprocess.Popen(cmd, shell=True)
253
  is_streaming = True
254
  with open(STATUS, "w") as f: json.dump({"live": True}, f)
255
 
256
+ # تشغيل خيط فحص البافر وإطلاق الـ Live بعد التأخير المطلق
257
  threading.Thread(target=monitor_and_switch_to_live, args=(yt, broadcast_id), daemon=True).start()
258
  else:
259
+ write_log("[System Error] إلغاء الجلسة بسبب فشل تهيئة الـ API.")
260
  with open(STATUS, "w") as f: json.dump({"live": False, "msg": "فشل إنشاء البث، جاري المحاولة لاحقاً..."}, f)
261
 
262
  else:
 
274
 
275
  time.sleep(POLL_INTERVAL)
276
  except Exception as main_e:
277
+ write_log(f"[Core Loop Error] خطأ في الحلقة الرئيسية: {main_e}")
278
  time.sleep(POLL_INTERVAL)
279
 
280
  # تشغيل خيط الرادار
 
283
  st.session_state.core_monitor_initialized = True
284
 
285
  # ══════════════════════════════════════════════════════════════════════════════ #
286
+ # واجهة العرض (Streamlit UI)
287
  # ══════════════════════════════════════════════════════════════════════════════ #
288
  st.set_page_config(page_title="AutoStream Pro", page_icon="📡", layout="wide")
289
  st.title("📡 رادار البث الآلي الذكي بالكامل")
 
301
 
302
  s = get_status()
303
  if s.get("live"):
304
+ st.success(f"🔴 حساب {TIKTOK_USERNAME} مباشر على يوتيوب بنجاح (محمي بنظام البافر)")
305
  else:
306
  st.info(s.get("msg", "بانتظار المذيع..."))
307
 
 
313
  if os.path.exists(LOG_FILE):
314
  with open(LOG_FILE, "r", encoding="utf-8") as lf:
315
  logs = lf.readlines()
 
316
  st.code("".join(logs[-40:]), language="text")
317
  else:
318
+ st.text("بانتظار تسجيل أولى البيانات من الرادار...")