Download app.py from hoainamgo/webui: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/spaces/hoainamgo/webui/resolve/main/app.py
- Command line
-
hf download hf://spaces/hoainamgo/webui/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hoainamgo/webui/resolve/main/app.py
11.3 kB
| import os | |
| import sys | |
| import time | |
| import psutil | |
| import subprocess | |
| import threading | |
| import gradio as gr | |
| import gdown | |
| STREAM_PROCESS = None | |
| STREAM_LOGS = [] | |
| IS_STREAMING = False | |
| IS_DOWNLOADING = False | |
| START_TIME = None | |
| STREAM_LOCK = threading.Lock() | |
| DEFAULT_KEY = os.getenv("YOUTUBE_STREAM_KEY", "sztg-vq5m-7855-t6hz-69zu") | |
| VOLUME_DIR = "/mv" | |
| READY_DIR = "ready_mvs" | |
| PLAYLIST_FILE = "playlist.txt" | |
| # 10 MV TriLyric 1080p chuẩn hóa 4Mbps & GOP 2s từ Google Drive Folder 1AeALxZS5fG4EHJ7JpxoNbs6IX58aRx_j | |
| MVS = [ | |
| {"id": "15932kX_Yzm6yKJ_kXIq8WspjUiuNyOF7", "name": "ready_01_MV_TieuNgaoDongA_full_v4_trilyric.mp4", "title": "MV 01: Tiếu Ngạo Đông A"}, | |
| {"id": "1mr3aO6675NJhu3RZIMaI1KXQjiV9i9TM", "name": "ready_02_MV_LaughterBlueSea_Full_TriLyric_1080p.mp4", "title": "MV 02: Laughter in the Blue Sea"}, | |
| {"id": "1Grsnc11Fh7m0A3AiD5jbnWZMeLxXMAez", "name": "ready_03_MV_CamKiemGiangHo_Full_TriLyric_1080p.mp4", "title": "MV 03: Cầm Kiếm Giang Hồ"}, | |
| {"id": "11FXBf6WAo7w-4rc14Cc_RT8jmGVAm8N0", "name": "ready_04_MV_DrinkingAloneLucDau_Full_TriLyric_1080p.mp4", "title": "MV 04: Độc Ẩm Lục Đầu Giang"}, | |
| {"id": "1IIyLFeCDTWR2sFRz6teYoaSUI974qLH0", "name": "ready_05_MV_WindPhuVanPeak_Full_TriLyric_1080p.mp4", "title": "MV 05: Gió Đỉnh Phù Vân"}, | |
| {"id": "1haOcPs4Ofkf6nhaw97iaZr_eRsWT36xU", "name": "ready_06_MV_BenVangCanChen_Full_TriLyric_1080p.mp4", "title": "MV 06: Bến Vắng Cạn Chén"}, | |
| {"id": "1F6j-j7pQ5-vrmXB3ah3-5ezkHK5j_64L", "name": "ready_07_MV_ThaoThaoBachDang_Full_TriLyric_1080p.mp4", "title": "MV 07: Thao Thao Bạch Đằng"}, | |
| {"id": "1BmAkgv1hh4Ltr1AZqrI_lkOWGSiJma2N", "name": "ready_08_MV_CarefreeLaughter_Full_TriLyric_1080p.mp4", "title": "MV 08: Tiếu Ngạo Nhân Sinh"}, | |
| {"id": "1VAbjxj1FlZhFNnvu6oKFWBE1icAhVo4J", "name": "ready_09_MV_GacKiemLoGom_Full_TriLyric_1080p.mp4", "title": "MV 09: Gác Kiếm Lò Gốm"}, | |
| {"id": "17slGHDwvY36YLf6_AW1emaYxN70GJ9rM", "name": "ready_10_MV_VanDamKhaiHoan_Full_TriLyric_1080p.mp4", "title": "MV 10: Vạn Dặm Khải Hoàn"} | |
| ] | |
| def append_log(msg): | |
| timestamp = time.strftime("%Y-%m-%d %H:%M:%S") | |
| log_line = f"[{timestamp}] {msg}" | |
| print(log_line) | |
| STREAM_LOGS.append(log_line) | |
| if len(STREAM_LOGS) > 150: | |
| STREAM_LOGS.pop(0) | |
| def _detect_ready_dir(): | |
| if os.path.isdir(VOLUME_DIR): | |
| missing_vol = [mv for mv in MVS if not (os.path.exists(os.path.join(VOLUME_DIR, mv["name"])) and os.path.getsize(os.path.join(VOLUME_DIR, mv["name"])) > 1024*1024)] | |
| if not missing_vol: | |
| return VOLUME_DIR | |
| return "ready_mvs" | |
| READY_DIR = _detect_ready_dir() | |
| def ensure_all_mvs_ready(): | |
| global IS_DOWNLOADING | |
| if READY_DIR == VOLUME_DIR: | |
| append_log(f"[VOLUME] Dùng 10 MV read-only từ volume {VOLUME_DIR} - bỏ qua tải Google Drive.") | |
| else: | |
| os.makedirs(READY_DIR, exist_ok=True) | |
| 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)] | |
| if not missing: | |
| append_log("[OK] Tất cả 10 MV chuẩn 4Mbps (GOP 2s) đã sẵn sàng trong thư mục.") | |
| generate_playlist() | |
| return True | |
| IS_DOWNLOADING = True | |
| append_log(f"Đang tải {len(missing)} MV chuẩn 4Mbps từ Google Drive Folder mới...") | |
| for idx, mv in enumerate(missing, 1): | |
| target = os.path.join(READY_DIR, mv["name"]) | |
| url = f"https://drive.google.com/uc?id={mv['id']}" | |
| append_log(f"[{idx}/{len(missing)}] Đang tải: {mv['title']}...") | |
| try: | |
| gdown.download(url, target, quiet=True) | |
| if os.path.exists(target) and os.path.getsize(target) > 1024*1024: | |
| append_log(f"-> Đã tải xong: {mv['name']} ({os.path.getsize(target)/(1024*1024):.1f} MB - Chuẩn 4Mbps GOP 2s)") | |
| else: | |
| append_log(f"[CẢNH BÁO] File {mv['name']} tải không thành công.") | |
| except Exception as e: | |
| append_log(f"[LỖI TẢI] {mv['title']}: {str(e)}") | |
| IS_DOWNLOADING = False | |
| generate_playlist() | |
| return True | |
| def generate_playlist(): | |
| if READY_DIR != VOLUME_DIR: | |
| os.makedirs(READY_DIR, exist_ok=True) | |
| lines = [] | |
| for mv in MVS: | |
| fpath = os.path.join(READY_DIR, mv["name"]) | |
| if os.path.exists(fpath) and os.path.getsize(fpath) > 1024*1024: | |
| lines.append(f"file '{fpath.replace(chr(92), '/')}'") | |
| if lines: | |
| with open(PLAYLIST_FILE, "w", encoding="utf-8") as f: | |
| f.write("\n".join(lines) + "\n") | |
| append_log(f"Đã cập nhật playlist {PLAYLIST_FILE} ({len(lines)} MV chuẩn 4Mbps).") | |
| return True | |
| return False | |
| def kill_existing_ffmpeg(): | |
| killed = 0 | |
| for proc in psutil.process_iter(['pid', 'name']): | |
| try: | |
| if 'ffmpeg' in proc.info['name'].lower(): | |
| proc.kill() | |
| killed += 1 | |
| except Exception: | |
| pass | |
| if killed > 0: | |
| append_log(f"Đã dọn dẹp {killed} tiến trình FFmpeg cũ.") | |
| def stream_worker(key: str): | |
| global STREAM_PROCESS, IS_STREAMING, START_TIME | |
| if not STREAM_LOCK.acquire(blocking=False): | |
| append_log("[CẢNH BÁO] stream_worker đã đang chạy. Bỏ qua lời gọi trùng lặp.") | |
| return | |
| try: | |
| kill_existing_ffmpeg() | |
| ensure_all_mvs_ready() | |
| if not os.path.exists(PLAYLIST_FILE) or os.path.getsize(PLAYLIST_FILE) == 0: | |
| append_log("[LỖI] Playlist trống! Không thể phát Live.") | |
| IS_STREAMING = False | |
| return | |
| IS_STREAMING = True | |
| START_TIME = time.time() | |
| rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{key}" | |
| append_log(f"Bắt đầu đẩy luồng RTMP lên YouTube Live (Chuẩn 4Mbps, GOP 2s, Concat Pass-through -c:v copy)...") | |
| cmd = [ | |
| "ffmpeg", | |
| "-re", | |
| "-f", "concat", | |
| "-safe", "0", | |
| "-stream_loop", "-1", | |
| "-i", PLAYLIST_FILE, | |
| "-c:v", "copy", | |
| "-c:a", "aac", | |
| "-b:a", "128k", | |
| "-ar", "48000", | |
| "-f", "flv", | |
| rtmp_url | |
| ] | |
| while IS_STREAMING: | |
| try: | |
| kill_existing_ffmpeg() | |
| append_log("Khởi động FFmpeg Stream Engine...") | |
| STREAM_PROCESS = subprocess.Popen( | |
| cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| universal_newlines=True | |
| ) | |
| for line in STREAM_PROCESS.stdout: | |
| if not IS_STREAMING: | |
| break | |
| if "frame=" in line or "bitrate=" in line or "fps=" in line: | |
| if len(STREAM_LOGS) > 0 and ("frame=" in STREAM_LOGS[-1] or "bitrate=" in STREAM_LOGS[-1]): | |
| STREAM_LOGS[-1] = f"[{time.strftime('%H:%M:%S')}] {line.strip()}" | |
| else: | |
| append_log(line.strip()) | |
| elif any(kw in line.lower() for kw in ["error", "warning", "opening"]): | |
| append_log(line.strip()) | |
| STREAM_PROCESS.wait() | |
| if IS_STREAMING: | |
| append_log("[CẢNH BÁO] Luồng ngắt quãng. Tự động kết nối lại sau 10s...") | |
| time.sleep(10) | |
| except Exception as e: | |
| append_log(f"[LỖI ENGINE] {str(e)}") | |
| time.sleep(10) | |
| finally: | |
| kill_existing_ffmpeg() | |
| STREAM_LOCK.release() | |
| def start_stream(key): | |
| global IS_STREAMING | |
| if not key or not key.strip(): | |
| return "Vui lòng nhập YouTube Stream Key!" | |
| if IS_STREAMING: | |
| return "Luồng Live đã đang chạy!" | |
| t = threading.Thread(target=stream_worker, args=(key.strip(),), daemon=True) | |
| t.start() | |
| return "Đã kích hoạt lệnh phát Live 24/7!" | |
| def stop_stream(): | |
| global IS_STREAMING, STREAM_PROCESS | |
| IS_STREAMING = False | |
| kill_existing_ffmpeg() | |
| if STREAM_PROCESS: | |
| try: | |
| STREAM_PROCESS.terminate() | |
| except Exception: | |
| pass | |
| append_log("Đã gửi lệnh dừng live.") | |
| return "Đã dừng Live." | |
| def get_status(): | |
| uptime_str = "Chưa phát" | |
| if IS_STREAMING and START_TIME: | |
| elapsed = int(time.time() - START_TIME) | |
| h, rem = divmod(elapsed, 3600) | |
| m, s = divmod(rem, 60) | |
| uptime_str = f"{h}h {m}m {s}s" | |
| status_txt = "🔴 ĐANG LIVE 24/7" if IS_STREAMING else "⚪ ĐÃ DỪNG" | |
| if IS_DOWNLOADING: | |
| status_txt += " (Đang tải MV chuẩn 4Mbps...)" | |
| cpu = f"{psutil.cpu_percent()}%" | |
| ram = f"{psutil.virtual_memory().percent}%" | |
| mv_data = [] | |
| for idx, mv in enumerate(MVS, 1): | |
| fpath = os.path.join(READY_DIR, mv["name"]) | |
| exists = os.path.exists(fpath) and os.path.getsize(fpath) > 1024*1024 | |
| sz = f"{os.path.getsize(fpath)/(1024*1024):.1f} MB" if exists else "Chưa có" | |
| st = "✅ Chuẩn 4Mbps" if exists else "⏳ Chờ tải" | |
| mv_data.append([idx, mv["title"], sz, st]) | |
| logs_txt = "\n".join(STREAM_LOGS[-30:]) | |
| return status_txt, uptime_str, cpu, ram, mv_data, logs_txt | |
| if DEFAULT_KEY: | |
| threading.Thread(target=stream_worker, args=(DEFAULT_KEY,), daemon=True).start() | |
| else: | |
| threading.Thread(target=ensure_all_mvs_ready, daemon=True).start() | |
| with gr.Blocks(title="YouTube 24/7 Live Streamer") as demo: | |
| gr.Markdown("# 🎬 Đông A Tiêu Dao Lục - YouTube Live Stream 24/7 (Chuẩn 4Mbps / GOP 2s)") | |
| gr.Markdown("Nguồn video: Google Drive Folder `1AeALxZS5fG4EHJ7JpxoNbs6IX58aRx_j` | Vận hành theo chuẩn **`livestream-ops-master`** (Concat Demuxer, GOP 2s, 0 Drop frames).") | |
| with gr.Row(): | |
| status_box = gr.Textbox(label="Trạng thái", value="Đang khởi tạo...", interactive=False) | |
| uptime_box = gr.Textbox(label="Thời gian Live", value="0s", interactive=False) | |
| cpu_box = gr.Textbox(label="CPU", value="0%", interactive=False) | |
| ram_box = gr.Textbox(label="RAM", value="0%", interactive=False) | |
| with gr.Row(): | |
| key_input = gr.Textbox(label="YouTube Stream Key", value=DEFAULT_KEY, type="password", scale=3) | |
| start_btn = gr.Button("▶️ Bắt Đầu Live", variant="primary", scale=1) | |
| stop_btn = gr.Button("⏹️ Dừng Live", variant="stop", scale=1) | |
| gr.Markdown("### 📂 Danh mục 10 MV TriLyric 1080p (Chuẩn hóa 4Mbps & GOP 2s)") | |
| mv_table = gr.Dataframe( | |
| headers=["#", "Tên MV", "Dung lượng", "Trạng thái"], | |
| datatype=["number", "str", "str", "str"], | |
| value=[] | |
| ) | |
| gr.Markdown("### 📜 Nhật ký phát sóng (Live Logs)") | |
| logs_box = gr.TextArea(label="FFmpeg Logs", lines=10, interactive=False) | |
| start_btn.click(fn=start_stream, inputs=[key_input], outputs=[status_box]) | |
| stop_btn.click(fn=stop_stream, inputs=[], outputs=[status_box]) | |
| timer = gr.Timer(value=5) | |
| timer.tick(fn=get_status, outputs=[status_box, uptime_box, cpu_box, ram_box, mv_table, logs_box]) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |