基于百度TTS API的本地TXT转MP3有声书工具开发实践
1. 项目概述当文字遇见声音作为一个常年和代码、文档打交道的程序员我有个挺私人的习惯喜欢“听”小说。无论是通勤路上还是做家务、健身的时候让眼睛休息一下用耳朵去感受故事的脉络是种很棒的体验。市面上听书软件不少但要么需要会员要么资源库不全最要命的是我想听的那些比较小众或者自己收藏的TXT格式小说往往找不到对应的有声资源。于是一个很自然的想法就冒出来了能不能自己动手把我电脑里那堆积如山的TXT小说批量转换成有声书这个需求的核心很简单输入一个TXT文件输出一个MP3音频文件。实现路径上语音合成TTS技术是关键。经过一番调研和对比我选择了百度的语音合成开放平台。原因有几个首先它的API接口清晰文档完善对于开发者非常友好其次提供了多种音色选择包括情感合成等进阶功能效果在免费方案中属于第一梯队最后它支持Python SDK这正是我最熟悉的语言可以快速集成和调试。这个项目本质上是一个本地化的TXT转MP3工具。它不依赖任何现成的听书APP完全由你自己掌控。你可以用它来处理任意TXT文本无论是网络小说、技术文档还是学习资料都能一键转换为语音。下面我就把从零开始搭建这个工具的全过程包括API申请、环境配置、代码编写、批量处理以及我踩过的各种坑毫无保留地分享出来。2. 核心思路与方案选型在动手写代码之前我们需要把整个流程想清楚。一个健壮的TXT朗读工具不能只是简单调用API还要考虑文本处理、错误重试、流程优化等实际问题。2.1 为什么选择百度语音合成市面上提供TTS服务的厂商很多比如科大讯飞、阿里云、腾讯云等。我选择百度智能云是基于以下几个维度的综合考量入门门槛与成本百度语音合成对新用户非常友好。注册实名认证后会赠送一定额度的免费调用量通常是每月一定次数的调用和一定时长的音频生成。对于个人将小说转换成音频这种非高频需求免费额度完全够用甚至绰绰有余。这让我们可以在零成本的情况下先验证想法和效果。合成效果与音色百度的语音合成效果在清晰度、自然度上表现不错。它提供了多种音色度小美、度小宇、度逍遥等并且支持调节语速、音调、音量。虽然和顶尖的真人配音有差距但对于小说朗读这种长时间聆听的场景其稳定、清晰、略带情感的表现已经足够满足需求。技术集成难度百度提供了非常详细的官方文档和Python SDK (baidu-aip)。安装只需要一条pip命令核心的合成接口调用也不过几行代码。这极大地降低了开发难度让我们可以把精力集中在业务逻辑如文本分割、文件管理上而不是纠结于复杂的网络协议和认证。稳定性和可靠性作为大厂的开放平台其API服务的稳定性和可用性是有保障的不必担心小服务商随时可能关闭服务的风险。注意使用任何云服务的API都需要仔细阅读其服务条款和计费说明。虽然初期有免费额度但如果你需要处理海量文本例如整个图书馆的小说就要关注可能产生的费用并设置好用量监控。2.2 整体工作流设计我们的工具需要像一个流水线一样工作。下图清晰地展示了从一本TXT小说到最终MP3音频集合的完整转换流程flowchart TD A[输入: 原始TXT小说文件] -- B{文本预处理与分割}; B -- C[生成文本片段列表]; C -- D[循环处理每个片段]; D -- E{调用百度TTS API}; E -- 成功 -- F[获取音频二进制数据]; E -- 失败 -- G{错误处理与重试}; G -- 重试N次后 -- H[记录失败片段]; G -- 重试成功 -- F; F -- I[保存为临时MP3文件]; I -- J{是否所有片段处理完成?}; J -- 否 -- D; J -- 是 -- K[合并所有临时MP3]; K -- L[输出: 最终完整MP3文件]; H -- M[生成错误报告日志];这个流程的核心思想是“分而治之”。因为单次API调用有文本长度限制百度目前是1024个汉字所以我们必须将长文本切割成小块逐块合成最后再拼接起来。同时网络请求可能失败完善的错误处理和重试机制是保证工具鲁棒性的关键。3. 前期准备与环境搭建磨刀不误砍柴工。在写代码之前我们需要先把“战场”布置好。3.1 创建百度智能云应用并获取密钥这是使用百度任何AI服务的第一步相当于给你的程序办一张“身份证”。注册与登录访问百度AI开放平台官网用你的百度账号登录。如果没有需要先注册。创建应用进入控制台在“语音技术”类别下找到“语音合成”点击“创建应用”。填写应用名称例如“我的TXT朗读工具”、应用描述选择“个人”即可。获取API Key和Secret Key应用创建成功后在应用详情页面你会看到AppID、API Key和Secret Key。这三者就是我们代码中用来认证的凭证。请务必妥善保管不要泄露。你可以把它们记录在一个本地的config.ini文件里但切记不要上传到GitHub等公开仓库。3.2 本地Python环境配置我推荐使用Python 3.8或以上版本版本太旧可能会遇到依赖库兼容性问题。安装必备库我们主要需要两个库。pip install baidu-aip pydubbaidu-aip: 百度AI开放平台的官方Python SDK封装了认证和请求的细节让我们用起来非常简单。pydub: 一个强大的音频处理库我们将用它来合并多个MP3文件以及进行简单的音频处理如调节音量、淡入淡出。可选但推荐的库pip install tqdmtqdm: 可以在命令行中显示美观的进度条。当处理一本几十万字的小说需要合成上百个音频片段时有一个进度条会让你清楚知道进展到哪了预计还要多久体验会好很多。3.3 项目目录结构规划一个好的目录结构能让代码更清晰文件管理更方便。建议在开始前就创建好如下目录txt_to_speech/ ├── src/ # 源代码目录 │ ├── main.py # 主程序入口 │ ├── tts_engine.py # 语音合成核心模块 │ └── file_utils.py # 文件处理工具模块 ├── input/ # 存放待处理的TXT小说 │ └── 《三体》.txt ├── output/ # 存放生成的MP3文件 │ └── 《三体》_full.mp3 ├── temp/ # 存放合成过程中的临时音频片段 │ ├── 《三体》_part_001.mp3 │ └── ... ├── logs/ # 存放运行日志和错误报告 │ └── error_20231027.log ├── config.ini # 配置文件存放API密钥.gitignore忽略 └── requirements.txt # 项目依赖库列表你可以先手动创建input、output、temp、logs这几个空文件夹。4. 核心代码实现与解析接下来我们进入最核心的编码环节。我会将功能模块化便于理解和维护。4.1 配置文件读取模块首先我们创建一个config.ini文件来安全地存储密钥。[baidu_tts] APP_ID 你的AppID API_KEY 你的API Key SECRET_KEY 你的Secret Key [tts_settings] # 发音人选择0为女声1为男声3为情感合成-度逍遥4为情感合成-度丫丫 PER 3 # 语速0-15默认为5中语速 SPD 5 # 音调0-15默认为5中语调 PIT 5 # 音量0-15默认为5中音量 VOL 7 # 音频格式3为mp34为pcm-16k5为pcm-8k6为wav AUE 3 [file_settings] # 每次请求合成的最大文本长度汉字 MAX_TEXT_LEN 800 # 临时文件前缀 TEMP_PREFIX temp_part_然后编写一个简单的函数来读取配置。# file_utils.py import configparser import os def load_config(config_pathconfig.ini): 加载配置文件 config configparser.ConfigParser() if not os.path.exists(config_path): raise FileNotFoundError(f配置文件 {config_path} 不存在请创建并填写API密钥。) config.read(config_path, encodingutf-8) return config4.2 语音合成引擎模块这是项目的核心负责与百度API交互。# tts_engine.py from aip import AipSpeech import os from file_utils import load_config class TtsEngine: def __init__(self, config_pathconfig.ini): config load_config(config_path) bd_cfg config[baidu_tts] tts_cfg config[tts_settings] # 初始化AipSpeech客户端 self.client AipSpeech(bd_cfg[APP_ID], bd_cfg[API_KEY], bd_cfg[SECRET_KEY]) # 合成参数 self.per tts_cfg.getint(PER, 3) self.spd tts_cfg.getint(SPD, 5) self.pit tts_cfg.getint(PIT, 5) self.vol tts_cfg.getint(VOL, 7) self.aue tts_cfg.getint(AUE, 3) self.max_text_len config[file_settings].getint(MAX_TEXT_LEN, 800) # 音频格式映射 self.fmt_map {3: mp3, 4: pcm, 5: pcm, 6: wav} self.fmt self.fmt_map.get(self.aue, mp3) def synthesize(self, text, idx0): 调用百度API合成单段语音 :param text: 待合成的文本 :param idx: 片段索引用于错误信息提示 :return: 成功返回音频二进制数据失败返回None try: result self.client.synthesis( text, zh, # 固定为中文 self.per, {spd: self.spd, pit: self.pit, vol: self.vol, aue: self.aue} ) # 识别返回结果 if not isinstance(result, dict): # 返回的是二进制数据合成成功 return result else: # 返回的是错误信息字典 error_msg result.get(error_msg, Unknown error) print(f 片段 {idx} 合成失败: {error_msg}) return None except Exception as e: print(f 片段 {idx} 请求异常: {e}) return None def save_audio(self, audio_data, file_path): 将二进制音频数据保存为文件 with open(file_path, wb) as f: f.write(audio_data)关键点解析AipSpeech是百度SDK的核心类初始化时需要三个密钥。synthesis方法的参数中per发音人的选择直接影响听感。3度逍遥和4度丫丫是情感合成音色朗读小说时抑扬顿挫更明显推荐使用。API返回有两种情况成功时直接返回二进制音频数据bytes类型失败时返回一个包含错误码的dict。我们的代码需要对此进行判断。MAX_TEXT_LEN我设置为800略低于1024的限制是为了给标点符号、英文单词留出余量避免因长度计算误差导致API报错。4.3 文本预处理与分割模块TXT文件可能包含各种“杂质”并且必须被切割成适合API调用的小块。# file_utils.py (续) import re def clean_text(text): 清洗文本移除不必要的字符和空行 # 移除 \r text text.replace(\r\n, \n).replace(\r, \n) # 移除过多的连续换行保留最多两个 text re.sub(r\n{3,}, \n\n, text) # 移除首尾空白字符 text text.strip() return text def split_text_by_length(text, max_len): 按最大长度分割文本尽量保证按段落分割。 :param text: 清洗后的完整文本 :param max_len: 单段最大长度汉字数 :return: 文本片段列表 paragraphs text.split(\n) chunks [] current_chunk for para in paragraphs: para para.strip() if not para: continue # 跳过空段落 # 如果当前段落本身就超长强制按字数切割 if len(para) max_len: if current_chunk: chunks.append(current_chunk) current_chunk # 将长段落切成小块 for i in range(0, len(para), max_len): chunk para[i:imax_len] chunks.append(chunk) else: # 如果加上新段落后不超过限制就加上 if len(current_chunk) len(para) 1 max_len: # 1 是换行符 if current_chunk: current_chunk \n para else: current_chunk para else: # 否则保存当前块开始新块 if current_chunk: chunks.append(current_chunk) current_chunk para # 添加最后一块 if current_chunk: chunks.append(current_chunk) # 二次检查防止有块因计算误差仍超长罕见情况 final_chunks [] for chunk in chunks: while len(chunk) max_len: # 找最后一个句号、问号、感叹号或逗号进行分割 split_pos -1 for punct in [。, , , , , ., !, ?, ,, ;]: pos chunk.rfind(punct, 0, max_len) if pos split_pos: split_pos pos if split_pos -1: # 找不到标点只能硬切 split_pos max_len final_chunks.append(chunk[:split_pos1]) chunk chunk[split_pos1:] final_chunks.append(chunk) return final_chunks实操心得简单的按固定字数切割会破坏句子结构导致合成语音在奇怪的地方停顿。因此我采用了“尽量按段落合并超长段落再按标点切割”的策略。这能最大程度保证合成音频的连贯性。计算文本长度时使用len(text)在Python 3中计算的是字符数对于中英文混合是准确的。如果你需要精确的汉字数可以用正则len(re.findall(r[\u4e00-\u9fff], text))但对于长度控制字符数已经足够。4.4 主程序流程控制模块现在我们把所有模块像拼图一样组合起来。# main.py import os import sys from pathlib import Path from tts_engine import TtsEngine from file_utils import clean_text, split_text_by_length, load_config from pydub import AudioSegment import logging from tqdm import tqdm # 进度条 def setup_logging(log_dirlogs): 设置日志 if not os.path.exists(log_dir): os.makedirs(log_dir) log_file os.path.join(log_dir, ftts_{Path(__file__).stem}.log) logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(log_file, encodingutf-8), logging.StreamHandler(sys.stdout) ] ) return logging.getLogger(__name__) def merge_audio_files(file_list, output_path, temp_dir): 使用pydub合并多个音频文件 logger logging.getLogger(__name__) combined AudioSegment.empty() for i, file in enumerate(file_list): try: audio AudioSegment.from_file(file) # 可选在片段间添加短暂的静音如200毫秒使章节间停顿更自然 # if i 0: # combined AudioSegment.silent(duration200) combined audio logger.debug(f已合并: {file}) except Exception as e: logger.error(f合并文件 {file} 时出错: {e}) # 即使某个片段损坏也尝试继续合并其余部分 continue finally: # 合并后删除临时文件 try: os.remove(file) except: pass # 导出最终文件 try: combined.export(output_path, formatmp3, bitrate128k) logger.info(f最终音频文件已保存至: {output_path}) # 清理空临时目录 if os.path.exists(temp_dir) and not os.listdir(temp_dir): os.rmdir(temp_dir) except Exception as e: logger.error(f导出最终文件失败: {e}) def main(): logger setup_logging() config load_config() # 1. 初始化TTS引擎 logger.info(初始化百度TTS引擎...) try: tts_engine TtsEngine() except Exception as e: logger.error(f初始化TTS引擎失败请检查配置文件: {e}) sys.exit(1) # 2. 指定输入文件 input_dir Path(input) txt_files list(input_dir.glob(*.txt)) if not txt_files: logger.error(f在 {input_dir} 目录下未找到任何TXT文件。) sys.exit(1) for txt_file in txt_files: logger.info(f开始处理文件: {txt_file.name}) # 3. 读取并清洗文本 try: with open(txt_file, r, encodingutf-8) as f: raw_text f.read() except UnicodeDecodeError: # 尝试GBK编码常见于Windows系统保存的TXT try: with open(txt_file, r, encodinggbk) as f: raw_text f.read() except Exception as e: logger.error(f无法读取文件 {txt_file} 请检查编码: {e}) continue cleaned_text clean_text(raw_text) if not cleaned_text: logger.warning(f文件 {txt_file.name} 内容为空跳过。) continue # 4. 分割文本 text_chunks split_text_by_length(cleaned_text, tts_engine.max_text_len) total_chunks len(text_chunks) logger.info(f文本分割完成共 {total_chunks} 个片段。) # 5. 准备输出目录 output_dir Path(output) temp_dir Path(temp) / txt_file.stem output_dir.mkdir(exist_okTrue) temp_dir.mkdir(parentsTrue, exist_okTrue) output_file output_dir / f{txt_file.stem}_full.mp3 temp_file_list [] # 6. 逐片段合成带进度条 logger.info(开始语音合成...) failed_indices [] for idx, chunk in enumerate(tqdm(text_chunks, desc合成进度, unit段)): temp_file_path temp_dir / f{config[file_settings].get(TEMP_PREFIX)}{idx:04d}.mp3 # 如果临时文件已存在可能是上次运行中断则跳过合成 if temp_file_path.exists(): logger.debug(f片段 {idx} 已存在跳过合成。) temp_file_list.append(str(temp_file_path)) continue # 合成音频加入重试机制 max_retries 3 audio_data None for retry in range(max_retries): audio_data tts_engine.synthesize(chunk, idx) if audio_data is not None: break else: logger.warning(f片段 {idx} 第 {retry1} 次重试...) time.sleep(1) # 失败后等待1秒再重试 if audio_data: tts_engine.save_audio(audio_data, temp_file_path) temp_file_list.append(str(temp_file_path)) else: logger.error(f片段 {idx} 合成失败已重试 {max_retries} 次。) failed_indices.append(idx) # 7. 合并音频 if temp_file_list: logger.info(f开始合并 {len(temp_file_list)} 个音频片段...) merge_audio_files(sorted(temp_file_list), output_file, temp_dir) logger.info(f文件 {txt_file.name} 处理完成) else: logger.error(f未成功合成任何音频片段处理失败。) # 8. 报告失败情况 if failed_indices: logger.warning(f本次处理中有 {len(failed_indices)} 个片段失败索引为: {failed_indices}) error_log_path Path(logs) / ffailed_{txt_file.stem}.txt with open(error_log_path, w, encodingutf-8) as f: for idx in failed_indices: if idx len(text_chunks): f.write(f 失败片段索引 {idx} \n) f.write(text_chunks[idx][:500] ...\n\n) # 只记录前500字符便于排查 logger.info(f失败片段文本已保存至: {error_log_path}) if __name__ __main__: import time main()5. 进阶优化与实用技巧基础功能跑通后我们可以从体验和效率上做很多优化。5.1 音色、语速与效果的调优在config.ini的[tts_settings]里调整参数对最终听感影响巨大。发音人 (PER)0(度小美)清晰女声通用。1(度小宇)清晰男声通用。3(度逍遥)情感合成男声。这是我朗读小说的首选声音沉稳有讲故事的感觉停顿和重音处理得更好。4(度丫丫)情感合成童声适合儿童读物或轻松内容。建议对于小说强烈推荐3(度逍遥)。可以先用一小段文本测试不同音色。语速 (SPD)范围0(最慢) 到15(最快)默认5。我个人习惯设为4或5。太慢容易睡着太快则听不清。你可以根据小说类型调整比如紧张的情节可以稍快 (6)抒情的部分可以稍慢 (4)。音调 (PIT)范围0(最低) 到15(最高)默认5。一般保持默认即可。调高会显得更尖锐调低更低沉。可以根据主角性别微调但效果有限。音量 (VOL)范围0(最轻) 到15(最响)默认5。建议设为7或8。合成后的音频整体音量可能偏小提高基础音量可以避免后续再用播放器放大。5.2 实现章节自动分割与命名如果TXT小说本身有清晰的章节标题如“第一章”、“第1节”我们可以实现按章节分割生成多个MP3文件方便管理。# 在 file_utils.py 中添加 import re def split_text_by_chapter(text): 按章节分割文本基于常见章节标题模式 :param text: 清洗后的完整文本 :return: 字典列表每个元素包含章节标题和内容 # 定义章节标题的正则表达式模式可根据你的小说格式调整 chapter_patterns [ r^\s*第[零一二三四五六七八九十百千万\d]章\s.*$, r^\s*第[零一二三四五六七八九十百千万\d]节\s.*$, r^\s*[上下卷]\s.*$, r^\s*\d\s.*$, # 纯数字开头 ] combined_pattern |.join(chapter_patterns) lines text.split(\n) chapters [] current_chapter_title 前言 # 默认第一章之前的标题 current_chapter_content [] for line in lines: line_stripped line.strip() # 检查是否是章节标题行 if line_stripped and re.match(combined_pattern, line_stripped): # 保存上一章 if current_chapter_content: chapters.append({ title: current_chapter_title, content: \n.join(current_chapter_content) }) # 开始新的一章 current_chapter_title line_stripped current_chapter_content [] else: current_chapter_content.append(line) # 添加最后一章 if current_chapter_content: chapters.append({ title: current_chapter_title, content: \n.join(current_chapter_content) }) return chapters然后在主程序中可以遍历chapters列表为每一章调用合成和合并流程并以章节标题命名最终文件。5.3 使用多线程/异步加速批量处理当你有几十本小说需要转换时顺序执行会非常慢。因为大部分时间花在等待网络API返回上CPU是空闲的。我们可以用多线程来并发请求。警告请谨慎使用多线程并尊重API的QPS每秒查询率限制。百度免费版API有并发限制疯狂请求可能导致IP被临时限制。建议控制线程数如3-5个并在请求间添加微小延迟。# 示例使用concurrent.futures实现线程池 from concurrent.futures import ThreadPoolExecutor, as_completed import time def synthesize_chunk_worker(args): 供线程池调用的工作函数 idx, chunk, tts_engine, temp_dir, prefix args temp_file_path temp_dir / f{prefix}{idx:04d}.mp3 if temp_file_path.exists(): return idx, str(temp_file_path), True # 已存在 audio_data None for retry in range(3): audio_data tts_engine.synthesize(chunk, idx) if audio_data: break time.sleep(1) if audio_data: tts_engine.save_audio(audio_data, temp_file_path) return idx, str(temp_file_path), False # 成功 else: return idx, None, False # 失败 # 在主程序合成部分替换循环 max_workers 3 # 控制并发数 with ThreadPoolExecutor(max_workersmax_workers) as executor: # 准备任务参数 tasks [(idx, chunk, tts_engine, temp_dir, config[file_settings].get(TEMP_PREFIX)) for idx, chunk in enumerate(text_chunks)] # 提交任务 future_to_idx {executor.submit(synthesize_chunk_worker, task): task[0] for task in tasks} # 处理结果 for future in tqdm(as_completed(future_to_idx), totallen(tasks), desc并发合成): idx, file_path, existed future.result() if file_path: temp_file_list.append(file_path) else: failed_indices.append(idx)6. 常见问题与故障排除在实际操作中你几乎一定会遇到下面这些问题。这里是我踩坑后的经验总结。6.1 合成失败错误码解析调用API失败时返回的字典中包含error_code和error_msg。以下是一些常见错误及解决方法错误码错误信息可能原因与解决方案3300输入参数不正确检查text参数是否为空或过长1024汉字。检查per,spd等参数值是否在允许范围内。3301音频合成错误服务器端合成失败。通常是文本内容或参数问题尝试缩短文本、简化标点或稍后重试。3302网络错误本地网络问题或百度服务暂时不可用。检查网络连接稍后重试。3303权限错误最常见API Key/Secret Key 错误或该应用未开通语音合成服务或免费额度已用尽。请去控制台检查应用状态和额度。3304请求超时网络延迟过高。适当增加请求超时时间可在初始化AipSpeech时设置timeout参数或检查代理设置。3305解码失败返回的音频数据异常。可能是网络传输中损坏重试即可。排查顺序遇到错误首先检查3303权限问题然后检查3300参数问题最后考虑3301和网络问题。6.2 音频合并出错或音量不一致问题使用pydub合并后发现某些片段音量特别小或特别大。原因不同片段合成时API返回的音频基础音量可能存在细微差异。解决在合并前可以对每个音频片段进行音量归一化。from pydub.effects import normalize def merge_audio_files_with_normalization(file_list, output_path): combined AudioSegment.empty() for file in file_list: audio AudioSegment.from_file(file) # 进行音量归一化处理 audio normalize(audio) combined audio combined.export(output_path, formatmp3)normalize()函数会将音频增益调整到最大不失真的水平使所有片段的响度一致。问题合并后的文件无法播放或时长不对。原因可能某个临时音频文件在合成或保存时已损坏。解决在合并循环中加入更严格的异常捕获并记录损坏的文件名。或者在合成每个片段后立即用AudioSegment.from_file()尝试加载一下验证文件完整性。6.3 处理超长文本与内存管理问题小说非常长如500万字分割出的片段上万导致内存占用过高。解决流式合并不要等所有片段合成完再合并。可以每合成N个如100个就合并一次生成一个“子文件”最后再将所有子文件合并。这样可以显著降低内存峰值。调整分割长度适当增加MAX_TEXT_LEN但不要超过1024减少总片段数。及时清理在主程序中合并完一个章节或一定数量片段后立即删除对应的临时文件。6.4 音质与网络优化问题合成的MP3音质有杂音或机械感强。解决调整发音人情感合成音色per3或4通常比标准音色更自然。调整语速和音调适当降低语速 (spd4)音调保持默认 (pit5)会让合成音更清晰。选择更高码率百度API合成的MP3默认似乎是中等码率。虽然无法直接指定但你可以用pydub在合并导出时选择更高的比特率如bitrate192k或256k。不过要注意这并不会提升原始合成音质只是减少了导出时的二次压缩损失。网络问题如果合成速度慢可以尝试在AipSpeech初始化时设置超时和重试。from aip import AipSpeech client AipSpeech(app_id, api_key, secret_key) # 设置连接超时和读取超时单位秒 client.setConnectionTimeoutInMillis(5000) client.setSocketTimeoutInMillis(10000)7. 项目总结与扩展思路走到这里一个功能完整、鲁棒性不错的本地TXT转语音工具就已经搭建完成了。回顾整个过程从API申请到代码调试最花时间的往往不是核心的合成调用而是文本预处理、错误处理和流程优化这些“边缘”细节。这也正是个人项目从“能用”到“好用”的关键。我个人在实际使用中通常会将这个脚本稍作包装比如添加一个简单的命令行界面让我可以指定输入文件、选择音色和输出目录。或者更进一步可以做一个带有图形界面用Tkinter或PyQt的小工具拖拽文件即可转换体验会更友好。这个项目的扩展潜力很大。例如你可以集成更多TTS引擎将百度、阿里、讯飞的API都封装进来做一个统一的接口哪个效果好、哪个有免费额度就用哪个。添加字幕文件生成在合成每个片段时记录其时间偏移和对应的文本最终生成SRT或LRC格式的字幕文件实现“有声书字幕”同步。云端部署与自动化将脚本部署到云服务器配合网盘API如百度网盘、Dropbox实现自动监测网盘特定文件夹有新TXT文件就自动转换并回传MP3打造全自动的个人有声书库。最后再分享一个我踩过的小坑标点符号的处理。有些TXT文件使用全角标点有些用半角还有的混用。这可能会导致文本长度计算不准或者合成语音停顿怪异。在clean_text函数中可以加入一步标点标准化比如将所有中文标点统一为全角英文标点统一为半角会让合成效果更稳定。

相关新闻