Skill: wangminrui2022/audio-enhancement-engine (version 1.0.3)

--- name: audio-enhancement-engine description: | 当用户想要**音频增强**、**提升音质**、**修复录音**、**降噪**、**语音修复**、**高保真音频**、**48kHz超分辨率**、**清理会议录音**、**音乐音质提升**、**批量处理音频**时自动触发。 集成 **VoiceFixer**（通用语音修复）与 **AudioSR**（高保真音频超级分辨率到48kHz）两种专业技术，支持单个音频文件或整个目录批量处理。 默认使用 VoiceFixer 进行降噪和清晰度提升；当用户提到“高保真”“音乐增强”“提升采样率”“48kHz”等需求时，自动切换到 AudioSR 模式。 支持 wav、mp3、flac、m4a、ogg 等常见格式，完全本地运行，输出统一为高质量 WAV 文件。 【重要约束】仅处理音频文件或音频文件夹，其他文件（如视频、图片、文档、纯文本）一律不触发此技能。 常见触发口语（越多越好）： - “帮我增强这个音频” - “修复这个录音的音质” - “给这个语音降噪” - “把这个音频提升到高保真” - “音乐音质增强 这个.mp3” - “批量处理音频文件夹” - “清理会议录音” - “提升音频采样率到48kHz” - “语音修复 这个 wav 文件” - “高保真增强音频” - “老旧录音修复” - “音频增强 目录路径” metadata: openclaw: requires: bins: - python user-invocable: true --- # Audio Enhancement Skill **功能**：本地音频增强与修复统一工具，集成 VoiceFixer（语音降噪/修复）和 AudioSR（高保真超级分辨率）。支持单文件与目录批量处理，自动适配最合适的增强模式，输出清晰、高质量的 48kHz WAV 文件。 ### 触发时机（Triggers） - 用户提供音频文件（.wav、.mp3、.flac、.m4a、.ogg 等）或音频文件夹路径，并表达增强音质、修复、降噪、高保真等意图。 - 用户说“音频增强”“修复录音”“降噪”“提升音质”“高保真”“48kHz”等关键词。 - 支持单个文件处理或整个文件夹批量处理（支持递归子目录）。 ### 支持的两种增强模式 1. **VoiceFixer 通用语音修复**（默认模式） - 擅长语音降噪、提升清晰度、修复轻微失真。 - 推荐用于：会议录音、访谈、播客、语音笔记、老旧录音。 2. **AudioSR 高保真音频超级分辨率**（启用 `--hifi` 时） - 将音频提升至 48kHz，显著增加高频细节和整体保真度。 - 推荐用于：音乐、演唱、人声、需要高音质的场景。 ## 参数提取指南 当决定调用此技能时，请从用户消息中准确提取以下参数： 1. **`<输入路径>`** (必填): 用户提供的音频文件路径或文件夹路径（支持相对/绝对路径）。 2. **`<输出路径>`** (选填): 用户指定的输出文件或目录路径。若未指定，默认在输入同级目录自动添加 `_enhanced` 后缀。 3. **`<模式选择>`** (选填): - 默认使用 VoiceFixer。 - 若用户提到“高保真”“音乐”“48kHz”“超分辨率”等，自动添加 `--hifi` 并使用 AudioSR。 4. **VoiceFixer 专用参数**（默认模式）: - `--mode`：0/1/2（推荐 1，默认 1） - `--cuda`：是否使用 GPU - `-r, --recursive`：是否递归子目录 5. **AudioSR 专用参数**（`--hifi` 模式）: - `--model_name`：`basic` 或 `speech`（人声推荐 speech） - `--ddim_steps`：扩散步数（默认 50，建议 50-100） - `--guidance_scale`：引导尺度（默认 3.5） - `--seed`：随机种子（默认 42） - `--device`：`cuda` 或 `cpu` ### 执行步骤 1. **解析路径**：识别用户提供的音频文件或文件夹路径。 2. **模式判断**：根据用户意图判断使用 VoiceFixer（默认）还是 AudioSR（含 `--hifi`）。 3. **默认目标**：若未指定输出路径，默认在输入目录生成带 `_enhanced_48k`（AudioSR）或 `_enhanced`（VoiceFixer）后缀的文件。 4. **调用命令**：使用以下兼容性命令启动脚本（优先 `python3`，失败则 `python`）。脚本会自动检查环境、初始化模型并处理。 ```bash (python3 scripts/enhancer.py -i "<输入路径>" [-o "<输出目录>"] [-m <0|1|2>] [--cuda] [-r] [--hifi] [--model_name <basic|speech>] [--ddim_steps <数值>] [--guidance_scale <数值>] [--seed <数值>] [--device <cuda|cpu>]) || (python scripts/enhancer.py -i "<输入路径>" [-o "<输出目录>"] [-m <0|1|2>] [--cuda] [-r] [--hifi] [--model_name <basic|speech>] [--ddim_steps <数值>] [--guidance_scale <数值>] [--seed <数值>] [--device <cuda|cpu>])

path: README.md
content: ### audio-enhancement-engine

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Python](https://img.shields.io/badge/Python-3.8%2B-green.svg)](https://www.python.org/)
[![OpenClaw](https://img.shields.io/badge/OpenClaw-Skill-orange.svg)](https://github.com/wangminrui2022)

**audio-enhancement-engine** 本地音频增强与修复统一工具，集成 VoiceFixer（语音降噪/修复）和 AudioSR（高保真超级分辨率）。支持单文件与目录批量处理，自动适配最合适的增强模式，输出清晰、高质量的 48kHz WAV 文件。

---

```markdown
# OpenClaw Audio Skill

**一个简单高效的音频增强与修复命令行工具**

支持两种主流音频增强技术：
- **AudioSR**：高保真音频超级分辨率（将音频提升至 48kHz，增加细节与高频）
- **VoiceFixer**：通用语音修复（降噪、提升清晰度、修复失真）

---

## ✨ 功能特点

- 支持**单个文件**和**整个目录**批量处理
- 同时集成两种专业音频增强模型
- 通过 `--hifi` 一键切换高保真模式与语音修复模式
- 支持 GPU 加速（CUDA）
- 自动创建输出目录，智能添加 `_enhanced` 后缀
- 详细的中文日志提示，处理状态清晰
- 兼容性强（包含 NumPy 兼容补丁）
- 支持递归处理子目录

---

## 📥 安装与使用

### 1. 克隆项目

```bash
git clone https://github.com/wangminrui2022/audio-enhancement-engine.git
```

> 首次运行 VoiceFixer 时会自动下载模型，AudioSR 同样会根据需要下载对应模型。

### 2. 基本使用

#### 方式一：默认使用 VoiceFixer（语音修复）

```bash
# 修复单个音频文件
python main.py -i input/audio.mp3

# 修复整个目录
python main.py -i recordings/

# 使用 GPU + 递归处理子目录
python main.py -i recordings/ --cuda -r
```

#### 方式二：使用 AudioSR 高保真增强（48kHz 超分辨率）

```bash
# 高保真增强单个文件
python main.py -i low_quality.wav --hifi

# 高保真增强目录，并指定输出目录
python main.py -i music_folder/ -o enhanced_music/ --hifi

# 使用更高参数提升质量
python main.py -i input.wav --hifi --ddim_steps 100 --guidance_scale 4.0
```

---
### **在 OpenClaw 聊天中**

你可以直接对你的 Agent 说：

帮我增强这个音频 recording.mp3”

复这个会议录音，音质太差了”

给这个音乐提升高保真音质 music.wav”

把这个音频提升到48kHz”

批量处理这个音频文件夹 audio_files/”

高保真增强这个播客音频”

帮我降噪这个语音笔记 voice.m4a”

老旧录音修复 folder_path/”

音乐音质增强 这个.mp3 --hifi”

清理这个失真录音并提升清晰度”

## 📋 命令行参数说明

### 通用参数

| 参数 | 缩写 | 类型 | 默认值 | 说明 |
|------|------|------|--------|------|
| `--input` | `-i` | str | **必填** | 输入文件或目录路径 |
| `--output` | `-o` | str | None | 输出路径（文件或目录） |

### VoiceFixer 参数（默认模式）

| 参数 | 缩写 | 类型 | 默认值 | 说明 |
|------|------|------|--------|------|
| `--mode` | `-m` | int | 1 | 增强模式 (0/1/2)，推荐使用 1 |
| `--cuda` | | bool | False | 是否使用 GPU 加速 |
| `--recursive` | `-r` | bool | False | 是否递归处理子目录 |

### AudioSR 高保真参数（需添加 `--hifi`）

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `--hifi` | bool | False | 启用 AudioSR 高保真模式 |
| `--model_name` | str | basic | 模型名称，可选 `basic` / `speech` |
| `--ddim_steps` | int | 50 | 扩散步数（越大质量越好，速度越慢） |
| `--guidance_scale` | float | 3.5 | 引导尺度 |
| `--seed` | int | 42 | 随机种子 |
| `--device` | str | None | 指定设备 `cuda` 或 `cpu` |

---

## 📂 项目结构

```
audio-enhancement-engine/
├── enhancer.py                # 主程序入口（命令行解析 + 调度）
├── hifi_audio_enhance.py      # AudioSR 高保真增强模块
├── voice_enhance.py           # VoiceFixer 语音修复模块
└── ...
```

---

## 🎯 使用场景推荐

| 场景 | 推荐模式 | 建议参数 |
|------|----------|----------|
| 会议录音、电话录音、播客降噪 | VoiceFixer | `mode=1` + `--cuda` |
| 老旧磁带、历史录音修复 | VoiceFixer | `mode=1` |
| 音乐、演唱、人声提升高频细节 | AudioSR | `--hifi --model_name speech` |
| 需要极致音质（音乐制作） | AudioSR | `--hifi --ddim_steps 100 --guidance_scale 4.0` |
| 批量处理大量文件 | 任一模式 | 配合 `-o` 指定输出目录 |

---

## ⚠️ 注意事项

1. **首次运行** VoiceFixer 或 AudioSR 时会自动下载模型，请保持网络畅通。
2. AudioSR 处理速度较慢（尤其是 `ddim_steps` 较大时），建议使用 GPU。
3. VoiceFixer 默认输出为 `.wav` 格式，AudioSR 默认输出为 48kHz `.wav`。
4. 建议为不同任务分别创建输出目录，避免混淆。

---

## 🛠️ 未来计划（可选）

- 支持更多音频增强模型
- 添加图形界面（Gradio / Streamlit）
- 支持批量配置文件
- 集成到 OpenClaw 主框架
- 支持实时音频流处理

---

## 📄 License

Apache License

---

## ❤️ 致谢

- [AudioSR](https://github.com/haoheliu/versatile_audio_super_resolution.git) - 音频超级分辨率
- [VoiceFixer](https://github.com/haoheliu/voicefixer.git) - 语音修复工具

---

**欢迎 Star ⭐ 支持项目！**

如有问题或建议，欢迎提交 Issue 或 Pull Request。

```

---

path: scripts/config.py
content: #!/usr/bin/env python3
# -*- coding: utf-8, -*-

"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞/https://github.com/wangminrui2022
License: Apache License
Description: 这段代码是一个典型的 Python 项目路径管理模块。
它利用 pathlib 库实现了一套自动化的路径解析方案，旨在解决硬编码（Hardcoding）路径带来的跨平台兼容性差和维护困难等问题。
这段代码的核心逻辑是**“以当前文件为基准，向上溯源寻找根目录”**。
这种方式能确保无论项目被部署在哪个文件夹下，只要目录结构保持不变，代码都能准确找到对应的资源。
"""

from pathlib import Path

_BASE_PATH = Path(__file__).resolve()

class ProjectPaths:
    """
    统一路径管理
    ProjectPaths 类将项目中常用的核心目录进行了静态封装：
    | 路径变量 | 指向位置 | 用途 |
    | :--- | :--- | :--- |
    | VENV_DIR | 根目录/venv | 虚拟环境目录 |
    | DATA_DIR | 根目录/data | 数据文件存放处 |
    | LOG_DIR | 根目录/logs | 日志记录输出目录 |
    | MODEL_DIR | 根目录/models | AI模型权重等资源目录 |    
    """
    SCRIPT_PATH = _BASE_PATH
    SKILL_ROOT = _BASE_PATH.parent.parent
    VENV_DIR = _BASE_PATH.parent.parent.parent / "venv" #venv虚拟环境目录在skills/venv通用目录
    DATA_DIR = SKILL_ROOT / "data"
    LOG_DIR = SKILL_ROOT / "logs"
    MODEL_DIR = SKILL_ROOT / "models"      # 模型下载目录

    @classmethod
    def get_subpath(cls, *parts):
        """
        动态获取基于根目录的子路径
        用法: ProjectPaths.get_subpath("data", "raw", "test.txt")
        """
        path = cls.SKILL_ROOT.joinpath(*parts)
        # 自动创建不存在的目录（可选）
        # path.parent.mkdir(parents=True, exist_ok=True)
        return path

# 也可以直接导出变量，方便外部 import
SCRIPT_PATH = ProjectPaths.SCRIPT_PATH
SKILL_ROOT = ProjectPaths.SKILL_ROOT
VENV_DIR = ProjectPaths.VENV_DIR
DATA_DIR = ProjectPaths.DATA_DIR
LOG_DIR = ProjectPaths.LOG_DIR
MODEL_DIR = ProjectPaths.MODEL_DIR

path: scripts/enhancer.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞 / https://github.com/wangminrui2022
License: Apache License
Description: 本地音频增强与修复统一工具，集成 VoiceFixer（语音降噪/修复）和 AudioSR（高保真超级分辨率）。支持单文件与目录批量处理，自动适配最合适的增强模式，输出清晰、高质量的 48kHz WAV 文件。
提供了一个简洁、统一的命令行工具，用于实现两种不同的音频处理需求：
    高保真音频超分辨率增强（AudioSR）
    通用语音修复增强（VoiceFixer）
核心功能
    通过一个命令行工具，同时支持两种音频增强技术，并根据用户选择的模式自动调用对应的处理函数，实现“一个入口、两种能力”的设计目标。
"""

from pathlib import Path
from logger_manager import LoggerManager
import ensure_package
ensure_package.pip("chardet")
ensure_package.pip("ftfy")
ensure_package.pip("gradio")
ensure_package.pip("phonemizer")
ensure_package.pip("progressbar")
ensure_package.pip("timm")
ensure_package.pip("unidecode")
ensure_package.pip("git+https://github.com/qiuqiangkong/torchlibrosa.git",fallback_zip="torchlibrosa-master.zip")
ensure_package.pip("matplotlib")
import hifi_audio_enhance
import voice_enhance
import env_manager
import argparse

logger = LoggerManager.setup_logger(logger_name="audio-enhancement-engine")

def main():
    global args  # 批量模式需要访问

    if args.hifi:
        logger.info(f" {args.hifi} 高保真 audiosr（音频超分辨率到48kHz）")
        # 高保真 audiosr（音频超分辨率到48kHz）
        hifi_audio_enhance.run_audio_enhancement(
            input_path=args.input,
            output_dir=args.output,
            model_name=args.model_name,
            ddim_steps=args.ddim_steps,
            guidance_scale=args.guidance_scale,
            seed=args.seed,
            device=args.device
        )
    else:
        logger.info(f" {args.hifi} voicefixer（通用语音修复）")
        # voicefixer（通用语音修复）
        voice_enhance.enhance_audio(
            input_path=args.input,
            output_path=args.output,
            mode=args.mode,
            use_cuda=args.cuda,
            recursive=args.recursive
        )
    
if __name__ == "__main__":
    env_manager.check_python_version()
    env_manager.setup_venv()

    parser = argparse.ArgumentParser(description="OpenClaw Audio Skill - 音频超分辨率 & 语音修复 统一工具")
    parser.add_argument("-i", "--input", type=str, required=True,help="输入路径：单个音频文件 或 目录")
    parser.add_argument("-o", "--output", type=str, default=None,help="输出目录路径（可选）")      

    #voice_enhance.py voicefixer（通用语音修复）
    parser.add_argument("-m", "--mode", type=int, choices=[0, 1, 2], default=1,help="增强模式 (推荐 1)，默认=1")
    parser.add_argument("--cuda", action="store_true", default=False,help="是否使用 GPU")
    parser.add_argument("-r", "--recursive", action="store_true",help="递归处理子目录（仅目录模式有效）")

    #hifi_audio_enhance.py 高保真 audiosr（音频超分辨率到48kHz）
    parser.add_argument("--hifi", action="store_true", help="高保真 audiosr（音频超分辨率到48kHz）")
    parser.add_argument("--model_name", type=str, default="basic",choices=["basic", "speech"],help="模型名称，默认 basic")
    parser.add_argument("--ddim_steps", type=int, default=50,help="扩散步数，默认 50")
    parser.add_argument("--guidance_scale", type=float, default=3.5,help="引导尺度，默认 3.5")
    parser.add_argument("--seed", type=int, default=42,help="随机种子，默认 42")
    parser.add_argument("--device", type=str, default=None,choices=["cuda", "cpu"],help="运行设备")

    args = parser.parse_args()

    main()

path: scripts/ensure_package.py
content: #!/usr/bin/env python3
# -*- coding: utf-8, -*-

"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞/https://github.com/wangminrui2022
License: Apache License
Description: 智能的 Python 包安装工具函数，支持多种安装方式（普通包、指定版本、git 仓库、本地 zip 包等），并具备完善的已安装检测和失败回退机制
功能特点：
    支持 git+https:// 方式安装 GitHub 仓库
    支持本地 .zip 包作为回退方案（git 下载失败时自动尝试）
    加强版已安装检测（import + importlib.metadata 双重检查）
    支持强制重新安装（force=True）
    对 git+ 安装失败提供友好提示和本地 zip 回退，不轻易抛出异常
    自动区分是否使用清华镜像加速（git+ 和本地 zip 不使用镜像）
    
"""

import subprocess
import sys
import os
import importlib.metadata
from packaging import version as pkg_version
from logger_manager import LoggerManager
logger = LoggerManager.setup_logger(logger_name="audio-enhancement-engine")

def fix_setuptools_for_legacy_packages():
    """专门为老项目（使用 pkg_resources 的 setup.py）修复 setuptools 版本"""
    logger.info("🔧 正在修复 setuptools 版本（兼容旧 GitHub 包构建）...")
    try:
        subprocess.check_call([
            sys.executable, "-m", "pip", "install",
            "--quiet", "--force-reinstall", "setuptools<=81.2.0", "wheel"
        ])
        logger.info("✅ setuptools 已切换到兼容版本 (<=81.2.0)")
    except Exception as e:
        logger.info(f"⚠️ setuptools 修复失败: {e}（可忽略，继续尝试安装）")

# ==================== 在文件最开头调用 ====================
fix_setuptools_for_legacy_packages()
import pkg_resources  # 用于更鲁棒的版本比较
from config import SKILL_ROOT

"""
参数,类型,默认值,说明
spec,str,无,安装规格，支持普通包名、包==版本、git+https://...、本地.zip 等
import_name,str,None,用于 import 检查的名称。若不传则自动使用包名（git 包建议显式传入）
sub_import,str,None,需要检查的子模块（较少使用）
min_version,str,None,最低版本要求
force,bool,False,是否强制重新安装（忽略已安装版本）
fallback_zip,str,None,"当 git+ 安装失败时，用于回退的本地 zip 包路径（如 ""versatile_audio_super_resolution-main.zip""）"
"""
def pip(spec: str, 
        import_name: str = None, 
        sub_import: str = None, 
        min_version: str = None,
        force: bool = False,
        fallback_zip: str = None):
    """
    智能安装函数 - 加强版 import + metadata 双重检测
    """
    # 从 spec 中尝试提取包名（对 git+ 更友好）
    check_name = parse_spec(spec)[0] if hasattr(parse_spec, '__call__') else spec.split('/')[-1].replace('.git', '')
    
    if import_name is None:
        import_name = check_name

    # ==================== 强制安装直接跳过检查 ====================
    if force:
        logger.warning(f"🔄 强制安装 {spec} ...")
        _install_package(spec, check_name, fallback_zip)
        return

    # ==================== 加强版已安装检测 ====================
    installed = False
    installed_version = None

    # 第一步：尝试 import 检查（最快）
    try:
        parts = import_name.split('.')
        mod = __import__(parts[0])
        for part in parts[1:]:
            mod = getattr(mod, part)
        if sub_import:
            getattr(mod, sub_import)
        logger.debug(f"✅ 通过 import 检测到 {import_name} 已安装")
        installed = True
    except Exception:
        pass

    # 第二步：用 importlib.metadata / pkg_resources 更可靠地检查（推荐方式）
    if not installed:
        try:
            import importlib.metadata
            installed_version = importlib.metadata.version(check_name)
            installed = True
            logger.debug(f"✅ 通过 metadata 检测到 {check_name} 已安装，版本 {installed_version}")
        except (importlib.metadata.PackageNotFoundError, Exception):
            pass

    # 第三步：兼容旧的 get_installed_version 函数（如果你已有）
    if not installed and 'get_installed_version' in globals():
        installed_version = get_installed_version(check_name)
        if installed_version:
            installed = True
            logger.debug(f"✅ 通过 get_installed_version 检测到 {check_name} 已安装")

    # 第四步：判断是否需要安装
    need_install = not installed

    # 如果有版本要求，再额外判断（git+ 包通常没有固定版本，这里可根据需要调整）
    if installed and (min_version or parse_spec(spec)[1]):
        target_ver = min_version or parse_spec(spec)[1]
        if not is_version_satisfied(installed_version, target_ver):
            logger.info(f"📦 {check_name} 当前版本 {installed_version}，需要升级到 {target_ver}")
            need_install = True

    if not need_install:
        logger.info(f"✅ {spec} 已满足要求，跳过安装")
        return

    # 执行安装
    _install_package(spec, check_name, fallback_zip)


def _install_package(spec: str, check_name: str, fallback_zip: str = None):
    """实际执行安装的内部函数（新增 git+ 失败 fallback 支持）"""
    logger.warning(f"🔧 正在安装 {spec} ...（首次运行会较慢）")
    
    cmd = [
        sys.executable, "-m", "pip", "install",
        "--upgrade",
        spec,
        "--quiet"
    ]
    
    # git+、http、zip 等特殊安装方式不使用清华镜像，避免冲突
    # if any(keyword in spec for keyword in ["git+", "://", ".zip", ".whl"]):
    #     cmd = [c for c in cmd if c != "-i" and not str(c).startswith("https://pypi.tuna")]
    # else:
    #     cmd.extend(["-i", "https://pypi.tuna.tsinghua.edu.cn/simple"])

    # git+、http、.zip、.whl 等特殊安装方式不使用清华镜像，避免源冲突或构建问题
    if any(keyword in spec.lower() for keyword in ["git+", "://", ".zip", ".whl"]):
        # 过滤掉所有 -i 和 tuna 镜像参数
        cmd = [c for c in cmd if c != "-i" and not str(c).startswith("https://pypi.tuna")]
        
        # 额外建议：为这类包添加常见构建修复参数（强烈推荐加上）
        extra_flags = ["--no-build-isolation", "--no-deps", "--use-pep517"]
        # 插入到合适位置（通常在 install 之后）
        try:
            install_idx = cmd.index("install")
            cmd[install_idx+1:install_idx+1] = extra_flags
        except ValueError:
            cmd.extend(extra_flags)
    else:
        # 普通包使用清华镜像加速
        cmd.extend(["-i", "https://pypi.tuna.tsinghua.edu.cn/simple"])

    try:
        subprocess.check_call(cmd)
        logger.info(f"✅ {spec} 安装/升级完成！")
        
    except subprocess.CalledProcessError as e:
        logger.error(f"❌ 安装 {spec} 失败！错误信息：{e}")
        # === git+ 失败后的回退逻辑 ===
        if fallback_zip and "git+" in spec:
            if os.path.exists(fallback_zip):
                logger.warning(f"⚠️ 尝试使用本地回退包: {fallback_zip}")
                try:
                    cmd_fallback = [
                        sys.executable, "-m", "pip", "install",
                        "--upgrade", fallback_zip, "--quiet"
                    ]
                    subprocess.check_call(cmd_fallback)
                    logger.info(f"✅ 使用本地包 {fallback_zip} 安装成功！")
                    return
                except subprocess.CalledProcessError as e2:
                    logger.error(f"❌ 本地回退包安装也失败！")
                    # 这里可以选择继续抛出或不抛出，根据你的需求
                    raise
            else:
                logger.warning(f"⚠️ 本地回退包不存在: {fallback_zip}")
                logger.warning(f"💡 请将 {fallback_zip}  放到 {SKILL_ROOT} 目录，重新启动安装")
                # 【关键修改】：不抛出异常，让程序继续往下运行
                return
        
        # 如果不是 git+ 失败，或者没有设置 fallback_zip，才真正报错
        logger.error(f"❌ 安装 {spec} 最终失败！")
        raise        


def parse_spec(spec: str):
    """解析安装规格"""
    if "==" in spec:
        name, ver = spec.split("==", 1)
        return name.strip(), ver.strip()
    
    if spec.startswith("git+"):
        if "#egg=" in spec:
            name = spec.split("#egg=")[-1].split("&")[0]
        else:
            name = spec.rstrip("/").split("/")[-1].replace(".git", "")
            # 特殊处理常见包名
            name = name.replace("torchlibrosa-master", "torchlibrosa")
            name = name.replace("voicefixer-main", "voicefixer")
        return name, None
    
    if spec.endswith((".zip", ".whl")):
        name = spec.split("/")[-1].split("-")[0].split("_")[0]
        name = name.replace("master", "").replace("main", "")
        if "torchlibrosa" in name:
            name = "torchlibrosa"
        elif "voicefixer" in name:
            name = "voicefixer"
        return name.strip(), None
    
    return spec, None


def get_installed_version(package_name: str):
    """获取已安装版本（双保险）"""
    try:
        return importlib.metadata.version(package_name)
    except Exception:
        try:
            return pkg_resources.get_distribution(package_name).version
        except Exception:
            return None


def is_version_satisfied(installed_ver: str | None, required: str | None):
    """版本比较"""
    if not installed_ver or not required:
        return bool(installed_ver)
    
    try:
        installed = pkg_version.parse(installed_ver)
        
        if required.startswith("=="):
            req_ver = pkg_version.parse(required[2:].strip())
            return installed == req_ver
        else:
            # 默认视为最低版本要求
            req_ver = pkg_version.parse(required.lstrip(">="))
            return installed >= req_ver
    except Exception:
        return False
"""
一个轻量级的包安装函数，主要用于快速安装普通 PyPI 包并支持简单的版本约束（如 <2、>=1.26 等）。
"""
def pip_v(pkg: str, version: str = None):
    """
    智能安装函数，支持版本约束
    示例：
        pip_v("urllib3", version="<2")
        pip_v("numpy", version=">=1.26.0")
    """
    import_name = pkg   # 默认相同，可后续扩展传参

    # ==================== 1. 检查是否已安装 + 版本是否满足 ====================
    try:
        __import__(import_name)
        
        # 尝试获取当前版本
        try:
            import importlib.metadata
            current_version = importlib.metadata.version(pkg)
            
            if version and not is_version_satisfied(current_version, version):  # 需要你已有这个函数
                logger.info(f"📦 {pkg} 当前版本 {current_version}，不满足 {version}，准备重新安装...")
            else:
                logger.info(f"✅ {pkg} 已安装（版本 {current_version}），跳过")
                return
        except:
            logger.info(f"✅ {pkg} 已通过 import 检测，跳过安装")
            return
            
    except ImportError:
        logger.debug(f"{pkg} 未通过 import 检测，准备安装...")

    # ==================== 2. 执行安装 ====================
    install_str = pkg
    if version:
        install_str = f"{pkg}{version}"   # 正确拼接方式

    logger.warning(f"🔧 正在安装 {install_str} ...")

    try:
        subprocess.check_call([
            sys.executable, "-m", "pip", "install",
            install_str,
            "-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
            "--quiet"
        ])
        logger.info(f"✅ {pkg} 安装完成！")
        
    except subprocess.CalledProcessError as e:
        logger.error(f"❌ {pkg} 安装失败！")
        raise

path: scripts/env_manager.py
content: #!/usr/bin/env python3
# -*- coding: utf-8, -*-

"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞/https://github.com/wangminrui2022
License: Apache License
Description: 这段代码是一个 Python 脚本的环境初始化与自动化配置模块。
它主要用于确保程序在正确的 Python 版本和虚拟环境中运行，并能根据用户的硬件（特别是 NVIDIA GPU）自动安装适配的 PyTorch 及其相关音频处理依赖。
该脚本充当了程序的“引导加载程序”（Bootstrapper），主要完成以下四项任务：
1、路径与日志管理：整合项目路径配置并初始化日志系统。
2、环境强制检查：严格限制 Python 运行版本（3.10 ~ 3.12）。
3、智能硬件探测：通过解析 nvidia-smi 自动识别 GPU、驱动版本及 CUDA 版本。
4、自动化依赖部署：根据硬件情况，自动安装对应版本的 PyTorch（GPU/CPU 版）及音频处理库（audio-separator, librosa 等）。
"""

import os
import sys
import subprocess
import venv
import shutil
import re
from pathlib import Path
from logger_manager import LoggerManager
from config import ProjectPaths,SCRIPT_PATH, SKILL_ROOT,VENV_DIR,LOG_DIR,MODEL_DIR

# 方式 A：直接使用导出的常量
print(f"代码目录: {SCRIPT_PATH}")
print(f"根目录是: {SKILL_ROOT}")
print(f"虚拟环境路径: {VENV_DIR}")
print(f"日志路径: {LOG_DIR}")
print(f"模型路径: {MODEL_DIR}")

# 方式 B：使用辅助方法获取更深的路径
model_path = ProjectPaths.get_subpath("models", "v1", "model.pkl")
print(f"模型保存路径: {model_path}")

# --- 日志系统初始化 ---
# 将配置好的 LOG_DIR 传给 LoggerManager
logger = LoggerManager.setup_logger(logger_name="audio-enhancement-engine")

def check_python_version():
    """严格检测 Python 版本，只支持 3.10 ~ 3.12"""
    major = sys.version_info.major
    minor = sys.version_info.minor
    if major != 3 or minor < 10 or minor > 12:
        logger.error(f"不支持的 Python 版本: {major}.{minor}。本技能仅支持 Python 3.10 ~ 3.12")
        print(f"\n❌ 错误：Python 版本必须是 3.10 ~ 3.12")
        print(f"当前版本: {major}.{minor}")
        print("请安装 Python 3.10、3.11 或 3.12 后再运行本技能。")
        sys.exit(1)
    logger.info(f"✅ Python 版本检测通过: {major}.{minor}")

def is_torch_gpu_installed(python_exe):
    """检测虚拟环境中是否安装了 GPU 版本 PyTorch"""
    try:
        result = subprocess.run(
            [str(python_exe), "-c",
             "import torch; print(torch.version.cuda or 'cpu')"],
            capture_output=True, text=True, timeout=10
        )
        if result.returncode == 0:
            cuda_version = result.stdout.strip()
            if cuda_version.lower() != "cpu" and cuda_version != "":
                return True
    except Exception:
        pass
    return False

def setup_venv():
    """自动创建虚拟环境 + 切换到 venv 执行主脚本（强化防递归 + 绝对路径版）"""
    # ==================== 防递归保护 ====================
    if os.getenv("RUNNING_IN_VENV") == "true":
        logger.info(f"✅ 已成功在虚拟环境中运行: {sys.executable}")
        return

    # 确定虚拟环境 python 路径
    if os.name == "nt":  # Windows
        venv_python = VENV_DIR / "Scripts" / "python.exe"
    else:
        venv_python = VENV_DIR / "bin" / "python"

    # 当前已经在虚拟环境中（大小写不敏感）
    if Path(sys.executable).resolve() == venv_python.resolve():
        logger.info(f"✅ 当前已在虚拟环境中运行")
        os.environ["RUNNING_IN_VENV"] = "true"
        return

    # ==================== 创建虚拟环境 + 安装依赖（保留你原来的全部逻辑） ====================
    if not VENV_DIR.exists():
        logger.info(f"正在创建虚拟环境: {VENV_DIR}")
        venv.create(VENV_DIR, with_pip=True)
        logger.info("虚拟环境创建成功")

        logger.info("正在升级 pip...")
        subprocess.check_call([str(venv_python), "-m", "pip", "install", "--upgrade", "pip"])

    # ==================== 检查 PyTorch GPU 是否已安装 ====================
    if Path(venv_python).exists() and is_torch_gpu_installed(venv_python):
        logger.info("✅ 虚拟环境中已有 GPU 版 PyTorch，无需重新安装")
    else:
        logger.info("ℹ️ 虚拟环境中 PyTorch 不是 GPU 版本，将重新安装 GPU 版")
        # ==================== 修复版 GPU 检测（解析完整 nvidia-smi） ====================
        logger.info("检测 GPU 和 CUDA 版本...")
        has_gpu = False
        cuda_ver = "unknown"
        driver = "unknown"

        nvidia_smi_path = shutil.which("nvidia-smi")
        if not nvidia_smi_path:
            possible_paths = [
                r"C:\Windows\System32\nvidia-smi.exe",  # 你的路径
                r"C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe",
                "/usr/bin/nvidia-smi",  # Ubuntu
                "/usr/local/cuda/bin/nvidia-smi",
            ]
            for p in possible_paths:
                if os.path.exists(p):
                    nvidia_smi_path = p
                    break

        if nvidia_smi_path:
            try:
                # 运行完整 nvidia-smi 并解析输出
                result = subprocess.run(
                    [nvidia_smi_path],
                    capture_output=True, text=True, timeout=10
                )
                if result.returncode == 0 and result.stdout:
                    output = result.stdout
                    # 提取 Driver Version
                    driver_match = re.search(r"Driver Version:\s*([\d.]+)", output)
                    if driver_match:
                        driver = driver_match.group(1)
                    # 提取 CUDA Version
                    cuda_match = re.search(r"CUDA Version:\s*([\d.]+)", output)
                    if cuda_match:
                        cuda_ver = cuda_match.group(1)
                    # 提取 GPU Name（确认有 GPU）
                    if "NVIDIA" in output and cuda_ver != "unknown":
                        has_gpu = True
                        #has_gpu = False#FFmpeg使用CPU即可
                        logger.info(f"✅ 检测到 NVIDIA GPU！驱动: {driver}，CUDA: {cuda_ver}")
            except Exception as e:
                logger.warning(f"nvidia-smi 执行失败: {e}")

        # ==================== 根据 CUDA 版本选 wheel ====================
        index_url = "https://download.pytorch.org/whl/cpu"  # 默认 CPU
        use_gpu = False
        if has_gpu:
            major_minor = '.'.join(cuda_ver.split('.')[:2])
            cuda_map = {
                "12.6": "cu126",
                "12.7": "cu126",
                "12.8": "cu128",
                "12.9": "cu128",
                "13.0": "cu129",
                "13.1": "cu129",
            }
            wheel = cuda_map.get(major_minor, "cu121")  # 默认 cu121 for 13+
            index_url = f"https://download.pytorch.org/whl/{wheel}"
            use_gpu = True
            logger.info(f"🎯 CUDA {cuda_ver} → 使用 {wheel} GPU 加速版")

        else:
            logger.info("ℹ️ 未检测到 GPU，使用 CPU 版")

        # 安装 PyTorch
        logger.info("正在安装 PyTorch（~2-3GB，请耐心等待）...")
        subprocess.check_call([
            str(venv_python), "-m", "pip", "install", "torch", "torchvision", "torchaudio",
            "--index-url", index_url
        ])

        # 验证
        verify = subprocess.run([
            str(venv_python), "-c",
            "import torch; "
            "print('GPU可用' if torch.cuda.is_available() else '仅CPU'); "
            "print('设备:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU')"
        ], capture_output=True, text=True, timeout=60)
        logger.info(f"PyTorch 验证结果: {verify.stdout.strip()}")

        # 安装 audio-separator + librosa（你提到的）
        if use_gpu:
            logger.info("安装 audio-separator GPU 版 + librosa...")
            subprocess.check_call([str(venv_python), "-m", "pip", "install", "audio-separator[gpu]", "librosa"])
        else:
            logger.info("安装 audio-separator CPU 版 + librosa...")
            subprocess.check_call([str(venv_python), "-m", "pip", "install", "audio-separator[cpu]", "librosa"])

        subprocess.check_call([str(venv_python), "-m", "pip", "install", "pydub"])
        subprocess.check_call([str(venv_python), "-m", "pip", "install", "huggingface-hub[tqdm]"])
        
        logger.info("✅ 虚拟环境及所有依赖安装完成！")

    # ==================== 关键修复：重新启动主脚本 ====================
    logger.info("🔄 正在切换到虚拟环境重新执行脚本...")

    # 自动获取主脚本绝对路径（最稳健方式）
    main_script = Path(sys.argv[0]).resolve()
    
    # 如果上面没找到（极少数情况），尝试从 env_manager 所在目录向上找
    if not main_script.exists() or "env_manager" in main_script.name.lower():
        possible_paths = [
            Path.cwd() / "scripts" / "enhancer.py",
            Path.cwd() / "enhancer.py",
            Path(__file__).parent.parent / "scripts" / "enhancer.py",   # 项目根目录/scripts/enhancer.py
        ]
        for p in possible_paths:
            if p.exists():
                main_script = p
                break

    if not main_script.exists():
        logger.error(f"❌ 无法找到主脚本路径: {main_script}")
        logger.error(f"当前 sys.argv[0] = {sys.argv[0]}")
        logger.error(f"当前工作目录 = {Path.cwd()}")
        sys.exit(1)

    # 传递环境变量防止递归
    env = os.environ.copy()
    env["RUNNING_IN_VENV"] = "true"

    logger.info(f"   当前Python: {sys.executable}")
    logger.info(f"   目标Python: {venv_python}")
    logger.info(f"   主脚本路径: {main_script}")

    # 启动新进程（保持当前工作目录）
    result = subprocess.run(
        [str(venv_python), str(main_script)] + sys.argv[1:],
        env=env,
        cwd=Path.cwd(),      # ← 关键！保证相对路径正确
    )

    sys.exit(result.returncode)

path: scripts/hifi_audio_enhance.py
content: 
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞 / https://github.com/wangminrui2022
License: Apache License
Description: 这是一个基于 AudioSR 的音频超级分辨率（Audio Super-Resolution）增强工具模块，
主要用于将低采样率或低质量的音频提升到更高音质（默认输出 48kHz）。
核心功能
    单文件增强：使用 AudioSR 模型对单个音频文件进行超级分辨率处理，显著提升音频的清晰度、细节和高频表现。
    目录批量处理：支持传入一个文件夹，自动识别并处理其中所有常见音频文件（.wav、.mp3、.flac、.ogg、.m4a、.aac 等）。
    灵活输出：可指定输出路径或输出目录，未指定时自动在输入文件同目录生成带 _enhanced_48k 后缀的新文件。
"""

from pathlib import Path
import ensure_package
import os
import numpy as np
import torch
from config import MODEL_DIR, SKILL_ROOT, VENV_DIR
from logger_manager import LoggerManager

# 安装依赖
ensure_package.pip("torch")
ensure_package.pip("torchaudio")
ensure_package.pip("torchvision")
ensure_package.pip("git+https://github.com/haoheliu/versatile_audio_super_resolution.git",
                   fallback_zip="versatile_audio_super_resolution-main.zip")
ensure_package.pip("huggingface_hub")

from audiosr import build_model, super_resolution, save_wave
import importlib.metadata

os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "0"   # 禁用高速下载器，避免锁文件错误
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"

# 日志与版本信息
logger = LoggerManager.setup_logger(logger_name="audio-enhancement-engine")

"""
使用 AudioSR 进行音频超级分辨率增强（支持返回 numpy 的版本）
"""
def enhance_audio_with_audiosr(
    input_path: str,
    output_path: str = None,
    model_name: str = "basic",
    ckpt_path: str = None,
    device: str = None,
    ddim_steps: int = 50,
    guidance_scale: float = 3.5,
    seed: int = 42
):
    # ================== NumPy 兼容补丁 ==================
    import numpy as np
    if not hasattr(np, 'float'):
        np.float = float
    if not hasattr(np, 'int'):
        np.int = int
    if not hasattr(np, 'complex'):
        np.complex = complex
    # ===================================================
    #     
    logger.info(f"正在增强音频: {input_path}")
    
    if device is None:
        device = "cuda" if torch.cuda.is_available() else "cpu"
    logger.info(f"使用设备: {device.upper()}")
    
    MODEL_DIR.mkdir(exist_ok=True)  # 确保模型目录存在

    model = build_model(ckpt_path=MODEL_DIR, model_name=model_name, device=device)

    # 自动生成输出路径（如果未指定）
    if output_path is None:
        input_path = Path(input_path)
        base_name = input_path.stem
        output_dir = input_path.parent
        output_dir.mkdir(exist_ok=True)
        output_path = output_dir / f"{base_name}_enhanced_48k.wav"# 默认输出到输入目录自动加 _enhanced_48k 后缀
    else:
        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)

    logger.info(f"开始超级分辨率增强... (steps={ddim_steps}, guidance={guidance_scale})")
    
    # 执行增强
    waveform = super_resolution(
        model,
        str(input_path),
        ddim_steps=ddim_steps,
        guidance_scale=guidance_scale,
        seed=seed
    )
    
    # 兼容 numpy 和 tensor 输出
    if isinstance(waveform, np.ndarray):
        if waveform.ndim == 1:
            waveform = waveform[None, None, :]          # (1, 1, time)
        elif waveform.ndim == 2:
            if waveform.shape[0] > waveform.shape[1]:   # (time, channels) → (channels, time)
                waveform = waveform.T
            waveform = waveform[None, :, :]             # (1, channels, time)
        waveform = torch.from_numpy(waveform).float()
    elif isinstance(waveform, torch.Tensor):
        waveform = waveform.cpu()

    # 保存音频
    save_wave(
        waveform,
        inputpath=str(input_path),
        savepath=str(output_path.parent),
        name=output_path.stem,
        samplerate=48000
    )
    
    logger.info(f"✅ 增强完成！输出文件：{output_path}")
    return str(output_path)


def process_directory(input_dir: str, output_dir: str = None, **kwargs):
    """处理整个目录下的所有音频文件"""
    input_path = Path(input_dir)
    if not input_path.is_dir():
        logger.info(f"❌ 错误：{input_dir} 不是一个有效的目录")
        return

    # 支持的音频格式（可自行扩展）
    audio_extensions = {'.wav', '.mp3', '.flac', '.ogg', '.m4a', '.aac'}

    audio_files = [f for f in input_path.iterdir() 
                   if f.is_file() and f.suffix.lower() in audio_extensions]

    if not audio_files:
        logger.info(f"⚠️ 目录 {input_dir} 中没有找到支持的音频文件")
        return

    logger.info(f"找到 {len(audio_files)} 个音频文件，开始批量处理...")

    for audio_file in audio_files:
        try:
            if output_dir:
                out_path = Path(output_dir) / f"{audio_file.stem}_enhanced_48k.wav"
            else:
                out_path = input_path.parent / f"{input_path.name}_enhanced_48k"# 默认输出到输入目录自动加 _enhanced_48k 后缀
            
            enhance_audio_with_audiosr(
                input_path=str(audio_file),
                output_path=str(out_path) if out_path else None,
                **kwargs
            )
        except Exception as e:
            logger.info(f"❌ 处理文件 {audio_file} 时出错: {e}")


def run_audio_enhancement(
    input_path: str,
    output_dir: str = None,
    model_name: str = "basic",
    ddim_steps: int = 50,
    guidance_scale: float = 3.5,
    seed: int = 42,
    device: str = None
):
    """
    OpenClaw 技能推荐调用的主入口函数
    - input_path: 单个音频文件路径 或 目录路径
    - output_dir: 输出目录（可选）
    """
    common_kwargs = {
        "model_name": model_name,
        "ddim_steps": ddim_steps,
        "guidance_scale": guidance_scale,
        "seed": seed,
        "device": device
    }

    path_obj = Path(input_path)

    if path_obj.is_file():
        logger.info("模式：单个文件增强")
        return enhance_audio_with_audiosr(
            input_path=input_path,
            output_path=output_dir,   # 这里传入的是具体文件路径或目录
            **common_kwargs
        )
    elif path_obj.is_dir():
        logger.info("模式：目录批量增强")
        process_directory(
            input_dir=input_path,
            output_dir=output_dir,
            **common_kwargs
        )
        return "目录批量处理完成"
    else:
        error_msg = f"❌ 错误：输入路径 {input_path} 不存在或无法访问"
        logger.info(error_msg)
        return error_msg

path: scripts/logger_manager.py
content: #!/usr/bin/env python3
# -*- coding: utf-8, -*-

"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞/https://github.com/wangminrui2022
License: Apache License
Description: 这段代码定义了一个名为 LoggerManager 的类，专门用于自动化配置和管理日志系统。
它通过封装 Python 的 logging 模块，为项目提供了一个既能输出到控制台，又能自动按天滚动保存的日志工具。
智能目录管理：利用 pathlib 自动检查并创建日志目录（LOG_DIR），防止因文件夹不存在而报错。
1、双通道输出：
    1、控制台 (Stdout)：方便开发者在运行程序时实时查看状态。
    2、文件 (File)：持久化记录运行日志，便于后续排查问题。
2、自动滚动备份：使用了 TimedRotatingFileHandler，设置每 1天 滚动一次日志文件，并保留最近 3天 的记录，有效防止日志文件无限增大占用硬盘空间。
3、防止日志重复：通过 if not logger.handlers 判断，确保在多次调用初始化方法时，不会重复绑定处理器（Handler），避免出现一行日志被打印多次的情况。
"""

import logging
import sys
from pathlib import Path
from logging.handlers import TimedRotatingFileHandler
from config import LOG_DIR

class LoggerManager:
    @staticmethod
    def setup_logger(logger_name: str = "log", log_filename: str = "skill_execution.log"):
        """
        初始化并配置日志记录器
        :param log_dir: 日志保存目录 (Path 对象)
        :param logger_name: logger 的名称
        :param log_filename: 日志文件的名称
        """
        # 确保日志目录存在 (parents=True 确保父级目录也会被创建)
        LOG_DIR.mkdir(parents=True, exist_ok=True)
        log_file = LOG_DIR / log_filename
        
        logger = logging.getLogger(logger_name)
        logger.setLevel(logging.INFO)
        
        # 避免在多次调用时重复添加 handler 导致日志重复输出
        if not logger.handlers:
            formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
            
            # 文件输出 Handler
            file_handler = TimedRotatingFileHandler(
                log_file, when="D", interval=1, backupCount=3, encoding="utf-8"
            )
            file_handler.setFormatter(formatter)
            logger.addHandler(file_handler)
            
            # 控制台输出 Handler
            console_handler = logging.StreamHandler(sys.stdout)
            console_handler.setFormatter(formatter)
            logger.addHandler(console_handler)
        
        return logger

path: scripts/upgrade_torch.py
content: import os
import subprocess
import sys
import platform
from pathlib import Path
from logger_manager import LoggerManager
import ensure_package
ensure_package.pip("requests")
ensure_package.pip("tqdm","tqdm")
import requests
from tqdm import tqdm

# ==================== 下载配置 ====================
WHEELS_DIR = Path("./pytorch_wheels")
WHEELS_DIR.mkdir(exist_ok=True)

logger = LoggerManager.setup_logger(logger_name="universal-voice-cloner")

# ==================== 动态生成 wheel（支持 Windows / Linux + cu129） ====================

python_tag = f"cp{sys.version_info.major}{sys.version_info.minor}"

if platform.system() == "Windows":
    platform_tag = "win_amd64"
elif platform.system() == "Linux":
    platform_tag = "manylinux_2_28_x86_64"
else:
    raise RuntimeError(f"暂不支持的系统: {platform.system()}")

WHEELS = {
    "torch": {
        "url": f"https://download.pytorch.org/whl/cu129/torch-2.9.1%2Bcu129-{python_tag}-{python_tag}-{platform_tag}.whl",
        "filename": f"torch-2.9.1+cu129-{python_tag}-{python_tag}-{platform_tag}.whl"
    },
    "torchvision": {
        "url": f"https://download.pytorch.org/whl/cu129/torchvision-0.24.1%2Bcu129-{python_tag}-{python_tag}-{platform_tag}.whl",
        "filename": f"torchvision-0.24.1+cu129-{python_tag}-{python_tag}-{platform_tag}.whl"
    },
    "torchaudio": {
        "url": f"https://download.pytorch.org/whl/cu129/torchaudio-2.9.1%2Bcu129-{python_tag}-{python_tag}-{platform_tag}.whl",
        "filename": f"torchaudio-2.9.1+cu129-{python_tag}-{python_tag}-{platform_tag}.whl"
    }
}

def download_file(url, filename, desc):
    filepath = WHEELS_DIR / filename
    if filepath.exists():
        logger.info(f"✅ {desc} 已存在，跳过下载")
        return
    
    logger.info(f"正在下载 {desc}（约 2.5GB，总共需要耐心等待）...")
    response = requests.get(url, stream=True)
    total_size = int(response.headers.get('content-length', 0))
    
    with open(filepath, "wb") as f, tqdm(
        total=total_size, 
        unit="B", 
        unit_scale=True, 
        unit_divisor=1024,
        desc=desc,
        ncols=100
    ) as bar:
        for chunk in response.iter_content(chunk_size=1024*1024):  # 1MB 块
            if chunk:
                f.write(chunk)
                bar.update(len(chunk))

def get_venv_pip() -> Path:
    """自动查找当前项目中的 venv/pip，支持任意深度的子文件夹（你的 universal-voice-cloner 结构也完美支持）"""
    
    # 1. 如果当前已经在 venv 中运行，直接使用当前 python 对应的 pip（最快最准）
    if hasattr(sys, "base_prefix") and sys.prefix != sys.base_prefix:
        return Path(sys.executable).parent / ("pip.exe" if sys.platform.startswith("win") else "pip")
    
    # 2. 不在 venv 时，自动往上查找项目里的 venv（支持你的多级目录结构）
    current = Path(__file__).resolve().parent   # 从当前脚本目录开始
    while current != current.parent:            # 一直往上直到根目录
        candidate = current / "venv"
        if candidate.is_dir():
            # 根据操作系统自动选择正确的 pip 路径
            pip_name = "pip.exe" if sys.platform.startswith("win") else "pip"
            pip_dir = "Scripts" if sys.platform.startswith("win") else "bin"
            pip_path = candidate / pip_dir / pip_name
            if pip_path.exists():
                return pip_path
        current = current.parent
    
    # 如果实在找不到，抛出明确错误
    raise FileNotFoundError(
        "❌ 找不到 venv 文件夹！\n"
        f"当前脚本路径: {Path(__file__).resolve()}\n"
        "请确认项目中存在 skills/venv 文件夹"
    )

def upgrade_torch():
    logger.info("=== PyTorch 2.11.0 CUDA 13.0 下载脚本（带真实进度条）===\n")
    logger.info(f"当前系统: {platform.system()} | Python: {sys.version_info.major}.{sys.version_info.minor} ({python_tag}) | 平台: {platform_tag}")
    
    for name, info in WHEELS.items():
        download_file(info["url"], info["filename"], name.capitalize())
    
    logger.info("\n✅ 全部下载完成！文件保存在 ./pytorch_wheels 文件夹")
    logger.info("\n现在开始本地安装（速度会快很多）...")

    #先卸载旧版本
    uninstall_old_torch()
    #修改 Python 进程的环境变量
    setup_cusparselt_env()
    setup_nvshmem_env()
    # 自动安装
    venv_pip = get_venv_pip()

    # 动态生成 wheel 路径
    wheel_paths = [str(WHEELS_DIR / info["filename"]) for info in WHEELS.values()]
    
    install_cmd = [
        str(venv_pip),
        "install",
        "--force-reinstall",
        "--no-deps",
        *wheel_paths
    ]
    
    logger.info("正在安装（这步通常很快）...")
    subprocess.run(install_cmd, check=True)
    
    # 验证（跨平台兼容）
    pip_str = str(venv_pip)
    if platform.system() == "Windows":
        python_exe = pip_str.replace("pip.exe", "python.exe")
    else:
        python_exe = pip_str.replace("pip", "python")   # Ubuntu venv 下 pip → python
    
    subprocess.run([python_exe, "-c", 
                   "import torch; print('✅ 成功！新版本:', torch.__version__); print('CUDA 可用:', torch.cuda.is_available())"])
    
    logger.info("\n🎉 全部完成！现在可以关闭这个窗口了。")

def setup_cusparselt_env():
    """
    查找 libcusparseLt.so 并将其目录加入 LD_LIBRARY_PATH，
    保证 PyTorch GPU 可以找到库。
    """
    try:
        # 查找 libcusparseLt.so 文件
        result = subprocess.run(
            ['find', '/usr/local', '-name', 'libcusparseLt.so*'],
            capture_output=True, text=True, check=True
        )
        paths = result.stdout.strip().split('\n')
        if not paths or paths == ['']:
            logger.info("⚠️ 未找到 libcusparseLt.so 文件")
            return False
        # 取第一个路径
        lib_dir = os.path.dirname(paths[0])
        logger.info(f"✅ 找到库目录: {lib_dir}")
        # 更新 LD_LIBRARY_PATH
        ld_path = os.environ.get("LD_LIBRARY_PATH", "")
        if lib_dir not in ld_path:
            os.environ["LD_LIBRARY_PATH"] = f"{lib_dir}:{ld_path}"
            logger.info(f"✅ 更新 LD_LIBRARY_PATH: {os.environ['LD_LIBRARY_PATH']}")
        return True
    except subprocess.CalledProcessError as e:
        logger.error(f"❌ 查找库文件出错: {e}")
        return False

def setup_nvshmem_env():
    """
    查找 libnvshmem_host.so* 并加入 LD_LIBRARY_PATH，保证 PyTorch GPU 可以找到库
    """
    try:
        result = subprocess.run(
            ['find', '/usr/local', '-name', 'libnvshmem_host.so*'],
            capture_output=True, text=True, check=True
        )
        paths = result.stdout.strip().split('\n')
        if not paths or paths == ['']:
            logger.info("⚠️ 未找到 libnvshmem_host.so 文件")
            return False

        lib_dir = os.path.dirname(paths[0])
        logger.info(f"✅ 找到 NVSHMEM 库目录: {lib_dir}")

        ld_path = os.environ.get("LD_LIBRARY_PATH", "")
        if lib_dir not in ld_path:
            os.environ["LD_LIBRARY_PATH"] = f"{lib_dir}:{ld_path}"
            logger.info(f"✅ 更新 LD_LIBRARY_PATH: {os.environ['LD_LIBRARY_PATH']}")

        return True

    except subprocess.CalledProcessError as e:
        logger.error(f"❌ 查找 NVSHMEM 库文件出错: {e}")
        return False

def uninstall_old_torch():
    """在升级前彻底卸载旧版 torch 相关包（可被其他函数调用）"""
    logger.info("=== 开始卸载旧版 PyTorch 包（torch / torchvision / torchaudio）===")
    
    venv_pip = get_venv_pip()          # 使用你脚本里已有的函数，自动适配 ../venv/bin/pip
    
    uninstall_cmd = [
        str(venv_pip),
        "uninstall",
        "-y",                          # 自动确认，不询问
        "torch",
        "torchvision",
        "torchaudio"
    ]
    
    try:
        result = subprocess.run(uninstall_cmd, check=True, capture_output=True, text=True)
        logger.info("✅ 旧版 PyTorch 包已全部卸载")
        if result.stdout:
            logger.debug(result.stdout.strip())   # 如果想看详细输出可以打开
    except subprocess.CalledProcessError as e:
        # 包不存在时 pip 也会返回非0，但我们希望继续升级
        logger.warning("卸载时出现警告（可能是部分包本来就不存在），继续执行升级...")
        logger.debug(e.stderr.strip() if e.stderr else "无错误输出")

path: scripts/voice_enhance.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Skill Name: audio-enhancement-engine
Author: 王岷瑞 / https://github.com/wangminrui2022
License: Apache License
Description: 这是一个基于 VoiceFixer 的音频修复与增强工具模块，主要用于修复和提升音频的质量，尤其适合处理带噪、失真、低清晰度的语音、录音或老旧音频文件。
核心功能
    单文件增强：对单个音频文件进行 VoiceFixer 修复处理，提升语音清晰度、抑制背景噪声、修复轻微失真。
    目录批量处理：支持处理整个文件夹下的音频文件，可选择是否递归子目录。
    智能输出：自动为处理后的文件添加 _enhanced 后缀，并统一输出为 .wav 格式（VoiceFixer 推荐格式）。
    灵活配置：支持三种增强模式（mode 0/1/2）、是否使用 GPU 加速，以及是否递归处理子文件夹。
"""

from pathlib import Path
import os
import ensure_package
from logger_manager import LoggerManager
import soundfile as sf
from tqdm import tqdm
ensure_package.pip("packaging","packaging","version")
ensure_package.pip("audio-separator==0.30.2",force=True)
ensure_package.pip("numpy==1.26.4",force=True)
ensure_package.pip("librosa==0.10.2",force=True)
ensure_package.pip("git+https://github.com/haoheliu/voicefixer.git",fallback_zip="voicefixer-main.zip")
ensure_package.pip("hf_transfer")
from packaging import version
from voicefixer import VoiceFixer

logger = LoggerManager.setup_logger(logger_name="audio-enhancement-engine")

def is_audio_file(file_path: Path) -> bool:
    """检查是否为支持的音频文件"""
    audio_extensions = {'.wav', '.flac', '.mp3', '.m4a', '.ogg'}
    return file_path.suffix.lower() in audio_extensions

def enhance_single_file(vf: VoiceFixer, input_path: Path, output_path: Path, mode: int = 1, use_cuda: bool = False):
    """处理单个音频文件"""
    # ================== NumPy 兼容补丁 ==================
    import numpy as np
    if not hasattr(np, 'float'):
        np.float = float
    if not hasattr(np, 'int'):
        np.int = int
    if not hasattr(np, 'complex'):
        np.complex = complex
    # ===================================================

    logger.info(f"🎵 处理文件: {input_path}")
    try:
        vf.restore(
            input=str(input_path),
            output=str(output_path),
            mode=mode,
            cuda=use_cuda
        )
        logger.info(f"✅ 处理完成 → {output_path}")
        return True
    except Exception as e:
        logger.info(f"❌ 处理失败 {input_path}: {e}")
        return False
    
def is_same_path(input_dir: Path, output_dir: Path) -> bool:
    return input_dir.resolve(strict=False) == output_dir.resolve(strict=False)

def enhance_directory(vf: VoiceFixer, input_dir: Path, output_dir: Path, 
                     mode: int = 1, use_cuda: bool = False, recursive: bool = False):
    """批量处理目录下的音频文件"""
    pattern = "**/*" if recursive else "*"
    audio_files = [f for f in input_dir.glob(pattern) if f.is_file() and is_audio_file(f)]

    if not audio_files:
        logger.info(f"⚠️ 在 {input_dir} 中未找到支持的音频文件")
        return

    logger.info(f"找到 {len(audio_files)} 个音频文件，开始批量增强...")

    success_count = 0
    for audio_file in tqdm(audio_files, desc="增强音频"):
        relative = audio_file.relative_to(input_dir)
        if is_same_path(input_dir, output_dir):
            out_file = output_dir / relative.with_stem(relative.stem + "_enhanced")
        else:
            out_file = output_dir / relative.with_stem(relative.stem)
        out_file = out_file.with_suffix(".wav")   # VoiceFixer 输出推荐使用 wav

        out_file.parent.mkdir(parents=True, exist_ok=True)

        if enhance_single_file(vf, audio_file, out_file, mode, use_cuda):
            success_count += 1

    logger.info(f"✅ 批量处理完成！成功 {success_count}/{len(audio_files)} 个文件")


def enhance_audio(
    input_path: str,
    output_path: str = None,
    mode: int = 1,
    use_cuda: bool = False,
    recursive: bool = False
):
    """
    OpenClaw 技能核心函数 - 外部调用入口
    
    参数:
        input_path: 输入文件或目录路径
        output_path: 输出路径（单个文件时为文件路径，目录时为目录路径）
        mode: 增强模式 0/1/2 （推荐使用 1）
        use_cuda: 是否使用 GPU
        recursive: 目录模式下是否递归子目录
    """
    input_path = Path(input_path).expanduser().resolve()

    # ====================== 初始化缓存和模型 ======================
    cache_dir = Path(os.environ.get("VOICEFIXER_CACHE_DIR", str(Path.home() / ".cache/voicefixer")))
    logger.info(f"当前使用的缓存目录：{cache_dir}")

    vocoder_path = cache_dir / "synthesis_module" / "44100" / "model.ckpt-1490000_trimed.pt"
    if vocoder_path.exists():
        logger.info(f"✅ Vocoder 文件存在，大小: {vocoder_path.stat().st_size / (1024*1024):.1f} MB")
    else:
        logger.info("⚠️ Vocoder 文件不存在！首次运行会自动下载。")

    # 初始化 VoiceFixer
    logger.info("正在初始化 VoiceFixer 模型...")
    vf = VoiceFixer()
    logger.info("✅ VoiceFixer 初始化完成")

    # ====================== 处理逻辑 ======================
    if input_path.is_file():
        if not is_audio_file(input_path):
            logger.info(f"❌ 输入不是支持的音频文件: {input_path}")
            return False

        if output_path:
            out_path = Path(output_path).expanduser().resolve()
        else:
            out_path = input_path.with_stem(input_path.stem + "_enhanced").with_suffix(".wav")

        out_path.parent.mkdir(parents=True, exist_ok=True)

        return enhance_single_file(vf, input_path, out_path, mode, use_cuda)

    elif input_path.is_dir():
        if output_path:
            out_dir = Path(output_path).expanduser().resolve()
        else:
            out_dir = input_path.parent / f"{input_path.name}_enhanced"# 默认输出到输入目录自动加 _enhanced 后缀
        # 确保目录存在
        out_dir.mkdir(parents=True, exist_ok=True)

        enhance_directory(vf, input_path, out_dir, mode, use_cuda, recursive)
        return True

    else:
        logger.info(f"❌ 输入路径不存在: {input_path}")
        return False

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