's'
This commit is contained in:
834
README.md
834
README.md
@@ -1,679 +1,259 @@
|
||||

|
||||
# DouyinLiveRecorder 转播版
|
||||
|
||||
## 💡简介
|
||||
[](https://www.python.org/downloads/release/python-3116/)
|
||||
[](https://github.com/ihmily/DouyinLiveRecorder)
|
||||
[](https://hub.docker.com/r/ihmily/douyin-live-recorder/tags)
|
||||

|
||||
[](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)
|
||||
[](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://github.com/ihmily/DouyinLiveRecorder)
|
||||
[](LICENSE)
|
||||
|
||||
一款**简易**的可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制以及直播状态推送。
|
||||
基于 [DouyinLiveRecorder](https://github.com/ihmily/DouyinLiveRecorder) 的**转播增强版**,将原本的录制下载功能扩展为**实时推流转播**,支持抖音/TikTok 等直播推送到 YouTube、HDMI 等目标。
|
||||
|
||||
</div>
|
||||
## 与原版的主要区别
|
||||
|
||||
## 😺已支持平台
|
||||
| 功能 | 原版 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
|
||||
- [ ] 更多平台正在更新中
|
||||
---
|
||||
|
||||
</div>
|
||||
## 功能特性
|
||||
|
||||
## 🎈项目结构
|
||||
### 转播模式
|
||||
|
||||
- **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
|
||||
```
|
||||
|
||||
</div>
|
||||
---
|
||||
|
||||
## 🌱使用说明
|
||||
## 快速开始
|
||||
|
||||
- 对于只想使用录制软件的小白用户,进入[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** 版本, **不过这完全是可选的**<br />
|
||||
> 使用以下命令安装
|
||||
> ```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 镜像源:<br />
|
||||
> 在 `pip` 命令使用 `-i` 参数指定, 如 `pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple`<br />
|
||||
> 或者在 `uv` 命令 `--index` 选项指定, 如 `uv sync --index https://pypi.tuna.tsinghua.edu.cn/simple`
|
||||
- 浏览器访问配置页面
|
||||
- 支持编辑 `youtube.ini`、`URL_config.ini`
|
||||
- 选择 youtube / tiktok / obs 等模式
|
||||
|
||||
<details>
|
||||
---
|
||||
|
||||
<summary>如果已安装 <b>Python>=3.10</b> 环境</summary>
|
||||
## 常见问题
|
||||
|
||||
- :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
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>如果未安装 <b>Python>=3.10</b> 环境</summary>
|
||||
|
||||
你可以使用 [**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
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
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
|
||||
|
||||
 
|
||||
|
||||
## ❤️贡献者
|
||||
|
||||
   [](https://github.com/ihmily)
|
||||
[](https://github.com/iridescentGray)
|
||||
[](https://github.com/annidy)
|
||||
[](https://github.com/wwkk2580)
|
||||
[](https://github.com/missuo)
|
||||
<a href="https://github.com/xueli12" target="_blank"><img src="https://github.com/xueli12.png?size=50" alt="xueli12" style="width:53px; height:51px;" /></a>
|
||||
<a href="https://github.com/kaine1973" target="_blank"><img src="https://github.com/kaine1973.png?size=50" alt="kaine1973" style="width:53px; height:51px;" /></a>
|
||||
<a href="https://github.com/yinruiqing" target="_blank"><img src="https://github.com/yinruiqing.png?size=50" alt="yinruiqing" style="width:53px; height:51px;" /></a>
|
||||
<a href="https://github.com/Max-Tortoise" target="_blank"><img src="https://github.com/Max-Tortoise.png?size=50" alt="Max-Tortoise" style="width:53px; height:51px;" /></a>
|
||||
[](https://github.com/justdoiting)
|
||||
[](https://github.com/dhbxs)
|
||||
[](https://github.com/wujiyu115)
|
||||
[](https://github.com/zhanghao333)
|
||||
<a href="https://github.com/gyc0123" target="_blank"><img src="https://github.com/gyc0123.png?size=50" alt="gyc0123" style="width:53px; height:51px;" /></a>
|
||||
|
||||
   [](https://github.com/HoratioShaw)
|
||||
[](https://github.com/nov30th)
|
||||
[](https://github.com/727155455)
|
||||
[](https://github.com/nixingshiguang)
|
||||
[](https://github.com/1411430556)
|
||||
[](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
|
||||
- 最后,祝大家新年快乐!
|
||||
|
||||
<details><summary>点击展开更多提交日志</summary>
|
||||
|
||||
- 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接口,上传即可用
|
||||
</details>
|
||||
 
|
||||
|
||||
## 有问题可以提issue, 我会在这里持续添加更多直播平台的录制 欢迎Star
|
||||
####
|
||||
MIT License
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
[youtube]
|
||||
# 只写 stream key(推荐方式)
|
||||
key = x04z-564w-aks7-embw-30y4
|
||||
key = x04z-564w-aks7-embw-30y4
|
||||
|
||||
# 是否启用 Demucs 人声分离(防 YouTube Content ID 版权识别)
|
||||
# 是 = 启用,只保留人声;否 = 使用原始音频
|
||||
enable_demucs = 是
|
||||
|
||||
# Demucs 是否优先使用 NVIDIA GPU 加速
|
||||
# 是 = 有显卡则用 GPU;否 = 强制 CPU;无显卡时自动回退到 CPU
|
||||
demucs_use_gpu = 是
|
||||
387
demucs_pipeline.py
Normal file
387
demucs_pipeline.py
Normal file
@@ -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
|
||||
@@ -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()
|
||||
|
||||
|
||||
12
requirements-youtube.txt
Normal file
12
requirements-youtube.txt
Normal file
@@ -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
|
||||
@@ -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
|
||||
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
|
||||
Reference in New Issue
Block a user