import platform import time import datetime import subprocess # ====================== 抖音 → YouTube 转推核心代码(终极稳定版)====================== try: # 时间戳和标题设置 timestamp_str = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) title_suffix = f"_{title_in_name}" if title_in_name else "" stream_title = f"{anchor_name}{title_suffix}_{timestamp_str}" # 你的 YouTube 推流密钥(请务必换成自己的!) YT_STREAM_KEY = "x04z-564w-aks7-embw-30y4" # food频道 youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}" print(f"{rec_info} 开始推流到 YouTube: {stream_title}") # ----------------- 自动检测并启用 NVENC(有N卡就硬件加速)----------------- codec_v = "libx264" preset_v = "veryfast" tune_param = ["-tune", "zerolatency"] try: if platform.system().lower() in ["windows", "linux"]: check = subprocess.run(["nvidia-smi"], capture_output=True, timeout=8) if check.returncode == 0: enc = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"], capture_output=True, text=True, timeout=8) if "h264_nvenc" in enc.stdout: codec_v = "h264_nvenc" preset_v = "p5" tune_param = ["-tune", "ll", "-rc", "vbr", "-cq", "23"] print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速") except Exception as e: print(f"NVENC检测异常: {e},使用CPU编码") # ----------------- FFmpeg 推流命令(已极致优化)----------------- ffmpeg_command = [ "ffmpeg", "-rw_timeout", "15000000", "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "8", "-fflags", "+genpts+discardcorrupt+igndts", "-err_detect", "ignore_err", "-thread_queue_size", "2048", "-analyzeduration", "3000000", "-probesize", "3000000", "-i", real_url, "-vf", "scale=720:1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:black", "-c:v", codec_v, "-preset", preset_v, *tune_param, "-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k", "-g", "50", "-r", "25", "-pix_fmt", "yuv420p", "-bf", "0", "-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2", "-af", "aresample=async=1:first_pts=0", "-flags", "+low_delay+global_header", "-fflags", "+genpts+nobuffer", "-threads", "8", "-f", "flv", youtube_rtmp ] proc = None start_time = time.time() # 本次推流开始时间 while True: try: # 清理上一次残留进程 if proc and proc.poll() is None: proc.terminate() try: proc.wait(timeout=6) except: proc.kill() print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] 启动YouTube推流 → {stream_title}") proc = subprocess.Popen( ffmpeg_command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True ) # 时间戳属性 proc.last_out_ts = time.time() last_frame_time = time.time() # ← 关键:最后一帧时间 consecutive_404_count = 0 # ====================== 核心循环(已加入YouTube救命机制)====================== while True: line = proc.stderr.readline() if line == '' and proc.poll() is not None: break if not line: time.sleep(0.01) continue line = line.strip() if line: proc.last_out_ts = time.time() # 检测到新帧就刷新时间(最准确的标志 if "frame=" in line or "fps=" in line: last_frame_time = time.time() print(f"FFmpeg: {line}") line_lower = line.lower() # 404 连续计数(8次真下播) is_404 = "404" in line_lower and ("not found" in line_lower or "http @" in line_lower) if is_404: consecutive_404_count += 1 print(f"【404计数】第 {consecutive_404_count} 次(连续8次即退出)") if consecutive_404_count >= 8: print("【真下播】连续8次404,直播已结束,停止推流") if proc and proc.poll() is None: proc.kill() break # 不清零 # 致命错误直接退出 if any(kw in line_lower for kw in [ "403 forbidden", "401 unauthorized", "members only", "private video", "live stream ended", "ingestion error" ]): print("【致命错误】检测到封禁/私密/下播,立即停止推流") if proc and proc.poll() is None: proc.kill() break # YouTube救命机制:12秒无新帧就必须处决!(YouTube最多忍15~20秒) if time.time() - start_time > 25: # 25秒后进入战斗模式 if time.time() - last_frame_time > 12: # 12秒没新帧 → 立刻重启 print(f"【YouTube救命】已 {time.time()-last_frame_time:.1f}秒无新帧,立即重启FFmpeg避免直播被杀!") if proc and proc.poll() is None: proc.kill() break # 双保险:20秒完全没日志输出也处决 if time.time() - start_time > 20: if time.time() - proc.last_out_ts > 20: print("【真死流】20秒无任何输出,强制重启进程") if proc and proc.poll() is None: proc.kill() break # 进程退出判断 returncode = proc.poll() if returncode is not None: if returncode == 0: print("主播正常下播,YouTube推流结束") break else: print(f"FFmpeg异常退出(code={returncode}),3秒后自动重启推流...") time.sleep(3) continue except Exception as e: print(f"推流过程中出现异常: {e}") time.sleep(5) continue # ====================== 彻底清理 ====================== if proc and proc.poll() is None: print("发现残留FFmpeg进程,执行kill") proc.kill() try: recording.discard(record_name) except: pass try: recording_time_list.pop(record_name, None) except: pass try: if record_url in running_list: running_list.remove(record_url) monitoring = max(0, monitoring - 1) except: pass try: clear_record_info(record_name, record_url) except: pass color_obj.print_colored(f"[{record_name}] YouTube推流已彻底停止并清理完毕\n", color_obj.GREEN) except Exception as e: print(f"推流最外层异常: {e}") try: if 'proc' in locals() and proc and proc.poll() is None: proc.kill() except: pass try: recording.discard(record_name) recording_time_list.pop(record_name, None) if record_url in running_list: running_list.remove(record_url) monitoring = max(0, monitoring - 1) clear_record_info(record_name, record_url) except: pass color_obj.print_colored(f"[{record_name}] 异常退出但已清理完毕\n", color_obj.RED)