python爬取一款免登录的在线语音合成接口

之前我也找了很多语音合成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)

最后,感谢阅读,制作不易,希望留下你的留言,谢谢。

当然,这个网站也有其它功能,不过我暂时还没做研究,如果有朋友做了对应的功能爬取,也欢迎分享讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值