Reooiq commited on
Commit
56bcdb4
ยท
verified ยท
1 Parent(s): 44fbcc3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -419
app.py CHANGED
@@ -1,8 +1,3 @@
1
- """
2
- ๐Ÿ“น TikTok Live Recorder - Hugging Face Spaces
3
- Record TikTok live streams and upload to Telegram (unlimited free storage)
4
- """
5
-
6
  import streamlit as st
7
  import subprocess
8
  import time
@@ -11,514 +6,255 @@ from datetime import datetime
11
  from pathlib import Path
12
  import requests
13
 
14
- # Page configuration
15
  st.set_page_config(
16
  page_title="TikTok Live Recorder",
17
  page_icon="๐Ÿ“น",
18
- layout="wide",
19
- initial_sidebar_state="collapsed"
20
  )
21
 
22
- # Custom CSS
23
  st.markdown("""
24
  <style>
25
- .main-header {
26
- font-size: 2.5rem;
27
- font-weight: bold;
28
- background: linear-gradient(90deg, #ff0050, #00f2ea);
29
- -webkit-background-clip: text;
30
- -webkit-text-fill-color: transparent;
31
- margin-bottom: 0.5rem;
32
- }
33
- .sub-header {
34
- color: #888;
35
- margin-bottom: 2rem;
36
- }
37
- .status-box {
38
- padding: 1rem;
39
- border-radius: 0.5rem;
40
- margin: 1rem 0;
41
- text-align: center;
42
- font-weight: bold;
43
- }
44
- .status-live {
45
- background: linear-gradient(90deg, #ff4444, #ff0000);
46
- color: white;
47
- animation: pulse 2s infinite;
48
- }
49
- .status-offline {
50
- background: linear-gradient(90deg, #444444, #222222);
51
- color: #aaa;
52
- }
53
- .status-recording {
54
- background: linear-gradient(90deg, #ff6b6b, #ee5a5a);
55
- color: white;
56
- animation: pulse 1s infinite;
57
- }
58
- .status-ready {
59
- background: linear-gradient(90deg, #00ff88, #00cc66);
60
- color: black;
61
- }
62
- @keyframes pulse {
63
- 0%, 100% { opacity: 1; }
64
- 50% { opacity: 0.7; }
65
- }
66
- .stat-card {
67
- background: rgba(255, 255, 255, 0.05);
68
- padding: 1.5rem;
69
- border-radius: 1rem;
70
- text-align: center;
71
- border: 1px solid rgba(255, 255, 255, 0.1);
72
- height: 100%;
73
- }
74
- .stat-value {
75
- font-size: 2.5rem;
76
- font-weight: bold;
77
- background: linear-gradient(90deg, #00f2ea, #ff0050);
78
- -webkit-background-clip: text;
79
- -webkit-text-fill-color: transparent;
80
- }
81
- .stat-label {
82
- font-size: 0.9rem;
83
- color: #888;
84
- margin-top: 0.5rem;
85
- }
86
  </style>
87
  """, unsafe_allow_html=True)
88
 
89
- # Session state initialization
90
  if 'recording' not in st.session_state:
91
  st.session_state.recording = False
92
  if 'process' not in st.session_state:
93
  st.session_state.process = None
94
- if 'stats' not in st.session_state:
95
- st.session_state.stats = {'viewers': 0, 'likes': 0, 'comments': 0, 'gifts': 0}
96
  if 'stream_url' not in st.session_state:
97
  st.session_state.stream_url = None
98
- if 'room_id' not in st.session_state:
99
- st.session_state.room_id = None
100
- if 'start_time' not in st.session_state:
101
- st.session_state.start_time = None
102
  if 'username' not in st.session_state:
103
  st.session_state.username = None
104
  if 'video_path' not in st.session_state:
105
  st.session_state.video_path = None
 
 
106
 
107
- # Create output directory
108
  OUTPUT_DIR = "/tmp/tiktok_recordings"
109
  Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
110
 
111
  def check_tiktok_live(username):
112
- """Check if TikTok user is live and get stream info"""
113
  try:
114
  from TikTokLive import TikTokLiveClient
115
-
116
  client = TikTokLiveClient(unique_id=f"@{username}")
117
  is_live = client.fetch_is_live()
118
 
119
  if not is_live:
120
- return {'is_live': False, 'error': 'ุงู„ู…ุณุชุฎุฏู… ู„ูŠุณ ุจุซ ู…ุจุงุดุฑ ุญุงู„ูŠุงู‹'}
121
 
122
  room_info = client.fetch_room_info()
123
-
124
  stream_url = None
 
125
  if hasattr(room_info, 'stream_url') and room_info.stream_url:
126
- if hasattr(room_info.stream_url, 'hls_pull_url') and room_info.stream_url.hls_pull_url:
127
  stream_url = room_info.stream_url.hls_pull_url
128
- elif hasattr(room_info.stream_url, 'flv_pull_url') and room_info.stream_url.flv_pull_url:
129
  stream_url = room_info.stream_url.flv_pull_url
130
 
131
  return {
132
  'is_live': True,
133
  'stream_url': stream_url,
134
  'room_id': getattr(room_info, 'webcast_room_id', None),
135
- 'title': getattr(room_info, 'title', f'ุจุซ @{username}'),
136
  'viewer_count': getattr(room_info, 'user_count', 0)
137
  }
138
  except Exception as e:
139
  return {'is_live': False, 'error': str(e)}
140
 
141
- def start_ffmpeg_recording(stream_url, output_path):
142
- """Start FFmpeg recording of the stream"""
143
  try:
144
- cmd = [
145
- 'ffmpeg',
146
- '-y',
147
- '-re',
148
- '-i', stream_url,
149
- '-c:v', 'copy',
150
- '-c:a', 'copy',
151
- '-f', 'mp4',
152
- output_path
153
- ]
154
-
155
- process = subprocess.Popen(
156
- cmd,
157
- stdout=subprocess.PIPE,
158
- stderr=subprocess.PIPE
159
- )
160
-
161
  return process
162
  except Exception as e:
163
- st.error(f"ูุดู„ ุจุฏุก ุงู„ุชุณุฌูŠู„: {e}")
164
  return None
165
 
166
- def stop_ffmpeg_recording():
167
- """Stop the FFmpeg recording process"""
168
  if st.session_state.process:
169
  try:
170
  st.session_state.process.terminate()
171
  st.session_state.process.wait(timeout=5)
172
  except:
173
  st.session_state.process.kill()
174
- finally:
175
- st.session_state.process = None
176
- st.session_state.recording = False
177
 
178
- def format_duration(seconds):
179
- """Format seconds to HH:MM:SS"""
180
- hours = int(seconds // 3600)
181
- minutes = int((seconds % 3600) // 60)
182
  secs = int(seconds % 60)
183
-
184
- if hours > 0:
185
- return f"{hours}:{minutes:02d}:{secs:02d}"
186
- return f"{minutes}:{secs:02d}"
187
-
188
- def get_file_size_mb(filepath):
189
- """Get file size in MB"""
190
- try:
191
- return os.path.getsize(filepath) / (1024 * 1024)
192
- except:
193
- return 0
194
 
195
- def upload_to_telegram_bot(file_path, bot_token, chat_id):
196
- """Upload video to Telegram using Bot API"""
197
  try:
198
  url = f"https://api.telegram.org/bot{bot_token}/sendVideo"
199
-
200
  with open(file_path, 'rb') as video:
201
  files = {'video': video}
202
  data = {
203
  'chat_id': chat_id,
204
- 'caption': f"๐Ÿ“น TikTok Live - @{st.session_state.username}\n๐Ÿ“… {datetime.now().strftime('%Y-%m-%d %H:%M')}\nโฑ๏ธ Duration: {format_duration(time.time() - st.session_state.start_time)}"
205
  }
206
-
207
  response = requests.post(url, files=files, data=data, timeout=300)
208
-
209
  if response.status_code == 200:
210
  result = response.json()
211
  if result.get('ok'):
212
- file_id = result['result']['video']['file_id']
213
- return {
214
- 'success': True,
215
- 'file_id': file_id,
216
- 'message': 'ุชู… ุงู„ุฑูุน ุจู†ุฌุงุญ ุฅู„ู‰ Telegram!'
217
- }
218
-
219
- return {'success': False, 'error': 'ูุดู„ ุงู„ุฑูุน'}
220
  except Exception as e:
221
  return {'success': False, 'error': str(e)}
222
 
223
- def prepare_internet_archive_upload(username, duration, stats):
224
- """Prepare metadata for Internet Archive upload"""
225
- identifier = f"tiktok-live-{username}-{int(time.time())}"
226
-
227
- metadata = {
228
- 'identifier': identifier,
229
- 'title': f'TikTok Live - @{username}',
230
- 'description': f'Recorded TikTok Live stream from @{username}',
231
- 'creator': username,
232
- 'date': datetime.now().isoformat(),
233
- 'subject': 'tiktok; live; stream',
234
- 'mediatype': 'movies',
235
- 'duration': format_duration(duration)
236
- }
237
-
238
- upload_url = f"https://archive.org/services/edit/{identifier}"
239
-
240
- return {
241
- 'success': True,
242
- 'identifier': identifier,
243
- 'upload_url': upload_url,
244
- 'metadata': metadata
245
- }
246
-
247
- # Main UI
248
  st.markdown('<h1 class="main-header">๐Ÿ“น TikTok Live Recorder</h1>', unsafe_allow_html=True)
249
- st.markdown('<p class="sub-header">ุชุณุฌูŠู„ ุจุซูˆุซ ุชูŠูƒ ุชูˆูƒ ุงู„ู…ุจุงุดุฑุฉ ู…ุน ุฑูุน ู…ุฌุงู†ูŠ ุบูŠุฑ ู…ุญุฏูˆุฏ ุนู„ู‰ Telegram</p>')
250
 
251
- # Main content
252
- col_main1, col_main2 = st.columns([2, 1])
253
 
254
- with col_main1:
255
- # Input section
256
- st.markdown("### ๐Ÿ”— ุงู„ุงุชุตุงู„ ุจุงู„ุจุซ ุงู„ู…ุจุงุดุฑ")
257
-
258
- username_input = st.text_input(
259
- "ุงุณู… ุงู„ู…ุณุชุฎุฏู… ููŠ TikTok",
260
- placeholder="ุฃุฏุฎู„ ุงู„ูŠูˆุฒุฑ ุจุฏูˆู† @"
261
- )
262
-
263
- col_btn1, col_btn2, col_btn3 = st.columns([1, 1, 1])
264
-
265
- with col_btn1:
266
- if st.button("๐Ÿ” ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุจุซ", use_container_width=True, key="check_live"):
267
- if username_input:
268
- with st.spinner("ุฌุงุฑูŠ ุงู„ุชุญู‚ู‚..."):
269
- result = check_tiktok_live(username_input)
270
-
271
- if result.get('is_live'):
272
- st.success(f"โœ… @{username_input} ูŠุจุซ ุงู„ุขู†!")
273
- st.session_state.stream_url = result.get('stream_url')
274
- st.session_state.room_id = result.get('room_id')
275
- st.session_state.username = username_input
276
- st.session_state.stats['viewers'] = result.get('viewer_count', 0)
277
- st.rerun()
278
- else:
279
- st.error(f"โŒ @{username_input} ู„ุง ูŠุจุซ ุงู„ุขู†. {result.get('error', '')}")
280
-
281
- with col_btn2:
282
- if st.session_state.stream_url:
283
- if st.session_state.recording:
284
- if st.button("โน๏ธ ุฅูŠู‚ุงู ุงู„ุชุณุฌูŠู„", type="secondary", use_container_width=True):
285
- stop_ffmpeg_recording()
286
- st.success("ุชู… ุฅูŠู‚ุงู ุงู„ุชุณุฌูŠู„!")
287
  st.rerun()
288
- else:
289
- if st.button("๐Ÿ”ด ุจุฏุก ุงู„ุชุณุฌูŠู„", type="primary", use_container_width=True):
290
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
291
- output_path = f"{OUTPUT_DIR}/tiktok_{st.session_state.username}_{timestamp}.mp4"
292
-
293
- st.session_state.process = start_ffmpeg_recording(st.session_state.stream_url, output_path)
294
-
295
- if st.session_state.process:
296
- st.session_state.recording = True
297
- st.session_state.start_time = time.time()
298
- st.session_state.video_path = output_path
299
- st.success("๐ŸŽฌ ุจุฏุฃ ุงู„ุชุณุฌูŠู„!")
300
- st.rerun()
301
-
302
- with col_btn3:
303
  if st.session_state.recording:
304
- status = "๐Ÿ”ด ุฌุงุฑูŠ ุงู„ุชุณุฌูŠู„"
305
- status_class = "status-recording"
306
- elif st.session_state.stream_url:
307
- status = "๐ŸŸข ุฌุงู‡ุฒ ู„ู„ุชุณุฌูŠู„"
308
- status_class = "status-ready"
309
  else:
310
- status = "โšช ุบูŠุฑ ู…ุชุตู„"
311
- status_class = "status-offline"
312
-
313
- st.markdown(f'<div class="status-box {status_class}">{status}</div>', unsafe_allow_html=True)
314
-
315
- # Recording status
 
 
 
 
 
 
316
  if st.session_state.recording:
317
- st.divider()
318
-
319
- duration = time.time() - st.session_state.start_time if st.session_state.start_time else 0
320
-
321
- col_stat1, col_stat2, col_stat3 = st.columns(3)
322
-
323
- with col_stat1:
324
- st.metric("ุงู„ุญุงู„ุฉ", "๐Ÿ”ด ุชุณุฌูŠู„", delta=None)
325
-
326
- with col_stat2:
327
- st.metric("ุงู„ู…ุฏุฉ", format_duration(duration), delta=None)
328
-
329
- with col_stat3:
330
- st.metric("ุงู„ู…ุณุชุฎุฏู…", f"@{st.session_state.username}", delta=None)
331
-
332
- st.progress(1.0, text="ุฌุงุฑูŠ ุงู„ุชุณุฌูŠู„...")
333
-
334
- # Upload section (after recording)
335
- if not st.session_state.recording and st.session_state.video_path and os.path.exists(st.session_state.video_path):
336
- st.divider()
337
- st.markdown("### โ˜๏ธ ุฑูุน ุงู„ุชุณุฌูŠู„")
338
-
339
- file_size = get_file_size_mb(st.session_state.video_path)
340
- st.info(f"๐Ÿ“Š ุญุฌู… ุงู„ู…ู„ู: {file_size:.1f} MB")
341
-
342
- upload_method = st.radio(
343
- "ุงุฎุชุฑ ู…ู†ุตุฉ ุงู„ุฑูุน",
344
- ["Telegram (ุชุฎุฒูŠู† ุบูŠุฑ ู…ุญุฏูˆุฏ ู…ุฌุงู†ูŠ)", "Internet Archive (ุฃุฑุดูŠู ุฏุงุฆู…)", "ุชุญู…ูŠู„ ู…ุญู„ูŠ ูู‚ุท"],
345
- index=0
346
- )
347
-
348
- if upload_method == "Telegram (ุชุฎุฒูŠู† ุบูŠุฑ ู…ุญุฏูˆุฏ ู…ุฌุงู†ูŠ)":
349
- st.markdown("""
350
- **โœ… ู…ู…ูŠุฒุงุช Telegram:**
351
- - ุชุฎุฒูŠู† ู…ุฌุงู†ูŠ ุบูŠุฑ ู…ุญุฏูˆุฏ
352
- - ู…ู„ูุงุช ุญุชู‰ 2 ุฌูŠุฌุงุจุงูŠุช
353
- - ุฑูˆุงุจุท ุฏุงุฆู…ุฉ
354
- - ุณู‡ูˆู„ุฉ ุงู„ู…ุดุงุฑูƒุฉ
355
- """)
356
-
357
- with st.expander("โš™๏ธ ุฅุนุฏุงุฏุงุช Telegram"):
358
- bot_token = st.text_input("Bot Token", placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", type="password")
359
- chat_id = st.text_input("Chat ID", placeholder="-1001234567890")
360
-
361
- st.info("""
362
- **ู„ู„ุญุตูˆู„ ุนู„ู‰ Bot Token ูˆ Chat ID:**
363
- 1. ุฃู†ุดุฆ ุจูˆุช ุนุจุฑ @BotFather ููŠ Telegram
364
- 2. ุงุญุตู„ ุนู„ู‰ Token ู…ู† @BotFather
365
- 3. ุฃุถู ุงู„ุจูˆุช ู„ู‚ู†ุงุฉ ุฃูˆ ู…ุฌู…ูˆุนุฉ
366
- 4. ุงุญุตู„ ุนู„ู‰ Chat ID ุนุจุฑ @getmyid_bot
367
- """)
368
-
369
- if st.button("๐Ÿ“ค ุฑูุน ุฅู„ู‰ Telegram", type="primary"):
370
- if bot_token and chat_id:
371
- with st.spinner("ุฌุงุฑูŠ ุงู„ุฑูุน ุฅู„ู‰ Telegram..."):
372
- result = upload_to_telegram_bot(st.session_state.video_path, bot_token, chat_id)
373
-
374
- if result.get('success'):
375
- st.success(f"โœ… {result['message']}")
376
- st.code(f"File ID: {result['file_id']}")
377
- else:
378
- st.error(f"โŒ {result.get('error', 'ูุดู„ ุงู„ุฑูุน')}")
379
- else:
380
- st.error("ูŠุฑุฌู‰ ุฅุฏุฎุงู„ Bot Token ูˆ Chat ID")
381
-
382
- elif upload_method == "Internet Archive (ุฃุฑุดูŠู ุฏุงุฆู…)":
383
- st.markdown("""
384
- **โœ… ู…ู…ูŠุฒุงุช Internet Archive:**
385
- - ุฃุฑุดูŠู ุฏุงุฆู… ูˆู…ุฌุงู†ูŠ
386
- - ู…ู†ุงุณุจ ู„ู„ุฃุจุญุงุซ ูˆุงู„ุชูˆุซูŠู‚
387
- - ูˆุตูˆู„ ุนุงู… ุฃูˆ ุฎุงุต
388
- """)
389
-
390
- if st.button("๐Ÿ“ค ุชุญุถูŠุฑ ุงู„ุฑูุน ุฅู„ู‰ Archive.org"):
391
- result = prepare_internet_archive_upload(
392
- st.session_state.username,
393
- time.time() - st.session_state.start_time,
394
- st.session_state.stats
395
- )
396
-
397
- if result.get('success'):
398
- st.success("ุชู… ุชุญุถูŠุฑ ุจูŠุงู†ุงุช ุงู„ุฑูุน!")
399
- st.link_button("๐ŸŒ ุงู„ุฐู‡ุงุจ ู„ุตูุญุฉ ุงู„ุฑูุน", result['upload_url'])
400
- st.json(result['metadata'])
401
-
402
- # Download button (always available)
403
- st.divider()
404
- with open(st.session_state.video_path, 'rb') as f:
405
- st.download_button(
406
- label="โฌ‡๏ธ ุชุญู…ูŠู„ ุงู„ููŠุฏูŠูˆ (MP4)",
407
- data=f,
408
- file_name=f"tiktok_{st.session_state.username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4",
409
- mime="video/mp4",
410
- use_container_width=True
411
- )
412
 
413
- with col_main2:
414
- # Statistics
415
- st.markdown("### ๐Ÿ“Š ุงู„ุฅุญุตุงุฆูŠุงุช")
416
 
417
- col1, col2 = st.columns(2)
418
- with col1:
419
- st.markdown(f"""
420
- <div class="stat-card">
421
- <div class="stat-value">{st.session_state.stats['viewers']:,}</div>
422
- <div class="stat-label">๐Ÿ‘๏ธ ุงู„ู…ุดุงู‡ุฏูŠู†</div>
423
- </div>
424
- """, unsafe_allow_html=True)
425
 
426
- with col2:
427
- st.markdown(f"""
428
- <div class="stat-card">
429
- <div class="stat-value">{st.session_state.stats['likes']:,}</div>
430
- <div class="stat-label">โค๏ธ ุงู„ู„ุงูŠูƒุงุช</div>
431
- </div>
432
- """, unsafe_allow_html=True)
433
 
434
- col3, col4 = st.columns(2)
435
- with col3:
436
- st.markdown(f"""
437
- <div class="stat-card">
438
- <div class="stat-value">{st.session_state.stats['comments']}</div>
439
- <div class="stat-label">๐Ÿ’ฌ ุงู„ุชุนู„ูŠู‚ุงุช</div>
440
- </div>
441
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
442
 
443
- with col4:
444
- st.markdown(f"""
445
- <div class="stat-card">
446
- <div class="stat-value">{st.session_state.stats['gifts']}</div>
447
- <div class="stat-label">๐ŸŽ ุงู„ุฌูˆู„ุงุช</div>
448
- </div>
449
- """, unsafe_allow_html=True)
450
 
451
- # Info box
452
  st.divider()
453
- st.markdown("### โ„น๏ธ ู…ุนู„ูˆู…ุงุช")
454
-
455
- st.info("""
456
- **ูƒูŠููŠุฉ ุงู„ุงุณุชุฎุฏุงู…:**
457
- 1. ุฃุฏุฎู„ ูŠูˆุฒุฑ ุชูŠูƒ ุชูˆูƒ
458
- 2. ุงุถุบุท "ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุจุซ"
459
- 3. ุงุถุบุท "ุจุฏุก ุงู„ุชุณุฌูŠู„"
460
- 4. ุงุถุบุท "ุฅูŠู‚ุงู ุงู„ุชุณุฌูŠู„" ุนู†ุฏ ุงู„ุงู†ุชู‡ุงุก
461
- 5. ุงุฎุชุฑ ู…ู†ุตุฉ ุงู„ุฑูุน ุฃูˆ ุญู…ู„ ู…ุญู„ูŠุงู‹
462
- """)
463
-
464
- st.warning("""
465
- **โš ๏ธ ู…ู„ุงุญุธุงุช:**
466
- - ูŠุฌุจ ุฃู† ูŠูƒูˆู† ุงู„ู…ุณุชุฎุฏู… ูŠุจุซ ุญุงู„ูŠุงู‹
467
- - ุงู„ุชุณุฌูŠู„ุงุช ู…ุคู‚ุชุฉ - ุญู…ู„ู‡ุง ุฃูˆ ุงุฑูุนู‡ุง ููˆุฑุงู‹
468
- - ุงุญุชุฑู… ุญู‚ูˆู‚ ุตู†ุงุน ุงู„ู…ุญุชูˆู‰
469
- """)
470
 
471
- # Footer
472
  st.divider()
473
  st.markdown("""
474
- <div style="text-align: center; color: #666; padding: 1rem;">
475
- <p>๐Ÿ“น TikTok Live Recorder - Built with โค๏ธ on Hugging Face Spaces</p>
476
- <p style="font-size: 0.8rem;">
477
- โš ๏ธ ู‡ุฐู‡ ุงู„ุฃุฏุงุฉ ุชุณุชุฎุฏู… APIs ุบูŠุฑ ุฑุณู…ูŠุฉ. ูŠุฑุฌู‰ ุงุญุชุฑุงู… ุญู‚ูˆู‚ ุตู†ุงุน ุงู„ู…ุญุชูˆู‰ ูˆุดุฑูˆุท ุฎุฏู…ุฉ TikTok.
478
- </p>
479
- </div>
480
- """, unsafe_allow_html=True)
481
 
482
- # Help expander
483
- with st.expander("๐Ÿ“– ุฏู„ูŠู„ ุงู„ุงุณุชุฎุฏุงู… ุงู„ูƒุงู…ู„"):
484
  st.markdown("""
485
- ### ๐ŸŽฏ ุงู„ู…ูŠุฒุงุช
486
-
487
- - โœ… **ุชุณุฌูŠู„ ุญู‚ูŠู‚ูŠ**: ูŠุณุชุฎุฏู… FFmpeg ู„ุงู„ุชู‚ุงุท ุงู„ุจุซ ู…ุจุงุดุฑุฉ ู…ู† ุฎูˆุงุฏู… TikTok
488
- - โœ… **ุฃูุถู„ ุฌูˆุฏุฉ**: ุงู„ุชุณุฌูŠู„ ุจุงู„ุฌูˆุฏุฉ ุงู„ุฃุตู„ูŠุฉ ุจุฏูˆู† ุฅุนุงุฏุฉ ุชุฑู…ูŠุฒ
489
- - โœ… **ุฑูุน ู…ุฌุงู†ูŠ**: Telegram ูŠูˆูุฑ ุชุฎุฒูŠู† ู…ุฌุงู†ูŠ ุบูŠุฑ ู…ุญุฏูˆุฏ (ุญุชู‰ 2GB ู„ู„ู…ู„ู)
490
- - โœ… **ุฃุฑุดูŠู ุฏุงุฆู…**: Internet Archive ู„ู„ุฃุฑุดูุฉ ุงู„ุฏุงุฆู…ุฉ
491
- - โœ… **ุชุญู…ูŠู„ ู…ุญู„ูŠ**: ุชุญู…ูŠู„ ุงู„ููŠุฏูŠูˆ ูƒู…ู„ู MP4
492
-
493
- ### ๐Ÿ“ค ู…ู†ุตุงุช ุงู„ุฑูุน
494
-
495
- #### Telegram (ู…ูˆุตู‰ ุจู‡)
496
- - ุชุฎุฒูŠู† ู…ุฌุงู†ูŠ ุบูŠุฑ ู…ุญุฏูˆุฏ
497
- - ู…ู„ูุงุช ุญุชู‰ 2 ุฌูŠุฌุงุจุงูŠุช (4GB ู„ู„ุจุฑูŠู…ูŠูˆู…)
498
- - ุฑูˆุงุจุท ุฏุงุฆู…ุฉ
499
- - ุณู‡ูˆู„ุฉ ุงู„ู…ุดุงุฑูƒุฉ
500
-
501
- #### Internet Archive
502
- - ุฃุฑุดูŠู ุฏุงุฆู… ูˆู…ุฌุงู†ูŠ
503
- - ู…ู†ุงุณุจ ู„ู„ุชูˆุซูŠู‚ ูˆุงู„ุฃุจุญุงุซ
504
- - ูˆุตูˆู„ ุนุงู… ุฃูˆ ุฎุงุต
505
-
506
- ### โš ๏ธ ุงู„ู‚ูŠูˆุฏ
507
-
508
- - ู…ุณุงุญุฉ Hugging Face ู…ุญุฏูˆุฏุฉ - ุงุญู…ู„ ุฃูˆ ุงุฑูุน ููˆุฑุงู‹
509
- - ูŠุฌุจ ุฃู† ูŠูƒูˆู† ุงู„ู…ุณุชุฎุฏู… LIVE ุญุงู„ูŠุงู‹
510
- - ุจุนุถ ุงู„ุจุซูˆุซ ู‚ุฏ ูŠูƒูˆู† ู„ู‡ุง ู‚ูŠูˆุฏ ุฌุบุฑุงููŠุฉ
511
-
512
- ### ๐Ÿ”ง ู…ุชุทู„ุจุงุช Telegram
513
 
514
- ู„ู„ุฑูุน ุนู„ู‰ Telegram ุชุญุชุงุฌ:
515
- 1. **Bot Token**: ุฃู†ุดุฆ ุจูˆุช ุนุจุฑ @BotFather
516
- 2. **Chat ID**: ู…ุนุฑู ุงู„ู‚ู†ุงุฉ/ุงู„ู…ุฌู…ูˆุนุฉ
517
 
518
- **ุงู„ุฎุทูˆุงุช:**
519
- 1. ุงูุชุญ Telegram ูˆุงุจุญุซ ุนู† @BotFather
520
- 2. ุฃุฑุณู„ /newbot ูˆุฃู†ุดุฆ ุจูˆุช ุฌุฏูŠุฏ
521
- 3. ุงุญูุธ Token ุงู„ุฐูŠ ุณุชุญุตู„ ุนู„ูŠู‡
522
- 4. ุฃุถู ุงู„ุจูˆุช ู„ู‚ู†ุงุชูƒ ุฃูˆ ู…ุฌู…ูˆุนุชูƒ
523
- 5. ุงุญุตู„ ุนู„ู‰ Chat ID ุนุจุฑ @getmyid_bot
524
  """)
 
 
 
 
 
 
1
  import streamlit as st
2
  import subprocess
3
  import time
 
6
  from pathlib import Path
7
  import requests
8
 
 
9
  st.set_page_config(
10
  page_title="TikTok Live Recorder",
11
  page_icon="๐Ÿ“น",
12
+ layout="wide"
 
13
  )
14
 
 
15
  st.markdown("""
16
  <style>
17
+ .main-header {
18
+ font-size: 2.5rem;
19
+ font-weight: bold;
20
+ background: linear-gradient(90deg, #ff0050, #00f2ea);
21
+ -webkit-background-clip: text;
22
+ -webkit-text-fill-color: transparent;
23
+ }
24
+ .status-box {
25
+ padding: 1rem;
26
+ border-radius: 0.5rem;
27
+ text-align: center;
28
+ font-weight: bold;
29
+ }
30
+ .status-recording {
31
+ background: linear-gradient(90deg, #ff6b6b, #ee5a5a);
32
+ color: white;
33
+ animation: pulse 1s infinite;
34
+ }
35
+ .status-ready {
36
+ background: linear-gradient(90deg, #00ff88, #00cc66);
37
+ color: black;
38
+ }
39
+ .status-offline {
40
+ background: linear-gradient(90deg, #444444, #222222);
41
+ color: #aaa;
42
+ }
43
+ @keyframes pulse {
44
+ 0%, 100% { opacity: 1; }
45
+ 50% { opacity: 0.7; }
46
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </style>
48
  """, unsafe_allow_html=True)
49
 
 
50
  if 'recording' not in st.session_state:
51
  st.session_state.recording = False
52
  if 'process' not in st.session_state:
53
  st.session_state.process = None
 
 
54
  if 'stream_url' not in st.session_state:
55
  st.session_state.stream_url = None
 
 
 
 
56
  if 'username' not in st.session_state:
57
  st.session_state.username = None
58
  if 'video_path' not in st.session_state:
59
  st.session_state.video_path = None
60
+ if 'start_time' not in st.session_state:
61
+ st.session_state.start_time = None
62
 
 
63
  OUTPUT_DIR = "/tmp/tiktok_recordings"
64
  Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
65
 
66
  def check_tiktok_live(username):
 
67
  try:
68
  from TikTokLive import TikTokLiveClient
 
69
  client = TikTokLiveClient(unique_id=f"@{username}")
70
  is_live = client.fetch_is_live()
71
 
72
  if not is_live:
73
+ return {'is_live': False, 'error': 'User not live'}
74
 
75
  room_info = client.fetch_room_info()
 
76
  stream_url = None
77
+
78
  if hasattr(room_info, 'stream_url') and room_info.stream_url:
79
+ if hasattr(room_info.stream_url, 'hls_pull_url'):
80
  stream_url = room_info.stream_url.hls_pull_url
81
+ elif hasattr(room_info.stream_url, 'flv_pull_url'):
82
  stream_url = room_info.stream_url.flv_pull_url
83
 
84
  return {
85
  'is_live': True,
86
  'stream_url': stream_url,
87
  'room_id': getattr(room_info, 'webcast_room_id', None),
 
88
  'viewer_count': getattr(room_info, 'user_count', 0)
89
  }
90
  except Exception as e:
91
  return {'is_live': False, 'error': str(e)}
92
 
93
+ def start_recording(stream_url, output_path):
 
94
  try:
95
+ cmd = ['ffmpeg', '-y', '-re', '-i', stream_url, '-c:v', 'copy', '-c:a', 'copy', '-f', 'mp4', output_path]
96
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  return process
98
  except Exception as e:
99
+ st.error(f"Error: {e}")
100
  return None
101
 
102
+ def stop_recording():
 
103
  if st.session_state.process:
104
  try:
105
  st.session_state.process.terminate()
106
  st.session_state.process.wait(timeout=5)
107
  except:
108
  st.session_state.process.kill()
109
+ st.session_state.process = None
110
+ st.session_state.recording = False
 
111
 
112
+ def format_time(seconds):
113
+ mins = int(seconds // 60)
 
 
114
  secs = int(seconds % 60)
115
+ return f"{mins}:{secs:02d}"
 
 
 
 
 
 
 
 
 
 
116
 
117
+ def upload_to_telegram(file_path, bot_token, chat_id):
 
118
  try:
119
  url = f"https://api.telegram.org/bot{bot_token}/sendVideo"
 
120
  with open(file_path, 'rb') as video:
121
  files = {'video': video}
122
  data = {
123
  'chat_id': chat_id,
124
+ 'caption': f"๐Ÿ“น TikTok Live - @{st.session_state.username}"
125
  }
 
126
  response = requests.post(url, files=files, data=data, timeout=300)
 
127
  if response.status_code == 200:
128
  result = response.json()
129
  if result.get('ok'):
130
+ return {'success': True, 'file_id': result['result']['video']['file_id']}
131
+ return {'success': False, 'error': 'Upload failed'}
 
 
 
 
 
 
132
  except Exception as e:
133
  return {'success': False, 'error': str(e)}
134
 
135
+ # UI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  st.markdown('<h1 class="main-header">๐Ÿ“น TikTok Live Recorder</h1>', unsafe_allow_html=True)
137
+ st.markdown("Record TikTok live streams and upload to Telegram for free unlimited storage")
138
 
139
+ st.markdown("### ๐Ÿ”— Connect to Live Stream")
140
+ username_input = st.text_input("TikTok Username", placeholder="Enter username without @")
141
 
142
+ col1, col2, col3 = st.columns(3)
143
+
144
+ with col1:
145
+ if st.button("๐Ÿ” Check Live", use_container_width=True):
146
+ if username_input:
147
+ with st.spinner("Checking..."):
148
+ result = check_tiktok_live(username_input)
149
+ if result.get('is_live'):
150
+ st.success(f"โœ… @{username_input} is LIVE!")
151
+ st.session_state.stream_url = result.get('stream_url')
152
+ st.session_state.username = username_input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  st.rerun()
154
+ else:
155
+ st.error(f"โŒ Not live: {result.get('error', '')}")
156
+
157
+ with col2:
158
+ if st.session_state.stream_url:
 
 
 
 
 
 
 
 
 
 
159
  if st.session_state.recording:
160
+ if st.button("โน๏ธ Stop", type="secondary", use_container_width=True):
161
+ stop_recording()
162
+ st.success("Recording stopped!")
163
+ st.rerun()
 
164
  else:
165
+ if st.button("๐Ÿ”ด Record", type="primary", use_container_width=True):
166
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
167
+ output_path = f"{OUTPUT_DIR}/tiktok_{st.session_state.username}_{timestamp}.mp4"
168
+ st.session_state.process = start_recording(st.session_state.stream_url, output_path)
169
+ if st.session_state.process:
170
+ st.session_state.recording = True
171
+ st.session_state.start_time = time.time()
172
+ st.session_state.video_path = output_path
173
+ st.success("๐ŸŽฌ Recording started!")
174
+ st.rerun()
175
+
176
+ with col3:
177
  if st.session_state.recording:
178
+ st.markdown('<div class="status-box status-recording">๐Ÿ”ด Recording</div>', unsafe_allow_html=True)
179
+ elif st.session_state.stream_url:
180
+ st.markdown('<div class="status-box status-ready">๐ŸŸข Ready</div>', unsafe_allow_html=True)
181
+ else:
182
+ st.markdown('<div class="status-box status-offline">โšช Offline</div>', unsafe_allow_html=True)
183
+
184
+ if st.session_state.recording:
185
+ st.divider()
186
+ duration = time.time() - st.session_state.start_time
187
+ col1, col2, col3 = st.columns(3)
188
+ col1.metric("Status", "๐Ÿ”ด Recording")
189
+ col2.metric("Duration", format_time(duration))
190
+ col3.metric("User", f"@{st.session_state.username}")
191
+ st.progress(1.0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
+ if not st.session_state.recording and st.session_state.video_path and os.path.exists(st.session_state.video_path):
194
+ st.divider()
195
+ st.markdown("### โ˜๏ธ Upload Video")
196
 
197
+ file_size = os.path.getsize(st.session_state.video_path) / (1024 * 1024)
198
+ st.info(f"๐Ÿ“Š File size: {file_size:.1f} MB")
 
 
 
 
 
 
199
 
200
+ upload_method = st.radio("Choose upload method", ["Telegram (Unlimited Free)", "Internet Archive", "Download Only"], index=0)
 
 
 
 
 
 
201
 
202
+ if upload_method == "Telegram (Unlimited Free)":
203
+ with st.expander("โš™๏ธ Telegram Settings", expanded=True):
204
+ bot_token = st.text_input("Bot Token", placeholder="123456:ABC-DEF...", type="password")
205
+ chat_id = st.text_input("Chat ID", placeholder="-1001234567890")
206
+ st.info("**Get Bot Token:** @BotFather on Telegram\n\n**Get Chat ID:** @getmyid_bot on Telegram")
207
+
208
+ if st.button("๐Ÿ“ค Upload to Telegram", type="primary"):
209
+ if bot_token and chat_id:
210
+ with st.spinner("Uploading to Telegram..."):
211
+ result = upload_to_telegram(st.session_state.video_path, bot_token, chat_id)
212
+ if result.get('success'):
213
+ st.success(f"โœ… Uploaded! File ID: {result['file_id']}")
214
+ else:
215
+ st.error(f"โŒ {result.get('error', 'Failed')}")
216
+ else:
217
+ st.error("Please enter Bot Token and Chat ID")
218
 
219
+ elif upload_method == "Internet Archive":
220
+ if st.button("๐Ÿ“ค Prepare Archive.org Upload"):
221
+ identifier = f"tiktok-{st.session_state.username}-{int(time.time())}"
222
+ upload_url = f"https://archive.org/services/edit/{identifier}"
223
+ st.success("Ready to upload!")
224
+ st.link_button("๐ŸŒ Go to Upload Page", upload_url)
 
225
 
 
226
  st.divider()
227
+ with open(st.session_state.video_path, 'rb') as f:
228
+ st.download_button(
229
+ label="โฌ‡๏ธ Download MP4",
230
+ data=f,
231
+ file_name=f"tiktok_{st.session_state.username}.mp4",
232
+ mime="video/mp4",
233
+ use_container_width=True
234
+ )
 
 
 
 
 
 
 
 
 
235
 
 
236
  st.divider()
237
  st.markdown("""
238
+ **โš ๏ธ Notes:**
239
+ - Recordings are temporary - download or upload immediately
240
+ - User must be LIVE to record
241
+ - Respect content creators' rights
242
+ """)
 
 
243
 
244
+ with st.expander("๐Ÿ“– How to Setup Telegram"):
 
245
  st.markdown("""
246
+ ### Step 1: Create Bot
247
+ 1. Open Telegram
248
+ 2. Search @BotFather
249
+ 3. Send /newbot
250
+ 4. Save the Token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ ### Step 2: Create Channel
253
+ 1. New Channel in Telegram
254
+ 2. Add your bot as Admin
255
 
256
+ ### Step 3: Get Chat ID
257
+ 1. Search @getmyid_bot
258
+ 2. Send any message
259
+ 3. Copy Current Chat ID
 
 
260
  """)