Reooiq commited on
Commit
4cb4020
·
verified ·
1 Parent(s): 664a6df

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +104 -183
streamlit_app.py CHANGED
@@ -1,201 +1,122 @@
1
- import streamlit as st
2
- import subprocess
3
- import time
4
  import os
5
- from datetime import datetime
6
- from pathlib import Path
 
 
 
7
  import requests
8
 
9
- # Page config MUST be first
10
- st.set_page_config(page_title="TikTok Live Recorder", page_icon="📹", layout="wide")
 
11
 
12
- # Loading screen
13
- @st.cache_resource
14
- def load_resources():
15
- return True
16
 
17
- if not load_resources():
18
- st.spinner("Loading resources...")
19
 
20
- st.markdown("""
21
- <style>
22
- .main-header {font-size: 2.5rem; font-weight: bold; background: linear-gradient(90deg, #ff0050, #00f2ea); -webkit-background-clip: text; -webkit-text-fill-color: transparent;}
23
- .status-box {padding: 1rem; border-radius: 0.5rem; text-align: center; font-weight: bold;}
24
- .status-recording {background: linear-gradient(90deg, #ff6b6b, #ee5a5a); color: white; animation: pulse 1s infinite;}
25
- .status-ready {background: linear-gradient(90deg, #00ff88, #00cc66); color: black;}
26
- .status-offline {background: linear-gradient(90deg, #444444, #222222); color: #aaa;}
27
- @keyframes pulse {0%, 100% {opacity: 1;} 50% {opacity: 0.7;}}
28
- </style>
29
- """, unsafe_allow_html=True)
30
 
31
- # Session state
32
- if 'recording' not in st.session_state:
33
- st.session_state.recording = False
34
- if 'process' not in st.session_state:
35
- st.session_state.process = None
36
- if 'stream_url' not in st.session_state:
37
- st.session_state.stream_url = None
38
- if 'username' not in st.session_state:
39
- st.session_state.username = None
40
- if 'video_path' not in st.session_state:
41
- st.session_state.video_path = None
42
- if 'start_time' not in st.session_state:
43
- st.session_state.start_time = None
44
- if 'viewers' not in st.session_state:
45
- st.session_state.viewers = 0
46
-
47
- OUTPUT_DIR = "/tmp/tiktok_recordings"
48
- Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
49
-
50
- def check_tiktok_live(username):
51
- try:
52
- from TikTokLive import TikTokLiveClient
53
- client = TikTokLiveClient(unique_id=f"@{username}")
54
- is_live = client.fetch_is_live()
55
- if not is_live:
56
- return {'is_live': False, 'error': 'User not live'}
57
- room_info = client.fetch_room_info()
58
- stream_url = None
59
- if hasattr(room_info, 'stream_url') and room_info.stream_url:
60
- if hasattr(room_info.stream_url, 'hls_pull_url'):
61
- stream_url = room_info.stream_url.hls_pull_url
62
- elif hasattr(room_info.stream_url, 'flv_pull_url'):
63
- stream_url = room_info.stream_url.flv_pull_url
64
- return {'is_live': True, 'stream_url': stream_url, 'room_id': getattr(room_info, 'webcast_room_id', None), 'viewer_count': getattr(room_info, 'user_count', 0)}
65
- except Exception as e:
66
- return {'is_live': False, 'error': str(e)}
67
 
68
- def start_recording(stream_url, output_path):
69
- try:
70
- cmd = ['ffmpeg', '-y', '-re', '-i', stream_url, '-c:v', 'copy', '-c:a', 'copy', '-f', 'mp4', output_path]
71
- return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
72
- except Exception as e:
73
- st.error(f"Error: {e}")
74
- return None
75
 
76
- def stop_recording():
77
- if st.session_state.process:
 
 
78
  try:
79
- st.session_state.process.terminate()
80
- st.session_state.process.wait(timeout=5)
81
- except:
82
- st.session_state.process.kill()
83
- st.session_state.process = None
84
- st.session_state.recording = False
85
-
86
- def format_time(seconds):
87
- mins = int(seconds // 60)
88
- secs = int(seconds % 60)
89
- return f"{mins}:{secs:02d}"
90
-
91
- def upload_to_telegram(file_path, bot_token, chat_id):
92
  try:
93
- url = f"https://api.telegram.org/bot{bot_token}/sendVideo"
94
- with open(file_path, 'rb') as video:
95
- response = requests.post(url, files={'video': video}, data={'chat_id': chat_id, 'caption': f"📹 TikTok Live - @{st.session_state.username}"}, timeout=300)
96
- if response.status_code == 200:
97
- result = response.json()
98
- if result.get('ok'):
99
- return {'success': True, 'file_id': result['result']['video']['file_id']}
100
- return {'success': False, 'error': 'Upload failed'}
101
  except Exception as e:
102
- return {'success': False, 'error': str(e)}
103
-
104
- # Main UI
105
- st.markdown('<h1 class="main-header">📹 TikTok Live Recorder</h1>', unsafe_allow_html=True)
106
- st.markdown("Record TikTok live streams with free unlimited upload to Telegram")
107
-
108
- st.markdown("### 🔗 Connect to Live Stream")
109
- username_input = st.text_input("TikTok Username (without @)")
110
-
111
- col1, col2, col3 = st.columns(3)
112
-
113
- with col1:
114
- if st.button("🔍 Check Live", use_container_width=True):
115
- if username_input:
116
- with st.spinner("Checking..."):
117
- result = check_tiktok_live(username_input)
118
- if result.get('is_live'):
119
- st.success(f"✅ @{username_input} is LIVE!")
120
- st.session_state.stream_url = result.get('stream_url')
121
- st.session_state.username = username_input
122
- st.session_state.viewers = result.get('viewer_count', 0)
123
- st.rerun()
124
- else:
125
- st.error(f"❌ Not live: {result.get('error', '')}")
126
-
127
- with col2:
128
- if st.session_state.stream_url:
129
- if st.session_state.recording:
130
- if st.button("⏹️ Stop", type="secondary", use_container_width=True):
131
- stop_recording()
132
- st.success("Recording stopped!")
133
- st.rerun()
134
- else:
135
- if st.button("🔴 Record", type="primary", use_container_width=True):
136
- output_path = f"{OUTPUT_DIR}/tiktok_{st.session_state.username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
137
- st.session_state.process = start_recording(st.session_state.stream_url, output_path)
138
- if st.session_state.process:
139
- st.session_state.recording = True
140
- st.session_state.start_time = time.time()
141
- st.session_state.video_path = output_path
142
- st.success("🎬 Recording started!")
143
- st.rerun()
144
-
145
- with col3:
146
- if st.session_state.recording:
147
- st.markdown('<div class="status-box status-recording">🔴 Recording</div>', unsafe_allow_html=True)
148
- elif st.session_state.stream_url:
149
- st.markdown('<div class="status-box status-ready">🟢 Ready</div>', unsafe_allow_html=True)
150
- else:
151
- st.markdown('<div class="status-box status-offline">⚪ Offline</div>', unsafe_allow_html=True)
152
-
153
- if st.session_state.recording:
154
- st.divider()
155
- duration = time.time() - st.session_state.start_time
156
- col1, col2, col3 = st.columns(3)
157
- col1.metric("Status", "🔴 Recording")
158
- col2.metric("Duration", format_time(duration))
159
- col3.metric("User", f"@{st.session_state.username}")
160
-
161
- if not st.session_state.recording and st.session_state.video_path and os.path.exists(st.session_state.video_path):
162
- st.divider()
163
- st.markdown("### ☁️ Upload Video")
164
- file_size = os.path.getsize(st.session_state.video_path) / (1024 * 1024)
165
- st.info(f"📊 File size: {file_size:.1f} MB")
166
 
167
- upload_method = st.radio("Choose upload method", ["Telegram (Unlimited Free)", "Internet Archive", "Download Only"], index=0)
168
 
169
- if upload_method == "Telegram (Unlimited Free)":
170
- with st.expander("⚙️ Telegram Settings", expanded=True):
171
- bot_token = st.text_input("Bot Token", placeholder="123456:ABC-DEF...", type="password")
172
- chat_id = st.text_input("Chat ID", placeholder="-1001234567890")
173
- st.info("**Get Bot Token:** @BotFather | **Get Chat ID:** @getmyid_bot")
 
 
174
 
175
- if st.button("📤 Upload to Telegram", type="primary"):
176
- if bot_token and chat_id:
177
- with st.spinner("Uploading..."):
178
- result = upload_to_telegram(st.session_state.video_path, bot_token, chat_id)
179
- if result.get('success'):
180
- st.success(f"✅ Uploaded! File ID: {result['file_id']}")
181
- else:
182
- st.error(f"❌ {result.get('error', 'Failed')}")
183
- else:
184
- st.error("Please enter Bot Token and Chat ID")
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- elif upload_method == "Internet Archive":
187
- if st.button("📤 Prepare Archive.org Upload"):
188
- identifier = f"tiktok-{st.session_state.username}-{int(time.time())}"
189
- st.link_button("🌐 Upload to Archive.org", f"https://archive.org/services/edit/{identifier}")
190
 
191
- st.divider()
192
- with open(st.session_state.video_path, 'rb') as f:
193
- st.download_button(label="⬇️ Download MP4", data=f, file_name=f"tiktok_{st.session_state.username}.mp4", mime="video/mp4", use_container_width=True)
194
-
195
- st.divider()
196
- st.markdown("**⚠️ Notes:** Recordings are temporary. User must be LIVE. Respect creators' rights.")
 
 
 
 
 
197
 
198
- with st.sidebar:
199
- st.markdown("### ℹ️ Info")
200
- st.write("**Status:** Online")
201
- st.info("Enter Telegram credentials after recording to upload")
 
 
 
 
 
 
 
 
1
+ import asyncio
 
 
2
  import os
3
+ import sys
4
+ import time
5
+ import subprocess
6
+ from TikTokLive import TikTokLiveClient
7
+ from TikTokLive.errors import UserOfflineError, UserNotFoundError
8
  import requests
9
 
10
+ # ==================== إعدادات التشغيل ====================
11
+ # ضع اسم المستخدم الخاص بالمذيع هنا (تأكد من كتابة الـ @)
12
+ TARGET_USERNAME = "@username_here"
13
 
14
+ # عدد الثواني للانتظار بين كل عملية تحقق من البث
15
+ POLL_INTERVAL = 60
 
 
16
 
17
+ # المجلد الذي سيتم حفظ الفيديوهات المسجلة بداخله
18
+ SAVE_DIR = "./recordings"
19
 
20
+ # محرك التسجيل المفضل (يفضل streamlink لأنه الأقوى في جلب الجودة التلقائية وبثبات)
21
+ # الخيارات المتاحة: "streamlink" أو "ffmpeg" أو "yt-dlp"
22
+ RECORDING_ENGINE = "streamlink"
 
 
 
 
 
 
 
23
 
24
+ # إرسال تنبيهات لهاتفك عند بدء البث (اختياري - اتركها فارغة إذا لم تكن تريدها)
25
+ DISCORD_WEBHOOK_URL = ""
26
+ # =======================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ os.makedirs(SAVE_DIR, exist_ok=True)
 
 
 
 
 
 
29
 
30
+ def send_notification(message):
31
+ """إرسال إشعارات فورية إلى Discord في حال توفر الرابط"""
32
+ print(f"[تنبيه] {message}")
33
+ if DISCORD_WEBHOOK_URL:
34
  try:
35
+ requests.post(DISCORD_WEBHOOK_URL, json={"content": message})
36
+ except Exception as e:
37
+ print(f"⚠️ فشل إرسال التنبيه: {e}")
38
+
39
+ async def check_if_live(username):
40
+ """
41
+ ✅ الدالة المصلحة للتحقق من أن المذيع متصل بالبث المباشر
42
+ تم استبدال الدالة الخاطئة fetch_is_live بالدالة الرسمية والمضمونة is_live()
43
+ """
44
+ client = TikTokLiveClient(unique_id=username)
 
 
 
45
  try:
46
+ # الاستدعاء الصحيح والحديث في مكتبة بايثون
47
+ is_live = await client.is_live()
48
+ return is_live
49
+ except UserNotFoundError:
50
+ print(f"❌ اسم المستخدم {username} غير موجود أو تم كتابته بشكل خاطئ.")
51
+ return False
 
 
52
  except Exception as e:
53
+ # في حال وجود جدار حماية أو مشكلة في الاتصال من السيرفر
54
+ print(f"⚠️ فشل التحقق من الحالة حالياً: {e}")
55
+ return False
56
+
57
+ def start_recording(username):
58
+ """بدء عملية تسجيل البث وحفظ الفيديو مباشرة"""
59
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
60
+ clean_user = username.replace("@", "")
61
+ output_file = os.path.join(SAVE_DIR, f"{clean_user}_{timestamp}.mp4")
62
+ live_url = f"https://www.tiktok.com/{username}/live"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ send_notification(f"🔴 المذيع {username} بدأ البث المباشر الآن! جاري بدء التسجيل...")
65
 
66
+ # تحديد أمر التسجيل بناءً على المحرك المختار
67
+ if RECORDING_ENGINE == "streamlink":
68
+ command = ["streamlink", live_url, "best", "-o", output_file, "--stream-segment-threads", "3"]
69
+ elif RECORDING_ENGINE == "ffmpeg":
70
+ command = ["ffmpeg", "-i", live_url, "-c", "copy", "-y", output_file]
71
+ else:
72
+ command = ["yt-dlp", "-o", output_file, live_url]
73
 
74
+ print(f"🚀 تشغيل أمر التسجيل: {' '.join(command)}")
75
+ try:
76
+ # تشغيل التسجيل كعملية فرعية لمتابعة تقدم التحميل في الوقت الفعلي
77
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
78
+
79
+ # طباعة سجلات التحميل في لوحة التحكم (Hugging Face Logs)
80
+ for line in process.stdout:
81
+ sys.stdout.write(line)
82
+ sys.stdout.flush()
83
+
84
+ process.wait()
85
+ send_notification(f"⏹️ انتهى البث المباشر أو توقف التسجيل لـ {username}. تم حفظ الفيديو في: {output_file}")
86
+ except Exception as e:
87
+ print(f"⚠️ حدث خطأ أثناء التسجيل: {e}")
88
+ send_notification(f"⚠️ حدث خطأ أثناء تسجيل بث {username}: {e}")
89
+
90
+ async def main():
91
+ print("=" * 50)
92
+ print(f"🌟 بدء تشغيل مسجل بث تيك توك التلقائي للمذيع: {TARGET_USERNAME}")
93
+ print(f"⏱️ فحص دوري كل: {POLL_INTERVAL} ثانية")
94
+ print(f"📹 مجلد حفظ الفيديوهات: {SAVE_DIR}")
95
+ print(f"⚙️ محرك التسجيل المعتمد: {RECORDING_ENGINE}")
96
+ print("=" * 50)
97
 
98
+ send_notification(f"🚀 تم تشغيل سكربت المراقبة لـ {TARGET_USERNAME}")
 
 
 
99
 
100
+ while True:
101
+ # الفحص الذكي باستخدام الدالة الصحيحة المعدلة
102
+ is_live = await check_if_live(TARGET_USERNAME)
103
+
104
+ if is_live:
105
+ print(f"🔥 المذيع {TARGET_USERNAME} أونلاين! جاري استدعاء مسجل الفيديو...")
106
+ start_recording(TARGET_USERNAME)
107
+ else:
108
+ print(f"😴 المذيع {TARGET_USERNAME} غير متصل حالياً. إعادة الفحص بعد {POLL_INTERVAL} ثانية...")
109
+
110
+ await asyncio.sleep(POLL_INTERVAL)
111
 
112
+ if __name__ == '__main__':
113
+ try:
114
+ asyncio.run(main())
115
+ except KeyboardInterrupt:
116
+ print("👋 تم إيقاف البرنامج بواسطة المستخدم.")
117
+ except Exception as e:
118
+ print(f"💥 خطأ غير متوقع: {e}")
119
+ # إعادة تشغيل السكربت تلقائياً في حال حدوث انقطاع مفاجئ للإنترنت بالسيرفر
120
+ print("🔄 جاري إعادة تشغيل السكربت تلقائياً بعد 10 ثوانٍ...")
121
+ time.sleep(10)
122
+ os.execv(sys.executable, ['python'] + sys.argv)