用本地TTS制作有声读物

APPLICATION Dec 30, 2024

在本指南中,我将带你了解使用 Nvidia 的 FastPitch 创建个性化有声读物解决方案的过程,从了解 TTS 基础知识到集成高级模型。提供了详细的说明和最佳实践,并在 Git 存储库中提供了完整的代码以供实际实施。

1、寻求个人有声读物解决方案:我的 TTS 之旅

让我带你回到一个激动人心且充满可能性的时刻:我刚刚于 2024 年 8 月 31 日出版了我的书《Elias:意义的探索》。这是一场哲学探索,需要多年的内省和经验才能实现。尽管我对这部作品感到自豪,但我很快意识到,要想接触到现代观众,我需要一本有声读物。世界正在发生变化,越来越多的人喜欢在通勤、锻炼甚至做家务时听自己喜欢的书。

但有声读物创作的现实让我震惊——让专业人士朗读的成本很高。市场上的人工智能解决方案,如 Google Cloud 或 Amazon Polly,似乎是一个快速解决方案,但缺少了一些东西。当然,它们的声音很优美,但它们不像我想要的那样可定制。另外,我不想在每本新书或每一项新项目中都依赖第三方服务。

这就是我突然想到的——为什么不构建自己的文本转语音 (TTS) 解决方案呢?毕竟,我的驱动器里有一大堆电子书。如果我可以将它们全部转换成有声读物,根据我的喜好进行定制,并在空闲时间收听它们,那会怎样?在我的计算机上拥有自己的个性化有声读物工厂的便利性实在是太好了,不容错过。

作为一名工程师,我一直喜欢掌控一切。我想掌控整个流程,创建一个可以超越这个项目的解决方案。我并不是在寻找通用的即插即用服务。我在寻找更具动态性的东西,可以与我一起成长的东西。

就在那时,我偶然发现了 Nvidia 的 NeMo 框架,特别是 FastPitch 和 HiFi-GAN 模型。NeMo 不仅仅是任何 TTS 解决方案——它是开源的,这意味着我可以根据自己的需求对其进行调整,它可以生成 CD 质量(44.1kHz)的音频。它在构建时还考虑到了灵活性,让我可以自由调整音调、节奏和语音风格以适应我书的本质。最好的部分是什么?我可以完全在本地机器上运行它。不依赖云,也没有经常性成本。

有了这样的解决方案,我可以:

  • 将我已经拥有的数十本电子书中的任何一本转换成有声读物
  • 定制聆听体验,调整每本书的语音风格
  • 为未来的项目创建内容——无论是旁白、播客还是画外音——一切都在我的掌控之中

这不仅仅是为我的书解决一个问题;而是要解锁整个音频生态系统的可能性。

2、了解文本转语音 (TTS) 系统的基础

在深入研究实际实施和代码之前,我总是建议我的团队:先确定逻辑设计。这是工程学中一个古老的教训——在编写一行代码之前,先从内到外了解系统的架构。很多次,我看到开发人员一头扎进编码中,却被无法预见的复杂性、缺少的功能或更糟糕的无休止的临时请求所蒙蔽。

所以,让我们在这里应用同样的原则。首先,让我们了解 TTS 系统的工作原理,以及所有不同组件如何组合在一起将书面文本转换为逼真的语音。如果没有正确的逻辑理解,我们的解决方案可能会奏效,但它无法轻松扩展或适应。

2.1 TTS 系统的构建块

从本质上讲,TTS 系统是一个具有一系列阶段的管道,可将文本转换为类似人类的语音。这些阶段通常分为三个主要步骤:

a) 文本预处理

在此,系统获取原始输入文本并“清理”它以进行处理。系统将文本分解为较小的单元,通常是句子或单词,并处理以下任务:

  • 规范化(例如,将数字或缩写转换为完整的单词)、
  • 处理标点符号,以及
  • 将文本拆分为可管理的块以进行下游处理。

此步骤确保文本已准备好进行下一步:音素生成。

b) 音素生成

这是该过程的语言部分。清理后的文本被转换成音素,音素是语言中最小的声音单位。例如,“cat”这个词可以分解为音素:/k/、/æ/ 和 /t/。

神经网络在这里发挥着巨大的作用。现代 TTS 系统使用机器学习根据输入文本预测这些音素,从而实现更准确的发音,包括棘手的单词、缩写或首字母缩略词。

c) 频谱图创建

一旦系统有了音素序列之后,下一步就是将它们转换成声音的视觉表示——梅尔频谱图。频谱图本质上是一张显示不同频率随时间变化的能量的图片。在 TTS 系统中,神经网络会生成此频谱图,稍后会将其转换为实际的声波。

将频谱图视为音频的蓝图。下一阶段涉及使用声码器将此蓝图变为现实。

2.2 FastPitch 和 HiFi-GAN:关键参与者

在构建高质量本地 TTS 系统的过程中,我选择了 FastPitch 和 HiFi-GAN 作为动态组合来实现这个项目。

FastPitch 是一种基于神经网络的文本到频谱图模型。它不是同类中的第一个,但它具有显着的优势。与 Tacotron 等可能存在较长推理时间和不稳定问题的旧模型不同,FastPitch 的设计速度快且高度可控。它使我们能够调整语音的音调和节奏——非常适合创建需要更个性化的有声读物,比如在我的情况下,我希望传递的内容与 Elias: The Quest of Meaning 的反思语调相匹配。

HiFi-GAN 是负责将这些声谱图转换为实际语音的声码器。声码器至关重要,因为它们会影响最终输出听起来的自然程度或机械感。HiFi-GAN 在这里表现出色,因为它可以生成高质量的音频,听起来比 WaveGlow 等旧模型更逼真,更少机械感。

将这两个模型结合起来的好处是,它们都工作得非常快,同时产生逼真、可定制的音频。它们一起形成了一个强大的管道:FastPitch 处理文本到声谱图的转换,HiFi-GAN 负责声谱图到语音。

2.3 FastPitch vs. 其他模型:它有什么不同?

你可能想知道,为什么是 FastPitch?为什么不是 Tacotron 或 WaveNet?为了更清楚起见,下面是 FastPitch 与其他最先进模型的快速比较:

  • Tacotron:Tacotron(和 Tacotron 2)也是文本到频谱图模型,但它们在实时推理中速度较慢,有时会出现跳过或重复单词等错误。FastPitch 速度更快,更稳定。
  • WaveGlow:这是一个较旧的声码器,与 Tacotron 等模型结合使用。虽然它产生了不错的结果,但 HiFi-GAN 显著提高了音质,使其更适合有声读物级别的保真度。
  • WaveNet:谷歌的模型,是首批高质量神经声码器之一,但同样,它资源密集,速度不如 HiFi-GAN。

简而言之,FastPitch 和 HiFi-GAN 在速度、质量和控制方面为我们提供了优势,使它们成为像我这样的项目的理想选择,我希望构建可扩展但又能适应不同类型的内容的东西。

对 TTS 管道的这种理解构成了逻辑基础。考虑到这种架构,我对深入研究实际编码和实现更有信心。毕竟,当您知道每个组件在做什么以及为什么重要时,构建一个强大的解决方案总是更容易。

3、为 TTS 设置本地环境

当谈到在本地机器上构建强大的 TTS 系统时,设置开发环境是至关重要的第一步。忽略或跳过此阶段将导致很多麻烦 — 依赖冲突、性能问题,或者更糟的是,可怕的“在我的计算机上工作”场景会破坏其他所有环境。

3.1 先决条件:硬件和软件

在深入安装步骤之前,了解最低要求至关重要 — 特别是如果你要处理 FastPitch 和 HiFi-GAN 等高级 TTS 模型。与云解决方案(例如 Amazon Polly)不同,在本地运行这些模型需要大量硬件资源,特别是如果你想要实时处理或生成高质量语音。

a) GPU 和 CUDA

如果有一件事需要提前知道,那就是你需要一个支持 CUDA 的 GPU 才能高效运行这些模型。如果没有 GPU,模型推理可能会慢得令人难以忍受,即使是使用较小的书籍或脚本也是如此。

  • CUDA 版本:确保你的 CUDA 版本与 PyTorch 和 Nvidia Nemo 兼容,因为 CUDA 驱动程序需要与您安装的库的版本相匹配。
  • 推荐的 GPU:对于涉及 FastPitch 和 HiFi-GAN 的 TTS 任务,像 Nvidia GTX 1080 或更高版本的 GPU 是理想的选择。我使用 GeForce GTX 1050 Ti,它可以充分处理这些模型,尽管更强大的 GPU 将提供更好的性能和更快的处理时间。

b) 软件依赖项

除了硬件之外,你还需要安装几个关键的软件依赖项:

  • Nvidia Nemo(用于 TTS 模型)、
  • PyTorch(因为模型是在其上构建的)、
  • CUDA Toolkit(用于 GPU 加速)、
  • Python 3.8+(必须与最新版本的 PyTorch 和 Nemo 兼容,尽管我发现 3.10 更好,并且在本项目中使用)。

3.2 使用 Conda 设置虚拟环境

在启动任何 Python 项目时,我建议做的第一件事之一就是创建一个虚拟环境。它有助于将项目依赖项与全局 Python 安装隔离开来。虽然 Poetry 可以处理环境,但最好使用 Conda 创建虚拟环境,特别是如果您你正在处理像 TTS 这样资源密集型的项目,这通常需要 GPU 支持和依赖于特定版本的库。

a) 安装 Conda(如果尚未安装):

你可以根据需要下载并安装 Miniconda 或 Anaconda。

b) 创建新的虚拟环境:

运行以下命令专门为此项目创建新环境:

conda create - name tts_env python=3.10.14

这将使用 Python 3.10 创建一个名为 tts_env 的新环境。

c) 激活虚拟环境:

创建环境后,使用以下命令激活它:

conda activate tts_env

这将隔离项目的依赖项,你将在此环境中工作。

3.3 使用 Poetry 管理依赖项

我多年担任软件架构师期间学到的一件事就是强大的依赖项管理系统的价值。无论你使用 Java、Python 还是其他任何语言进行编码,正确设置开发环境都至关重要。

在 Python 中,我们传统上使用 requirements.txt 来管理依赖项。但是,作为使用 Java 并喜欢使用 Maven 进行依赖项管理的人,我发现 Poetry 是 Python 生态系统中真正的游戏规则改变者。它为你提供了与 Maven 相同的结构化方法,但对于 Python,可确保你的所有依赖项都得到整齐管理、版本得到控制并且一切都得到简化。

a) 安装 Poetry

首先安装 Poetry,它将处理你的依赖项。

curl -sSL https://install.python-poetry.org | python3 -

这是我用于此项目的 pyproject.toml 文件,其中包含设置顶级文本转语音系统所需的所有依赖项。通过使用 Poetry,你只需运行 poetry install,即可避免逐个安装每个库的麻烦。

[tool.poetry]
name = "pdf2audio"
version = "0.1.0"
description = "Convert PDF book to audiobook using NeMo and FastPitch"
authors = ["mrmanna"]
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.10,<4.0"
nemo-toolkit = "^1.23.0"
torch = "^2.4.0"
pdfplumber = "^0.11.4"
librosa = "^0.10.2.post1"
matplotlib = "^3.9.2"
einops = "^0.8.0"
huggingface-hub = "^0.23.2"
transformers = "^4.44.2"
pydub = "^0.25.1"

[tool.poetry.group.dev.dependencies]
hydra-core = "^1.3.2"
pytorch-lightning = "^2.4.0"
sentencepiece = "^0.2.0"
pandas = "^2.2.2"
editdistance = "^0.8.1"
lhotse = "^1.27.0"
pyannote-audio = "^3.3.1"
webdataset = "^0.2.100"
datasets = "^2.21.0"
jiwer = "^3.0.4"
ipython = "^8.27.0"
wandb = "^0.17.8"
nemo-text-processing = "^1.1.0"
nltk = "^3.9.1"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
start = "pdf2audio.pdf2audio:main"

使用此配置,你只需运行:

poetry install

这将一次性下载所有必需的包。之后,我们必须通过 pip 安装一些依赖项。

我们必须将 gcc 更新到 12 以安装所需的 Cython 和 youtokentome。

conda install -c conda-forge gcc_linux-64=12
pip install cython youtokentome

然后我们可以像这样运行应用程序

poetry run start <yourpdfile.pdf> <youroutputfile.wav>

如果你不使用此 poetry toml 文件,则必须按照以下方式安装 PyTorch 和 Nvidia Nemo:

3.4 安装 PyTorch 和 CUDA

首先,你需要安装具有适当版本 CUDA 的 PyTorch。例如:

poetry add torch torchvision torchaudio - extra-index-url https://download.pytorch.org/whl/cu117

3.5 安装 Nvidia Nemo

使用 Poetry 处理我们的环境并安装 PyTorch,我们现在可以安装 Nvidia Nemo。Nemo 是专门为语音相关任务(如 TTS)构建的模型和脚本的集合。

poetry add nemo_toolkit['tts']

3.6 下载并配置 FastPitch 和 HiFi-GAN 模型

设置好 Nemo 后,就可以下载预先训练好的 FastPitch 和 HiFi-GAN 模型了。以下命令从 Nvidia 的存储库中提取它们:

nemo-downloads https://api.ngc.nvidia.com/v2/models/nvidia/nemo/tts_en_fastpitch_multispeaker/versions/1.10.0
nemo-downloads https://api.ngc.nvidia.com/v2/models/nvidia/nemo/tts_hifigan/versions/1.0.0

多讲话人模型 — tts_en_fastpitch_multispeaker — 对我来说是一个改变游戏规则的东西。它不仅提供多种声音,而且我发现类似 Cori 的讲话人是制作有声读物级语音的最佳扬声器之一。多讲话人模型允许你选择与你的书籍或项目基调相匹配的声音。

3.7 在本地机器上优化模型性能

设置好环境后,你很容易直接开始生成音频。但是,TTS 模型可能占用大量资源,尤其是在个人 GPU 上运行它们时。我总结了一些优化性能和避免硬件压力的技巧:

  • 批处理:

在文本块上运行模型,而不是一次处理整本书。这不仅可以防止内存问题,还允许你微调每个块的传递,特别是当文本的不同部分需要不同的节奏或语调时。

  • 内存管理:

如果你使用的 GPU 内存有限(例如,低于 10GB),请在处理频谱图时注意批处理大小。大批处理大小可能会使 GPU 过载,导致内存不足错误。

  • 使用混合精度:

利用混合精度训练/推理来减少内存使用量,而不会在模型精度方面做出太大牺牲这是一个小调整,但对性能有很大影响。

你可以在 PyTorch 中轻松激活此模式:

with torch.cuda.amp.autocast():
 # model inference here
  • 尝试较低的采样率:

虽然模型默认为 44100Hz 以获得高质量音频,但有时你可以降低采样率(例如,降低到 22050Hz)而不会明显降低质量。这既减少了处理时间,又减少了内存使用量。

设置环境可能看起来很繁琐,但它是我们接下来要做的一切的基础。一旦完成,我们就可以从文本生成高质量、逼真的音频——无论是用于有声读物、播客还是任何其他项目。跳过这一步就像试图用解开的鞋带跑马拉松。现在花点时间,你未来的自己会感谢你。

4、将 PDF 转换为音频:构建系统

在深入研究复杂的代码结构之前,解释我们正在构建的内容很重要。从本质上讲,我们的目标是从 PDF 文件创建有声读物。这涉及几个步骤,从提取文本到分块,再将这些块转换为音频文件,最后将它们合并成一个连贯的有声读物。

关键步骤:

  • 加载 PDF 文本 我们使用 pdfplumber 从 PDF 中提取文本。它会处理每一页,提取文本数据,这些数据稍后将输入到 TTS 引擎中。
  • 文本预处理 处理文本不仅仅是从文件中提取单词。我们需要对其进行预处理 - 删除特殊符号和无效字符,并确保我们有干净、可读的文本供我们的模型处理。正则表达式有助于清理文本,删除任何不必要的内容。
  • 将文本拆分成块 由于内存和处理限制,大型文本无法一次性输入到 TTS 引擎中。我们使用分块策略,根据句子结构和标点符号拆分文本。这确保我们不会打断自然的语音流,并避免在较小的硬件设置上出现性能问题。
  • 使用 FastPitch 和 HiFi-GAN 生成语音 一旦我们有了可管理的区块,我们就会将它们输入 Nvidia FastPitch 模型以生成频谱图。然后,HiFi-GAN 会处理该频谱图以生成逼真的音频。每个区块的音频文件都会保存下来,然后合并成一本有声读物。

5、代码说明

让我们从 PDF 中提取文本开始说明代码:

import pdfplumber
import re
# Function to extract text from PDF
def extract_text_from_pdf(pdf_path):
 text = ""
 with pdfplumber.open(pdf_path) as pdf:
 for page in pdf.pages:
 text += page.extract_text() or "" # Handle None if text extraction fails
 return text
# Function to preprocess text (remove invalid characters, symbols)
def preprocess_text(text):
 # Remove unwanted characters and symbols using regex
 text = re.sub(r'[^\w\s.,!?\'"-]', '', text) # Keep only letters, numbers, and common punctuation
 return text
  • 提取文本:此代码处理 PDF 的每一页,提取文本。
  • 预处理文本:它使用正则表达式清理文本以删除特殊字符。

后续步骤:

从这里开始,处理后的文本被拆分分成可管理的块并发送到 TTS 引擎。

5.1 分块策略:最佳实践

为什么分块很重要?将长段落输入 TTS 模型可能会使它们不堪重负,导致性能瓶颈、错误甚至内存过载。智能分块策略可确保我们高效处理文本而不影响质量。

分块方法:

按句子拆分:我们根据标点符号(.、?、!)将文本拆分成较小的块。这可以保持音频中的自然停顿,并确保没有单词或句子被分割。

# Function to split text into chunks by sentence
def split_text_by_sentence(text, max_chunk_size=250):
 # Split text by sentence-ending punctuation marks (., !, ?)
 sentences = re.split(r'(?<=[.!?])\s+', text)
 
 chunks = []
 current_chunk = ""
 
 for sentence in sentences:
 # Check if adding the next sentence would exceed the chunk size
 if len(current_chunk) + len(sentence) > max_chunk_size:
 # Save the current chunk and start a new one
 chunks.append(current_chunk.strip())
 current_chunk = sentence
 else:
 # Otherwise, keep adding sentences to the current chunk
 current_chunk += " " + sentence
 
 # Add the last chunk if it exists
 if current_chunk:
 chunks.append(current_chunk.strip())
 
 return chunks

说明:此函数按句子分割文本,确保每个块都在定义的大小(250 c)以下字符)。它避免在中间分割句子,从而保留自然的语音流。

为什么这很重要:

通过控制块大小,我们可以确保更好的资源管理,包括内存和模型性能。当模型处理完整句子时,它还可以提高语音生成的连贯性。

5.2 TTS 模型集成:FastPitch 和 HiFi-GAN

一旦我们准备好文本块,就该使用 FastPitch 和 HiFi-GAN 生成语音了。这些模型是最先进的,可以协同工作以提供自然的音频。

模型的工作原理:

  • FastPitch:一种基于神经网络的模型,可将文本转换为频谱图。该频谱图代表音频特征,本质上是声波的视觉表示。
  • HiFi-GAN:一旦我们有了频谱图,HiFi-GAN 就会接管生成音频本身。该模型负责将视觉频谱图转换为高质量声音。
import torch
import nemo.collections.tts as nemo_tts
from nemo.collections.tts.models import FastPitchModel, HifiGanModel
import numpy as np
from scipy.io.wavfile import write
# Load models once
def load_models():
 fastpitch_model = FastPitchModel.from_pretrained("tts_en_fastpitch_multispeaker").to(device).eval()
 hifigan_model = HifiGanModel.from_pretrained("tts_en_hifitts_hifigan_ft_fastpitch").to(device).eval()
 return fastpitch_model, hifigan_model
# Function to convert text to speech
def text_to_speech(text, fastpitch_model, hifigan_model, output_file="output.wav"):
 with torch.no_grad():
 parsed = fastpitch_model.parse(text)
 spectrogram = fastpitch_model.generate_spectrogram(tokens=parsed, speaker=92) # Cori Samuel voice
 audio = hifigan_model.convert_spectrogram_to_audio(spec=spectrogram)
 # Ensure audio is in correct format
 audio = np.clip(audio.cpu().numpy(), -1.0, 1.0)
 audio = np.int16(audio * 32767)
 write(output_file, 44100, audio)

说明:我们加载 FastPitch 和 HiFi-GAN 模型,然后将文本块传递给 TTS 引擎。speaker=92 对应于预定义语音(Cori Samuel),使语音输出一致。

5.3 将块组合成完整的有声读物

一旦所有文本块都被处理成单独的音频文件,我们就会将它们组合成一个连续的音频文件,以形成完整的有声读物。·

from pydub import AudioSegment
# Function to merge audio files
def merge_audio_files(file_list, output_file):
 combined = AudioSegment.from_wav(file_list[0])
 for file in file_list[1:]:
 audio = AudioSegment.from_wav(file)
 combined += audio
 combined.export(output_file, format="wav")

说明:我们加载每个分块的音频文件,并使用 pydub 的 AudioSegment 将它们组合起来。最终输出是一个构成整个有声读物的单个 .wav 文件。

5.4 完整代码部分

拆分各部分和说明后,以下是完整代码,它们合为一个块,提供完整的解决方案:

import pdfplumber
import torch
import nemo.collections.tts as nemo_tts
from nemo.collections.tts.models import FastPitchModel, HifiGanModel
import argparse
import numpy as np
from scipy.io.wavfile import write
from pydub import AudioSegment
import os
import re
# Set environment variable for memory management
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# Load models once
def load_models():
 # Load the FastPitch model
 fastpitch_model = FastPitchModel.from_pretrained("tts_en_fastpitch_multispeaker").to(device).eval()
 # Load the HiFi-GAN model
 hifigan_model = HifiGanModel.from_pretrained("tts_en_hifitts_hifigan_ft_fastpitch").to(device).eval()
 return fastpitch_model, hifigan_model
# Function to extract text from PDF
def extract_text_from_pdf(pdf_path):
 text = ""
 with pdfplumber.open(pdf_path) as pdf:
 for page in pdf.pages:
 text += page.extract_text() or "" # Handle None if text extraction fails
 return text
# Function to preprocess text (remove invalid characters, symbols)
def preprocess_text(text):
 # Remove unwanted characters and symbols using regex
 text = re.sub(r'[^\w\s.,!?\'"-]', '', text) # Keep only letters, numbers, and common punctuation
 return text
# Function to convert text to speech
def text_to_speech(text,fastpitch_model, hifigan_model, output_file="output.wav"):
 
 with torch.no_grad(): # Disable gradient calculations
 # Convert text to spectrogram
 parsed = fastpitch_model.parse(text)
#speaker id:
# 92 Cori Samuel
# 6097 Phil Benson
# 9017 John Van Stan
# 6670 Mike Pelton
# 6671 Tony Oliva
# 8051 Maria Kasper
# 9136 Helen Taylor
# 11614 Sylviamb
# 11697 Celine Major
# 12787 LikeManyWaters
 spectrogram = fastpitch_model.generate_spectrogram(tokens=parsed,speaker=92)
 
 # Convert spectrogram to audio
 audio = hifigan_model.convert_spectrogram_to_audio(spec=spectrogram)
 
 # Ensure audio is in the correct format
 audio = audio.cpu().numpy()
 
 # Debugging: Print statistics about the audio
 # print(f"Audio min: {np.min(audio)}, max: {np.max(audio)}, dtype: {audio.dtype}")
 
 # Normalize audio to ensure it is within [-1.0, 1.0]
 audio = np.clip(audio, -1.0, 1.0)
 
 # Convert to int16 format
 audio = np.int16(audio * 32767)
 
 # Debugging: Print statistics after conversion
 # print(f"Audio after scaling shape: {audio.shape},shape1: {audio.shape[1]}, min: {np.min(audio)}, max: {np.max(audio)}, dtype: {audio.dtype}")
 
 # # Ensure audio data is 1D
 # if len(audio.shape) > 1:
 # audio = audio.flatten()
 if len(audio.shape) > 1:
 if audio.shape[0] == 1: # Mono
 audio = audio[0] # Convert from (1, N) to (N,)
 elif audio.shape[1] == 2: # Stereo
 # Stereo handling, if applicable
 audio = audio.astype(np.int16) # Ensure the audio is in the correct format
 else:
 raise ValueError("Unsupported audio channel format")
 else:
 # If audio has no channel dimension, treat it as mono
 audio = audio.flatten()
 
 # # Verify that audio is within valid range for int16
 if np.any(audio < -32768) or np.any(audio > 32767):
 raise ValueError("Audio data out of bounds for int16 format")
 
 # Save audio
 try:
 write(output_file, 44100, audio) # Save audio with scipy
 except ValueError as e:
 print(f"Error writing audio file: {e}")
 print(f"Audio data range: min={np.min(audio)}, max={np.max(audio)}")
 if torch.cuda.is_available():
 torch.cuda.empty_cache()
# Function to split text into chunks by sentence
def split_text_by_sentence(text, max_chunk_size=250):
 # Split text by sentence-ending punctuation marks (., !, ?)
 sentences = re.split(r'(?<=[.!?])\s+', text)
 
 chunks = []
 current_chunk = ""
 
 for sentence in sentences:
 # Check if adding the next sentence would exceed the chunk size
 if len(current_chunk) + len(sentence) > max_chunk_size:
 # Save the current chunk and start a new one
 chunks.append(current_chunk.strip())
 current_chunk = sentence
 else:
 # Otherwise, keep adding sentences to the current chunk
 current_chunk += " " + sentence
 
 # Add the last chunk if it exists
 if current_chunk:
 chunks.append(current_chunk.strip())
 
 return chunks
# Function to process text in chunks
def process_text_in_chunks(text, fastpitch_model, hifigan_model, output_base_path, chunk_size=250):
 chunk_files = []
 # Use the updated split_text_by_sentence function to create chunks without splitting sentences
 chunks = split_text_by_sentence(text, chunk_size)
 
 for i, chunk in enumerate(chunks):
 chunk_file = f"{output_base_path}_chunk_{i}.wav"
 # Call the text_to_speech function for each chunk
 text_to_speech(chunk, fastpitch_model, hifigan_model, output_file=chunk_file)
 chunk_files.append(chunk_file)
 
 return chunk_files
def merge_audio_files(file_list, output_file):
 # Load all audio chunks and concatenate them
 combined = AudioSegment.from_wav(file_list[0])
 for file in file_list[1:]:
 audio = AudioSegment.from_wav(file)
 combined += audio
 
 # Export the combined audio to a single file
 combined.export(output_file, format="wav")
def main():
 # Set up argument parser
 parser = argparse.ArgumentParser(description="Convert a PDF book to an audiobook.")
 parser.add_argument("pdf_path", type=str, help="Path to the source PDF file.")
 parser.add_argument("output_wav_path", type=str, help="Base path to save the output WAV files.")
 # Parse arguments
 args = parser.parse_args()
 # Load the models once
 fastpitch_model, hifigan_model = load_models()
 # Extract text from the PDF
 text = extract_text_from_pdf(args.pdf_path)
 # Process text in chunks
 chunk_files = process_text_in_chunks(text, fastpitch_model, hifigan_model,output_base_path=args.output_wav_path)
 
 # Merge all audio chunks into one file
 merge_audio_files(chunk_files, output_file=args.output_wav_path)
 
 # Optionally clean up chunk files
 for chunk_file in chunk_files:
 os.remove(chunk_file)
if __name__ == "__main__":
 main()

# to run in convert pdf to audio from CLI use - 'poetry run python'

注意:此代码包含最少的错误处理。我鼓励你通过解决使用过程中遇到的任何问题来增强它。

6、结束语

本文的代码可以从github获得。

构建自定义 TTS 解决方案代表着一个激动人心的机会,可以根据我们的精确需求定制语音合成。定制系统的好处显而易见:

  • 成本效益:从长远来看,定制解决方案更经济,尤其是在避免重复许可费用的情况下。
  • 增强质量控制:直接控制 TTS 流程的每个方面,确保输出符合我们标准的更高质量的内容。
  • 创作灵活性:自由尝试不同的声音、风格和功能为内容创作开辟了新的可能性。

展望未来,创新潜力更大。想象一下创建一个基于云的 TTS 服务,利用 FastAPI 公开与您的应用程序无缝集成的 REST API。通过以这种方式包装 FastPitch 模型,您可以为全球用户提供可扩展的按需 TTS 功能。

以下是如何实现这一目标的愿景:

  • 云部署:启动基于云的 TTS 服务,允许用户通过Web界面访问高级文本转语音功能。
  • FastAPI 集成:使用 FastAPI 开发 RESTful API,使开发人员能够毫不费力地将你的 TTS 功能集成到自己的项目中。
  • 用户友好界面:使用 Angular 或 React 构建时尚、直观的 UI,为用户提供强大且易于导航的平台来生成和自定义语音输出。
  • 可扩展性和性能:确保你的服务可扩展且高效运行,处理不同的负载并始终如一地提供高质量的 TTS 输出。

通过采用这些先进的策略,我们不仅可以增强我们的 TTS 解决方案,还可以让其他人也这样做。我鼓励开发人员和有声读物创作者深入研究开源 TTS 技术并探索这些机会。通过试验这些工具,我们可以更好地控制生产成本和质量,为更具创新性和吸引力的内容铺平道路。TTS 的未来是光明的,通过正确的方法,我们可以塑造它以满足我们的创意愿景。


原文链接:How to Build a High-Quality Text-to-Speech (TTS) System Locally with Nvidia NeMo FastPitch

汇智网翻译整理,转载请标明出处

Tags