Initial commit: skills library

- 70 skills with code and documentation
- Add .gitignore (ignore __pycache__, output/, temp/, venv/)
- Clean up test intermediates and caches
This commit is contained in:
hmo
2026-04-26 19:27:40 +08:00
commit 04db423416
861 changed files with 210414 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
# Audio Generator Skill
文本转音频生成技能,使用 Edge TTS 生成高质量中文语音。
## 功能特性
- 支持 Markdown、纯文本、分隔符三种格式
- 自动分割长文本为章节
- 使用微软 Edge TTS,语音自然流畅
- 支持批量生成和增量更新
- 可指定特定章节生成
## 安装依赖
```bash
pip install edge-tts
```
## 使用方法
### 生成音频
```bash
# 生成纯文本音频
python scripts/generate_audio.py text.txt
# 生成 Markdown 音频
python scripts/generate_audio.py doc.md --format markdown
# 指定输出目录
python scripts/generate_audio.py text.txt --output-dir ./my_audio
```
### 合并音频
```bash
# 合并指定目录所有MP3为一个完整版
python scripts/merge_audio.py ./audio_chapters
# 指定输出文件名
python scripts/merge_audio.py ./audio_chapters --output 完整版.mp3
# 指定文件模式
python scripts/merge_audio.py ./audio_chapters --pattern "chapter_*.mp3"
```
### 高级用法
```bash
# 使用不同语音
python scripts/generate_audio.py text.txt --voice zh-CN-YunxiNeural
# 只生成特定章节
python scripts/generate_audio.py text.txt --chapters "第一章,第二章"
# 按分隔符分割(适用于有【章节标题】的文本)
python scripts/generate_audio.py text.txt --format separator
```
## 可用语音
| 语音代码 | 性别 | 特点 |
|---------|------|------|
| zh-CN-XiaoxiaoNeural | 女 | 晓晓,适合长文朗读,自然流畅 |
| zh-CN-YunxiNeural | 男 | 云希,适合旁白解说 |
| zh-CN-YunjianNeural | 男 | 云健,适合新闻播报 |
| zh-CN-XiaoyiNeural | 女 | 晓伊,温柔亲切 |
完整语音列表:https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4
## 文本格式建议
### Markdown 格式
```markdown
## 第一章:标题
内容...
## 第二章:标题
内容...
```
### 分隔符格式(推荐)
```
================================================================
【第一章】
内容...
================================================================
【第二章】
内容...
```
## 输出结构
```
output_dir/
├── 01_第一章标题.mp3
├── 02_第二章标题.mp3
├── 03_第三章标题.mp3
└── ...
```
## 工作流程集成
这个技能通常用于以下工作流程:
1. **知识库转音频**
- 将知识库文档转换为可听的音频课程
- 便于通勤、运动时学习
2. **长文朗读**
- 将长篇文章分段生成音频
- 支持离线收听
3. **内容创作**
- 为视频配音
- 生成播客内容
## 最佳实践
1. **预处理文本**
- 移除不必要的 Markdown 标记
- 添加适当的口语化表达
- 添加术语解释
2. **章节划分**
- 每个章节控制在 5-10 分钟
- 过长内容可以进一步细分
3. **语音选择**
- 课程内容:晓晓(女声)或云希(男声)
- 故事朗读:根据角色选择合适语音
- 新闻播报:云健
## 故障排除
### 生成失败
- 检查 edge-tts 是否安装:`pip list | grep edge`
- 检查网络连接(Edge TTS 需要联网)
- 检查文本编码是否为 UTF-8
### 文件名问题
- 脚本会自动清理非法字符
- 章节标题过长会被截断
## 更新日志
### v1.0.0 (2026-02-19)
- 初始版本
- 支持三种文本格式
- 支持章节过滤
- 支持多种语音
## License
MIT License
+339
View File
@@ -0,0 +1,339 @@
# Audio Generator
**文本转音频生成技能**
将文本内容转换为自然流畅的中文语音,支持多种格式和批量生成。
---
## 触发词
- "生成音频"
- "文本转语音"
- "文字转音频"
- "制作有声书"
- "生成朗读音频"
---
## 功能描述
### 核心功能
1. **多格式支持**
- Markdown 格式(按标题分割)
- 纯文本格式(按大小分割)
- 分隔符格式(按【章节】分割)
2. **高质量语音**
- 使用微软 Edge TTS 引擎
- 支持多种中文语音(晓晓、云希等)
- 语音自然流畅,适合长文朗读
3. **智能分割**
- 自动识别章节结构
- 支持自定义分割策略
- 过滤过短内容
4. **批量处理**
- 批量生成所有章节
- 支持增量更新
- 可指定特定章节生成
5. **音频合并**
- 合并多个章节为完整版
- 自动按文件名排序
- 保持原有文件不变
- 支持自定义输出文件名
---
## 使用场景
### 场景一:知识库转音频课程
用户:"帮我把这个知识库文档转成音频课程"
执行步骤:
1. 读取知识库文档
2. 分析文档结构
3. 转换为适合朗读的纯文本
4. 按章节生成音频文件
5. 合并为完整版音频
6. 创建播放说明文档
### 场景二:长文分段朗读
用户:"把这篇文章分成几个音频文件"
执行步骤:
1. 分析文本长度
2. 按合适长度分割
3. 生成多个音频文件
4. 合并为完整版
5. 提供播放建议
### 场景三:特定章节生成
用户:"只生成第一章和第三章的音频"
执行步骤:
1. 解析用户指定的章节
2. 定位对应文本内容
3. 只生成指定章节的音频
4. 保持文件命名连贯
### 场景四:音频合并
用户:"帮我把这些章节音频合并成一个完整版"
执行步骤:
1. 扫描目录中的所有音频文件
2. 按文件名排序
3. 使用 ffmpeg 合并
4. 生成完整版文件
5. 保留原有分章节版本
---
## 工作流程
```
文本输入
格式识别 (Markdown/纯文本/分隔符)
章节分割
内容过滤(跳过过短段落)
音频生成(异步批量)
输出到指定目录
合并为完整版(可选)
生成播放说明文档
```
---
## 输入要求
### 文本格式
**推荐格式 - 分隔符格式:**
```
================================================================
【第一章:标题】
内容内容内容...
================================================================
【第二章:标题】
内容内容内容...
```
**Markdown 格式:**
```markdown
## 第一章:标题
内容...
## 第二章:标题
内容...
```
**纯文本格式:**
- 将自动按字符数分割
- 建议预处理添加章节标记
### 内容建议
1. **口语化处理**
- 移除 Markdown 标记(#、*、-等)
- 转换为口语化表达
- 添加适当的停顿和强调
2. **添加术语解释**
- 专业术语首次出现时添加解释
- 使用通俗易懂的语言
3. **控制章节长度**
- 每个章节 5-10 分钟为宜
- 过长内容建议细分
---
## 输出说明
### 文件结构
```
output_dir/
├── 01_第一章标题.mp3
├── 02_第二章标题.mp3
├── 03_第三章标题.mp3
└── ...
```
### 音频规格
- 格式:MP3
- 编码:128kbps
- 采样率:24kHz
- 语音:Microsoft Edge TTS
---
## 参数配置
### 命令行参数
```bash
python scripts/generate_audio.py <input_file> [options]
Options:
--format {plain,markdown,separator} 文本格式 (默认: plain)
--output-dir DIR 输出目录 (默认: ./audio_output)
--voice VOICE 语音模型 (默认: zh-CN-XiaoxiaoNeural)
--chapters CHAPTERS 只生成指定章节,逗号分隔
```
### 推荐语音
| 语音代码 | 性别 | 适用场景 |
|---------|------|---------|
| zh-CN-XiaoxiaoNeural | 女声 | 课程内容、长文朗读(默认) |
| zh-CN-YunxiNeural | 男声 | 旁白解说、故事朗读 |
| zh-CN-YunjianNeural | 男声 | 正式场合、官方内容 |
| zh-CN-XiaoyiNeural | 女声 | 故事朗读、温柔亲切 |
| zh-CN-YunyangNeural | 男声 | 新闻播报、官方声明 |
### 命令行选择音色
```bash
# 使用男声(云希)
python scripts/generate_audio.py text.txt --voice zh-CN-YunxiNeural
# 使用女声(晓晓,默认)
python scripts/generate_audio.py text.txt --voice zh-CN-XiaoxiaoNeural
```
---
## 依赖要求
```bash
pip install edge-tts
```
**系统要求:**
- Python 3.7+
- 网络连接(Edge TTS 需要联网)
- 磁盘空间(每小时音频约 50MB
---
## 示例用法
### 示例 1:基本使用
```bash
python scripts/generate_audio.py article.txt
```
### 示例 2Markdown 文档
```bash
python scripts/generate_audio.py document.md --format markdown --output-dir ./audio
```
### 示例 3:指定章节
```bash
python scripts/generate_audio.py book.txt --chapters "第一章,第三章,第五章"
```
### 示例 4:使用男声
```bash
python scripts/generate_audio.py text.txt --voice zh-CN-YunxiNeural
```
### 示例 5:合并音频文件
```bash
# 合并所有章节为一个完整版
python scripts/merge_audio.py ./audio_chapters
# 合并并指定输出文件名
python scripts/merge_audio.py ./audio_chapters -o 完整课程.mp3
# 合并特定模式的文件
python scripts/merge_audio.py ./audio_chapters --pattern "part_*.mp3"
```
---
## 故障排除
| 问题 | 原因 | 解决方案 |
|-----|------|---------|
| 生成失败 | edge-tts 未安装 | `pip install edge-tts` |
| 网络错误 | 无法连接 Edge TTS | 检查网络连接 |
| 乱码 | 文本编码错误 | 确保文本为 UTF-8 编码 |
| 文件名为空 | 章节标题非法 | 检查并清理标题中的特殊字符 |
| 部分章节缺失 | 内容过短被过滤 | 调整 MIN_SECTION_LENGTH 或合并短章节 |
---
## 最佳实践
1. **预处理文本**
- 移除不适合朗读的内容(代码、表格等)
- 添加口语化过渡
- 确保术语有解释
2. **测试小样本**
- 先生成一个章节测试
- 确认语音效果满意后再批量生成
3. **合理分割**
- 根据内容逻辑分割
- 控制单个音频时长
- 保持章节间连贯性
4. **添加元数据**
- 创建播放说明文档
- 标注章节内容摘要
- 提供学习建议
---
## 相关技能
- **videocut-subtitle**: 视频字幕生成
- **video-creator**: 视频创作
- **story-to-scenes**: 故事拆镜
---
## 更新记录
### v1.0.0 (2026-02-19)
- 初始版本发布
- 支持三种文本格式
- 支持章节过滤
- 支持多种语音选择
---
## 维护者
- 创建者:AI Assistant
- 联系方式:通过 OpenCode 平台
---
## License
MIT License
+2
View File
@@ -0,0 +1,2 @@
# audio-generator - dependencies
edge-tts>=0.0.1
+263
View File
@@ -0,0 +1,263 @@
#!/usr/bin/env python3
"""
Audio Generator Skill
文本转音频生成工具
功能:
- 支持Markdown和纯文本两种格式
- 自动分割长文本为章节
- 使用edge-tts生成高质量中文语音
- 支持批量生成和增量更新
依赖:
- pip install edge-tts
使用:
python scripts/generate_audio.py <input_file> [options]
示例:
# 生成纯文本音频
python scripts/generate_audio.py text.txt --format plain --output-dir ./audio
# 生成Markdown音频(按标题分割)
python scripts/generate_audio.py doc.md --format markdown --output-dir ./audio
# 只生成特定章节
python scripts/generate_audio.py text.txt --chapters "第一章,第二章"
"""
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
import asyncio
import argparse
import edge_tts
import os
from pathlib import Path
# 默认配置
DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural" # 晓晓,适合长文朗读
DEFAULT_FORMAT = "plain"
DEFAULT_OUTPUT_DIR = "./audio_output"
MIN_SECTION_LENGTH = 200 # 最小章节长度(字符)
def clean_filename(title: str, max_length: int = 30) -> str:
"""清理文件名,移除非法字符"""
illegal_chars = ["/", "\\", ":", "?", '"', "<", ">", "|", "*", "#"]
clean = title
for char in illegal_chars:
clean = clean.replace(char, "-")
return clean[:max_length].strip()
def split_by_markdown(content: str) -> list:
"""按Markdown标题分割文本"""
sections = []
current_section = []
current_title = "未命名章节"
for line in content.split("\n"):
if line.startswith("##") and len(line) > 2:
if current_section:
sections.append((current_title, "\n".join(current_section)))
current_title = line.replace("#", "").strip()
current_section = [line]
else:
current_section.append(line)
if current_section:
sections.append((current_title, "\n".join(current_section)))
return sections
def split_by_separator(content: str, separator: str = "===") -> list:
"""按分隔符和章节标记分割文本"""
sections = []
current_section = []
current_title = "开场"
for line in content.split("\n"):
if line.startswith(separator) and len(line) > 10:
if current_section:
sections.append((current_title, "\n".join(current_section)))
current_section = []
elif line.startswith("") and line.endswith(""):
if current_section and len("\n".join(current_section)) > 100:
sections.append((current_title, "\n".join(current_section)))
current_section = []
current_title = line.replace("", "").replace("", "").strip()
else:
current_section.append(line)
if current_section:
sections.append((current_title, "\n".join(current_section)))
return sections
def split_plain_text(content: str, chunk_size: int = 2000) -> list:
"""将纯文本按大小分割"""
lines = content.split("\n")
sections = []
current_chunk = []
current_size = 0
chunk_num = 1
for line in lines:
current_chunk.append(line)
current_size += len(line)
if current_size >= chunk_size:
sections.append((f"{chunk_num}部分", "\n".join(current_chunk)))
current_chunk = []
current_size = 0
chunk_num += 1
if current_chunk:
sections.append((f"{chunk_num}部分", "\n".join(current_chunk)))
return sections
async def generate_audio_file(text: str, output_path: str, voice: str) -> bool:
"""生成单个音频文件"""
try:
communicate = edge_tts.Communicate(text, voice)
await communicate.save(output_path)
return True
except Exception as e:
print(f" 生成失败: {str(e)[:80]}")
return False
async def generate_audio_files(
sections: list, output_dir: str, voice: str, filter_chapters=None
) -> tuple:
"""批量生成音频文件"""
os.makedirs(output_dir, exist_ok=True)
generated = 0
total_chars = 0
for i, (title, content) in enumerate(sections):
content_len = len(content.strip())
# 跳过太短的章节
if content_len < MIN_SECTION_LENGTH:
continue
# 如果指定了章节过滤
if filter_chapters and title not in filter_chapters:
continue
clean_title = clean_filename(title)
output_file = os.path.join(output_dir, f"{i + 1:02d}_{clean_title}.mp3")
print(f"[{i + 1}/{len(sections)}] {title[:40]}...")
print(f" 字数: {content_len}")
success = await generate_audio_file(content, output_file, voice)
if success:
generated += 1
total_chars += content_len
file_size = os.path.getsize(output_file) / 1024 / 1024
print(f" ✓ 完成 [{file_size:.1f}MB]")
else:
print(f" ✗ 失败")
print()
return generated, total_chars
def main():
parser = argparse.ArgumentParser(
description="文本转音频生成工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s input.txt # 默认生成纯文本音频
%(prog)s doc.md --format markdown # Markdown格式
%(prog)s text.txt --voice zh-CN-YunxiNeural # 使用男声
%(prog)s text.txt --output-dir ./my_audio # 指定输出目录
""",
)
parser.add_argument("input_file", help="输入文本文件路径")
parser.add_argument(
"--format",
choices=["plain", "markdown", "separator"],
default=DEFAULT_FORMAT,
help="文本格式 (默认: plain)",
)
parser.add_argument(
"--output-dir",
default=DEFAULT_OUTPUT_DIR,
help=f"输出目录 (默认: {DEFAULT_OUTPUT_DIR})",
)
parser.add_argument(
"--voice", default=DEFAULT_VOICE, help=f"语音模型 (默认: {DEFAULT_VOICE})"
)
parser.add_argument("--chapters", help="只生成指定章节,用逗号分隔")
args = parser.parse_args()
# 检查输入文件
if not os.path.exists(args.input_file):
print(f"错误: 找不到文件 {args.input_file}")
sys.exit(1)
# 读取文件
print(f"正在读取: {args.input_file}")
with open(args.input_file, "r", encoding="utf-8") as f:
content = f.read()
print(f"文件大小: {len(content)} 字符")
print()
# 分割章节
if args.format == "markdown":
sections = split_by_markdown(content)
elif args.format == "separator":
sections = split_by_separator(content)
else:
sections = split_plain_text(content)
print(f"共识别 {len(sections)} 个章节/段落")
print(f"使用语音: {args.voice}")
print(f"输出目录: {args.output_dir}")
print()
# 解析章节过滤
filter_chapters = []
if args.chapters:
filter_chapters = [c.strip() for c in args.chapters.split(",")]
print(f"只生成章节: {', '.join(filter_chapters)}")
print()
# 生成音频
print("开始生成音频文件...")
print("=" * 60)
result = asyncio.run(
generate_audio_files(sections, args.output_dir, args.voice, filter_chapters)
)
generated, total_chars = result
print("=" * 60)
print(f"音频生成完成!")
print(f"共生成 {generated} 个音频文件")
print(f"总字数: {total_chars}")
print(f"预计总时长: {total_chars // 250} 分钟")
print(f"保存位置: {args.output_dir}")
print("=" * 60)
if __name__ == "__main__":
main()
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""
Audio Merger - 音频合并工具
功能:
- 合并多个音频文件为一个完整版
- 自动按文件名排序
- 支持指定输出文件名
- 保持原有文件不变
依赖:
- ffmpeg(必须安装并添加到PATH)
使用方法:
python scripts/merge_audio.py <audio_dir> [options]
示例:
# 合并指定目录所有MP3
python scripts/merge_audio.py ./audio_chapters
# 指定输出文件名
python scripts/merge_audio.py ./audio_chapters --output 完整版.mp3
# 指定文件模式(默认*.mp3)
python scripts/merge_audio.py ./audio_chapters --pattern "*.mp3"
"""
import os
import argparse
import subprocess
import sys
from pathlib import Path
def get_audio_files(audio_dir: str, pattern: str = "*.mp3") -> list:
"""获取目录中的音频文件并按文件名排序"""
audio_path = Path(audio_dir)
if not audio_path.exists():
print(f"错误: 目录不存在 {audio_dir}")
return []
files = list(audio_path.glob(pattern))
files.sort()
# 过滤掉已经存在的完整版文件(避免重复合并)
files = [
f for f in files if "完整版" not in f.name and "complete" not in f.name.lower()
]
return [str(f) for f in files]
def merge_audio_files(audio_files: list, output_file: str, audio_dir: str) -> bool:
"""使用 ffmpeg 合并音频文件"""
if not audio_files:
print("错误: 没有找到音频文件")
return False
print(f"找到 {len(audio_files)} 个音频文件")
print("文件列表:")
for i, f in enumerate(audio_files[:5], 1):
print(f" {i}. {os.path.basename(f)}")
if len(audio_files) > 5:
print(f" ... 还有 {len(audio_files) - 5} 个文件")
print()
# 创建 ffmpeg concat 列表文件
concat_file = os.path.join(audio_dir, ".merge_list.txt")
try:
with open(concat_file, "w", encoding="utf-8") as f:
for audio_file in audio_files:
# 使用相对路径
rel_path = os.path.relpath(audio_file, audio_dir)
f.write(f"file '{rel_path}'\n")
print(f"开始合并到: {os.path.basename(output_file)}")
# 构建 ffmpeg 命令
cmd = [
"ffmpeg",
"-y", # 覆盖已存在的文件
"-f",
"concat",
"-safe",
"0",
"-i",
concat_file,
"-c",
"copy", # 直接复制,不重新编码
output_file,
]
# 执行合并
result = subprocess.run(cmd, capture_output=True, cwd=audio_dir)
if result.returncode == 0:
file_size = os.path.getsize(output_file) / (1024 * 1024)
print(f"✓ 合并成功!")
print(f" 输出文件: {os.path.basename(output_file)}")
print(f" 文件大小: {file_size:.1f} MB")
print(f" 包含文件: {len(audio_files)}")
return True
else:
print(f"✗ 合并失败")
error_msg = (
result.stderr.decode("utf-8", errors="ignore")
if result.stderr
else "未知错误"
)
if "ffmpeg" in error_msg.lower() and "not found" in error_msg.lower():
print(" 提示: 未找到 ffmpeg,请先安装 ffmpeg 并添加到 PATH")
else:
print(f" 错误: {error_msg[:200]}")
return False
finally:
# 清理临时文件
if os.path.exists(concat_file):
os.remove(concat_file)
def main():
parser = argparse.ArgumentParser(
description="音频文件合并工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s ./audio_chapters
%(prog)s ./audio_chapters --output 完整版.mp3
%(prog)s ./audio_chapters --pattern "chapter_*.mp3"
注意:
需要先安装 ffmpeg: https://ffmpeg.org/download.html
""",
)
parser.add_argument("audio_dir", help="音频文件所在目录")
parser.add_argument(
"--output", "-o", help="输出文件名(默认: 00_完整版_合并音频.mp3)"
)
parser.add_argument(
"--pattern", default="*.mp3", help="文件匹配模式(默认: *.mp3"
)
args = parser.parse_args()
# 检查目录
if not os.path.isdir(args.audio_dir):
print(f"错误: 目录不存在 {args.audio_dir}")
sys.exit(1)
# 获取音频文件
audio_files = get_audio_files(args.audio_dir, args.pattern)
if not audio_files:
print("没有找到音频文件,退出")
sys.exit(1)
# 确定输出文件名
if args.output:
output_file = os.path.join(args.audio_dir, args.output)
else:
output_file = os.path.join(args.audio_dir, "00_完整版_合并音频.mp3")
# 检查输出文件是否已存在
if os.path.exists(output_file):
print(f"警告: 输出文件已存在,将被覆盖: {os.path.basename(output_file)}")
response = input("是否继续? (y/n): ")
if response.lower() != "y":
print("已取消")
sys.exit(0)
print("=" * 60)
print("音频合并工具")
print("=" * 60)
print()
# 执行合并
success = merge_audio_files(audio_files, output_file, args.audio_dir)
print()
print("=" * 60)
if success:
print("合并完成!")
else:
print("合并失败!")
print("=" * 60)
if __name__ == "__main__":
main()