Skill: sanwecn/telegram-offline-voice (version 0.1.3)

--- name: telegram-offline-voice description: 本地生成 Telegram 语音消息，支持自动清洗、分段与临时文件管理。 metadata: {"openclaw":{"emoji":"🎙️","os":["linux"],"requires":{"bins":["ffmpeg","uv"]}}} --- # telegram-offline-voice 🎙️ **本地生成，一键封装** — 使用 Microsoft Edge-TTS 生成高质量中文语音，完全离线处理。 ## 💡 为什么需要这个升级？ 原生的 TTS 方案通常只能生成 MP3 附件，且无法处理 Markdown 标记和超长文本。本项目通过工程化封装，将“语音合成”进化为“语音交互”： - **告别标记音**：自动识别并清洗 `**`, `#`, `[link]` 等 Markdown 符号，避免 AI 读出这些“代码噪音”。 - **智能对话流**：超长文本不再是一读到底，而是按句号、感叹号自动切分为多个语音气泡，听感更像真人在发语音。 - **并发安全**：针对多代理/子代理并行调用的场景，使用 UUID 隔离临时文件，彻底杜绝文件读写冲突。 - **零token消耗**：完全基于 Edge-TTS 本地生成，无需 OpenAI/Azure 的付费 Token。 ## ✨ 特性 - 🔒 **隐私保护**：100% 本地音频处理，不经过任何额外云端 TTS 提供商。 - 💰 **零token消耗**：使用免费的 Edge-TTS 引擎，省下昂贵的 API 额度。 - 🎯 **一键生成**：内置 `voice_gen.py` 脚本，自动完成“文本->MP3->OGG”的全过程。 - 🧹 **自动清洗**：自动剔除 Markdown 符号和 URL 链接，让朗读更自然。 - ✂️ **智能分段**：超长文本自动按标点符号切分为多个语音气泡。 - 🛡️ **安全并发**：使用 UUID 命名临时文件，支持多代理同时调用。 ## 🛠️ 安装依赖 ```bash # 需要 Python 环境和 FFmpeg sudo apt update && sudo apt install ffmpeg python3-pip -y # 推荐安装 uv 以极速运行封装脚本 curl -LsSf https://astral.sh/uv/install.sh | sh ``` ## 🚀 使用方法 (推荐) 直接调用封装脚本，一键生成 Telegram 原生语音气泡路径： ```bash uv run {baseDir}/scripts/voice_gen.py --text "您的待播报内容" ``` ## ⚙️ 技术细节 ### 参数说明 - `--text` / `-t`: 待生成的文本（必填）。 - `--voice`: 声线选择，默认 `zh-CN-XiaoxiaoNeural`（晓晓）。 - `--rate`: 语速调节，默认 `+5%`。 - `--outdir`: 临时文件存放目录，默认 `/tmp`。 ### 自动化清洗规则 脚本会自动移除以下内容以确保朗读流畅： - Markdown 符号：`**`, `*`, `_`, `` ` ``, `#` - 链接逻辑：`[文本](url)` 以及所有 `http/https` 开头的链接 - 分割线：`---`, `***` 等 --- ## 👨💻 关于作者 由 **[@sanwe](https://x.com/sanwe)** 调优并维护。 欢迎关注我的推特获取更多 OpenClaw 进阶玩法：[https://x.com/sanwe](https://x.com/sanwe)

path: .clawhub/origin.json
content: version: 1
registry: https://clawhub.ai
slug: telegram-offline-voice
installedVersion: 0.1.2
installedAt: 1770462459631

path: README.md
content: # Telegram Offline Voice 🎙️

**让你的 AI 助手开口说话：本地化、工程化、零成本的 Telegram 语音解决方案。**

这是一个专为 OpenClaw 和 AI 智能体设计的语音生成技能。它不仅能将文字转为声音，更能通过智能清洗和分段，将枯燥的 AI 输出转化为自然、优雅的 Telegram 语音气泡。

## 🚀 重大工程化更新

本次更新将本项目从一个简单的“工具脚本”提升到了“生产级组件”：

1.  **智能文本清洗**：内置强大的正则引擎，自动剔除 Markdown 语法噪音（如加粗、代码块、链接地址等），让 AI 像真人一样读出干净的文本。
2.  **长文本自动分段**：突破 TTS 的长度限制和听觉疲劳，根据标点符号自动切分为连续的语音气泡，完美适配 Telegram 的交互习惯。
3.  **UUID 临时文件管理**：采用唯一标识符处理并发请求，即使多个子代理同时工作，也不会发生文件覆盖或读取冲突。
4.  **一键封装脚本**：全新的 `voice_gen.py`，支持 `uv` 运行，无需复杂的环境配置，一个命令直接输出最终的 `.ogg` 文件。

## ✨ 核心优势

- 🔒 **隐私保护**：100% 本地音频处理，不经过任何额外云端 TTS 提供商。
- 💰 **零token消耗**：使用免费的 Edge-TTS 引擎，省下昂贵的 API 额度。
- 🎧 **原生体验**：生成的 OGG Opus 编码文件会被 Telegram 识别为“语音消息”，而非普通的音频文件附件。

## 🔧 快速开始

### 1. 安装依赖
```bash
sudo apt update && sudo apt install ffmpeg python3-pip -y
```

### 2. 运行生成
```bash
uv run scripts/voice_gen.py --text "你好，我是阿威。这是我的最新版语音引擎测试！"
```

## 📖 文档

有关参数配置说明和 OpenClaw 集成指南，请阅读 [SKILL.md](./SKILL.md)。

---

## 👨💻 联系作者

调优与维护：**[@sanwe](https://x.com/sanwe)**
Twitter: [https://x.com/sanwe](https://x.com/sanwe)

欢迎 Star 或提交 PR 共同完善 OpenClaw 生态！🦞


path: _meta.json
content: ownerId: kn7aj45td3vx58pdme9zf0ggbn802txr
slug: telegram-offline-voice
version: 0.1.2
publishedAt: 1769885194111

path: scripts/voice_gen.py
content: #!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "edge-tts",
#     "aiofiles",
# ]
# ///

import asyncio
import edge_tts
import re
import os
import subprocess
import uuid
import argparse
import sys
from pathlib import Path

def clean_text(text):
    # Remove Markdown symbols
    text = re.sub(r'\*\*|\*|__|_|`|#+\s+|\[.*?\]\(.*?\)', '', text)
    # Remove URLs
    text = re.sub(r'https?://\S+', '', text)
    # Remove common technical symbols/headers
    text = re.sub(r'---|\*\*\*|>>>', ' ', text)
    # Collapse multiple spaces/newlines
    text = re.sub(r'\s+', ' ', text).strip()
    return text

def split_text(text, max_chars=300):
    """Splits text into chunks by punctuation."""
    if len(text) <= max_chars:
        return [text]
    
    # Split by common sentence enders
    parts = re.split(r'([。！？；.!?;])', text)
    chunks = []
    current_chunk = ""
    
    for i in range(0, len(parts)-1, 2):
        sentence = parts[i] + parts[i+1]
        if len(current_chunk) + len(sentence) <= max_chars:
            current_chunk += sentence
        else:
            if current_chunk:
                chunks.append(current_chunk)
            current_chunk = sentence
            
    if current_chunk:
        chunks.append(current_chunk)
    
    # Handle the last part if it doesn't end with punctuation
    if len(parts) % 2 != 0 and parts[-1]:
        if chunks and len(chunks[-1]) + len(parts[-1]) <= max_chars:
            chunks[-1] += parts[-1]
        else:
            chunks.append(parts[-1])
            
    return chunks

async def generate_voice(text, voice, rate, output_path):
    communicate = edge_tts.Communicate(text, voice, rate=rate)
    await communicate.save(output_path)

def convert_to_ogg(input_path, output_path):
    cmd = [
        "ffmpeg", "-y", "-i", str(input_path),
        "-c:a", "libopus", "-b:a", "48k",
        "-ac", "1", "-ar", "48000",
        "-application", "voip", str(output_path)
    ]
    subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

async def main():
    parser = argparse.ArgumentParser(description="Smart Voice Generator for Telegram")
    parser.add_argument("--text", "-t", required=True, help="Text to convert")
    parser.add_argument("--voice", default="zh-CN-XiaoxiaoNeural", help="Voice to use")
    parser.add_argument("--rate", default="+5%", help="Speed rate")
    parser.add_argument("--outdir", default="/tmp", help="Output directory")
    
    args = parser.parse_args()
    
    cleaned = clean_text(args.text)
    if not cleaned:
        print("Error: No text left after cleaning.", file=sys.stderr)
        sys.exit(1)
        
    chunks = split_text(cleaned)
    output_files = []
    
    for chunk in chunks:
        session_id = str(uuid.uuid4())
        raw_mp3 = Path(args.outdir) / f"{session_id}.mp3"
        final_ogg = Path(args.outdir) / f"{session_id}.ogg"
        
        try:
            await generate_voice(chunk, args.voice, args.rate, raw_mp3)
            convert_to_ogg(raw_mp3, final_ogg)
            output_files.append(str(final_ogg))
        finally:
            if raw_mp3.exists():
                os.remove(raw_mp3)
                
    # Print results for OpenClaw to pick up
    for f in output_files:
        print(f"MEDIA: {f}")

if __name__ == "__main__":
    asyncio.run(main())


Security scan verdicts — ClawScan: suspicious | static analysis:  | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)