hoainamgo commited on
Commit
24af2f3
·
verified ·
1 Parent(s): 10df199

Upload folder using huggingface_hub

Browse files
Files changed (8) hide show
  1. Dockerfile +21 -0
  2. README.md +8 -6
  3. README.md.bak +18 -0
  4. app.py +226 -3
  5. app.py.bak +207 -0
  6. packages.txt +1 -0
  7. requirements.txt +4 -0
  8. requirements.txt.bak +4 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Cài đặt ffmpeg và các công cụ cần thiết
4
+ RUN apt-get update && \
5
+ apt-get install -y --no-install-recommends ffmpeg curl procps && \
6
+ apt-get clean && \
7
+ rm -rf /var/lib/apt/lists/*
8
+
9
+ WORKDIR /app
10
+
11
+ # Cài đặt python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy toàn bộ mã nguồn và video
16
+ COPY . .
17
+
18
+ # Mở cổng 7860 (cổng mặc định của Hugging Face Spaces)
19
+ EXPOSE 7860
20
+
21
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,12 +1,14 @@
1
  ---
2
- title: Webui
3
- emoji: 💻
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 5.9.1
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
  ---
2
+ title: YouTube 24/7 Live Streamer
3
+ emoji: 🔴
4
+ colorFrom: red
5
+ colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 4.26.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # YouTube 24/7 Live Stream Space - Album Đông A Tiêu Dao Lục
13
+
14
+ Live stream 24/7 10 MV TriLyric 1080p theo chuẩn `livestream-ops-master`.
README.md.bak ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: YouTube 24/7 Live Streamer
3
+ emoji: 🔴
4
+ colorFrom: red
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # YouTube 24/7 Live Stream Space
11
+
12
+ Hệ thống phát Live Stream YouTube 24/7 vận hành theo chuẩn `livestream-ops-master`.
13
+
14
+ ### Cách sử dụng:
15
+ 1. Vào mục **Settings** của Space này -> **Variables and secrets**.
16
+ 2. Thêm Secret: `YOUTUBE_STREAM_KEY` = Mã khóa luồng YouTube của bạn.
17
+ 3. Space sẽ tự động kết nối và phát live liên tục 24/7.
18
+ 4. Dùng **UptimeRobot** ping vào URL của Space để giữ online vĩnh viễn không bị sleep.
app.py CHANGED
@@ -1,4 +1,227 @@
1
- # import subprocess
 
 
 
 
 
 
 
2
 
3
- # Start Open WebUI server
4
- subprocess.run(["open-webui", "serve"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ import psutil
5
+ import subprocess
6
+ import threading
7
+ import gradio as gr
8
+ import gdown
9
 
10
+ STREAM_PROCESS = None
11
+ STREAM_LOGS = []
12
+ IS_STREAMING = False
13
+ IS_DOWNLOADING = False
14
+ START_TIME = None
15
+
16
+ DEFAULT_KEY = os.getenv("YOUTUBE_STREAM_KEY", "sztg-vq5m-7855-t6hz-69zu")
17
+ READY_DIR = "ready_mvs"
18
+ PLAYLIST_FILE = "playlist.txt"
19
+
20
+ MVS = [
21
+ {"id": "1ghqyGINCZbfA7KajlwNpetVq5xclTzQW", "name": "ready_01_MV_TieuNgaoDongA_full_v4_trilyric.mp4", "title": "MV 01: Tiếu Ngạo Đông A"},
22
+ {"id": "1g2-fyGwxpmCglhg3_DT8u7Ugno88UG8D", "name": "ready_02_MV_LaughterBlueSea_Full_TriLyric_1080p.mp4", "title": "MV 02: Laughter in the Blue Sea"},
23
+ {"id": "14md9igmn_JYHgNDAsKbRbE78ZFQfufyr", "name": "ready_03_MV_CamKiemGiangHo_Full_TriLyric_1080p.mp4", "title": "MV 03: Cầm Kiếm Giang Hồ"},
24
+ {"id": "1FNXyQA2El-Ikjep-VVmKLFnh9N0BVIbH", "name": "ready_04_MV_DrinkingAloneLucDau_Full_TriLyric_1080p.mp4", "title": "MV 04: Độc Ẩm Lục Đầu Giang"},
25
+ {"id": "1FXoOUAlkqdvMZLGAtsDBhP3Gzula7s-v", "name": "ready_05_MV_WindPhuVanPeak_Full_TriLyric_1080p.mp4", "title": "MV 05: Gió Đỉnh Phù Vân"},
26
+ {"id": "1AzRqFArjLqKtbQdJXx000FUKse87Edar", "name": "ready_06_MV_BenVangCanChen_Full_TriLyric_1080p.mp4", "title": "MV 06: Bến Vắng Cạn Chén"},
27
+ {"id": "1QeCXltTbGW5GSXrrN8t4gJnQnmrQrbho", "name": "ready_07_MV_ThaoThaoBachDang_Full_TriLyric_1080p.mp4", "title": "MV 07: Thao Thao Bạch Đằng"},
28
+ {"id": "1BwC1CyvYD_r5Ar75JgfHIW6pu8IOGX5-", "name": "ready_08_MV_CarefreeLaughter_Full_TriLyric_1080p.mp4", "title": "MV 08: Tiếu Ngạo Nhân Sinh"},
29
+ {"id": "1E-A2_0am9x0NCBOneFKZf_ty_OYUNgd6", "name": "ready_09_MV_GacKiemLoGom_Full_TriLyric_1080p.mp4", "title": "MV 09: Gác Kiếm Lò Gốm"},
30
+ {"id": "1f19iqmUGPH31aDNT6DLy8lTA9pGw9Pqv", "name": "ready_10_MV_VanDamKhaiHoan_Full_TriLyric_1080p.mp4", "title": "MV 10: Vạn Dặm Khải Hoàn"}
31
+ ]
32
+
33
+ def append_log(msg):
34
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
35
+ log_line = f"[{timestamp}] {msg}"
36
+ print(log_line)
37
+ STREAM_LOGS.append(log_line)
38
+ if len(STREAM_LOGS) > 150:
39
+ STREAM_LOGS.pop(0)
40
+
41
+ def ensure_all_mvs_ready():
42
+ global IS_DOWNLOADING
43
+ os.makedirs(READY_DIR, exist_ok=True)
44
+ missing = [mv for mv in MVS if not (os.path.exists(os.path.join(READY_DIR, mv["name"])) and os.path.getsize(os.path.join(READY_DIR, mv["name"])) > 1024*1024)]
45
+
46
+ if not missing:
47
+ append_log("[OK] Tất cả 10 MV đã sẵn sàng trong thư mục ready_mvs.")
48
+ generate_playlist()
49
+ return True
50
+
51
+ IS_DOWNLOADING = True
52
+ append_log(f"Đang tải {len(missing)} MV từ Google Drive vào hệ thống...")
53
+
54
+ for idx, mv in enumerate(missing, 1):
55
+ target = os.path.join(READY_DIR, mv["name"])
56
+ url = f"https://drive.google.com/uc?id={mv['id']}"
57
+ append_log(f"[{idx}/{len(missing)}] Đang tải: {mv['title']}...")
58
+ try:
59
+ gdown.download(url, target, quiet=True)
60
+ if os.path.exists(target) and os.path.getsize(target) > 1024*1024:
61
+ append_log(f"-> Hoàn tất: {mv['name']} ({os.path.getsize(target)/(1024*1024):.1f} MB)")
62
+ else:
63
+ append_log(f"[CẢNH BÁO] File {mv['name']} tải không thành công.")
64
+ except Exception as e:
65
+ append_log(f"[LỖI TẢI] {mv['title']}: {str(e)}")
66
+
67
+ IS_DOWNLOADING = False
68
+ generate_playlist()
69
+ return True
70
+
71
+ def generate_playlist():
72
+ os.makedirs(READY_DIR, exist_ok=True)
73
+ lines = []
74
+ for mv in MVS:
75
+ fpath = os.path.join(READY_DIR, mv["name"])
76
+ if os.path.exists(fpath) and os.path.getsize(fpath) > 1024*1024:
77
+ lines.append(f"file '{fpath.replace(chr(92), '/')}'")
78
+
79
+ if lines:
80
+ with open(PLAYLIST_FILE, "w", encoding="utf-8") as f:
81
+ f.write("\n".join(lines) + "\n")
82
+ append_log(f"Đã cập nhật playlist {PLAYLIST_FILE} ({len(lines)} MV).")
83
+ return True
84
+ return False
85
+
86
+ def stream_worker(key: str):
87
+ global STREAM_PROCESS, IS_STREAMING, START_TIME
88
+ ensure_all_mvs_ready()
89
+
90
+ if not os.path.exists(PLAYLIST_FILE) or os.path.getsize(PLAYLIST_FILE) == 0:
91
+ append_log("[LỖI] Playlist trống! Không thể phát Live.")
92
+ IS_STREAMING = False
93
+ return
94
+
95
+ IS_STREAMING = True
96
+ START_TIME = time.time()
97
+ rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{key}"
98
+
99
+ append_log(f"Bắt đầu đẩy luồng RTMP lên YouTube Live (-c:v copy pass-through)...")
100
+
101
+ cmd = [
102
+ "ffmpeg",
103
+ "-re",
104
+ "-f", "concat",
105
+ "-safe", "0",
106
+ "-stream_loop", "-1",
107
+ "-i", PLAYLIST_FILE,
108
+ "-c:v", "copy",
109
+ "-c:a", "aac",
110
+ "-b:a", "128k",
111
+ "-ar", "48000",
112
+ "-f", "flv",
113
+ rtmp_url
114
+ ]
115
+
116
+ while IS_STREAMING:
117
+ try:
118
+ append_log("Khởi động FFmpeg Stream Engine...")
119
+ STREAM_PROCESS = subprocess.Popen(
120
+ cmd,
121
+ stdout=subprocess.PIPE,
122
+ stderr=subprocess.STDOUT,
123
+ universal_newlines=True
124
+ )
125
+ for line in STREAM_PROCESS.stdout:
126
+ if not IS_STREAMING:
127
+ break
128
+ if "frame=" in line or "bitrate=" in line or "fps=" in line:
129
+ if len(STREAM_LOGS) > 0 and ("frame=" in STREAM_LOGS[-1] or "bitrate=" in STREAM_LOGS[-1]):
130
+ STREAM_LOGS[-1] = f"[{time.strftime('%H:%M:%S')}] {line.strip()}"
131
+ else:
132
+ append_log(line.strip())
133
+ elif any(kw in line.lower() for kw in ["error", "warning", "opening"]):
134
+ append_log(line.strip())
135
+
136
+ STREAM_PROCESS.wait()
137
+ if IS_STREAMING:
138
+ append_log("[CẢNH BÁO] Luồng ngắt quãng. Tự động kết nối lại sau 10s...")
139
+ time.sleep(10)
140
+ except Exception as e:
141
+ append_log(f"[LỖI ENGINE] {str(e)}")
142
+ time.sleep(10)
143
+
144
+ def start_stream(key):
145
+ global IS_STREAMING
146
+ if not key or not key.strip():
147
+ return "Vui lòng nhập YouTube Stream Key!"
148
+ if IS_STREAMING:
149
+ return "Luồng Live đã đang chạy!"
150
+
151
+ t = threading.Thread(target=stream_worker, args=(key.strip(),), daemon=True)
152
+ t.start()
153
+ return "Đã kích hoạt lệnh phát Live 24/7!"
154
+
155
+ def stop_stream():
156
+ global IS_STREAMING, STREAM_PROCESS
157
+ IS_STREAMING = False
158
+ if STREAM_PROCESS:
159
+ STREAM_PROCESS.terminate()
160
+ append_log("Đã gửi lệnh dừng live.")
161
+ return "Đã dừng Live."
162
+
163
+ def get_status():
164
+ uptime_str = "Chưa phát"
165
+ if IS_STREAMING and START_TIME:
166
+ elapsed = int(time.time() - START_TIME)
167
+ h, rem = divmod(elapsed, 3600)
168
+ m, s = divmod(rem, 60)
169
+ uptime_str = f"{h}h {m}m {s}s"
170
+
171
+ status_txt = "🔴 ĐANG LIVE 24/7" if IS_STREAMING else "⚪ ĐÃ DỪNG"
172
+ if IS_DOWNLOADING:
173
+ status_txt += " (Đang tải MV...)"
174
+
175
+ cpu = f"{psutil.cpu_percent()}%"
176
+ ram = f"{psutil.virtual_memory().percent}%"
177
+
178
+ mv_data = []
179
+ for idx, mv in enumerate(MVS, 1):
180
+ fpath = os.path.join(READY_DIR, mv["name"])
181
+ exists = os.path.exists(fpath) and os.path.getsize(fpath) > 1024*1024
182
+ sz = f"{os.path.getsize(fpath)/(1024*1024):.1f} MB" if exists else "Chưa có"
183
+ st = "✅ Sẵn sàng" if exists else "⏳ Chờ tải"
184
+ mv_data.append([idx, mv["title"], sz, st])
185
+
186
+ logs_txt = "\n".join(STREAM_LOGS[-30:])
187
+ return status_txt, uptime_str, cpu, ram, mv_data, logs_txt
188
+
189
+ # Tự động tải nền và start nếu có stream key
190
+ threading.Thread(target=ensure_all_mvs_ready, daemon=True).start()
191
+ if DEFAULT_KEY:
192
+ threading.Thread(target=stream_worker, args=(DEFAULT_KEY,), daemon=True).start()
193
+
194
+ with gr.Blocks(title="YouTube 24/7 Live Streamer") as demo:
195
+ gr.Markdown("# 🎬 Đông A Tiêu Dao Lục - YouTube Live Stream 24/7 Controller")
196
+ gr.Markdown("Vận hành theo chuẩn **`livestream-ops-master`** (Concat Demuxer, GOP 2s, 0 Drop frames).")
197
+
198
+ with gr.Row():
199
+ status_box = gr.Textbox(label="Trạng thái", value="Đang khởi tạo...", interactive=False)
200
+ uptime_box = gr.Textbox(label="Thời gian Live", value="0s", interactive=False)
201
+ cpu_box = gr.Textbox(label="CPU", value="0%", interactive=False)
202
+ ram_box = gr.Textbox(label="RAM", value="0%", interactive=False)
203
+
204
+ with gr.Row():
205
+ key_input = gr.Textbox(label="YouTube Stream Key", value=DEFAULT_KEY, type="password", scale=3)
206
+ start_btn = gr.Button("▶️ Bắt Đầu Live", variant="primary", scale=1)
207
+ stop_btn = gr.Button("⏹️ Dừng Live", variant="stop", scale=1)
208
+
209
+ gr.Markdown("### 📂 Danh mục 10 MV TriLyric 1080p (Playlist Loop)")
210
+ mv_table = gr.Dataframe(
211
+ headers=["#", "Tên MV", "Dung lượng", "Trạng thái"],
212
+ datatype=["number", "str", "str", "str"],
213
+ value=[]
214
+ )
215
+
216
+ gr.Markdown("### 📜 Nhật ký phát sóng (Live Logs)")
217
+ logs_box = gr.TextArea(label="FFmpeg Logs", lines=10, interactive=False)
218
+
219
+ start_btn.click(fn=start_stream, inputs=[key_input], outputs=[status_box])
220
+ stop_btn.click(fn=stop_stream, inputs=[], outputs=[status_box])
221
+
222
+ # Tự động cập nhật mỗi 5 giây
223
+ timer = gr.Timer(value=5)
224
+ timer.tick(fn=get_status, outputs=[status_box, uptime_box, cpu_box, ram_box, mv_table, logs_box])
225
+
226
+ if __name__ == "__main__":
227
+ demo.launch(server_name="0.0.0.0", server_port=7860)
app.py.bak ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ import psutil
5
+ import subprocess
6
+ import threading
7
+ from fastapi import FastAPI, Request, Form
8
+ from fastapi.responses import HTMLResponse, JSONResponse
9
+ import uvicorn
10
+
11
+ app = FastAPI(title="YouTube 24/7 Live Streamer")
12
+
13
+ STREAM_PROCESS = None
14
+ STREAM_LOGS = []
15
+ IS_STREAMING = False
16
+ START_TIME = None
17
+
18
+ VIDEO_PATH = os.getenv("VIDEO_PATH", "stream_video.mp4")
19
+ STREAM_KEY = os.getenv("YOUTUBE_STREAM_KEY", "")
20
+
21
+ def append_log(msg):
22
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
23
+ log_line = f"[{timestamp}] {msg}"
24
+ print(log_line)
25
+ STREAM_LOGS.append(log_line)
26
+ if len(STREAM_LOGS) > 100:
27
+ STREAM_LOGS.pop(0)
28
+
29
+ def stream_worker(key: str):
30
+ global STREAM_PROCESS, IS_STREAMING, START_TIME
31
+ IS_STREAMING = True
32
+ START_TIME = time.time()
33
+ rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{key}"
34
+
35
+ append_log(f"Bắt đầu luồng phát Live Stream tới YouTube...")
36
+
37
+ # Check if video exists
38
+ target_video = VIDEO_PATH
39
+ if not os.path.exists(target_video):
40
+ # find first mp4 file
41
+ mp4_files = [f for f in os.listdir(".") if f.endswith(".mp4")]
42
+ if mp4_files:
43
+ target_video = mp4_files[0]
44
+ append_log(f"Sử dụng file video tìm thấy: {target_video}")
45
+ else:
46
+ append_log(f"[ERROR] Không tìm thấy file video .mp4 nào trong thư mục!")
47
+ IS_STREAMING = False
48
+ return
49
+
50
+ cmd = [
51
+ "ffmpeg",
52
+ "-re",
53
+ "-stream_loop", "-1",
54
+ "-i", target_video,
55
+ "-c:v", "copy",
56
+ "-c:a", "aac",
57
+ "-b:a", "128k",
58
+ "-ar", "48000",
59
+ "-f", "flv",
60
+ rtmp_url
61
+ ]
62
+
63
+ while IS_STREAMING:
64
+ try:
65
+ append_log("Khởi chạy tiến trình FFmpeg...")
66
+ STREAM_PROCESS = subprocess.Popen(
67
+ cmd,
68
+ stdout=subprocess.PIPE,
69
+ stderr=subprocess.STDOUT,
70
+ universal_newlines=True
71
+ )
72
+ for line in STREAM_PROCESS.stdout:
73
+ if not IS_STREAMING:
74
+ break
75
+ if "frame=" in line or "bitrate=" in line:
76
+ if len(STREAM_LOGS) > 0 and ("frame=" in STREAM_LOGS[-1] or "bitrate=" in STREAM_LOGS[-1]):
77
+ STREAM_LOGS[-1] = f"[{time.strftime('%H:%M:%S')}] {line.strip()}"
78
+ else:
79
+ append_log(line.strip())
80
+ elif "error" in line.lower() or "warning" in line.lower():
81
+ append_log(line.strip())
82
+
83
+ STREAM_PROCESS.wait()
84
+ if IS_STREAMING:
85
+ append_log("[CẢNH BÁO] Luồng FFmpeg bị gián đoạn. Tự động khởi động lại sau 10 giây...")
86
+ time.sleep(10)
87
+ except Exception as e:
88
+ append_log(f"[LỖI] {str(e)}")
89
+ time.sleep(10)
90
+
91
+ def start_background_stream(key: str):
92
+ t = threading.Thread(target=stream_worker, args=(key,), daemon=True)
93
+ t.start()
94
+
95
+ @app.on_event("startup")
96
+ def on_startup():
97
+ append_log("Hệ thống khởi động thành công.")
98
+ if STREAM_KEY:
99
+ append_log("Phát hiện Stream Key từ Environment/Secrets. Tự động bắt đầu live...")
100
+ start_background_stream(STREAM_KEY)
101
+
102
+ @app.get("/health")
103
+ @app.get("/ping")
104
+ def health_check():
105
+ return {
106
+ "status": "healthy",
107
+ "streaming": IS_STREAMING,
108
+ "uptime": f"{int(time.time() - START_TIME)}s" if START_TIME else "0s"
109
+ }
110
+
111
+ @app.get("/", response_class=HTMLResponse)
112
+ def index():
113
+ uptime_str = "Chưa phát"
114
+ if IS_STREAMING and START_TIME:
115
+ elapsed = int(time.time() - START_TIME)
116
+ hours, remainder = divmod(elapsed, 3600)
117
+ minutes, seconds = divmod(remainder, 60)
118
+ uptime_str = f"{hours}h {minutes}m {seconds}s"
119
+
120
+ cpu_usage = psutil.cpu_percent()
121
+ ram_usage = psutil.virtual_memory().percent
122
+
123
+ log_html = "<br>".join(STREAM_LOGS[-25:])
124
+
125
+ status_badge = '<span style="background:#28a745;color:white;padding:4px 10px;border-radius:15px;font-weight:bold;">Đang Phát Live 24/7</span>' if IS_STREAMING else '<span style="background:#dc3545;color:white;padding:4px 10px;border-radius:15px;font-weight:bold;">Đang Dừng</span>'
126
+
127
+ html = f"""
128
+ <!DOCTYPE html>
129
+ <html lang="vi">
130
+ <head>
131
+ <meta charset="UTF-8">
132
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
133
+ <title>YouTube Live Stream Controller</title>
134
+ <meta http-equiv="refresh" content="10">
135
+ <style>
136
+ body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; margin: 0; padding: 20px; }}
137
+ .container {{ max-width: 900px; margin: auto; background: #1e293b; padding: 25px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); }}
138
+ h1 {{ color: #38bdf8; margin-top: 0; display: flex; align-items: center; justify-content: space-between; }}
139
+ .stats {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }}
140
+ .card {{ background: #0f172a; padding: 15px; border-radius: 8px; border: 1px solid #334155; }}
141
+ .card h3 {{ margin: 0 0 5px 0; font-size: 14px; color: #94a3b8; }}
142
+ .card p {{ margin: 0; font-size: 20px; font-weight: bold; color: #f1f5f9; }}
143
+ .logs {{ background: #020617; padding: 15px; border-radius: 8px; font-family: monospace; font-size: 12px; max-height: 300px; overflow-y: auto; color: #a3e635; border: 1px solid #1e293b; }}
144
+ .btn {{ background: #0284c7; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 14px; }}
145
+ .btn-danger {{ background: #e11d48; }}
146
+ input[type="password"], input[type="text"] {{ width: 70%; padding: 10px; border-radius: 6px; border: 1px solid #475569; background: #0f172a; color: white; box-sizing: border-box; }}
147
+ .form-group {{ margin: 15px 0; }}
148
+ .tip {{ font-size: 13px; color: #94a3b8; margin-top: 15px; }}
149
+ </style>
150
+ </head>
151
+ <body>
152
+ <div class="container">
153
+ <h1>YouTube 24/7 Live Controller {status_badge}</h1>
154
+ <div class="stats">
155
+ <div class="card">
156
+ <h3>Thời gian live</h3>
157
+ <p>{uptime_str}</p>
158
+ </div>
159
+ <div class="card">
160
+ <h3>CPU Sử dụng</h3>
161
+ <p>{cpu_usage}%</p>
162
+ </div>
163
+ <div class="card">
164
+ <h3>RAM Sử dụng</h3>
165
+ <p>{ram_usage}%</p>
166
+ </div>
167
+ </div>
168
+
169
+ <div class="form-group">
170
+ <form action="/action" method="post" style="display:flex; gap:10px;">
171
+ <input type="password" name="key" placeholder="Nhập YouTube Stream Key (nếu chưa cấu hình Secret)..." value="{STREAM_KEY}">
172
+ {'<button type="submit" name="action" value="stop" class="btn btn-danger">Dừng Live</button>' if IS_STREAMING else '<button type="submit" name="action" value="start" class="btn">Bắt Đầu Live</button>'}
173
+ </form>
174
+ </div>
175
+
176
+ <h3>Nhật ký hoạt động (Logs)</h3>
177
+ <div class="logs">
178
+ {log_html if log_html else 'Chưa có nhật ký nào...'}
179
+ </div>
180
+
181
+ <p class="tip">💡 <b>Mẹo giữ 24/7:</b> Dán đường link URL của Space này vào <a href="https://uptimerobot.com" target="_blank" style="color:#38bdf8;">UptimeRobot.com</a> để tự động ping mỗi 5 phút.</p>
182
+ </div>
183
+ </body>
184
+ </html>
185
+ """
186
+ return html
187
+
188
+ @app.post("/action")
189
+ def control_action(action: str = Form(...), key: str = Form(None)):
190
+ global IS_STREAMING, STREAM_PROCESS
191
+ if action == "start":
192
+ use_key = key.strip() if key else STREAM_KEY
193
+ if use_key:
194
+ if not IS_STREAMING:
195
+ start_background_stream(use_key)
196
+ else:
197
+ append_log("[LỖI] Không có Stream Key để bắt đầu.")
198
+ elif action == "stop":
199
+ IS_STREAMING = False
200
+ if STREAM_PROCESS:
201
+ STREAM_PROCESS.terminate()
202
+ append_log("Đã gửi lệnh dừng tiến trình FFmpeg.")
203
+
204
+ return HTMLResponse('<script>window.location.href="/";</script>')
205
+
206
+ if __name__ == "__main__":
207
+ uvicorn.run(app, host="0.0.0.0", port=7860)
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.26.0
2
+ gdown>=5.1.0
3
+ psutil>=5.9.8
4
+ requests>=2.31.0
requirements.txt.bak ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi>=0.110.0
2
+ uvicorn>=0.28.0
3
+ psutil>=5.9.8
4
+ jinja2>=3.1.3