之前我也找了很多语音合成api,对于长度不超过100字符的短文本,价格高达0.13¥/次请求,真的太贵了。
站内关于爬取语音合成接口的文章,有百度语音合成的爬取,近几天应用的时候发现换接口了,虽然说原本的还没废弃。
所以我找了一款可以在线操作,也可以爬取的语音合成网站。话不多说,马上开始。
首先访问链接进入网页
ok,进到界面以后,选择左边这个智能配音,然后调整右边这些参数,点击试听一下,可以在线生成语音并下载。注意文本字符数不能超过100。
但对于大批量文本这样处理起来很麻烦,咱们用请求的方式实现
首先,来到检查-network界面
做语音合成请求的时候,会调用texttoaudio的接口,来到headers界面
可以看到,这是用的post请求方式,我们需要准备三部分,url,headers和data。
url在上方就有,抄下来就行
# 构造 URL
url = "https://user.api.hudunsoft.com/v1/alivoice/texttoaudio"
翻到下面,查看headers,看着很多,其实能用到的就两个,一个是content_type,另一个是UA防伪链。由于这个网站不需要登陆哈,咱们就不用填cookie了。
# 设置请求头
headers = {
'content-type': 'application/json',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}
OK,重点来了,来看这里的payload界面,一堆的东西,都是要用的,不过有很多固定参数就是了,用不到的写死就好。
这里建议bgid设为0,它本身的背景音乐功能貌似没做好,尽量不要用。
这里的voice音色,我们只需要点击试听,就可以在network中找到对应变量了。
"""
bg_volume: int, 1-10, 背景音乐音量,用不到可以写5
bgid: str, 默认为0,用不到就写死
client: str, 客户端方式, "web", 写死
client_ver: 客户端服务器,默认"3.0.3.0",暂时写死
deviceid: str, 设备ID,拿到后直接写死就好
format: str, 保存方式,mp3/wav, 可以写死
pitch_rate: int, 1-10, 语调
soft_version: str, 软版本,暂时写死
source: str, 出处,默认437,暂时写死
speech_rate: int, 1-10, 语速
text: str, 需要合成的文本
title: str, 相当于python中的text[:5]切片处理,代码逻辑写死
voice: str, 使用的音色
volume: int, 1-10, 音量
"""
# 构造请求参数
params = {
'text': text,
'bgid': bgid,
'bg_volume': bg_volume,
'format': format,
'voice': voice,
'volume': volume,
'speech_rate': speech_rate,
'pitch_rate': pitch_rate,
'title': text[:5],
'deviceid': deviceid,
'client': client,
'client_ver': client_ver,
'soft_version': soft_version,
'source': source
}
post请求之后,请求的response信息,类似于这样的
{"code":0,"data":{"is_complete":1,"file_link":"http:\/\/asr-tmp-resource.oss-cn-shanghai.aliyuncs.com\/text-to-voice\/0c897307d067230aa3d785acc1746988.mp3?OSSAccessKeyId=LTAItIQd1kGPPB9P&Expires=1728916181&Signature=PEUQKdILy47E8%2F9UTvSZgnE1cnc%3D","queue_id":"","is_cache":1},"message":"ok!"}
我们只需要简单处理就好
完整代码如下,自己传入参数就好
# coding=utf-8
import requests
import json
def request_url(headers, files, bg_volume=5, bgid=0, client="web", client_ver="3.0.3.0",
deviceid="bb26ce91d9cd41df91157481c7a87f61", format="mp3", pitch_rate=5, soft_version="1.0.0.0",
source=437, speech_rate=5,
text="杨花落尽子规啼,闻道龙标过五溪。我寄愁心与明月,随风直到夜郎西。",
voice="yunxiaoye", volume=5):
"""
合成语音,并保存到目标路径,不要添加背景音乐,容易请求失败。
headers: dict, 请求头
files: str, 保存到路径
bg_volume: int, 1-10, 背景音乐音量,用不到可以写5
bgid: str, 默认为0,用不到就写死
client: str, 客户端方式, "web", 写死
client_ver: 客户端服务器,默认"3.0.3.0",暂时写死
deviceid: str, 设备ID,拿到后直接写死就好
format: str, 保存方式,mp3/wav, 可以写死
pitch_rate: int, 1-10, 语调
soft_version: str, 软版本,暂时写死
source: str, 出处,默认437,暂时写死
speech_rate: int, 1-10, 语速
text: str, 需要合成的文本
title: str, 相当于python中的text[:5]切片处理,代码逻辑写死
voice: str, 使用的音色
volume: int, 1-10, 音量
"""
# 构造请求参数
params = {
'text': text,
'bgid': bgid,
'bg_volume': bg_volume,
'format': format,
'voice': voice,
'volume': volume,
'speech_rate': speech_rate,
'pitch_rate': pitch_rate,
'title': text[:5],
'deviceid': deviceid,
'client': client,
'client_ver': client_ver,
'soft_version': soft_version,
'source': source
}
json_params = json.dumps(params)
# 构造 URL
url = "https://user.api.hudunsoft.com/v1/alivoice/texttoaudio"
# 发送 POST 请求
response = requests.post(url, headers=headers, data=json_params)
# 打印响应内容
print(response.text)
# 解析响应数据
response_data = response.json()
if response_data.get("code") == 0 and response_data.get("data"):
file_link = response_data["data"]["file_link"]
# 下载文件并保存为 MP3
audio_response = requests.get(file_link)
if audio_response.status_code == 200:
with open(files, 'wb') as fout:
fout.write(audio_response.content)
print(f"Audio file saved to {files}")
else:
print(f"Failed to download audio file. Status code: {audio_response.status_code}")
else:
print("Failed to get audio link.")
if __name__ == "__main__":
# 定义请求头
headers = {
'content-type': 'application/json',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}
# 调用函数
request_url(headers, 'output.mp3')
请求成功之后,控制台会输出response信息,以及对应的运行文件的同级目录下就会出现同样的output.3mp3文件。
当然,大批量文本可以用循环,然后多次请求做运行。
超过100字符,我能想到的是根据标点符号拆分成句子,然后将每句话按照一定间隔进行组合,示例代码如下。
我这里拿木兰诗做了测试,可以正常运行哈。
这里的moviepy需要通过pip install moviepy安装依赖,很简单的。
另外,我这里的处理已经尽可能的规避纯标点符号了,如果像是⑪这样的特殊符号,可能识别不出来进而影响语音合成,自己去做对应处理哈。
# coding=utf-8
import re
import requests
import json
from moviepy.editor import AudioFileClip, AudioClip, concatenate_audioclips
# 根据标点符号将文本以换行符分割成句子。
def split_and_merge_sentences(text):
"""将句子按照标点符号分割成列表,并用换行符分割"""
# 定义句子结束的正则表达式,包括中文和英文标点
pattern = r'[。?!!?,,;;::]' # 匹配句子结束符号
# 使用re.split进行分割,但需要过滤掉匹配到的分隔符本身
parts = re.split(f'({pattern})', text)
# 过滤掉空字符串和分隔符,只保留句子
# 重新组合句子和标点符号
sentences = [''.join(part) for part in zip(parts[::2], parts[1::2])] # 偶数索引为句子,奇数为标点
# 如果句子数量为奇数,说明最后一个句子没有标点,单独处理
if len(parts) % 2 != 0:
sentences.append(parts[-1])
if not sentences[-1]:
sentences.pop(-1)
merged_sentences = [sentence for sentence in sentences if sentence and len(sentence) > 1]
# 添加换行符
for i in range(len(merged_sentences)):
merged_sentences[i] += '\n'
# 连接成新字符串
result = ''.join(merged_sentences)
return result
# 处理带标点的文本,确保每句以标点结尾,并合并多句为单行。
def prepare_subtitles(text):
"""
准备字幕文本,确保每句结尾都有标点,并将多句合并为一行。
参数:
text: str, 原始文本字符串。
返回:
str, 准备好的字幕文本。
"""
# 将文本按行分割,去除空行
lines = text.split('\n')
lines = [i.strip() for i in lines if i.strip()]
# 给没有标点的句子添加句号
for index in range(len(lines)):
if not lines[index].endswith(('。', '!', '?', '!', '?', ':', ':', ',', ',', ';', ';', '、', '.',)):
lines[index] += '。'
# 将每句话合并为一行
processed_lines = []
for line in lines:
# 分割并合并句子
processed_line = split_and_merge_sentences(line)
processed_lines.append(processed_line)
# 去除只含单个标点符号的行
filtered_lines = [line for line in processed_lines if len(line) > 1]
# 合并所有行到一个字符串中
final_text = ''.join(filtered_lines)
return final_text.strip()
def request_url(headers, files, bg_volume=5, bgid=0, client="web", client_ver="3.0.3.0",
deviceid="bb26ce91d9cd41df91157481c7a87f61", format="mp3", pitch_rate=5, soft_version="1.0.0.0",
source=437, speech_rate=5,
text="杨花落尽子规啼,闻道龙标过五溪。我寄愁心与明月,随风直到夜郎西。",
voice="yunxiaoye", volume=5):
"""
合成语音,并保存到目标路径,不要添加背景音乐,容易请求失败。
headers: dict, 请求头
files: str, 保存到路径
bg_volume: int, 1-10, 背景音乐音量,用不到可以写5
bgid: str, 默认为0,用不到就写死
client: str, 客户端方式, "web", 写死
client_ver: 客户端服务器,默认"3.0.3.0",暂时写死
deviceid: str, 设备ID,拿到后直接写死就好
format: str, 保存方式,mp3/wav, 可以写死
pitch_rate: int, 1-10, 语调
soft_version: str, 软版本,暂时写死
source: str, 出处,默认437,暂时写死
speech_rate: int, 1-10, 语速
text: str, 需要合成的文本
title: str, 相当于python中的text[:5]切片处理,代码逻辑写死
voice: str, 使用的音色
volume: int, 1-10, 音量
"""
# 构造请求参数
params = {
'text': text,
'bgid': bgid,
'bg_volume': bg_volume,
'format': format,
'voice': voice,
'volume': volume,
'speech_rate': speech_rate,
'pitch_rate': pitch_rate,
'title': text[:5],
'deviceid': deviceid,
'client': client,
'client_ver': client_ver,
'soft_version': soft_version,
'source': source
}
json_params = json.dumps(params)
# 构造 URL
url = "https://user.api.hudunsoft.com/v1/alivoice/texttoaudio"
# 发送 POST 请求
response = requests.post(url, headers=headers, data=json_params)
# 打印响应内容
# print(response.text)
# 解析响应数据
response_data = response.json()
if response_data.get("code") == 0 and response_data.get("data"):
file_link = response_data["data"]["file_link"]
# 下载文件并保存为 MP3
audio_response = requests.get(file_link)
if audio_response.status_code == 200:
with open(files, 'wb') as fout:
fout.write(audio_response.content)
print(f"Audio file saved to {files}")
else:
print(f"Failed to download audio file. Status code: {audio_response.status_code}")
else:
print("Failed to get audio link.")
# 按照顺序合并音频文件,并添加空白间隔
def merge_mp3s_with_gaps(mp3_paths, gap_duration, output_path, batch_size=50):
# 加载所有音频文件
clips = [AudioFileClip(path) for path in mp3_paths]
# 创建空白间歇音频
gap_clip = AudioClip(lambda t: 0, duration=gap_duration)
# 分批处理
final_clip = None
for i in range(0, len(clips), batch_size):
batch_clips = clips[i:i + batch_size]
batch_final_clip = batch_clips[0]
for clip in batch_clips[1:]:
batch_final_clip = concatenate_audioclips([batch_final_clip, gap_clip, clip])
if final_clip is None:
final_clip = batch_final_clip
else:
final_clip = concatenate_audioclips([final_clip, gap_clip, batch_final_clip])
# 保存合并后的音频文件
final_clip.write_audiofile(output_path)
print(f"完整音频文件{output_path}生成成功")
if __name__ == "__main__":
# 你的文章,最好提前做好处理
content = """唧唧复唧唧,木兰当户织。不闻机杼声,唯闻女叹息。
问女何所思,问女何所忆。女亦无所思,女亦无所忆。昨夜见军帖,可汗大点兵,军书十二卷,卷卷有爷名。阿爷无大儿,木兰无长兄,愿为市鞍马,从此替爷征。
东市买骏马,西市买鞍鞯,南市买辔头,北市买长鞭。旦辞爷娘去,暮宿黄河边,不闻爷娘唤女声,但闻黄河流水鸣溅溅。旦辞黄河去,暮至黑山头,不闻爷娘唤女声,但闻燕山胡骑鸣啾啾。
万里赴戎机,关山度若飞。朔气传金柝,寒光照铁衣。将军百战死,壮士十年归。
归来见天子,天子坐明堂。策勋十二转,赏赐百千强。可汗问所欲,木兰不用尚书郎,愿驰千里足,送儿还故乡。
爷娘闻女来,出郭相扶将;阿姊闻妹来,当户理红妆;小弟闻姊来,磨刀霍霍向猪羊。开我东阁门,坐我西阁床,脱我战时袍,著我旧时裳。当窗理云鬓,对镜帖花黄。出门看火伴,火伴皆惊忙:同行十二年,不知木兰是女郎。
雄兔脚扑朔,雌兔眼迷离;双兔傍地走,安能辨我是雄雌?"""
sentences = prepare_subtitles(content)
sentences_list = sentences.split("\n")
# 定义请求头
headers = {
'content-type': 'application/json',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}
# 初始化合成片段路径列表
audio_part_paths = []
for index, sentence in enumerate(sentences_list):
request_url(headers=headers, files=f"audio_part_{index}.mp3", text=sentence)
audio_part_paths.append(f"audio_part_{index}.mp3")
output_path = "complete.mp3"
merge_mp3s_with_gaps(audio_part_paths, gap_duration=0.1, output_path=output_path)
最后,感谢阅读,制作不易,希望留下你的留言,谢谢。
当然,这个网站也有其它功能,不过我暂时还没做研究,如果有朋友做了对应的功能爬取,也欢迎分享讨论。