webui / app.py
hoainamgo's picture
Upload app.py with huggingface_hub
9f07c00 verified
Raw History Blame Contribute Delete
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)