Reoo88 commited on
Commit
e767db3
·
verified ·
1 Parent(s): 8aa8039

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +36 -63
streamlit_app.py CHANGED
@@ -11,9 +11,9 @@ from TikTokLive import TikTokLiveClient
11
  from TikTokLive.events import CommentEvent, DisconnectEvent, ConnectEvent
12
  from TikTokLive.client.errors import UserOfflineError, UserNotFoundError
13
 
14
- # ---------- تحميل الخط تلقائياً ----------
15
- FONT_PATH = "Palestine.ttf"
16
- FONT_URL = "https://github.com/mokhtarbsaid/rare-arabic-fonts/raw/main/palestine/palestine-regular.ttf"
17
 
18
  def download_font():
19
  if not os.path.exists(FONT_PATH):
@@ -24,56 +24,41 @@ def download_font():
24
 
25
  download_font()
26
 
27
- # ---------- إعدادات التعليقات ----------
28
  comments_list = []
29
- MAX_COMMENTS = 8
30
  lock = threading.Lock()
31
 
32
- # متغير التحكم في التعليقات الوهمية (متغير عام عادي)
33
- fake_comments_enabled = True
34
- fake_counter = 0
35
-
36
- def add_fake_comment():
37
- global fake_counter
38
- fake_counter += 1
39
- fake_comments = [
40
- f"زائر {fake_counter}: بالتوفيق",
41
- f"محب {fake_counter}: بث رائع",
42
- f"صديق {fake_counter}: متابع من العراق",
43
- ]
44
- return fake_comments[fake_counter % 3]
45
-
46
- def create_comment_overlay():
47
- width, height = 720, 1280
48
  img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
49
  draw = ImageDraw.Draw(img)
 
50
  try:
 
51
  font = ImageFont.truetype(FONT_PATH, 28)
52
  except:
53
  font = ImageFont.load_default()
54
-
55
  with lock:
56
  current = comments_list[-MAX_COMMENTS:]
57
-
 
58
  y_offset = height - 60
59
  for comment in reversed(current):
 
60
  draw.text((12, y_offset + 2), comment, font=font, fill=(0, 0, 0, 180))
 
61
  draw.text((10, y_offset), comment, font=font, fill=(255, 255, 255, 255))
62
- y_offset -= 40
63
-
64
  buf = BytesIO()
65
  img.save(buf, format="PNG")
66
  buf.seek(0)
67
  return buf.read()
68
 
69
- # ---------- مولّد التعليقات الوهمية ----------
70
- def fake_comment_generator():
71
- while fake_comments_enabled:
72
- time.sleep(3)
73
- with lock:
74
- comments_list.append(add_fake_comment())
75
-
76
- # ---------- عميل تعليقات تيك توك ----------
77
  def run_tiktok_client(username):
78
  while True:
79
  try:
@@ -83,7 +68,7 @@ def run_tiktok_client(username):
83
 
84
  @client.on(ConnectEvent)
85
  async def on_connect(event: ConnectEvent):
86
- st.session_state["client_status"] = "✅ متصل بغرفة البث"
87
 
88
  @client.on(CommentEvent)
89
  async def on_comment(event: CommentEvent):
@@ -93,26 +78,25 @@ def run_tiktok_client(username):
93
 
94
  @client.on(DisconnectEvent)
95
  async def on_disconnect(event: DisconnectEvent):
96
- st.session_state["client_status"] = "🔌 انقطع الاتصال، إعادة المحاولة..."
97
 
98
- st.session_state["client_status"] = "🔄 جاري الاتصال بـ TikTok..."
99
  loop.run_until_complete(client.start())
100
- except UserOfflineError:
101
- st.session_state["client_status"] = "⚠️ الحساب غير متصل حالياً"
102
- except UserNotFoundError:
103
- st.session_state["client_status"] = "❌ اسم المستخدم غير موجود"
104
  except Exception as e:
105
- st.session_state["client_status"] = f"❌ خطأ: {str(e)[:50]}"
106
  time.sleep(10)
107
 
108
- # ---------- تشغيل البث مع صورة التعليقات ----------
109
  def start_streaming(yt_key, tiktok_username):
 
110
  streamlink_cmd = f"streamlink https://www.tiktok.com/@{tiktok_username}/live best --hls-live-edge 3 --stdout"
111
 
 
112
  ffmpeg_cmd = (
113
- f"ffmpeg -thread_queue_size 8192 -i pipe:0 "
114
- f"-f image2pipe -r 2 -i pipe:1 "
115
- f"-filter_complex \"[0:v]scale=720:1280[vid];[vid][1:v]overlay=0:0\" "
116
  f"-map \"[outv]\" -map 0:a? "
117
  f"-c:v libx264 -preset ultrafast -tune zerolatency -b:v 1500k -maxrate 2000k -bufsize 3000k -g 30 "
118
  f"-c:a aac -ar 48000 -b:a 192k -ac 2 "
@@ -122,9 +106,12 @@ def start_streaming(yt_key, tiktok_username):
122
  try:
123
  print(f"[INFO] بدء البث من @{tiktok_username} إلى يوتيوب...")
124
 
 
125
  sl_process = subprocess.Popen(streamlink_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
 
126
  ffmpeg_process = subprocess.Popen(ffmpeg_cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
127
 
 
128
  def feed_overlay():
129
  while ffmpeg_process.poll() is None:
130
  try:
@@ -133,11 +120,12 @@ def start_streaming(yt_key, tiktok_username):
133
  ffmpeg_process.stdin.flush()
134
  except:
135
  break
136
- time.sleep(0.5)
137
  ffmpeg_process.stdin.close()
138
 
139
  threading.Thread(target=feed_overlay, daemon=True).start()
140
 
 
141
  while ffmpeg_process.poll() is None:
142
  data = sl_process.stdout.read(8192)
143
  if not data:
@@ -155,40 +143,25 @@ def start_streaming(yt_key, tiktok_username):
155
  st.set_page_config(page_title="بث تيك توك مع تعليقات")
156
  st.title("📱 بث تيك توك إلى يوتيوب مع تعليقات متحركة")
157
 
158
- if "client_status" not in st.session_state:
159
- st.session_state["client_status"] = "⏳ في انتظار البدء..."
160
-
161
  with st.form(key="stream_form"):
162
  tiktok_user = st.text_input("معرف مستخدم تيك توك (بدون @)").strip()
163
  yt_stream_key = st.text_input("مفتاح بث يوتيوب (RTMP Key)", type="password").strip()
164
- enable_fake = st.checkbox("تفعيل التعليقات الوهمية للتجربة", value=True)
165
  submit_button = st.form_submit_button(label="ابدأ البث الآن")
166
 
167
  if submit_button:
168
  if not tiktok_user or not yt_stream_key:
169
  st.error("يرجى إدخال اسم المستخدم ومفتاح البث.")
170
  else:
171
- # تعديل قيمة المتغير العام مباشرة (لا نحتاج global في هذا المستوى)
172
- fake_comments_enabled = enable_fake
173
-
174
- if fake_comments_enabled:
175
- threading.Thread(target=fake_comment_generator, daemon=True).start()
176
-
177
  threading.Thread(target=run_tiktok_client, args=(tiktok_user,), daemon=True).start()
 
178
  threading.Thread(target=start_streaming, args=(yt_stream_key, tiktok_user), daemon=True).start()
179
  st.success("تم بدء العمليات في الخلفية. انتظر ظهور البث على يوتيوب.")
180
 
181
- # عرض حالة اتصال التعليقات
182
- st.subheader("📡 حالة اتصال التعليقات")
183
- status_text = st.empty()
184
- status_text.text(st.session_state["client_status"])
185
-
186
  # عرض التعليقات الحية
187
  st.subheader("💬 أحدث التعليقات")
188
  placeholder = st.empty()
189
-
190
  while True:
191
- status_text.text(st.session_state["client_status"])
192
  with lock:
193
  current = comments_list[-8:] if comments_list else ["لا توجد تعليقات بعد"]
194
  placeholder.text("\n".join(current))
 
11
  from TikTokLive.events import CommentEvent, DisconnectEvent, ConnectEvent
12
  from TikTokLive.client.errors import UserOfflineError, UserNotFoundError
13
 
14
+ # ---------- إعدادات ثابتة ----------
15
+ FONT_PATH = "arial.ttf" # تأكد من وجود ملف خط في مجلد المشروع
16
+ FONT_URL = "https://github.com/mokhtarbsaid/rare-arabic-fonts/raw/main/palestine/palestine-regular.ttf" # خط احتياطي
17
 
18
  def download_font():
19
  if not os.path.exists(FONT_PATH):
 
24
 
25
  download_font()
26
 
27
+ # متغيرات التعليقات
28
  comments_list = []
29
+ MAX_COMMENTS = 10
30
  lock = threading.Lock()
31
 
32
+ # ---------- دالة توليد صورة التعليقات ----------
33
+ def create_comment_overlay(width=720, height=1280):
34
+ """تولد صورة PNG شفافة تحتوي على أحدث التعليقات"""
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
36
  draw = ImageDraw.Draw(img)
37
+
38
  try:
39
+ # استخدام حجم خط مناسب للتعليقات
40
  font = ImageFont.truetype(FONT_PATH, 28)
41
  except:
42
  font = ImageFont.load_default()
43
+
44
  with lock:
45
  current = comments_list[-MAX_COMMENTS:]
46
+
47
+ # رسم التعليقات من الأسفل إلى الأعلى
48
  y_offset = height - 60
49
  for comment in reversed(current):
50
+ # ظل أسود خفيف
51
  draw.text((12, y_offset + 2), comment, font=font, fill=(0, 0, 0, 180))
52
+ # نص أبيض
53
  draw.text((10, y_offset), comment, font=font, fill=(255, 255, 255, 255))
54
+ y_offset -= 45 # تباعد عمودي بين التعليقات
55
+
56
  buf = BytesIO()
57
  img.save(buf, format="PNG")
58
  buf.seek(0)
59
  return buf.read()
60
 
61
+ # ---------- عميل تيك توك (لجلب التعليقات) ----------
 
 
 
 
 
 
 
62
  def run_tiktok_client(username):
63
  while True:
64
  try:
 
68
 
69
  @client.on(ConnectEvent)
70
  async def on_connect(event: ConnectEvent):
71
+ print("✅ تم الاتصال بغرفة البث لجلب التعليقات")
72
 
73
  @client.on(CommentEvent)
74
  async def on_comment(event: CommentEvent):
 
78
 
79
  @client.on(DisconnectEvent)
80
  async def on_disconnect(event: DisconnectEvent):
81
+ print("🔌 انقطع اتصال التعليقات، إعادة المحاولة...")
82
 
 
83
  loop.run_until_complete(client.start())
84
+ except (UserOfflineError, UserNotFoundError):
85
+ print("⚠️ المستخدم غير متصل حالياً")
 
 
86
  except Exception as e:
87
+ print(f"❌ خطأ في اتصال التعليقات: {e}")
88
  time.sleep(10)
89
 
90
+ # ---------- تشغيل البث ----------
91
  def start_streaming(yt_key, tiktok_username):
92
+ # 1. أمر streamlink لجلب الفيديو
93
  streamlink_cmd = f"streamlink https://www.tiktok.com/@{tiktok_username}/live best --hls-live-edge 3 --stdout"
94
 
95
+ # 2. أمر ffmpeg مع استقبال صورة التعليقات من أنبوب
96
  ffmpeg_cmd = (
97
+ f"ffmpeg -thread_queue_size 8192 -i pipe:0 " # الفيديو من streamlink
98
+ f"-f image2pipe -r 1 -i pipe:1 " # صورة التعليقات من الأنبوب الثاني
99
+ f"-filter_complex \"[0:v]scale=720:1280[vid];[vid][1:v]overlay=0:0\" " # تراكب الصورة على الفيديو
100
  f"-map \"[outv]\" -map 0:a? "
101
  f"-c:v libx264 -preset ultrafast -tune zerolatency -b:v 1500k -maxrate 2000k -bufsize 3000k -g 30 "
102
  f"-c:a aac -ar 48000 -b:a 192k -ac 2 "
 
106
  try:
107
  print(f"[INFO] بدء البث من @{tiktok_username} إلى يوتيوب...")
108
 
109
+ # تشغيل streamlink
110
  sl_process = subprocess.Popen(streamlink_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
111
+ # تشغيل ffmpeg مع أنبوبين (stdin للفيديو والصورة)
112
  ffmpeg_process = subprocess.Popen(ffmpeg_cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
113
 
114
+ # خيط لتغذية صورة التعليقات
115
  def feed_overlay():
116
  while ffmpeg_process.poll() is None:
117
  try:
 
120
  ffmpeg_process.stdin.flush()
121
  except:
122
  break
123
+ time.sleep(1) # تحديث كل ثانية
124
  ffmpeg_process.stdin.close()
125
 
126
  threading.Thread(target=feed_overlay, daemon=True).start()
127
 
128
+ # تغذية الفيديو من streamlink
129
  while ffmpeg_process.poll() is None:
130
  data = sl_process.stdout.read(8192)
131
  if not data:
 
143
  st.set_page_config(page_title="بث تيك توك مع تعليقات")
144
  st.title("📱 بث تيك توك إلى يوتيوب مع تعليقات متحركة")
145
 
 
 
 
146
  with st.form(key="stream_form"):
147
  tiktok_user = st.text_input("معرف مستخدم تيك توك (بدون @)").strip()
148
  yt_stream_key = st.text_input("مفتاح بث يوتيوب (RTMP Key)", type="password").strip()
 
149
  submit_button = st.form_submit_button(label="ابدأ البث الآن")
150
 
151
  if submit_button:
152
  if not tiktok_user or not yt_stream_key:
153
  st.error("يرجى إدخال اسم المستخدم ومفتاح البث.")
154
  else:
155
+ # تشغيل عميل التعليقات
 
 
 
 
 
156
  threading.Thread(target=run_tiktok_client, args=(tiktok_user,), daemon=True).start()
157
+ # بدء البث
158
  threading.Thread(target=start_streaming, args=(yt_stream_key, tiktok_user), daemon=True).start()
159
  st.success("تم بدء العمليات في الخلفية. انتظر ظهور البث على يوتيوب.")
160
 
 
 
 
 
 
161
  # عرض التعليقات الحية
162
  st.subheader("💬 أحدث التعليقات")
163
  placeholder = st.empty()
 
164
  while True:
 
165
  with lock:
166
  current = comments_list[-8:] if comments_list else ["لا توجد تعليقات بعد"]
167
  placeholder.text("\n".join(current))