本地全自动AI配音方案NarratoAI+OmniVoice+FunASR

经常做短视频、解说视频的朋友应该都懂,最耗时间的不是剪辑画面,而是写文案、配AI语音、自动识别字幕,来回切换好几个工具,还容易有水印、音色生硬、识别错别字多的问题。
今天给大家分享一套100%本地部署、全程无水印、免费开源的AI解说视频全自动制作套装:NarratoAI + OmniVoice本地配音 + FunASR精准字幕识别,一次性搞定AI文案生成、真人质感配音、高精度语音转字幕、视频自动合成,个人自用、批量做号都完全够用。
先简单白话讲下三个工具的分工和关联,新手也能一眼看懂:
1、NarratoAI(核心主控)
整套流程的“大脑”,是我们操作的主界面。支持AI自动生成短视频文案、批量剪辑、画面匹配,同时可以外接第三方TTS配音、ASR字幕识别服务。但它自带的Edge配音音色比较少、质感一般,而且没有本地高精度字幕功能,所以需要搭配另外两个工具补强。
2、OmniVoice-local(本地TTS配音)
专门解决NarratoAI原生配音拉胯的问题,提供超多真人质感音色,语速、语调、停顿都可以精细调节,全程本地部署,不用联网调用第三方接口,不会限流、不会泄露文案素材。作为外部API服务对接NarratoAI,替代原生配音。
3、FunASR(高精度语音字幕识别)
阿里开源的语音识别工具,准确率吊打普通在线字幕工具。官方没有适配Docker部署,我自己适配了专属Dockerfile和docker-compose,实现一键容器化部署,作为本地ASR接口给NarratoAI调用,自动生成精准字幕,错别字极少。
4、关于大模型
有两个免费的方案:1.本地部署ollama,然后拉取免费的云端模型gemma4:31b cloud,这个测试下来目前都可以免费用,不占用本地算力;2.使用智普的接口,免费模型分别为glm-4.6v-flash和glm-4.7-flash.不过对比测试下来还是gemma4:31b cloud生成的效果好一些,智普的模型有时候解说和视频对应不上。
三者+免费大模型api搭配就是最强本地AI视频工作流:NarratoAI统筹全程,OmniVoice负责好听的本地AI配音,FunASR负责精准字幕识别,全程本地化、无付费、无水印、无接口限制,下面直接上从零开始的保姆级部署教程,包含源码拉取、镜像构建、容器部署、报错修复全流程。
前置准备:一台装有Docker、Docker-Compose的Linux服务器(Centos、Ubuntu均可),提前装好git工具,所有命令全程复制粘贴即可。
一、部署NarratoAI主程序(修复OmniVoice接口报错版)
这一步是部署核心主控面板,重点解决原版源码OmniVoice接口调用失败、404报错、连接被拒绝的BUG,很多人部署成功却无法调用本地配音,都是代码接口路径不匹配导致的,下面直接给修复后的完整部署流程。
1.1 拉取官方源码
# 创建项目目录并进入
mkdir -p /data/NarratoAI && cd /data/NarratoAI
# 拉取官方开源源码
git clone https://github.com/linyqh/NarratoAI.git
1.2 修复OmniVoice接口调用代码错误(关键步骤)
原版源码存在接口路径硬编码问题,固定请求/tts路径,部分版本端口映射后会出现404、连接拒绝报错,直接修改核心调用代码,适配标准OmniVoice接口格式,彻底解决报错。
编辑语音服务核心配置文件:
vim app/services/voice.py
找到 omnivoice_tts 函数,替换错误的接口请求逻辑,核心修复两点:
1. 适配OmniVoice标准POST请求格式,修正请求头、参数格式;
2. 移除错误的路径拼接,兼容本地服务端口映射,解决111连接拒绝、404未找到报错。
修复后核心代码参考(直接覆盖原有报错代码):
# 修复后的OmniVoice调用核心代码
def omnivoice_tts(text: str, voice_name: str, voice_file: str, speed: float = 1.0) -> Union[SubMaker, None]:
"""
使用 OmniVoice-Pack FastAPI 服务进行语音合成。
支持自动音色、指令音色和参考音频克隆三种模式。
"""
omnivoice_config = getattr(config, "omnivoice", {}) or {}
api_url = _normalize_omnivoice_api_url(omnivoice_config.get("api_url", "http://127.0.0.1:7866/tts"))
mode = omnivoice_config.get("mode", "auto")
language = (omnivoice_config.get("language", "zh") or "").strip()
instruct = (omnivoice_config.get("instruct", "") or "").strip()
ref_text = (omnivoice_config.get("ref_text", "") or "").strip()
parsed_voice = parse_omnivoice_voice(voice_name)
if mode != "voice_clone" and parsed_voice and os.path.isfile(parsed_voice):
mode = "voice_clone"
reference_audio_path = ""
if mode == "voice_clone":
candidate = parsed_voice
if candidate and os.path.isfile(candidate):
reference_audio_path = candidate
else:
reference_audio_path = parse_omnivoice_voice(omnivoice_config.get("reference_audio", "") or "")
if not reference_audio_path or not os.path.exists(reference_audio_path):
logger.error(f"OmniVoice 参考音频文件不存在: {reference_audio_path}")
return None
elif mode != "voice_design":
instruct = ""
data = {
"text": text.strip(),
"language": language,
**_optional_omnivoice_generation_data(speed),
}
if mode == "voice_design" and instruct:
data["instruct"] = instruct
if mode == "voice_clone" and ref_text:
data["ref_text"] = ref_text
data["sample"]="/samples/觉醒年代 01.mp4_20251029_195734.mp3"
proxies = _get_configured_proxies()
for attempt in range(3):
files = {}
try:
if reference_audio_path:
files["ref_audio"] = open(reference_audio_path, "rb")
logger.info(f"第 {attempt + 1} 次调用 OmniVoice API: {api_url}, mode={mode}")
response = requests.post(
api_url,
files=files or None,
# data=data,
json=data,
proxies=proxies,
timeout=240,
)
if response.status_code == 200 and _download_omnivoice_audio(response, api_url, voice_file, proxies):
logger.info(f"OmniVoice 成功生成音频: {voice_file}, 大小: {os.path.getsize(voice_file)} 字节")
sub_maker = new_sub_maker()
duration = get_audio_duration_from_file(voice_file)
duration_ms = int(duration * 1000) if duration > 0 else max(1000, int(len(text) * 200))
add_subtitle_event(sub_maker, 0, duration_ms * 10000, text)
return sub_maker
logger.error(f"OmniVoice API 调用失败: {response.status_code} - {response.text}")
except requests.exceptions.Timeout:
logger.error(f"OmniVoice API 调用超时 (尝试 {attempt + 1}/3)")
except requests.exceptions.RequestException as e:
logger.error(f"OmniVoice API 网络错误: {str(e)} (尝试 {attempt + 1}/3)")
except Exception as e:
logger.error(f"OmniVoice TTS 处理错误: {str(e)} (尝试 {attempt + 1}/3)")
finally:
for file_obj in files.values():
try:
file_obj.close()
except Exception:
pass
if attempt < 2:
time.sleep(2)
logger.error("OmniVoice TTS 生成失败,已达到最大重试次数")
return None
保存退出,至此OmniVoice接口报错问题彻底修复,后续部署配音服务即可正常调用。
1.3 构建Docker镜像+部署容器
源码自带Dockerfile,直接一键构建部署,无需额外修改配置。
# 构建镜像
docker-compose build --no-cache
# 启动容器
docker-compose up -d
#docker-compose文件参考如下:
services:
narratoai-webui:
build:
context: .
dockerfile: Dockerfile
image: narratoai:latest
container_name: narratoai-webui
privileged: true
ports:
- "8501:8501"
volumes:
- ./storage:/NarratoAI/storage
- ./config.toml:/NarratoAI/config.toml:rw
- ./resource:/NarratoAI/resource:rw
environment:
- PYTHONUNBUFFERED=1
- TZ=Asia/Shanghai
restart: unless-stopped
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
devices:
- "/dev/dri:/dev/dri"
# 健康检查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8501/_stcore/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
#networks:
# - default1
#networks:
# default1:
# external: true
1.4 访问测试
浏览器打开:服务器IP:8501,即可进入NarratoAI主界面,部署完成。此时主程序可正常运行,但OmniVoice配音、FunASR字幕功能还无法使用,需继续部署下方两个外部API服务。
二、部署OmniVoice-local本地TTS配音服务
专门为NarratoAI提供本地AI配音接口,音色丰富、音质自然,无调用次数限制,全程离线可用,适配上面修复后的接口代码。
2.1 拉取OmniVoice官方源码
# 创建目录并拉取源码
mkdir -p /data/OmniVoice && cd /data/OmniVoice
git clone https://github.com/pasadei/OmniVoice-local.git .
2.2 构建镜像并启动容器
# 构建镜像
docker-compose build --no-cache
# 启动容器,固定7866端口,和NarratoAI配置对应
docker-compose up -d
2.3 接口连通性测试
# 本地测试接口是否正常启动
curl http://127.0.0.1:7866/tts #改成自己的ip
无报错即为部署成功,随后进入NarratoAI配置文件,绑定该接口地址:
编辑 /data/NarratoAI/config.toml,修改如下配置:
[tts]
default_engine = "omnivoice"
omnivoice_api_url = "http://192.168.2.99:7866/tts" #改成自己的ip
修改后重启NarratoAI容器即可生效。
三、部署FunASR本地字幕识别服务(自定义Docker部署)
重点说明:阿里官方FunASR不支持Docker部署,没有现成容器配置,我已经通过豆包适配好了专属的 Dockerfile 和 docker-compose.yml,直接复制使用,一键容器化部署,完美对接NarratoAI。
3.1 拉取FunASR官方源码
mkdir -p /data/FunASR && cd /data/FunASR
git clone https://github.com/modelscope/FunASR.git
3.2 新建自定义Dockerfile(适配容器部署)
在FunASR根目录新建 Dockerfile 文件,复制以下全部内容:
# 基础镜像:PyTorch 2.4 + CUDA 12.1 适配 GPU,支持 vLLM
FROM pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime
# 设置环境变量
ENV PYTHONUNBUFFERED=1
PYTHONDONTWRITEBYTECODE=1
PIP_NO_CACHE_DIR=off
PIP_DISABLE_PIP_VERSION_CHECK=on
FUNASR_HOME=/app/FunASR
HF_HOME=/app/model_cache
# 创建工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends
git
ffmpeg
curl
vim
&& rm -rf /var/lib/apt/lists/*
# 【核心:复制本地仓库源码到容器】
# 前置要求:Dockerfile 和 FunASR 项目文件夹放在同一级目录
COPY ./funasrsource ${FUNASR_HOME}
# 切换到项目目录
WORKDIR ${FUNASR_HOME}
# 升级pip,安装项目依赖
RUN pip install --upgrade pip setuptools wheel
# 方式1:本地源码可编辑安装
RUN pip install -e ./
# 安装API服务、vLLM加速依赖
RUN pip install vllm fastapi uvicorn python-multipart
# 暴露FunASR默认API端口
EXPOSE 8000
# 模型缓存持久化挂载目录
VOLUME ["/app/model_cache", "/app/audio_data"]
# 默认启动命令:OpenAI兼容API服务
# 稳定轻量模型推荐CMD
CMD ["funasr-server", "--device", "cuda", "--host", "0.0.0.0", "--port", "8000", "--model", "iic/SenseVoiceSmall"]
3.3 新建docker-compose.yml文件
同目录下新建 docker-compose.yml,实现便捷启停、开机自启:
version: "3.8"
services:
funasr-gpu:
build:
context: ./
dockerfile: Dockerfile
container_name: funasr-api
image: funasr-api:latest
privileged: true
# GPU 调度(必须安装nvidia-docker container)
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# 端口映射:宿主机8000 -> 容器8000
ports:
- "8005:8000"
# 目录挂载
volumes:
# 模型缓存持久化,避免每次拉取模型
- ./model_cache:/app/model_cache
# 本地音频目录挂载,容器内可读取本地音频文件
- ./audio_data:/app/audio_data
# 新增持久化tmp目录,避免容器内部tmp空间不足
#- ./tmp_upload:/tmp
# 环境变量
environment:
- TZ=Asia/Shanghai
- HF_HOME=/app/model_cache
# HF国内镜像,加速模型下载,规避海外网络
- HF_ENDPOINT=https://hf-mirror.com
#- CUDA_VISIBLE_DEVICES=0 # 指定使用0号显卡,多卡可修改为0,1
# 关键:全局禁用vLLM自动预加载,消除报错
- FUNASR_DISABLE_VLLM=1
# 容器重启策略
restart: unless-stopped
tmpfs: /tmp:size=5G
command: funasr-server --device cuda --host 0.0.0.0 --port 8000 --model iic/SenseVoiceSmall
# 网络
networks:
- default1
# 顶层声明外部网络
#networks:
#default1:
#external: true
3.4 一键部署启动FunASR服务
# 构建并启动容器
docker-compose up -d
# 查看运行状态
docker-compose ps
3.6 对接NarratoAI
编辑NarratoAI配置文件 config.toml,添加FunASR接口地址:
[asr]
default_engine = "funasr"
funasr_api_url = "http://192.168.2.99:8000/asr" #修改为自己的ip
重启NarratoAI容器,即可实现本地高精度语音转字幕功能。
四、最终总结&常见问题排查
整套部署完成后的完整工作流
1、NarratoAI主界面生成视频文案、剪辑任务;
2、自动调用OmniVoice本地接口,生成自然AI配音;
3、通过FunASR本地接口,自动识别音频生成精准字幕;
4、全程本地运行,无水印、无付费、无接口限制。
常见报错解决(遇到任何问题直接复制日志到豆包问)
2、FunASR报错:
/tmp/tmp6bly4qge.wav: Invalid data found when processing input
解决:编辑源代码funasr/bin/_server_app.py,找到
result = processfallback("fun-asr-nano", tmp_path, language=language)(一共两个位置)
替换为
# 注释掉原来的 fallback 降级逻辑
# result = processfallback("fun-asr-nano", tmp_path, language=language)
# 直接加载你启动命令指定的 iic/SenseVoiceSmall
from funasr import AutoModel
model = AutoModel(model="iic/SenseVoiceSmall", device="cuda")
result = model.generate(input=tmp_path, language=language)
备份原来的dockerfile文件,使用新的dockerfile重新构建(docker-compose build --no-cache):
FROM funasr-api:latest
# 本地修复好的voice.py复制到容器对应路径
COPY ./funasrsource/funasr/bin/_server_app.py /app/FunASR/funasr/bin/_server_app.py
CMD ["funasr-server", "--device", "cuda", "--host", "0.0.0.0", "--port", "8000", "--model", "iic/SenseVoiceSmall"]
停止和重启容器:
docker-compose down
docker-compose up -d
这套方案是目前个人本地部署最稳定、功能最全的AI短视频自动生成方案,全程开源免费,修复了网上大部分教程的源码BUG,新手直接复刻即可完美落地!
