| import os |
| import sys |
| import time |
| import subprocess |
| import threading |
| import http.server |
| import socketserver |
|
|
| |
| |
| |
| INPUT_STREAM = "http://10k.lucastv.pro/2c99755ae255/gJnMAT2/465825" |
| OUTPUT_DIR = "/app/output_hls" |
| PORT = 7860 |
|
|
| HTTP_HEADERS = ( |
| "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) " |
| "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\r\n" |
| "Referer: https://xeno.env.pm/\r\n" |
| ) |
|
|
| LOCAL_LOGO_TARGET = "/tmp/real_uhd.png" |
|
|
| print("=== [1/4] Khởi tạo thư mục đầu ra HLS ===", flush=True) |
| for folder in ["720p30", "720p50", "1080p30", "1080p50"]: |
| os.makedirs(os.path.join(OUTPUT_DIR, folder), exist_ok=True) |
|
|
| |
| placeholder_master = """#EXTM3U |
| #EXT-X-VERSION:3 |
| #EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=1280x720 |
| 720p30/playlist.m3u8 |
| """ |
| with open(os.path.join(OUTPUT_DIR, "master.m3u8"), "w") as f: |
| f.write(placeholder_master) |
|
|
| print("=== [2/4] Kích hoạt Python HTTP Server trên cổng 7860 (Ngăn triệt để lỗi 503) ===", flush=True) |
| def start_web_server(): |
| os.chdir(OUTPUT_DIR) |
| Handler = http.server.SimpleHTTPRequestHandler |
| socketserver.TCPServer.allow_reuse_address = True |
| |
| with socketserver.TCPServer(("", PORT), Handler) as httpd: |
| print(f"-> HTTP Server đang lắng nghe trên cổng {PORT}", flush=True) |
| httpd.serve_forever() |
|
|
| server_thread = threading.Thread(target=start_web_server, daemon=True) |
| server_thread.start() |
| time.sleep(1) |
|
|
| print("=== [3/4] Kiểm tra và chuẩn bị tài nguyên logo ===", flush=True) |
| if os.path.exists("/app/logo1.png"): |
| LOCAL_LOGO_TARGET = "/app/logo1.png" |
| print("-> Sử dụng /app/logo1.png sẵn có.", flush=True) |
| else: |
| subprocess.run([ |
| "ffmpeg", "-f", "lavfi", "-i", "color=c=black@0:s=1920x1080", |
| "-vframes", "1", LOCAL_LOGO_TARGET, "-y" |
| ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| print("-> Đã tạo canvas logo dự phòng.", flush=True) |
|
|
| print("=== [4/4] Khởi chạy tiến trình FFmpeg Transcode đa luồng thích ứng ===", flush=True) |
|
|
| ffmpeg_cmd = [ |
| "ffmpeg", "-y", "-re", |
| "-fflags", "+genpts+igndts+discardcorrupt", |
| "-probesize", "2000000", |
| "-analyzeduration", "2000000", |
| "-headers", HTTP_HEADERS, |
| "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5", |
| "-i", INPUT_STREAM, |
| "-i", LOCAL_LOGO_TARGET, |
| "-filter_complex", |
| "[1:v]scale=3840:-1[scaled_logo];" |
| "[0:v][scaled_logo]overlay=0:0,format=yuv420p,split=4[v1][v2][v3][v4];" |
| "[v1]scale=1280:720,fps=30[v720p30];" |
| "[v2]scale=1280:720,fps=50[v720p50];" |
| "[v3]scale=1920:1080,fps=30[v1080p30];" |
| "[v4]scale=1920:1080,fps=50[v1080p50];" |
| "[0:a]aresample=44100:async=1,pan=stereo|" |
| "FL=0.5*FL+0.707*FC+0.5*BL+0.5*LFE|" |
| "FR=0.5*FR+0.707*FC+0.5*BR+0.5*LFE,asplit=4[a1][a2][a3][a4]", |
| |
| "-map", "[v720p30]", "-map", "[a1]", "-c:v:0", "libx264", "-preset", "ultrafast", "-b:v:0", "1200k", "-maxrate:v:0", "1500k", "-bufsize:v:0", "3000k", "-g:v:0", "60", "-keyint_min:v:0", "60", "-sc_threshold:v:0", "0", "-c:a:0", "aac", "-b:a:0", "128k", |
| "-map", "[v720p50]", "-map", "[a2]", "-c:v:1", "libx264", "-preset", "ultrafast", "-b:v:1", "1800k", "-maxrate:v:1", "2200k", "-bufsize:v:1", "4500k", "-g:v:1", "100", "-keyint_min:v:1", "100", "-sc_threshold:v:1", "0", "-c:a:1", "aac", "-b:a:1", "128k", |
| "-map", "[v1080p30]", "-map", "[a3]", "-c:v:2", "libx264", "-preset", "ultrafast", "-b:v:2", "2500k", "-maxrate:v:2", "3000k", "-bufsize:v:2", "6000k", "-g:v:2", "60", "-keyint_min:v:2", "60", "-sc_threshold:v:2", "0", "-c:a:2", "aac", "-b:a:2", "192k", |
| "-map", "[v1080p50]", "-map", "[a4]", "-c:v:3", "libx264", "-preset", "ultrafast", "-b:v:3", "3500k", "-maxrate:v:3", "4000k", "-bufsize:v:3", "8000k", "-g:v:3", "100", "-keyint_min:v:3", "100", "-sc_threshold:v:3", "0", "-c:a:3", "aac", "-b:a:3", "192k", |
| |
| "-f", "hls", |
| "-hls_time", "4", |
| "-start_number", "0", |
| "-hls_list_size", "6", |
| "-hls_flags", "delete_segments+independent_segments", |
| "-master_pl_name", "master.m3u8", |
| "-var_stream_map", "v:0,a:0,name:720p30 v:1,a:1,name:720p50 v:2,a:2,name:1080p30 v:3,a:3,name:1080p50", |
| f"{OUTPUT_DIR}/%v/segment_%05d.ts", |
| f"{OUTPUT_DIR}/%v/playlist.m3u8" |
| ] |
|
|
| while True: |
| print("-> Bắt đầu tiến trình FFmpeg Transcode...", flush=True) |
| process = subprocess.Popen(ffmpeg_cmd) |
| process.wait() |
| print(f"-> FFmpeg thoát với mã {process.returncode}. Khởi động lại sau 3 giây...", flush=True) |
| time.sleep(3) |
|
|