import os import subprocess import threading import time import glob import asyncio import uuid import spider import stream # import JS_SCRIPT_PATH # import utils # ----------------- HLS2YouTube 推流器 ----------------- class HLS2YouTube: def __init__(self, cache_dir="cache", segment_time=10, check_interval=1): self.cache_dir = cache_dir self.segment_time = segment_time self.check_interval = check_interval self.recording_threads = {} os.makedirs(cache_dir, exist_ok=True) def start_record_and_push(self, real_url, youtube_key, room_name="room"): tmp_segment_prefix = os.path.join(self.cache_dir, f"{room_name}_out") # 清空旧分段 for f in glob.glob(f"{tmp_segment_prefix}*.ts"): os.remove(f) # ----------------- 录制线程 ----------------- def record_thread(): while True: record_cmd = [ "ffmpeg", "-rw_timeout", "50000000", "-user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "-headers", "Referer: https://www.douyin.com/", "-fflags", "+nobuffer", "-flags", "low_delay", "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "10", "-i", real_url, "-c", "copy", "-f", "segment", "-segment_time", str(self.segment_time), "-reset_timestamps", "1", f"{tmp_segment_prefix}%03d.ts" ] try: subprocess.run(record_cmd) except Exception as e: print(f"[{room_name}] 录制异常: {e}, 5秒后重试") time.sleep(5) # ----------------- 推流线程 ----------------- def push_thread(): pushed_files = set() while True: try: segments = sorted(glob.glob(f"{tmp_segment_prefix}*.ts")) new_segments = [s for s in segments if s not in pushed_files] for seg in new_segments: push_cmd = [ "ffmpeg", "-re", "-i", seg, "-c:v", "libx264", "-preset", "veryfast", "-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k", "-r", "30", "-g", "60", "-c:a", "aac", "-b:a", "128k", "-f", "flv", f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" ] subprocess.run(push_cmd) pushed_files.add(seg) time.sleep(self.check_interval) except Exception as e: print(f"[{room_name}] 推流异常: {e}, 5秒后重试") time.sleep(5) t1 = threading.Thread(target=record_thread, daemon=True) t2 = threading.Thread(target=push_thread, daemon=True) t1.start() t2.start() self.recording_threads[room_name] = (t1, t2) print(f"[{room_name}] 录制+推流线程已启动") _hls2yt_instance = HLS2YouTube() # ----------------- 抖音解析逻辑 ----------------- async def get_douyin_real_url(record_url, record_quality="原画"): # import spider # 你的抖音爬虫模块 # import stream # 你的流解析模块 # 根据 URL 类型调用不同接口 if 'v.douyin.com' not in record_url and '/user/' not in record_url: json_data = await spider.get_douyin_stream_data(url=record_url) else: json_data = await spider.get_douyin_app_stream_data(url=record_url) port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) return port_info.get("record_url") # ----------------- 启动推流 ----------------- def start_douyin_to_youtube(record_url, youtube_key, room_name=None): if not room_name: room_name = f"抖音_{str(uuid.uuid4())[:8]}" real_url = asyncio.run(get_douyin_real_url(record_url)) if not real_url: print("获取真实直播流失败") return print(f"[{room_name}] 实际直播流: {real_url}") _hls2yt_instance.start_record_and_push(real_url, youtube_key, room_name) # ----------------- 示例 ----------------- if __name__ == "__main__": DOUYIN_URL = "https://live.douyin.com/210443559964" # 填入抖音直播 URL YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" # 填入直播 Key start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY) # 保持主线程 while True: time.sleep(60)