diff --git a/README.md b/README.md index 8e0a31b..d126c2a 100644 --- a/README.md +++ b/README.md @@ -1,679 +1,259 @@ -![video_spider](https://socialify.git.ci/ihmily/DouyinLiveRecorder/image?font=Inter&forks=1&language=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light) +# DouyinLiveRecorder 转播版 -## 💡简介 -[![Python Version](https://img.shields.io/badge/python-3.11.6-blue.svg)](https://www.python.org/downloads/release/python-3116/) -[![Supported Platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux-blue.svg)](https://github.com/ihmily/DouyinLiveRecorder) -[![Docker Pulls](https://img.shields.io/docker/pulls/ihmily/douyin-live-recorder?label=Docker%20Pulls&color=blue&logo=docker)](https://hub.docker.com/r/ihmily/douyin-live-recorder/tags) -![GitHub issues](https://img.shields.io/github/issues/ihmily/DouyinLiveRecorder.svg) -[![Latest Release](https://img.shields.io/github/v/release/ihmily/DouyinLiveRecorder)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest) -[![Downloads](https://img.shields.io/github/downloads/ihmily/DouyinLiveRecorder/total)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest) +[![Python Version](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) +[![Supported Platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Ubuntu-blue.svg)](https://github.com/ihmily/DouyinLiveRecorder) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -一款**简易**的可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制以及直播状态推送。 +基于 [DouyinLiveRecorder](https://github.com/ihmily/DouyinLiveRecorder) 的**转播增强版**,将原本的录制下载功能扩展为**实时推流转播**,支持抖音/TikTok 等直播推送到 YouTube、HDMI 等目标。 - +## 与原版的主要区别 -## 😺已支持平台 +| 功能 | 原版 DouyinLiveRecorder | 本转播版 | +|------|-------------------------|----------| +| 核心用途 | 录制直播到本地文件 | 实时转播到 YouTube / HDMI | +| 抖音 → YouTube | 不支持 | ✅ 支持,含 AI 人声分离防版权 | +| 抖音 → HDMI/SRS | 不支持 | ✅ 支持(TikTok 模式) | +| 录制到本地 | ✅ 支持 | ✅ 保留支持 | +| 平台支持 | 40+ 平台 | 继承全部平台 | -- [x] 抖音 -- [x] TikTok -- [x] 快手 -- [x] 虎牙 -- [x] 斗鱼 -- [x] YY -- [x] B站 -- [x] 小红书 -- [x] bigo -- [x] blued -- [x] SOOP(原AfreecaTV) -- [x] 网易cc -- [x] 千度热播 -- [x] PandaTV -- [x] 猫耳FM -- [x] Look直播 -- [x] WinkTV -- [x] TTingLive(原Flextv) -- [x] PopkonTV -- [x] TwitCasting -- [x] 百度直播 -- [x] 微博直播 -- [x] 酷狗直播 -- [x] TwitchTV -- [x] LiveMe -- [x] 花椒直播 -- [x] 流星直播 -- [x] ShowRoom -- [x] Acfun -- [x] 映客直播 -- [x] 音播直播 -- [x] 知乎直播 -- [x] CHZZK -- [x] 嗨秀直播 -- [x] vv星球直播 -- [x] 17Live -- [x] 浪Live -- [x] 畅聊直播 -- [x] 飘飘直播 -- [x] 六间房直播 -- [x] 乐嗨直播 -- [x] 花猫直播 -- [x] Shopee -- [x] Youtube -- [x] 淘宝 -- [x] 京东 -- [x] Faceit -- [x] 咪咕 -- [x] 连接直播 -- [x] 来秀直播 -- [x] Picarto -- [ ] 更多平台正在更新中 +--- - +## 功能特性 -## 🎈项目结构 +### 转播模式 + +- **YouTube 转播**:抖音直播 → YouTube Live,支持 AI 人声分离(Demucs)降低音乐版权识别 +- **TikTok/HDMI 转播**:抖音直播 → SRS 推流 → HDMI 采集卡或 OBS +- **本地录制**:保留原版全部录制能力 + +### 防版权识别(YouTube 专用) + +- **Demucs AI 人声分离**:只保留人声,去除背景音乐,从源头避免 Content ID 匹配 +- **NVIDIA GPU 加速**:自动检测 CUDA,优先使用 GPU 加速 Demucs +- **无 GPU 回退**:无显卡时自动回退到 CPU,不报错 +- **可配置开关**:可在 `config/youtube.ini` 中关闭 Demucs,使用原始音频 + +### 循环值守与多直播间 + +- 自动轮询直播间状态 +- 主播下播后自动停止推流,并快速重检 +- 支持多直播间 URL 配置,单路推流时自动切换 +- 直播状态推送(钉钉、微信、Telegram 等) + +--- + +## 项目结构 ``` . -└── DouyinLiveRecorder/ - ├── /config -> (config record) - ├── /logs -> (save runing log file) - ├── /backup_config -> (backup file) - ├── /douyinliverecorder -> (package) - ├── initializer.py-> (check and install nodejs) - ├── spider.py-> (get live data) - ├── stream.py-> (get live stream address) - ├── utils.py -> (contains utility functions) - ├── logger.py -> (logger handdle) - ├── room.py -> (get room info) - ├── ab_sign.py-> (generate dy token) - ├── /javascript -> (some decrypt code) - ├── main.py -> (main file) - ├── ffmpeg_install.py -> (ffmpeg install script) - ├── demo.py -> (call package test demo) - ├── msg_push.py -> (send live status update message) - ├── ffmpeg.exe -> (record video) - ├── index.html -> (play m3u8 and flv video) - ├── requirements.txt -> (library dependencies) - ├── docker-compose.yaml -> (Container Orchestration File) - ├── Dockerfile -> (Application Build Recipe) - ├── StopRecording.vbs -> (stop recording script on Windows) - ... +├── main.py # 主入口(本地录制 + SRS 转播) +├── youtube.py # YouTube 转播入口 +├── tiktok.py # TikTok/HDMI 转播入口 +├── douyin_youtube_ffplay.py # 抖音 → YouTube 推流(含 Demucs 管道) +├── douyin_srs_ffplay.py # 抖音 → SRS/HDMI 推流 +├── web.py # Web 配置界面 +├── config/ +│ ├── config.ini # 通用配置 +│ ├── youtube.ini # YouTube 推流配置(stream key、Demucs 开关) +│ └── URL_config.ini # 直播间 URL 列表 +├── src/ # 爬虫、流解析等核心逻辑 +├── backup/ # 备份与辅助模块 +├── requirements.txt +├── pyproject.toml +└── README.md ``` - +--- -## 🌱使用说明 +## 快速开始 -- 对于只想使用录制软件的小白用户,进入[Releases](https://github.com/ihmily/DouyinLiveRecorder/releases) 中下载最新发布的 zip压缩包即可,里面有打包好的录制软件。(有些电脑可能会报毒,直接忽略即可,如果下载时被浏览器屏蔽,请更换浏览器下载) +### 1. 环境要求 -- 压缩包解压后,在 `config` 文件夹内的 `URL_config.ini` 中添加录制直播间地址,一行一个直播间地址。如果要自定义配置录制,可以修改`config.ini` 文件,推荐将录制格式修改为`ts`。 -- 以上步骤都做好后,就可以运行`DouyinLiveRecorder.exe` 程序进行录制了。录制的视频文件保存在同目录下的 `downloads` 文件夹内。 +- **Python** >= 3.10 +- **FFmpeg**(需支持 libx264、h264_nvenc) +- **可选**:NVIDIA GPU + CUDA(用于 Demucs 加速) -- 另外,如果需要录制TikTok、AfreecaTV等海外平台,请在配置文件中设置开启代理并添加proxy_addr链接 如:`127.0.0.1:7890` (这只是示例地址,具体根据实际填写)。 +### 2. 安装依赖 -- 假如`URL_config.ini`文件中添加的直播间地址,有个别直播间暂时不想录制又不想移除链接,可以在对应直播间的链接开头加上`#`,那么将停止该直播间的监测以及录制。 +```bash +# 克隆或下载项目 +cd douyinyoutube -- 软件默认录制清晰度为 `原画` ,如果要单独设置某个直播间的录制画质,可以在添加直播间地址时前面加上画质即可,如`超清,https://live.douyin.com/745964462470` 记得中间要有`,` 分隔。 +# 创建虚拟环境(推荐) +python -m venv .venv +# Windows: +.venv\Scripts\activate +# Ubuntu/Linux: +source .venv/bin/activate -- 如果要长时间挂着软件循环监测直播,最好循环时间设置长一点(咱也不差没录制到的那几分钟),避免因请求频繁导致被官方封禁IP 。 - -- 要停止直播录制,Windows平台可执行StopRecording.vbs脚本文件,或者在录制界面使用 `Ctrl+C ` 组合键中断录制,若要停止其中某个直播间的录制,可在`URL_config.ini`文件中的地址前加#,会自动停止对应直播间的录制并正常保存已录制的视频。 -- 最后,欢迎右上角给本项目一个star,同时也非常乐意大家提交pr。 - -  - -直播间链接示例: +# 安装基础依赖 +pip install -r requirements.txt +# 若使用 YouTube 转播 + Demucs 防版权,需额外安装: +pip install -r requirements-youtube.txt +# GPU 版本(NVIDIA)需先安装 CUDA 版 PyTorch: +# pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 ``` -抖音: + +### 3. 配置 + +#### YouTube 转播 + +1. 编辑 `config/youtube.ini`: + +```ini +[youtube] +# YouTube 直播 stream key(从 YouTube 直播控制台获取) +key = xxxx-xxxx-xxxx-xxxx + +# 是否启用 Demucs 人声分离(防版权识别) +# 是 = 启用,否 = 使用原始音频 +enable_demucs = 是 + +# Demucs 是否优先使用 GPU(有 NVIDIA 显卡时) +# 是 = 优先 GPU,否 = 强制 CPU +demucs_use_gpu = 是 +``` + +2. 编辑 `config/URL_config.ini`,添加抖音直播间地址: + +```ini https://live.douyin.com/745964462470 -https://v.douyin.com/iQFeBnt/ -https://live.douyin.com/yall1102 (链接+抖音号) -https://v.douyin.com/CeiU5cbX (主播主页地址) - -TikTok: -https://www.tiktok.com/@pearlgaga88/live - -快手: -https://live.kuaishou.com/u/yall1102 - -虎牙: -https://www.huya.com/52333 - -斗鱼: -https://www.douyu.com/3637778?dyshid= -https://www.douyu.com/topic/wzDBLS6?rid=4921614&dyshid= - -YY: -https://www.yy.com/22490906/22490906 - -B站: -https://live.bilibili.com/320 - -小红书(直播间分享地址): -http://xhslink.com/xpJpfM - -bigo直播: -https://www.bigo.tv/cn/716418802 - -buled直播: -https://app.blued.cn/live?id=Mp6G2R - -SOOP: -https://play.sooplive.co.kr/sw7love - -网易cc: -https://cc.163.com/583946984 - -千度热播: -https://qiandurebo.com/web/video.php?roomnumber=33333 - -PandaTV: -https://www.pandalive.co.kr/live/play/bara0109 - -猫耳FM: -https://fm.missevan.com/live/868895007 - -Look直播: -https://look.163.com/live?id=65108820&position=3 - -WinkTV: -https://www.winktv.co.kr/live/play/anjer1004 - -FlexTV(TTinglive):: -https://www.flextv.co.kr/channels/593127/live - -PopkonTV: -https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117 -https://www.popkontv.com/channel/notices?mcid=wjfal007&mcPartnerCode=P-00117 - -TwitCasting: -https://twitcasting.tv/c:uonq - -百度直播: -https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category - -微博直播: -https://weibo.com/l/wblive/p/show/1022:2321325026370190442592 - -酷狗直播: -https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom= - -TwitchTV: -https://www.twitch.tv/gamerbee - -LiveMe: -https://www.liveme.com/zh/v/17141543493018047815/index.html - -花椒直播: -https://www.huajiao.com/l/345096174 - -流星直播: -https://www.7u66.com/100960 - -ShowRoom: -https://www.showroom-live.com/room/profile?room_id=480206 (主播主页地址) - -Acfun: -https://live.acfun.cn/live/179922 - -映客直播: -https://www.inke.cn/liveroom/index.html?uid=22954469&id=1720860391070904 - -音播直播: -https://live.ybw1666.com/800002949 - -知乎直播: -https://www.zhihu.com/people/ac3a467005c5d20381a82230101308e9 (主播主页地址) - -CHZZK: -https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2 - -嗨秀直播: -https://www.haixiutv.com/6095106 - -VV星球直播: -https://h5webcdn-pro.vvxqiu.com//activity/videoShare/videoShare.html?h5Server=https://h5p.vvxqiu.com&roomId=LP115924473&platformId=vvstar - -17Live: -https://17.live/en/live/6302408 - -浪Live: -https://www.lang.live/en-US/room/3349463 - -畅聊直播: -https://live.tlclw.com/106188 - -飘飘直播: -https://m.pp.weimipopo.com/live/preview.html?uid=91648673&anchorUid=91625862&app=plpl - -六间房直播: -https://v.6.cn/634435 - -乐嗨直播: -https://www.lehaitv.com/8059096 - -花猫直播: -https://h.catshow168.com/live/preview.html?uid=19066357&anchorUid=18895331 - -Shopee: -https://sg.shp.ee/GmpXeuf?uid=1006401066&session=802458 - -Youtube: -https://www.youtube.com/watch?v=cS6zS5hi1w0 - -淘宝(需cookie): -https://tbzb.taobao.com/live?liveId=532359023188 -https://m.tb.cn/h.TWp0HTd - -京东: -https://3.cn/28MLBy-E - -Faceit: -https://www.faceit.com/zh/players/Compl1/stream - -连接直播: -https://show.lailianjie.com/10000258 - -咪咕直播: -https://www.miguvideo.com/p/live/120000541321 - -来秀直播: -https://www.imkktv.com/h5/share/video.html?uid=1845195&roomId=1710496 - -Picarto: -https://www.picarto.tv/cuteavalanche +https://live.douyin.com/xxx,主播名 ``` -  +3. 在 `config/config.ini` 中将 `视频保存格式` 设为 `ts`(触发 YouTube 推流逻辑) -## 🎃源码运行 -使用源码运行,可参考下面的步骤。 +#### TikTok/HDMI 转播 -1.首先拉取或手动下载本仓库项目代码 +- 使用 `tiktok.py` 入口,配置 SRS 推流地址 +- 详见 `tiktok/` 目录说明 + +### 4. 运行 ```bash -git clone https://github.com/ihmily/DouyinLiveRecorder.git +# YouTube 转播 +python youtube.py + +# 或使用 uv +uv run youtube.py ``` -2.进入项目文件夹,安装依赖 +--- + +## 详细配置说明 + +### config/youtube.ini + +| 配置项 | 说明 | 默认值 | +|--------|------|--------| +| key | YouTube 直播 stream key | 必填 | +| enable_demucs | 是否启用 Demucs 人声分离 | 是 | +| demucs_use_gpu | 是否优先使用 NVIDIA GPU | 是 | + +### config/config.ini + +- **视频保存格式**:设为 `ts` 时,youtube.py 会走 YouTube 推流逻辑,而非本地录制 +- **循环时间**:轮询间隔(秒) +- **代理**:海外平台需配置代理 + +### URL_config.ini 格式 + +``` +# 单行一个直播间 +https://live.douyin.com/745964462470 + +# 可加画质和备注 +超清,https://live.douyin.com/xxx +https://live.douyin.com/yyy,主播名 + +# 注释掉不监测 +# https://live.douyin.com/zzz +``` + +--- + +## Demucs 防版权说明 + +### 原理 + +- 使用 [Demucs](https://github.com/facebookresearch/demucs) 对混音进行人声分离 +- 只保留人声轨道推送到 YouTube,背景音乐被物理移除 +- Content ID 无法匹配已移除的音乐 + +### 延迟与性能 + +- **延迟**:约 4–8 秒(与 chunk 大小相关) +- **GPU**:有 NVIDIA 显卡时自动使用 CUDA 加速 +- **CPU**:无 GPU 时自动回退,不影响运行 + +### 回退机制 + +1. **Demucs 未安装**:自动使用原始 FFmpeg 推流 +2. **GPU 不可用**:自动使用 CPU 运行 Demucs +3. **enable_demucs = 否**:直接使用原始音频 + +--- + +## 平台兼容性 + +| 平台 | 录制 | YouTube 转播 | Demucs | +|------|------|--------------|--------| +| Windows | ✅ | ✅ | ✅(CPU/GPU) | +| Ubuntu Server | ✅ | ✅ | ✅(优先) | +| 其他 Linux | ✅ | ✅ | ✅ | + +--- + +## Web 配置界面 ```bash -cd DouyinLiveRecorder +python web.py ``` -> [!TIP] -> - 不论你是否已安装 **Python>=3.10** 环境, 都推荐使用 [**uv**](https://github.com/astral-sh/uv) 运行, 因为它可以自动管理虚拟环境和方便地管理 **Python** 版本, **不过这完全是可选的**
-> 使用以下命令安装 -> ```bash -> # 在 macOS 和 Linux 上安装 uv -> curl -LsSf https://astral.sh/uv/install.sh | sh -> ``` -> ```powershell -> # 在 Windows 上安装 uv -> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -> ``` -> - 如果安装依赖速度太慢, 你可以考虑使用国内 pip 镜像源:
-> 在 `pip` 命令使用 `-i` 参数指定, 如 `pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple`
-> 或者在 `uv` 命令 `--index` 选项指定, 如 `uv sync --index https://pypi.tuna.tsinghua.edu.cn/simple` +- 浏览器访问配置页面 +- 支持编辑 `youtube.ini`、`URL_config.ini` +- 选择 youtube / tiktok / obs 等模式 -
+--- - 如果已安装 Python>=3.10 环境 +## 常见问题 - - :white_check_mark: 在虚拟环境中安装 (推荐) - - 1. 创建虚拟环境 - - - 使用系统已安装的 Python, 不使用 uv - - ```bash - python -m venv .venv - ``` - - - 使用 uv, 默认使用系统 Python, 你可以添加 `--python` 选项指定 Python 版本而不使用系统 Python [uv官方文档](https://docs.astral.sh/uv/concepts/python-versions/) - - ```bash - uv venv - ``` - - 2. 在终端激活虚拟环境 (在未安装 uv 或你想要手动激活虚拟环境时执行, 若已安装 uv, 可以跳过这一步, uv 会自动激活并使用虚拟环境) - - **Bash** 中 - ```bash - source .venv/Scripts/activate - ``` - - **Powershell** 中 - ```powershell - .venv\Scripts\activate.ps1 - ``` - - **Windows CMD** 中 - ```bat - .venv\Scripts\activate.bat - ``` - - 3. 安装依赖 - - ```bash - # 使用 pip (若安装太慢或失败, 可使用 `-i` 指定镜像源) - pip3 install -U pip && pip3 install -r requirements.txt - # 或者使用 uv (可使用 `--index` 指定镜像源) - uv sync - # 或者 - uv pip sync requirements.txt - ``` - - - :x: 在系统 Python 环境中安装 (不推荐) - - ```bash - pip3 install -U pip && pip3 install -r requirements.txt - ``` - -
- -
- - 如果未安装 Python>=3.10 环境 - - 你可以使用 [**uv**](https://github.com/astral-sh/uv) 安装依赖 - - ```bash - # uv 将使用 3.10 及以上的最新 python 发行版自动创建并使用虚拟环境, 可使用 --python 选项指定 python 版本, 参见 https://docs.astral.sh/uv/reference/cli/#uv-sync--python 和 https://docs.astral.sh/uv/reference/cli/#uv-pip-sync--python - uv sync - # 或 - uv pip sync requirements.txt - ``` - -
- -3.安装[FFmpeg](https://ffmpeg.org/download.html#build-linux),如果是Windows系统,这一步可跳过。对于Linux系统,执行以下命令安装 - -CentOS执行 +### 1. Demucs 安装失败 ```bash -yum install epel-release -yum install ffmpeg +# 仅 CPU +pip install demucs torch torchaudio + +# 使用国内镜像 +pip install demucs torch torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple ``` -Ubuntu则执行 +### 2. 无 NVIDIA 显卡 -```bash -apt update -apt install ffmpeg -``` +- 程序会自动使用 CPU 运行 Demucs +- 可在 `youtube.ini` 中设置 `demucs_use_gpu = 否` 以跳过 GPU 检测 -macOS 执行 +### 3. 推流中断 -**如果已经安装 Homebrew 请跳过这一步** +- 检查网络与代理 +- 检查 YouTube stream key 是否有效 +- 查看控制台日志定位错误 -```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` +### 4. 抖音风控 -```bash -brew install ffmpeg -``` +- 适当增大循环时间 +- 确保 Cookie 有效(录制抖音需配置) -4.运行程序 +--- -```python -python main.py +## 致谢 -``` -或 +- 原项目:[ihmily/DouyinLiveRecorder](https://github.com/ihmily/DouyinLiveRecorder) +- 人声分离:[facebookresearch/demucs](https://github.com/facebookresearch/demucs) -```bash -uv run main.py -``` +--- -其中Linux系统请使用`python3 main.py` 运行。 +## 许可证 -  -## 🐋容器运行 - -在运行命令之前,请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/) - -1.快速启动 - -最简单方法是运行项目中的 [docker-compose.yaml](https://github.com/ihmily/DouyinLiveRecorder/blob/main/docker-compose.yaml) 文件,只需简单执行以下命令: - -```bash -docker-compose up -``` - -可选 `-d` 在后台运行。 - - - -2.构建镜像(可选) - -如果你只想简单的运行程序,则不需要做这一步。Docker镜像仓库中代码版本可能不是最新的,如果要运行本仓库主分支最新代码,可以本地自定义构建,通过修改 [docker-compose.yaml](https://github.com/ihmily/DouyinLiveRecorder/blob/main/docker-compose.yaml) 文件,如将镜像名修改为 `douyin-live-recorder:latest`,并取消 `# build: .` 注释,然后再执行 - -```bash -docker build -t douyin-live-recorder:latest . -docker-compose up -``` - -或者直接使用下面命令进行构建并启动 - -```bash -docker-compose -f docker-compose.yaml up -``` - - - -3.停止容器实例 - -```bash -docker-compose stop -``` - - - -4.注意事项 - -①在docker容器内运行本程序之前,请先在配置文件中添加要录制的直播间地址。 - -②在容器内时,如果手动中断容器运行停止录制,会导致正在录制的视频文件损坏! - -**无论哪种运行方式,为避免手动中断或者异常中断导致录制的视频文件损坏的情况,推荐使用 `ts` 格式保存**。 - -  - -## 🤖相关项目 - -- StreamCap: https://github.com/ihmily/StreamCap -- streamget: https://github.com/ihmily/streamget - -  - -## ❤️贡献者 - -   [![Hmily](https://github.com/ihmily.png?size=50)](https://github.com/ihmily) -[![iridescentGray](https://github.com/iridescentGray.png?size=50)](https://github.com/iridescentGray) -[![annidy](https://github.com/annidy.png?size=50)](https://github.com/annidy) -[![wwkk2580](https://github.com/wwkk2580.png?size=50)](https://github.com/wwkk2580) -[![missuo](https://github.com/missuo.png?size=50)](https://github.com/missuo) -xueli12 -kaine1973 -yinruiqing -Max-Tortoise -[![justdoiting](https://github.com/justdoiting.png?size=50)](https://github.com/justdoiting) -[![dhbxs](https://github.com/dhbxs.png?size=50)](https://github.com/dhbxs) -[![wujiyu115](https://github.com/wujiyu115.png?size=50)](https://github.com/wujiyu115) -[![zhanghao333](https://github.com/zhanghao333.png?size=50)](https://github.com/zhanghao333) -gyc0123 - -   [![HoratioShaw](https://github.com/HoratioShaw.png?size=50)](https://github.com/HoratioShaw) -[![nov30th](https://github.com/nov30th.png?size=50)](https://github.com/nov30th) -[![727155455](https://github.com/727155455.png?size=50)](https://github.com/727155455) -[![nixingshiguang](https://github.com/nixingshiguang.png?size=50)](https://github.com/nixingshiguang) -[![1411430556](https://github.com/1411430556.png?size=50)](https://github.com/1411430556) -[![Ovear](https://github.com/Ovear.png?size=50)](https://github.com/Ovear) -  - -## ⏳提交日志 - -- 20251024 - - 修复抖音风控无法获取数据问题 - - - 新增soop.com录制支持 - - - 修复bigo录制 - -- 20250127 - - 新增淘宝、京东、faceit直播录制 - - 修复小红书直播流录制以及转码问题 - - 修复畅聊、VV星球、flexTV直播录制 - - 修复批量微信直播推送 - - 新增email发送ssl和port配置 - - 新增强制转h264配置 - - 更新ffmpeg版本 - - 重构包为异步函数! - -- 20241130 - - 新增shopee、youtube直播录制 - - 新增支持自定义m3u8、flv地址录制 - - 新增自定义执行脚本,支持python、bat、bash等 - - 修复YY直播、花椒直播和小红书直播录制 - - 修复b站标题获取错误 - - 修复log日志错误 -- 20241030 - - 新增嗨秀直播、vv星球直播、17Live、浪Live、SOOP、畅聊直播(原时光直播)、飘飘直播、六间房直播、乐嗨直播、花猫直播等10个平台直播录制 - - 修复小红书直播录制,支持小红书作者主页地址录制直播 - - 新增支持ntfy消息推送,以及新增支持批量推送多个地址(逗号分隔多个推送地址) - - 修复Liveme直播录制、twitch直播录制 - - 新增Windows平台一键停止录制VB脚本程序 -- 20241005 - - 新增邮箱和Bark推送 - - 新增直播注释停止录制 - - 优化分段录制 - - 重构部分代码 -- 20240928 - - 新增知乎直播、CHZZK直播录制 - - 修复音播直播录制 -- 20240903 - - 新增抖音双屏录制、音播直播录制 - - 修复PandaTV、bigo直播录制 -- 20240713 - - 新增映客直播录制 -- 20240705 - - 新增时光直播录制 -- 20240701 - - 修复虎牙直播录制2分钟断流问题 - - 新增自定义直播推送内容 -- 20240621 - - 新增Acfun、ShowRoom直播录制 - - 修复微博录制、新增直播源线路 - - 修复斗鱼直播60帧录制 - - 修复酷狗直播录制 - - 修复TikTok部分无法解析直播源 - - 修复抖音无法录制连麦直播 -- 20240510 - - 修复部分虎牙直播间录制错误 -- 20240508 - - 修复花椒直播录制 - - 更改文件路径解析方式 [@kaine1973](https://github.com/kaine1973) -- 20240506 - - 修复抖音录制画质解析bug - - 修复虎牙录制 60帧最高画质问题 - - 新增流星直播录制 -- 20240427 - - 新增LiveMe、花椒直播录制 -- 20240425 - - 新增TwitchTV直播录制 -- 20240424 - - 新增酷狗直播录制、优化PopkonTV直播录制 -- 20240423 - - 新增百度直播录制、微博直播录制 - - 修复斗鱼录制直播回放的问题 - - 新增直播源地址显示以及输出到日志文件设置 -- 20240311 - - 修复海外平台录制bug,增加画质选择,增强录制稳定性 - - 修复虎牙录制bug (虎牙`一起看`频道 有特殊限制,有时无法录制) -- 20240309 - - 修复虎牙直播、小红书直播和B站直播录制 - - 新增5个直播平台录制,包括winktv、flextv、look、popkontv、twitcasting - - 新增部分海外平台账号密码配置,实现自动登录并更新配置文件中的cookie - - 新增自定义配置需要使用代理录制的平台 - - 新增只推送开播消息不进行录制设置 - - 修复了一些bug -- 20240209 - - 优化AfreecaTV录制,新增账号密码登录获取cookie以及持久保存 - - 修复了小红书直播因官方更新直播域名,导致无法录制直播的问题 - - 修复了更新URL配置文件的bug - - 最后,祝大家新年快乐! - -
点击展开更多提交日志 - -- 20240129 - - 新增猫耳FM直播录制 -- 20240127 - - 新增千度热播直播录制、新增pandaTV(韩国)直播录制 - - 新增telegram直播状态消息推送,修复了某些bug - - 新增自定义设置不同直播间的录制画质(即每个直播间录制画质可不同) - - 修改录制视频保存路径为 `downloads` 文件夹,并且分平台进行保存。 -- 20240114 - - 新增网易cc直播录制,优化ffmpeg参数,修改AfreecaTV输入直播地址格式 - - 修改日志记录器 @[iridescentGray](https://github.com/iridescentGray) -- 20240102 - - 修复Linux上运行,新增docker配置文件 -- 20231210 - - 修复录制分段bug,修复bigo录制检测bug - - 新增自定义修改录制主播名 - - 新增AfreecaTV直播录制,修复某些可能会发生的bug -- 20231207 - - 新增blued直播录制,修复YY直播录制,新增直播结束消息推送 -- 20231206 - - 新增bigo直播录制 -- 20231203 - - 新增小红书直播录制(全网首发),目前小红书官方没有切换清晰度功能,因此直播录制也只有默认画质 - - 小红书录制暂时无法循环监测,每次主播开启直播,都要重新获取一次链接 - - 获取链接的方式为 将直播间转发到微信,在微信中打开后,复制页面的链接。 -- 20231030 - - 本次更新只是进行修复,没时间新增功能。 - - 欢迎各位大佬提pr 帮忙更新维护 -- 20230930 - - 新增抖音从接口获取直播流,增强稳定性 - - 修改快手获取直播流的方式,改用从官方接口获取 - - 祝大家中秋节快乐! -- 20230919 - - 修复了快手版本更新后录制出错的问题,增加了其自动获取cookie(~~稳定性未知~~) - - 修复了TikTok显示正在直播但不进行录制的问题 -- 20230907 - - 修复了因抖音官方更新了版本导致的录制出错以及短链接转换出错 - - 修复B站无法录制原画视频的bug - - 修改了配置文件字段,新增各平台自定义设置Cookie -- 20230903 - - 修复了TikTok录制时报644无法录制的问题 - - 新增直播状态推送到钉钉和微信的功能,如有需要请看 [设置推送教程](https://d04vqdiqwr3.feishu.cn/docx/XFPwdDDvfobbzlxhmMYcvouynDh?from=from_copylink) - - 最近比较忙,其他问题有时间再更新 -- 20230816 - - 修复斗鱼直播(官方更新了字段)和快手直播录制出错的问题 -- 20230814 - - 新增B站直播录制 - - 写了一个在线播放M3U8和FLV视频的网页源码,打开即可食用 -- 20230812 - - 新增YY直播录制 -- 20230808 - - 修复主播重新开播无法再次录制的问题 -- 20230807 - - 新增了斗鱼直播录制 - - 修复显示录制完成之后会重新开始录制的问题 -- 20230805 - - 新增了虎牙直播录制,其暂时只能用flv视频流进行录制 - - Web API 新增了快手和虎牙这两个平台的直播流解析(TikTok要代理) -- 20230804 - - 新增了快手直播录制,优化了部分代码 - - 上传了一个自动化获取抖音直播间页面Cookie的代码,可以用于录制 -- 20230803 - - 通宵更新 - - 新增了国际版抖音TikTok的直播录制,去除冗余 简化了部分代码 -- 20230724 - - 新增了一个通过抖音直播间地址获取直播视频流链接的API接口,上传即可用 -
-   - -## 有问题可以提issue, 我会在这里持续添加更多直播平台的录制 欢迎Star -#### +MIT License diff --git a/config/youtube.ini b/config/youtube.ini index 9d34b07..7efb7ff 100644 --- a/config/youtube.ini +++ b/config/youtube.ini @@ -1,3 +1,11 @@ [youtube] # 只写 stream key(推荐方式) -key = x04z-564w-aks7-embw-30y4 \ No newline at end of file +key = x04z-564w-aks7-embw-30y4 + +# 是否启用 Demucs 人声分离(防 YouTube Content ID 版权识别) +# 是 = 启用,只保留人声;否 = 使用原始音频 +enable_demucs = 是 + +# Demucs 是否优先使用 NVIDIA GPU 加速 +# 是 = 有显卡则用 GPU;否 = 强制 CPU;无显卡时自动回退到 CPU +demucs_use_gpu = 是 \ No newline at end of file diff --git a/demucs_pipeline.py b/demucs_pipeline.py new file mode 100644 index 0000000..ce72e2b --- /dev/null +++ b/demucs_pipeline.py @@ -0,0 +1,387 @@ +# demucs_pipeline.py +# Demucs 人声分离管道:FFmpeg 拉流 → Demucs 分离人声 → FFmpeg 推流 +# 兼容 Windows / Ubuntu,支持 NVIDIA GPU 加速与 CPU 回退 +""" +Demucs 音频管道模块 +用于将混音分离为人声,推送到 YouTube 时避免 Content ID 识别背景音乐。 +""" +import os +import sys +import socket +import threading +import subprocess +import queue +import time +import platform + +# 可选依赖:Demucs 和 PyTorch +DEMUCS_AVAILABLE = False +DEMUCS_MODEL = None +TORCH_AVAILABLE = False + +try: + import torch + TORCH_AVAILABLE = True +except ImportError: + pass + +try: + from demucs.pretrained import get_model + from demucs.apply import apply_model + DEMUCS_AVAILABLE = True +except ImportError: + pass + + +def _check_demucs_available(): + """检查 Demucs 是否可用""" + return DEMUCS_AVAILABLE and TORCH_AVAILABLE + + +def _load_demucs_model(use_gpu: bool): + """加载 Demucs 模型,支持 GPU/CPU 回退""" + global DEMUCS_MODEL + if not _check_demucs_available(): + return None + try: + # htdemucs 较轻量,适合实时;htdemucs_ft 质量更好但更慢 + model_name = "htdemucs" + device = "cpu" + if use_gpu and TORCH_AVAILABLE and torch.cuda.is_available(): + try: + device = "cuda" + except Exception: + device = "cpu" + model = get_model(model_name) + model.eval() + return model.to(device) + except Exception as e: + print(f"[WARN] Demucs 模型加载失败,回退原始音频: {e}") + return None + + +# Demucs 模型采样率 +DEMUCS_SAMPLE_RATE = 44100 +DEMUCS_CHANNELS = 2 +# 每次处理的音频长度(秒),影响延迟与质量 +DEMUCS_CHUNK_SEC = 2.0 +# 字节 per sample (s16le: 2 bytes) +BYTES_PER_SAMPLE = 2 + + +def _demucs_separate_vocals(model, audio_chunk, device): + """对音频块进行人声分离,返回人声轨道""" + import torch + # audio_chunk: numpy or tensor, shape (channels, samples) + if hasattr(audio_chunk, 'numpy'): + wav = audio_chunk + else: + import numpy as np + wav = torch.from_numpy(audio_chunk).float() + # Demucs 期望 (batch, channels, samples) + if wav.dim() == 2: + wav = wav.unsqueeze(0) + with torch.no_grad(): + out = apply_model(model, wav, device=device, progress=False, overlap=0.25) + # out: (batch, sources, channels, samples) + # sources: [drums, bass, other, vocals] + vocals = out[0, 3] # 取 vocals + return vocals.cpu().numpy() + + +def _run_demucs_worker(audio_read_pipe, audio_write_socket, model, device, stop_event): + """Demucs 工作线程:从 FFmpeg 读音频,分离人声,写入 socket""" + import numpy as np + chunk_samples = int(DEMUCS_SAMPLE_RATE * DEMUCS_CHUNK_SEC * DEMUCS_CHANNELS) + chunk_bytes = chunk_samples * BYTES_PER_SAMPLE + buf = b"" + + def read_audio(size): + if hasattr(audio_read_pipe, 'recv'): + return audio_read_pipe.recv(size) + return audio_read_pipe.read(size) + + try: + while not stop_event.is_set(): + try: + data = read_audio(65536) + if not data: + break + buf += data + except (socket.error, OSError, ValueError): + break + while len(buf) >= chunk_bytes: + chunk = buf[:chunk_bytes] + buf = buf[chunk_bytes:] + # s16le -> float + arr = np.frombuffer(chunk, dtype=np.int16) + arr = arr.reshape(-1, DEMUCS_CHANNELS).T.astype(np.float32) / 32768.0 + try: + vocals = _demucs_separate_vocals(model, arr, device) + # float -> s16le + out = (vocals * 32768).astype(np.int16).tobytes() + try: + audio_write_socket.sendall(out) + except (socket.error, OSError): + return + except Exception as e: + print(f"[WARN] Demucs 处理异常: {e}") + # 回退:直接透传原始音频 + try: + audio_write_socket.sendall(chunk) + except (socket.error, OSError): + return + # 剩余 buffer + if buf and audio_write_socket: + try: + arr = np.frombuffer(buf, dtype=np.int16) + arr = arr.reshape(-1, DEMUCS_CHANNELS).T.astype(np.float32) / 32768.0 + vocals = _demucs_separate_vocals(model, arr, device) + out = (vocals * 32768).astype(np.int16).tobytes() + audio_write_socket.sendall(out) + except Exception: + pass + except Exception as e: + print(f"[WARN] Demucs worker 异常: {e}") + finally: + try: + audio_write_socket.shutdown(socket.SHUT_WR) + audio_write_socket.close() + except Exception: + pass + + +def run_demucs_pipeline( + real_url: str, + youtube_rtmp: str, + douyin_headers: str, + nvenc_params: dict, + end_keywords: list, + stderr_queue_timeout: float, + no_frame_timeout: int, + start_check_after: int, + stale_output_seconds: int, + stream_ended_exception, + cleanup_proc_fn, + stderr_reader_fn, +): + """ + 运行 Demucs 管道:拉流 → 人声分离 → 推流 + 若主播下播应停止则 raise stream_ended_exception + """ + + # 检查 Demucs + use_gpu = nvenc_params.get("demucs_use_gpu", True) + model = _load_demucs_model(use_gpu) + if model is None: + raise RuntimeError("Demucs 模型不可用,将回退原始推流") + + device = next(model.parameters()).device + device_str = "cuda" if "cuda" in str(device) else "cpu" + print(f"[INFO] Demucs 人声分离已启用 ({device_str})") + + # TCP 服务器用于音频(跨平台) + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + port = server.getsockname()[1] + server.listen(1) + + tcp_audio_url = f"tcp://127.0.0.1:{port}" + stop_event = threading.Event() + conn = [None] # 用 list 以便在闭包中修改 + + def accept_conn(): + server.settimeout(120) + try: + c, _ = server.accept() + conn[0] = c + except Exception: + pass + finally: + try: + server.close() + except Exception: + pass + + accept_thread = threading.Thread(target=accept_conn, daemon=True) + accept_thread.start() + + # FFmpeg 拉流(视频) + ffmpeg_v_cmd = [ + "ffmpeg", "-y", "-loglevel", "info", "-nostdin", "-re", + "-stats", "-stats_period", "1", + "-rw_timeout", "30000000", + "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", + "-reconnect_delay_max", "5", + "-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets", + "-err_detect", "ignore_err", + "-headers", douyin_headers, + "-i", real_url, + "-map", "0:v", "-c:v", "copy", "-f", "nut", "pipe:1", + ] + + # FFmpeg 拉流(音频) + ffmpeg_a_cmd = [ + "ffmpeg", "-y", "-loglevel", "error", "-nostdin", "-re", + "-rw_timeout", "30000000", + "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1", + "-headers", douyin_headers, + "-i", real_url, + "-map", "0:a", "-f", "s16le", "-ac", str(DEMUCS_CHANNELS), + "-ar", str(DEMUCS_SAMPLE_RATE), "pipe:1", + ] + + # FFmpeg 推流(视频 stdin + 音频 tcp) + vf = "fps=30,scale=720:1280:force_original_aspect_ratio=decrease:flags=lanczos,pad=720:1280:(ow-iw)/2:(oh-ih)/2:black" + ffmpeg_out_cmd = [ + "ffmpeg", "-y", "-loglevel", "info", "-nostdin", + "-f", "nut", "-i", "pipe:0", + "-f", "s16le", "-ac", str(DEMUCS_CHANNELS), "-ar", str(DEMUCS_SAMPLE_RATE), + "-i", tcp_audio_url, + "-map", "0:v", "-map", "1:a", + "-vf", vf, + "-c:v", nvenc_params.get("codec_v", "libx264"), + "-preset", nvenc_params.get("preset_v", "fast"), + "-profile:v", "high", + "-b:v", "2600k", "-maxrate", "3000k", "-bufsize", "5200k", + "-vsync", "cfr", "-g", "60", "-keyint_min", "60", "-r", "30", + "-bf", "0", "-sc_threshold", "0", + "-x264-params", "force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0", + "-pix_fmt", "yuv420p", + "-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2", + "-af", "aresample=async=1:first_pts=0", + "-flags", "+global_header", "-flvflags", "no_duration_filesize", + "-f", "flv", youtube_rtmp, + ] + if nvenc_params.get("tune_param"): + idx = ffmpeg_out_cmd.index("-profile:v") + for p in reversed(nvenc_params["tune_param"]): + ffmpeg_out_cmd.insert(idx, p) + + proc_v = None + proc_a = None + proc_out = None + demucs_thread = None + + try: + proc_v = subprocess.Popen( + ffmpeg_v_cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=1024 * 1024, + ) + proc_a = subprocess.Popen( + ffmpeg_a_cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + bufsize=1024 * 1024, + ) + proc_out = subprocess.Popen( + ffmpeg_out_cmd, + stdin=subprocess.PIPE, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + bufsize=1024 * 1024, + ) + + # 等待 TCP 连接(FFmpeg_out 会连接) + accept_thread.join(timeout=15) + if conn[0] is None: + raise RuntimeError("Demucs 管道: 音频 TCP 连接超时") + + # 视频转发线程 + def copy_video(): + try: + while proc_v.poll() is None and proc_out.poll() is None: + chunk = proc_v.stdout.read(1024 * 256) + if not chunk: + break + proc_out.stdin.write(chunk) + except Exception: + pass + finally: + try: + proc_out.stdin.close() + except Exception: + pass + + copy_t = threading.Thread(target=copy_video, daemon=True) + copy_t.start() + + # Demucs 工作线程(从 proc_a 读,处理后写 conn) + # proc_a 输出是 stdout,conn 是 socket。需要把 proc_a.stdout 的数据通过 socket 发到 FFmpeg。 + # FFmpeg_out 的第二个输入是 tcp,已经连接了 conn。所以我们从 proc_a 读,Demucs 处理,写 conn。 + # 但 proc_a.stdout 是 pipe,conn 是 socket。我们需要一个线程读 proc_a 并写 conn。 + # 注意:proc_a 输出到 stdout,我们读。conn 是 FFmpeg 作为 client 连上来的,所以 FFmpeg 在读,我们写 conn。 + demucs_thread = threading.Thread( + target=_run_demucs_worker, + args=(proc_a.stdout, conn[0], model, device, stop_event), + daemon=True, + ) + demucs_thread.start() + + # 监控 proc_v 的 stderr(与源连接,用于检测下播) + stderr_q = queue.Queue() + reader_t = threading.Thread(target=stderr_reader_fn, args=(proc_v, stderr_q)) + reader_t.daemon = True + reader_t.start() + + start_time = last_frame_time = time.time() + proc_v.last_out_ts = time.time() + + while True: + try: + line = stderr_q.get(timeout=stderr_queue_timeout) + except queue.Empty: + if time.time() - getattr(proc_v, "last_out_ts", time.time()) > stale_output_seconds: + print("[WARN] 长时间无任何日志,强制重启进程") + break + continue + + if line is None: + if proc_v.poll() is not None: + break + continue + + line = line.strip() + if line.startswith("__ERR__READER__"): + print(f"[ERROR] stderr reader 异常: {line}") + continue + + if line: + proc_v.last_out_ts = time.time() + print(f"[FFmpeg] {line}") + line_lower = line.lower() + if any(kw in line_lower for kw in end_keywords): + print("[ERROR] 检测到主播真下播或被明确拒绝,停止推流") + raise stream_ended_exception() + if "frame=" in line_lower or "fps=" in line_lower: + last_frame_time = time.time() + if time.time() - start_time > start_check_after: + if time.time() - last_frame_time > no_frame_timeout: + print(f"[WARN] 连续 {no_frame_timeout}s 无新帧,判定主播已下播") + raise stream_ended_exception() + + except stream_ended_exception: + raise + except Exception as e: + print(f"[ERROR] Demucs 管道异常: {e}") + import traceback + traceback.print_exc() + finally: + stop_event.set() + cleanup_proc_fn(proc_v) + cleanup_proc_fn(proc_a) + cleanup_proc_fn(proc_out) + try: + if conn[0]: + conn[0].close() + except Exception: + pass + try: + server.close() + except Exception: + pass diff --git a/douyin_youtube_ffplay.py b/douyin_youtube_ffplay.py index 9f492ac..cc44373 100644 --- a/douyin_youtube_ffplay.py +++ b/douyin_youtube_ffplay.py @@ -11,6 +11,17 @@ import re import os import configparser +# Demucs 管道(可选,用于防版权) +try: + from demucs_pipeline import ( + _check_demucs_available, + run_demucs_pipeline, + ) + _DEMUCS_MODULE_AVAILABLE = True +except ImportError: + _DEMUCS_MODULE_AVAILABLE = False + _check_demucs_available = lambda: False + class StreamEnded(Exception): """主播真实下播的专用异常""" @@ -85,19 +96,30 @@ def start_douyin_youtube_ffplay( default_key = "qxvb-r47b-r5ju-6ud3-6k7z" # 备用默认 key youtube_rtmp = rtmp_base + default_key # 默认值 + enable_demucs = False + demucs_use_gpu = True if os.path.exists(config_file): config = configparser.ConfigParser() config.read(config_file, encoding="utf-8") if config.has_section("youtube"): for key_name, value in config.items("youtube"): - value = value.strip() - if value: - if value.startswith("rtmp://"): - youtube_rtmp = value - else: - youtube_rtmp = rtmp_base + value + key_lower = key_name.strip().lower() + val = value.strip() + if key_lower == "enable_demucs": + enable_demucs = val.lower() in ("1", "true", "yes", "是") + elif key_lower == "demucs_use_gpu": + demucs_use_gpu = val.lower() not in ("0", "false", "no", "否") + elif val and key_lower in ("key", "stream_key", "streamkey"): + youtube_rtmp = val if val.startswith("rtmp://") else rtmp_base + val print(f"[INFO] 从 {config_file} 读取 YouTube stream key: {youtube_rtmp}") - break # 只取第一个有效 key,保持单路推流(不加多路逻辑) + # 兼容:若上面未匹配到 key,取第一个非配置项的值作为 stream key + if youtube_rtmp == rtmp_base + default_key: + for k, v in config.items("youtube"): + if k.strip().lower() not in ("enable_demucs", "demucs_use_gpu") and v.strip(): + v = v.strip() + youtube_rtmp = v if v.startswith("rtmp://") else rtmp_base + v + print(f"[INFO] 从 {config_file} 读取 YouTube stream key: {youtube_rtmp}") + break # ====================== 常量配置 ====================== MAX_RETRY_DELAY = 90 @@ -151,6 +173,14 @@ def start_douyin_youtube_ffplay( except Exception as e: print(f"[WARN] NVENC 检测失败,回退软件编码: {e}") + nvenc_params = { + "codec_v": codec_v, + "preset_v": preset_v, + "tune_param": tune_param, + "demucs_use_gpu": demucs_use_gpu, + } + use_demucs = enable_demucs and _DEMUCS_MODULE_AVAILABLE and _check_demucs_available() + # 720p (最原始) ffmpeg_command = [ "ffmpeg", "-y", "-loglevel", "info", "-nostdin", "-re", @@ -435,20 +465,45 @@ def start_douyin_youtube_ffplay( stderr_q = queue.Queue() print(f"[INFO][{datetime.datetime.now():%H:%M:%S}] 启动 YouTube 推流 → {stream_title}") - proc = subprocess.Popen( - ffmpeg_command, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE, - text=True, - bufsize=1, - universal_newlines=True - ) - proc.last_out_ts = time.time() - reader_t = threading.Thread(target=_stderr_reader_thread, args=(proc, stderr_q)) - reader_t.daemon = True - reader_t.start() + proc = None + if use_demucs: + try: + run_demucs_pipeline( + real_url=real_url, + youtube_rtmp=youtube_rtmp, + douyin_headers=douyin_headers, + nvenc_params=nvenc_params, + end_keywords=END_KEYWORDS, + stderr_queue_timeout=STDERR_QUEUE_TIMEOUT, + no_frame_timeout=NO_FRAME_TIMEOUT, + start_check_after=START_CHECK_AFTER, + stale_output_seconds=STALE_OUTPUT_SECONDS, + stream_ended_exception=StreamEnded, + cleanup_proc_fn=cleanup_proc, + stderr_reader_fn=_stderr_reader_thread, + ) + except StreamEnded: + raise + except Exception as e: + print(f"[WARN] Demucs 管道异常,回退原始推流: {e}") + use_demucs = False + + if not use_demucs: + proc = subprocess.Popen( + ffmpeg_command, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + proc.last_out_ts = time.time() + + reader_t = threading.Thread(target=_stderr_reader_thread, args=(proc, stderr_q)) + reader_t.daemon = True + reader_t.start() start_time = last_frame_time = time.time() diff --git a/requirements-youtube.txt b/requirements-youtube.txt new file mode 100644 index 0000000..cd82fb8 --- /dev/null +++ b/requirements-youtube.txt @@ -0,0 +1,12 @@ +# YouTube 转播 + Demucs 人声分离(防版权)所需依赖 +# 在 requirements.txt 基础上额外安装: +# pip install -r requirements.txt -r requirements-youtube.txt + +# CPU 版本 +demucs>=4.0.0 +torch>=2.0.0 +torchaudio>=2.0.0 + +# 若使用 NVIDIA GPU,请安装 CUDA 版 PyTorch 后覆盖: +pip uninstall torch torchaudio +pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 diff --git a/requirements.txt b/requirements.txt index 18f105b..58ced78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,11 @@ pycryptodome>=3.20.0 distro>=1.9.0 tqdm>=4.67.1 httpx[http2]>=0.28.1 -PyExecJS>=1.5.1 \ No newline at end of file +PyExecJS>=1.5.1 + +# 可选:YouTube 转播时 Demucs 人声分离(防版权识别) +# pip install demucs torch torchaudio +# GPU 版本: pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu118 +# demucs>=4.0.0 +# torch>=2.0.0 +# torchaudio>=2.0.0 \ No newline at end of file