背景
疫情期间发现一个有趣的现象,有一类短视频父母刷抖音的时候经常会看到,这类视频只有一个或多个简单的背景图片,配合一段文字录音,讲一段新闻、故事、或者鸡汤。我想可能是他们对纯文本或者纯语音的内容都不感兴趣,更容易接受短视频这样简单的内容形式。又想到腾讯云有语音合成的产品,加上ffmpeg等视频处理工具,是不是可以批量生成一些这类短视频呢。
要求
1. 了解linux shell
2. 了解python
准备
1. Mac或linux系统
2. 需要播报的文本内容
3. 需要使用的背景图片
过程
以mac系统环境为例:
1. 安装环境
以mac系统环境为例:
安装ffmpeg
brew install ffmpeg
2. 需要播报的文本内容
这里以深圳垃圾分类的相关内容为例,保存为content.txt
可回收物,是指可循环利用和资源化利用的废纸、废塑料、废玻璃、废金属、废弃织物、废弃电子产品等;有害垃圾,是指对人体健康或者自然环境造成直接或者潜在危害应当专门处置的废电池、废灯管、弃置药品、废杀虫剂、废油漆、废日用化学品、废水银产品等;其他垃圾,是指除可回收物、有害垃圾之外的其他废弃物。
3. 需要使用的背景图片
自行准备,保存为pic.jpg
4. 参考腾讯云语音合成文档开通腾讯云语音合成服务
5. 下载腾讯云语音合成python SDK
6. 语音合成脚本
使用的默认参数获取文本内容的语音合成结果,如果想调整合成的效果可以参考腾讯云语音合成相关文档。
这里使用一句话语音合成接口,由于该接口有字数限制,主要思路是,将待合成的文本,按照少于字数限制的最后一个标点切分,这样切分出来的句子既能满足字数限制,又能尽可能保证合成时一句话上下文的完整性。
代码语言:javascript复制# coding=utf8
import base64
import math
import struct
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.tts.v20190823 import tts_client, models
SECRET_ID = "your-secret-id"
SECRET_KEY = "your-secret-key"
def int32_to_4byte(int32):
byte_list = [0, 0, 0, 0]
for i in range(4):
base = math.pow(256, (3 - i))
byte_list[3 - i] = int(int32 / base)
int32 = int32 % base
return byte_list
def request_tencent_tts(text):
cred = credential.Credential(SECRET_ID, SECRET_KEY)
client = tts_client.TtsClient(cred, "ap-shanghai")
request = models.TextToVoiceRequest()
request.Text = text
request.SessionId = "session_id_123"
request.ModelType = 1
request.Volume = 0
request.Speed = 0
request.ProjectId = 0
request.VoiceType = 0
request.PrimaryLanguage = 1
request.SampleRate = 16000
request.Codec = "wav"
response = client.TextToVoice(request)
audio = base64.b64decode(response.Audio)
return audio
def tts(content_fname, wav_fname):
tts_split_char = set([',', '。', ';'])
with open(content_fname, "r", encoding="utf8") as f:
content = f.read()
step = 110
text = ""
last_split = 0
last_punctuation = 0
all_wav_pcm = b''
audio_list = []
for i in range(len(content)):
if content[i] in tts_split_char:
last_punctuation = i
if i - last_split >= 110 or i == len(content) - 1:
if content[i] in tts_split_char:
last_punctuation = i
text = content[last_split:last_punctuation]
if text != "":
#print("text: %s" % text)
last_split = last_punctuation 1
audio = request_tencent_tts(text)
audio_list.append(audio)
pcm = []
head = []
for audio in audio_list:
print("audio length: %s" % len(audio))
pcm = pcm list(audio[44:])
head = list(audio[:44])
string = ""
for c in head:
string = string " " str(c)
print(string)
# 拿最后一个片段的文件头修改后拼接
file_length = len(pcm) 44
byte_list = int32_to_4byte(file_length - 8)
head[4] = byte_list[0]
head[5] = byte_list[1]
head[6] = byte_list[2]
head[7] = byte_list[3]
byte_list = int32_to_4byte(len(pcm))
head[40] = byte_list[0]
head[41] = byte_list[1]
head[42] = byte_list[2]
head[43] = byte_list[3]
wav = head pcm
with open(wav_fname, 'wb') as f:
for byte in wav:
f.write(struct.pack('B', byte))
if __name__ == "__main__"
tts("content.txt", "content.wav")
7. 根据语音合成结果生成字幕srt脚本
主要思路是以逗号、句号等标点符号切分整个文本,得到N个文本片段。以每个片段在整段文本中的比例为锚点,在上一步生成的语音文件中,找到同样比例的时间点,作为该段字幕出现的时间点,这样字幕与语音就对齐了。
代码语言:javascript复制# coding=utf8
from pydub import AudioSegment
def timestamp_format(msecond):
sec, msec = divmod(msecond, 1000)
minute, sec = divmod(sec, 60)
hour, minute = divmod(minute, 60)
timestamp_hour_min_sec = "{0:02d}:{1:02d}:{2:02d},{3}".format(hour, minute, sec, msec)
print("timestamp_hour_min_sec: %s" % timestamp_hour_min_sec)
return timestamp_hour_min_sec
def srt(speech_duration_msecond, content_fname, srt_fname):
with open(content_fname, 'r') as f:
content = f.read()
srt_split_char = set([',', '。', ':'])
srt_text_list = []
srt_timestamp_list = ['00:00:00,0']
length = len(content)
last_split_char_index = 0
for i in range(length):
if content[i] in srt_split_char:
timestamp_msecond = int(i / length * speech_duration_msecond)
item = content[last_split_char_index : i]
last_split_char_index = i 1
srt_text_list.append(item)
timestamp_hour_min_sec = timestamp_format(timestamp_msecond)
srt_timestamp_list.append(timestamp_hour_min_sec)
with open(srt_fname, 'w', encoding='utf8') as f:
for i in range(len(srt_text_list)):
item_timestamp_begin = srt_timestamp_list[i]
item_timestamp_end = srt_timestamp_list[i 1]
string = "{}n{} --> {}n{}nn".format(i 1, item_timestamp_begin, item_timestamp_end, srt_text_list[i])
f.write(string)
def get_wav_duration(fname):
sound= AudioSegment.from_wav(fname)
duration = sound.duration_seconds * 1000 # 音频时长(ms)
return duration
if __name__ == "__main__":
duration = get_wav_duration('content.wav')
print("duration: %d" % duration)
srt(duration, 'content.txt', 'content.srt')
8. 将srt字幕转换成ass字幕
由于srt字幕不支持直接合成到视频中直接播放,需要先转换成ass字幕。
代码语言:javascript复制ffmpeg -i content.srt content.ass
9. 生成视频
通过ffmpeg将语音文件content.wav,字幕文件content.ass,背景图片pic.jpg,合成一个完整的视频,ffmpeg相关命令请搜索相关文档。
代码语言:javascript复制ffmpeg -threads 2 -y -loop 1 -i pic.jpg -r 25 -i content.wav -vf ass=content.ass output.mp4
10. 完成
按顺序执行上面的操作,就可以批量导入文本和图片,批量生成一批图文结合带语音播报的短视频内容。