mirror of
https://github.com/yzfly/douyin-mcp-server.git
synced 2026-09-14 20:17:26 +08:00
v1.3.0: 新增 Claude Code Skill,支持批量提取文案
- 新增 douyin-video Skill,支持命令行批量提取视频文案 - 文案自动保存为 Markdown 格式 - 每个视频独立文件夹存储 - 支持 --save-video 同时保存视频 - 统一环境变量名为 API_KEY - 更新 README 文档
This commit is contained in:
+5
-1
@@ -45,4 +45,8 @@ htmlcov/
|
||||
*.whl
|
||||
|
||||
.claude
|
||||
temp/
|
||||
temp/
|
||||
|
||||
# Local
|
||||
.env
|
||||
output/
|
||||
@@ -1,48 +1,41 @@
|
||||
# 抖音无水印视频文本提取 MCP 服务器
|
||||
# 抖音无水印视频下载与文案提取
|
||||
|
||||
[](https://badge.fury.io/py/douyin-mcp-server)
|
||||
[](https://pypi.org/project/douyin-mcp-server/)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
一个基于 Model Context Protocol (MCP) 的服务器,可以从抖音分享链接下载无水印视频,提取音频并转换为文本。
|
||||
从抖音分享链接下载无水印视频,提取音频并转换为文本。
|
||||
|
||||
<a href="https://glama.ai/mcp/servers/@yzfly/douyin-mcp-server">
|
||||
<img width="380" height="200" src="https://glama.ai/mcp/servers/@yzfly/douyin-mcp-server/badge" alt="douyin-mcp-server MCP server" />
|
||||
</a>
|
||||
|
||||
## 📋 项目声明
|
||||
## 📦 两种使用方式
|
||||
|
||||
**官方文档地址:** https://github.com/yzfly/douyin-mcp-server
|
||||
|
||||
请以本项目的 [README.md](https://github.com/yzfly/douyin-mcp-server/blob/main/README.md) 文件为准,了解项目的功能特性、使用方法、API 配置说明等详细信息。
|
||||
|
||||
**重要提醒:** 第三方平台如因自身 MCP Server 功能支持度限制而无法正常使用,请联系相应平台方。本项目不提供任何形式的技术支持或保证,用户需自行承担使用本项目可能产生的任何损失或损害。
|
||||
|
||||
**法律声明:**
|
||||
1. 本项目基于 Apache 2.0 协议发布
|
||||
2. 本项目仅供学习和研究使用,不得用于任何违法或违规目的
|
||||
3. 本项目的使用必须遵守相关法律法规
|
||||
4. 本项目的作者和贡献者不对项目的任何部分承担法律责任
|
||||
| 方式 | 适用场景 | 说明 |
|
||||
|------|----------|------|
|
||||
| **MCP Server** | Claude Desktop、Cherry Studio 等 | MCP 协议集成 |
|
||||
| **Claude Code Skill** | Claude Code CLI | 命令行批量提取,Markdown 输出 |
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
- 🎵 **无水印视频获取** - 从抖音分享链接获取高质量无水印视频
|
||||
- 🎵 **无水印视频获取** - 从抖音分享链接获取高质量无水印视频(无需 API 密钥)
|
||||
- 🎧 **智能音频提取** - 自动从视频中提取音频内容
|
||||
- 📝 **AI 文本识别** - 使用先进的语音识别技术提取文本内容
|
||||
- 📝 **AI 文本识别** - 使用硅基流动 SenseVoice API 提取文案
|
||||
- 📄 **Markdown 输出** - 文案自动保存为 Markdown 格式(Skill 模式)
|
||||
- 🧹 **自动清理** - 智能清理处理过程中的临时文件
|
||||
- 🔧 **灵活配置** - 支持自定义 API 配置,默认使用 [阿里云百炼 API](https://help.aliyun.com/zh/model-studio/get-api-key?)
|
||||
|
||||
## 🚀 快速开始
|
||||
---
|
||||
|
||||
## 🚀 方式一:MCP Server (v1.3.0)
|
||||
|
||||
适用于 Claude Desktop、Cherry Studio 等支持 MCP 协议的应用。
|
||||
|
||||
### 步骤 1:获取 API 密钥
|
||||
|
||||
前往 [阿里云百炼 API](https://help.aliyun.com/zh/model-studio/get-api-key?) 获取您的 `DASHSCOPE_API_KEY`:
|
||||
前往 [硅基流动](https://cloud.siliconflow.cn/i/TxUlXG3u) 注册并获取 API Key(有免费额度)。
|
||||
|
||||

|
||||
|
||||
### 步骤 2:配置环境变量
|
||||
|
||||
在 Claude Desktop、Cherry Studio 等支持 MCP Server 的应用配置文件中添加以下配置:
|
||||
### 步骤 2:配置 MCP Server
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -51,200 +44,142 @@
|
||||
"command": "uvx",
|
||||
"args": ["douyin-mcp-server"],
|
||||
"env": {
|
||||
"DASHSCOPE_API_KEY": "sk-xxxx"
|
||||
"API_KEY": "sk-xxxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3:开始使用
|
||||
### 工具说明
|
||||
|
||||
配置完成后,您就可以在支持的应用中正常调用 MCP 工具了。
|
||||
| 工具 | 功能 | 需要 API |
|
||||
|------|------|----------|
|
||||
| `get_douyin_download_link` | 获取无水印下载链接 | ❌ |
|
||||
| `extract_douyin_text` | 提取视频文案 | ✅ |
|
||||
| `parse_douyin_video_info` | 解析视频信息 | ❌ |
|
||||
|
||||
## ⚙️ API 配置说明
|
||||
---
|
||||
|
||||
### 当前版本(>= 1.2.0)
|
||||
## 🛠️ 方式二:Claude Code Skill
|
||||
|
||||
最新版本默认使用阿里云百炼 API,具有以下优势:
|
||||
- ✅ 识别效果更好
|
||||
- ✅ 处理速度更快
|
||||
- ✅ 本地资源消耗更小
|
||||
适用于 Claude Code CLI,支持批量提取文案并保存为 Markdown 文件。
|
||||
|
||||
**配置步骤:**
|
||||
1. 前往 [阿里云百炼](https://help.aliyun.com/zh/model-studio/get-api-key?) 开通 API 服务
|
||||
2. 获取 API Key 并配置到环境变量 `DASHSCOPE_API_KEY` 中
|
||||
### 安装 Skill
|
||||
|
||||
### 旧版本兼容(<= 1.1.0)
|
||||
|
||||
如果您需要使用旧版本,请使用以下配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"douyin-mcp": {
|
||||
"command": "uvx",
|
||||
"args": ["douyin-mcp-server@1.1.0"],
|
||||
"env": {
|
||||
"DOUYIN_API_KEY": "your-api-key-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 旧版本使用硅基流动 API,需要在 [硅基流动](https://cloud.siliconflow.cn/i/TxUlXG3u) 注册账号并获取 API Key。
|
||||
|
||||
📖 [1.1.0 版本文档](https://pypi.org/project/douyin-mcp-server/1.1.0/)
|
||||
|
||||
## 🛠️ 工具说明
|
||||
|
||||
### `get_douyin_download_link`
|
||||
|
||||
获取抖音视频的无水印下载链接。
|
||||
|
||||
**参数:**
|
||||
- `share_link` (string): 抖音分享链接或包含链接的文本
|
||||
|
||||
**返回:**
|
||||
- JSON 格式的下载链接和视频信息
|
||||
|
||||
**特点:** 无需 API 密钥即可使用
|
||||
|
||||
### `extract_douyin_text`
|
||||
|
||||
完整的文本提取工具,一站式完成视频到文本的转换。
|
||||
|
||||
**处理流程:**
|
||||
1. 解析抖音分享链接
|
||||
2. 直接使用视频 URL 进行语音识别
|
||||
3. 返回提取的文本内容
|
||||
|
||||
**参数:**
|
||||
- `share_link` (string): 抖音分享链接或包含链接的文本
|
||||
- `model` (string, 可选): 语音识别模型,默认使用 `paraformer-v2`
|
||||
|
||||
**环境变量要求:**
|
||||
- `DASHSCOPE_API_KEY`: 阿里云百炼 API 密钥(必需)
|
||||
|
||||
### `parse_douyin_video_info`
|
||||
|
||||
轻量级视频信息解析工具。
|
||||
|
||||
**参数:**
|
||||
- `share_link` (string): 抖音分享链接
|
||||
|
||||
**特点:** 仅解析视频基本信息,不下载视频文件
|
||||
|
||||
### 资源访问
|
||||
|
||||
- `douyin://video/{video_id}`: 通过视频 ID 获取详细信息
|
||||
|
||||
## 📦 系统要求
|
||||
|
||||
### 运行环境
|
||||
- **Python**: 3.10 或更高版本
|
||||
|
||||
### 依赖库
|
||||
- `requests` - HTTP 请求处理
|
||||
- `ffmpeg-python` - 音视频处理
|
||||
- `tqdm` - 进度条显示
|
||||
- `mcp` - Model Context Protocol 支持
|
||||
- `dashscope` - 阿里云百炼 API 客户端
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
- 🔑 **API 密钥必需**:文本提取功能需要有效的阿里云百炼 API 密钥
|
||||
- 🆓 **部分功能免费**:获取下载链接功能无需 API 密钥
|
||||
- 📱 **格式支持**:支持大部分抖音视频格式
|
||||
- 🚀 **性能优化**:使用阿里云百炼 API 获得更快更准确的识别效果
|
||||
|
||||
## 🔧 开发指南
|
||||
|
||||
### 本地开发环境搭建
|
||||
下载 `douyin-video.skill` 文件,解压到 skills 目录:
|
||||
|
||||
```bash
|
||||
# 全局安装
|
||||
unzip douyin-video.skill -d ~/.claude/skills/
|
||||
|
||||
# 或项目级安装
|
||||
unzip douyin-video.skill -d .claude/skills/
|
||||
```
|
||||
|
||||
### 配置环境变量
|
||||
|
||||
```bash
|
||||
export API_KEY="your-siliconflow-api-key"
|
||||
```
|
||||
|
||||
### 使用方法
|
||||
|
||||
```bash
|
||||
# 获取视频信息(无需 API)
|
||||
python douyin_downloader.py -l "抖音链接" -a info
|
||||
|
||||
# 下载视频
|
||||
python douyin_downloader.py -l "抖音链接" -a download -o ./videos
|
||||
|
||||
# 提取文案
|
||||
python douyin_downloader.py -l "抖音链接" -a extract -o ./output
|
||||
|
||||
# 提取文案并保存视频
|
||||
python douyin_downloader.py -l "抖音链接" -a extract -o ./output --save-video
|
||||
```
|
||||
|
||||
### 输出格式
|
||||
|
||||
```
|
||||
output/
|
||||
├── 7600361826030865707/
|
||||
│ └── transcript.md # Markdown 格式文案
|
||||
└── 7581044356631612699/
|
||||
├── transcript.md
|
||||
└── *.mp4 # --save-video 时保存
|
||||
```
|
||||
|
||||
**transcript.md 示例:**
|
||||
|
||||
```markdown
|
||||
# 视频标题
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 视频ID | `7600361826030865707` |
|
||||
| 提取时间 | 2026-01-30 14:19:00 |
|
||||
| 下载链接 | [点击下载](url) |
|
||||
|
||||
---
|
||||
|
||||
## 文案内容
|
||||
|
||||
(语音识别文字)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 系统要求
|
||||
|
||||
- **Python**: 3.8+
|
||||
- **FFmpeg**: 必须安装
|
||||
- macOS: `brew install ffmpeg`
|
||||
- Ubuntu: `apt install ffmpeg`
|
||||
|
||||
---
|
||||
|
||||
## 🔧 本地开发
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/yzfly/douyin-mcp-server.git
|
||||
cd douyin-mcp-server
|
||||
|
||||
# 安装依赖(开发模式)
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 启动服务器进行测试
|
||||
python -m douyin_mcp_server.server
|
||||
```
|
||||
|
||||
### Claude Desktop 本地开发配置
|
||||
|
||||
在 Claude Desktop 配置文件中添加本地开发配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"douyin-mcp": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--directory",
|
||||
"/path/to/your/douyin-mcp-server",
|
||||
"python",
|
||||
"-m",
|
||||
"douyin_mcp_server"
|
||||
],
|
||||
"env": {
|
||||
"DASHSCOPE_API_KEY": "your-api-key-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
### 使用风险
|
||||
- 使用者对本项目的使用完全自主决定,并自行承担所有风险
|
||||
- 作者对使用者因使用本项目而产生的任何损失、责任或风险概不负责
|
||||
- 本项目仅供学习和研究使用
|
||||
- 使用者需遵守相关法律法规
|
||||
- 禁止用于侵犯知识产权的行为
|
||||
- 作者不对使用本项目产生的损失承担责任
|
||||
|
||||
### 代码质量
|
||||
- 本项目基于现有知识和技术开发,作者努力确保代码的正确性和安全性
|
||||
- 但不保证代码完全没有错误或缺陷,使用者需自行评估和测试
|
||||
---
|
||||
|
||||
### 第三方依赖
|
||||
- 本项目依赖的第三方库、插件或服务遵循各自的开源或商业许可
|
||||
- 使用者需自行查阅并遵守相应协议
|
||||
- 作者不对第三方组件的稳定性、安全性及合规性承担责任
|
||||
## 📝 更新日志
|
||||
|
||||
### 法律合规
|
||||
- 使用者必须自行研究相关法律法规,确保使用行为合法合规
|
||||
- 任何违反法律法规导致的法律责任和风险,均由使用者自行承担
|
||||
- 禁止使用本工具从事任何侵犯知识产权的行为
|
||||
- 开发者不参与、不支持、不认可任何非法内容的获取或分发
|
||||
### v1.3.0 (最新)
|
||||
|
||||
### 数据处理
|
||||
- 本项目不对使用者的数据收集、存储、传输等处理活动的合规性承担责任
|
||||
- 使用者应自行遵守相关法律法规,确保数据处理行为合法正当
|
||||
- ✨ **Claude Code Skill**:新增命令行工具,支持批量提取文案
|
||||
- 📄 **Markdown 输出**:文案自动保存为 Markdown 格式
|
||||
- 🎯 **双 API 支持**:同时支持硅基流动和阿里云百炼 API
|
||||
|
||||
### 责任限制
|
||||
- 使用者不得将项目作者、贡献者或相关方与使用行为联系起来
|
||||
- 不得要求作者对使用项目产生的任何损失或损害负责
|
||||
- 基于本项目的二次开发、修改或编译程序与原作者无关
|
||||
### v1.2.0
|
||||
|
||||
### 知识产权
|
||||
- 本项目不授予使用者任何专利许可
|
||||
- 若使用本项目导致专利纠纷或侵权,使用者自行承担全部风险和责任
|
||||
- 未经书面授权,不得用于商业宣传、推广或再授权
|
||||
- 🔄 **API 切换**:升级为阿里云百炼 API(环境变量 `DASHSCOPE_API_KEY`)
|
||||
|
||||
### 服务终止
|
||||
- 作者保留随时终止向违反声明的使用者提供服务的权利
|
||||
- 可能要求违规使用者销毁已获取的代码及衍生作品
|
||||
- 作者保留在不另行通知的情况下更新本声明的权利
|
||||
### v1.1.0
|
||||
|
||||
**⚠️ 重要提醒:在使用本项目前,请认真阅读并完全理解上述免责声明。如有疑问或不同意任何条款,请勿使用本项目。继续使用即视为完全接受上述声明并自愿承担所有风险和后果。**
|
||||
- 🐛 **问题修复**:修复文件名过长导致的错误
|
||||
|
||||
### v1.0.0
|
||||
|
||||
- 🎉 首次发布
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
@@ -252,27 +187,4 @@ Apache License 2.0
|
||||
|
||||
## 👨💻 作者
|
||||
|
||||
- **yzfly** - [yz.liu.me@gmail.com](mailto:yz.liu.me@gmail.com)
|
||||
- GitHub: [https://github.com/yzfly](https://github.com/yzfly)
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!我们期待您的参与和贡献。
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v1.2.0 (最新)
|
||||
- 🚀 **性能提升**:更快、更准确的视频文案提取
|
||||
- 🔄 **API 升级**:切换到阿里云百炼 API,显著提升识别准确率
|
||||
- 🔧 **配置更新**:环境变量从 `DOUYIN_API_KEY` 更新为 `DASHSCOPE_API_KEY`
|
||||
|
||||
### v1.1.0
|
||||
- 🐛 **问题修复**:修复提取视频时文件名过长导致的错误
|
||||
|
||||
### v1.0.0
|
||||
- 🎉 **首次发布**:初始版本
|
||||
- ✨ **核心功能**:支持抖音视频文本提取
|
||||
- 🔗 **链接获取**:支持获取无水印视频下载链接
|
||||
- 🔐 **环境配置**:从环境变量读取 API 密钥
|
||||
- 🧹 **自动清理**:自动清理临时文件
|
||||
- ⚙️ **灵活配置**:支持自定义 API 配置
|
||||
**yzfly** - [GitHub](https://github.com/yzfly) | [Email](mailto:yz.liu.me@gmail.com)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,153 @@
|
||||
---
|
||||
name: douyin-video
|
||||
description: "抖音无水印视频下载和文案提取工具. 从抖音分享链接获取无水印视频下载链接, 下载视频, 提取视频中的语音文案并自动保存到文件. 适用场景包括获取抖音视频信息, 下载无水印视频, 批量提取视频文案. 当用户需要处理抖音视频链接或提取视频内容时触发."
|
||||
---
|
||||
|
||||
# 抖音无水印视频下载和文案提取
|
||||
|
||||
从抖音分享链接获取无水印视频下载链接, 下载视频, 并使用语音识别提取视频中的文案, 自动保存到文件.
|
||||
|
||||
## 功能概述
|
||||
|
||||
- **获取下载链接**: 从抖音分享链接解析出无水印视频的直接下载地址 (无需 API 密钥)
|
||||
- **下载视频**: 将无水印视频下载到本地指定目录
|
||||
- **提取文案**: 通过语音识别从视频中提取文字内容 (需要硅基流动 API 密钥)
|
||||
- **自动保存**: 每个视频的文案自动保存到独立文件夹 (视频ID为文件夹名)
|
||||
|
||||
## 环境要求
|
||||
|
||||
### 依赖安装
|
||||
|
||||
```bash
|
||||
pip install requests ffmpeg-python
|
||||
```
|
||||
|
||||
### 系统要求
|
||||
|
||||
- FFmpeg 必须安装在系统中 (用于音视频处理)
|
||||
- macOS: `brew install ffmpeg`
|
||||
- Ubuntu: `apt install ffmpeg`
|
||||
|
||||
### API 密钥配置 (仅文案提取需要)
|
||||
|
||||
文案提取功能使用硅基流动 API, 需要设置环境变量:
|
||||
|
||||
```bash
|
||||
export API_KEY="your-siliconflow-api-key"
|
||||
```
|
||||
|
||||
获取 API 密钥: https://cloud.siliconflow.cn/
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方法一: 使用脚本 (推荐)
|
||||
|
||||
```bash
|
||||
# 获取视频信息和下载链接 (无需 API 密钥)
|
||||
python douyin_downloader.py --link "抖音分享链接" --action info
|
||||
|
||||
# 下载视频到指定目录
|
||||
python douyin_downloader.py --link "抖音分享链接" --action download --output ./videos
|
||||
|
||||
# 提取视频文案并保存到文件 (需要 API_KEY 环境变量)
|
||||
python douyin_downloader.py --link "抖音分享链接" --action extract --output ./output
|
||||
|
||||
# 提取文案并同时保存视频
|
||||
python douyin_downloader.py --link "抖音分享链接" --action extract --output ./output --save-video
|
||||
|
||||
# 安静模式 (减少输出)
|
||||
python douyin_downloader.py --link "抖音分享链接" --action extract --output ./output --quiet
|
||||
```
|
||||
|
||||
### 输出目录结构
|
||||
|
||||
提取文案后, 每个视频会保存到独立文件夹:
|
||||
|
||||
```
|
||||
output/
|
||||
├── 7600361826030865707/ # 视频ID为文件夹名
|
||||
│ └── transcript.md # Markdown 格式文案文件
|
||||
├── 7581044356631612699/
|
||||
│ ├── transcript.md
|
||||
│ └── 7581044356631612699.mp4 # 使用 --save-video 时保存
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Markdown 文案格式
|
||||
|
||||
```markdown
|
||||
# 视频标题
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 视频ID | `7600361826030865707` |
|
||||
| 提取时间 | 2026-01-30 14:19:00 |
|
||||
| 下载链接 | [点击下载](url) |
|
||||
|
||||
---
|
||||
|
||||
## 文案内容
|
||||
|
||||
(语音识别的文字内容)
|
||||
```
|
||||
|
||||
### 方法二: 在 Python 代码中调用
|
||||
|
||||
```python
|
||||
from douyin_downloader import get_video_info, download_video, extract_text
|
||||
|
||||
# 获取视频信息
|
||||
info = get_video_info("抖音分享链接")
|
||||
print(f"视频ID: {info['video_id']}")
|
||||
print(f"标题: {info['title']}")
|
||||
print(f"下载链接: {info['url']}")
|
||||
|
||||
# 下载视频
|
||||
video_path = download_video("抖音分享链接", output_dir="./videos")
|
||||
|
||||
# 提取文案并保存到文件
|
||||
result = extract_text("抖音分享链接", output_dir="./output")
|
||||
print(f"文案已保存到: {result['output_path']}")
|
||||
print(result['text'])
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 获取视频信息
|
||||
|
||||
1. 解析抖音分享链接, 提取真实的视频 URL
|
||||
2. 模拟移动端请求获取页面数据
|
||||
3. 从页面 JSON 数据中提取无水印视频地址
|
||||
4. 返回视频 ID, 标题和下载链接
|
||||
|
||||
### 提取视频文案
|
||||
|
||||
1. 解析分享链接获取视频信息
|
||||
2. 下载视频到临时目录
|
||||
3. 使用 FFmpeg 从视频中提取音频 (MP3 格式)
|
||||
4. 调用硅基流动 SenseVoice API 进行语音识别
|
||||
5. 清理临时文件, 返回识别的文本
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 无法解析链接
|
||||
|
||||
- 确保链接是有效的抖音分享链接
|
||||
- 链接格式通常为 `https://v.douyin.com/xxxxx/` 或完整的抖音视频 URL
|
||||
|
||||
### 提取文案失败
|
||||
|
||||
- 检查 `API_KEY` 环境变量是否已设置
|
||||
- 确保 API 密钥有效且有足够的配额
|
||||
- 确保 FFmpeg 已正确安装
|
||||
|
||||
### 下载速度慢
|
||||
|
||||
- 这取决于网络条件和视频大小
|
||||
- 脚本会显示下载进度
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 本工具仅供学习和研究使用
|
||||
- 使用时需遵守相关法律法规
|
||||
- 请勿用于任何侵犯版权或违法的目的
|
||||
@@ -0,0 +1,390 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
抖音无水印视频下载和文案提取工具
|
||||
|
||||
功能:
|
||||
1. 从抖音分享链接获取无水印视频下载链接
|
||||
2. 下载视频并提取音频
|
||||
3. 使用硅基流动 API 从音频中提取文本
|
||||
4. 自动保存文案到文件 (一个视频一个文件夹)
|
||||
|
||||
环境变量:
|
||||
- API_KEY: 硅基流动 API 密钥 (用于文案提取功能)
|
||||
|
||||
使用示例:
|
||||
# 获取下载链接 (无需 API 密钥)
|
||||
python douyin_downloader.py --link "抖音分享链接" --action info
|
||||
|
||||
# 下载视频
|
||||
python douyin_downloader.py --link "抖音分享链接" --action download --output ./videos
|
||||
|
||||
# 提取文案并保存到文件 (需要 API_KEY 环境变量)
|
||||
python douyin_downloader.py --link "抖音分享链接" --action extract --output ./output
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""检查必要的依赖是否已安装"""
|
||||
missing = []
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
missing.append("requests")
|
||||
try:
|
||||
import ffmpeg
|
||||
except ImportError:
|
||||
missing.append("ffmpeg-python")
|
||||
|
||||
if missing:
|
||||
print(f"缺少依赖: {', '.join(missing)}")
|
||||
print(f"请运行: pip install {' '.join(missing)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
check_dependencies()
|
||||
|
||||
import requests
|
||||
import ffmpeg
|
||||
|
||||
# 请求头,模拟移动端访问
|
||||
HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/121.0.2277.107 Version/17.0 Mobile/15E148 Safari/604.1'
|
||||
}
|
||||
|
||||
# 硅基流动 API 配置
|
||||
DEFAULT_API_BASE_URL = "https://api.siliconflow.cn/v1/audio/transcriptions"
|
||||
DEFAULT_MODEL = "FunAudioLLM/SenseVoiceSmall"
|
||||
|
||||
|
||||
class DouyinProcessor:
|
||||
"""抖音视频处理器"""
|
||||
|
||||
def __init__(self, api_key: str = "", api_base_url: Optional[str] = None, model: Optional[str] = None):
|
||||
self.api_key = api_key
|
||||
self.api_base_url = api_base_url or DEFAULT_API_BASE_URL
|
||||
self.model = model or DEFAULT_MODEL
|
||||
self.temp_dir = Path(tempfile.mkdtemp())
|
||||
|
||||
def __del__(self):
|
||||
"""清理临时目录"""
|
||||
if hasattr(self, 'temp_dir') and self.temp_dir.exists():
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def parse_share_url(self, share_text: str) -> dict:
|
||||
"""从分享文本中提取无水印视频链接"""
|
||||
# 提取分享链接
|
||||
urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', share_text)
|
||||
if not urls:
|
||||
raise ValueError("未找到有效的分享链接")
|
||||
|
||||
share_url = urls[0]
|
||||
share_response = requests.get(share_url, headers=HEADERS)
|
||||
video_id = share_response.url.split("?")[0].strip("/").split("/")[-1]
|
||||
share_url = f'https://www.iesdouyin.com/share/video/{video_id}'
|
||||
|
||||
# 获取视频页面内容
|
||||
response = requests.get(share_url, headers=HEADERS)
|
||||
response.raise_for_status()
|
||||
|
||||
pattern = re.compile(
|
||||
pattern=r"window\._ROUTER_DATA\s*=\s*(.*?)</script>",
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
find_res = pattern.search(response.text)
|
||||
|
||||
if not find_res or not find_res.group(1):
|
||||
raise ValueError("从HTML中解析视频信息失败")
|
||||
|
||||
# 解析JSON数据
|
||||
json_data = json.loads(find_res.group(1).strip())
|
||||
VIDEO_ID_PAGE_KEY = "video_(id)/page"
|
||||
NOTE_ID_PAGE_KEY = "note_(id)/page"
|
||||
|
||||
if VIDEO_ID_PAGE_KEY in json_data["loaderData"]:
|
||||
original_video_info = json_data["loaderData"][VIDEO_ID_PAGE_KEY]["videoInfoRes"]
|
||||
elif NOTE_ID_PAGE_KEY in json_data["loaderData"]:
|
||||
original_video_info = json_data["loaderData"][NOTE_ID_PAGE_KEY]["videoInfoRes"]
|
||||
else:
|
||||
raise Exception("无法从JSON中解析视频或图集信息")
|
||||
|
||||
data = original_video_info["item_list"][0]
|
||||
|
||||
# 获取视频信息
|
||||
video_url = data["video"]["play_addr"]["url_list"][0].replace("playwm", "play")
|
||||
desc = data.get("desc", "").strip() or f"douyin_{video_id}"
|
||||
|
||||
# 替换文件名中的非法字符
|
||||
desc = re.sub(r'[\\/:*?"<>|]', '_', desc)
|
||||
|
||||
return {
|
||||
"url": video_url,
|
||||
"title": desc,
|
||||
"video_id": video_id
|
||||
}
|
||||
|
||||
def download_video(self, video_info: dict, output_dir: Optional[Path] = None, show_progress: bool = True) -> Path:
|
||||
"""下载视频"""
|
||||
if output_dir is None:
|
||||
output_dir = self.temp_dir
|
||||
else:
|
||||
output_dir = Path(output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
filename = f"{video_info['video_id']}.mp4"
|
||||
filepath = output_dir / filename
|
||||
|
||||
if show_progress:
|
||||
print(f"正在下载视频: {video_info['title']}")
|
||||
|
||||
response = requests.get(video_info['url'], headers=HEADERS, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# 获取文件大小
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
|
||||
# 下载文件
|
||||
downloaded = 0
|
||||
with open(filepath, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
if show_progress and total_size > 0:
|
||||
progress = downloaded / total_size * 100
|
||||
print(f"\r下载进度: {progress:.1f}%", end="", flush=True)
|
||||
|
||||
if show_progress:
|
||||
print(f"\n视频下载完成: {filepath}")
|
||||
return filepath
|
||||
|
||||
def extract_audio(self, video_path: Path, show_progress: bool = True) -> Path:
|
||||
"""从视频文件中提取音频"""
|
||||
audio_path = video_path.with_suffix('.mp3')
|
||||
|
||||
if show_progress:
|
||||
print("正在提取音频...")
|
||||
try:
|
||||
(
|
||||
ffmpeg
|
||||
.input(str(video_path))
|
||||
.output(str(audio_path), acodec='libmp3lame', q=0)
|
||||
.run(capture_stdout=True, capture_stderr=True, overwrite_output=True)
|
||||
)
|
||||
if show_progress:
|
||||
print(f"音频提取完成: {audio_path}")
|
||||
return audio_path
|
||||
except Exception as e:
|
||||
raise Exception(f"提取音频时出错: {str(e)}")
|
||||
|
||||
def extract_text_from_audio(self, audio_path: Path, show_progress: bool = True) -> str:
|
||||
"""从音频文件中提取文字"""
|
||||
if not self.api_key:
|
||||
raise ValueError("未设置 API 密钥,请设置环境变量 DOUYIN_API_KEY")
|
||||
|
||||
if show_progress:
|
||||
print("正在识别语音...")
|
||||
files = {
|
||||
'file': (audio_path.name, open(audio_path, 'rb'), 'audio/mpeg'),
|
||||
'model': (None, self.model)
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self.api_base_url, files=files, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
# 解析响应
|
||||
result = response.json()
|
||||
if 'text' in result:
|
||||
return result['text']
|
||||
else:
|
||||
return response.text
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"提取文字时出错: {str(e)}")
|
||||
finally:
|
||||
files['file'][1].close()
|
||||
|
||||
def cleanup_files(self, *file_paths: Path):
|
||||
"""清理指定的文件"""
|
||||
for file_path in file_paths:
|
||||
if file_path.exists():
|
||||
file_path.unlink()
|
||||
|
||||
|
||||
def get_video_info(share_link: str) -> dict:
|
||||
"""获取视频信息和下载链接"""
|
||||
processor = DouyinProcessor()
|
||||
return processor.parse_share_url(share_link)
|
||||
|
||||
|
||||
def download_video(share_link: str, output_dir: str = ".") -> Path:
|
||||
"""下载视频到指定目录"""
|
||||
processor = DouyinProcessor()
|
||||
video_info = processor.parse_share_url(share_link)
|
||||
return processor.download_video(video_info, Path(output_dir))
|
||||
|
||||
|
||||
def extract_text(share_link: str, api_key: Optional[str] = None, output_dir: Optional[str] = None,
|
||||
save_video: bool = False, show_progress: bool = True) -> dict:
|
||||
"""
|
||||
从视频中提取文案并保存到文件
|
||||
|
||||
返回:
|
||||
dict: 包含 video_info, text, output_path 的字典
|
||||
"""
|
||||
api_key = api_key or os.getenv('API_KEY')
|
||||
if not api_key:
|
||||
raise ValueError("未设置环境变量 API_KEY,请先获取硅基流动 API 密钥")
|
||||
|
||||
processor = DouyinProcessor(api_key)
|
||||
|
||||
if show_progress:
|
||||
print("正在解析抖音分享链接...")
|
||||
video_info = processor.parse_share_url(share_link)
|
||||
|
||||
if show_progress:
|
||||
print("正在下载视频...")
|
||||
video_path = processor.download_video(video_info, show_progress=show_progress)
|
||||
|
||||
if show_progress:
|
||||
print("正在提取音频...")
|
||||
audio_path = processor.extract_audio(video_path, show_progress=show_progress)
|
||||
|
||||
if show_progress:
|
||||
print("正在从音频中提取文本...")
|
||||
text_content = processor.extract_text_from_audio(audio_path, show_progress=show_progress)
|
||||
|
||||
result = {
|
||||
"video_info": video_info,
|
||||
"text": text_content,
|
||||
"output_path": None
|
||||
}
|
||||
|
||||
# 保存到文件
|
||||
if output_dir:
|
||||
output_base = Path(output_dir)
|
||||
video_folder = output_base / video_info['video_id']
|
||||
video_folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 保存文案为 Markdown 格式
|
||||
transcript_path = video_folder / "transcript.md"
|
||||
with open(transcript_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"# {video_info['title']}\n\n")
|
||||
f.write(f"| 属性 | 值 |\n")
|
||||
f.write(f"|------|----|\n")
|
||||
f.write(f"| 视频ID | `{video_info['video_id']}` |\n")
|
||||
f.write(f"| 提取时间 | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} |\n")
|
||||
f.write(f"| 下载链接 | [点击下载]({video_info['url']}) |\n\n")
|
||||
f.write(f"---\n\n")
|
||||
f.write(f"## 文案内容\n\n")
|
||||
f.write(text_content)
|
||||
|
||||
result["output_path"] = str(video_folder)
|
||||
|
||||
if show_progress:
|
||||
print(f"文案已保存到: {transcript_path}")
|
||||
|
||||
# 保存视频 (可选)
|
||||
if save_video:
|
||||
saved_video_path = video_folder / f"{video_info['video_id']}.mp4"
|
||||
shutil.copy2(video_path, saved_video_path)
|
||||
if show_progress:
|
||||
print(f"视频已保存到: {saved_video_path}")
|
||||
|
||||
# 清理临时文件
|
||||
if show_progress:
|
||||
print("正在清理临时文件...")
|
||||
processor.cleanup_files(video_path, audio_path)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="抖音无水印视频下载和文案提取工具",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
# 获取视频信息和下载链接
|
||||
python douyin_downloader.py --link "抖音分享链接" --action info
|
||||
|
||||
# 下载视频
|
||||
python douyin_downloader.py --link "抖音分享链接" --action download --output ./videos
|
||||
|
||||
# 提取文案并保存到文件 (需要设置 DOUYIN_API_KEY 环境变量)
|
||||
python douyin_downloader.py --link "抖音分享链接" --action extract --output ./output
|
||||
|
||||
# 提取文案并同时保存视频
|
||||
python douyin_downloader.py --link "抖音分享链接" --action extract --output ./output --save-video
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--link", "-l", required=True, help="抖音分享链接或包含链接的文本")
|
||||
parser.add_argument("--action", "-a", choices=["info", "download", "extract"],
|
||||
default="info", help="操作类型: info(获取信息), download(下载视频), extract(提取文案)")
|
||||
parser.add_argument("--output", "-o", default="./output", help="输出目录 (默认 ./output)")
|
||||
parser.add_argument("--api-key", "-k", help="硅基流动 API 密钥 (也可通过 DOUYIN_API_KEY 环境变量设置)")
|
||||
parser.add_argument("--save-video", "-v", action="store_true", help="提取文案时同时保存视频")
|
||||
parser.add_argument("--quiet", "-q", action="store_true", help="安静模式,减少输出")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.action == "info":
|
||||
info = get_video_info(args.link)
|
||||
print("\n" + "=" * 50)
|
||||
print("视频信息:")
|
||||
print("=" * 50)
|
||||
print(f"视频ID: {info['video_id']}")
|
||||
print(f"标题: {info['title']}")
|
||||
print(f"下载链接: {info['url']}")
|
||||
print("=" * 50)
|
||||
|
||||
elif args.action == "download":
|
||||
video_path = download_video(args.link, args.output)
|
||||
print(f"\n视频已保存到: {video_path}")
|
||||
|
||||
elif args.action == "extract":
|
||||
result = extract_text(
|
||||
args.link,
|
||||
args.api_key,
|
||||
output_dir=args.output,
|
||||
save_video=args.save_video,
|
||||
show_progress=not args.quiet
|
||||
)
|
||||
|
||||
if not args.quiet:
|
||||
print("\n" + "=" * 50)
|
||||
print("提取完成!")
|
||||
print("=" * 50)
|
||||
print(f"视频ID: {result['video_info']['video_id']}")
|
||||
print(f"标题: {result['video_info']['title']}")
|
||||
if result['output_path']:
|
||||
print(f"保存位置: {result['output_path']}")
|
||||
print("=" * 50)
|
||||
print("\n文案内容:\n")
|
||||
print(result['text'][:500] + "..." if len(result['text']) > 500 else result['text'])
|
||||
print("\n" + "=" * 50)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n错误: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +1,6 @@
|
||||
"""抖音无水印链接提取 MCP 服务器"""
|
||||
|
||||
__version__ = "1.2.0"
|
||||
__version__ = "1.3.0"
|
||||
__author__ = "yzfly"
|
||||
__email__ = "yz.liu.me@gmail.com"
|
||||
|
||||
|
||||
@@ -242,13 +242,13 @@ async def extract_douyin_text(
|
||||
返回:
|
||||
- 提取的文本内容
|
||||
|
||||
注意: 需要设置环境变量 DASHSCOPE_API_KEY
|
||||
注意: 需要设置环境变量 API_KEY
|
||||
"""
|
||||
try:
|
||||
# 从环境变量获取API密钥
|
||||
api_key = os.getenv('DASHSCOPE_API_KEY')
|
||||
api_key = os.getenv('API_KEY')
|
||||
if not api_key:
|
||||
raise ValueError("未设置环境变量 DASHSCOPE_API_KEY,请在配置中添加阿里云百炼API密钥")
|
||||
raise ValueError("未设置环境变量 API_KEY,请在配置中添加阿里云百炼API密钥")
|
||||
|
||||
processor = DouyinProcessor(api_key, model)
|
||||
|
||||
@@ -328,11 +328,11 @@ def douyin_text_extraction_guide() -> str:
|
||||
|
||||
## 环境变量配置
|
||||
请确保设置了以下环境变量:
|
||||
- `DASHSCOPE_API_KEY`: 阿里云百炼API密钥
|
||||
- `API_KEY`: 阿里云百炼API密钥
|
||||
|
||||
## 使用步骤
|
||||
1. 复制抖音视频的分享链接
|
||||
2. 在Claude Desktop配置中设置环境变量 DASHSCOPE_API_KEY
|
||||
2. 在Claude Desktop配置中设置环境变量 API_KEY
|
||||
3. 使用相应的工具进行操作
|
||||
|
||||
## 工具说明
|
||||
@@ -349,7 +349,7 @@ def douyin_text_extraction_guide() -> str:
|
||||
"command": "uvx",
|
||||
"args": ["douyin-mcp-server"],
|
||||
"env": {
|
||||
"DASHSCOPE_API_KEY": "your-dashscope-api-key-here"
|
||||
"API_KEY": "your-api-key-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "douyin-mcp-server"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
description = "MCP server for downloading Douyin videos and extracting text"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
||||
Reference in New Issue
Block a user