Comparison with the previous single-account version https://www.niuboyi.com/thread-1050-1-1.html. This version is multi-account; purchasing either one gives you the other for free.
You can configure different video materials, clone different voices, use different copy, and use different background audio for different accounts.
It only includes the video mixed-editing and compositing part. The copy and original video material processing are prepared in advance; combining them together at present is not quite reasonable.
All core capabilities involved in the script are built with local programs, with no extra fees and no updates required; once deployed, it can be used forever.
The moderator's company computer is a GTX2060, and it takes about 3 minutes on average to produce a 1-minute video.
During automatic production, the program creates them one by one according to the copy in the 文案.txt prepared in advance.
This version is for multiple projects; different accounts use different characters, different voices, and different copy.
Script process
1. Audio cloning
2. Generate audio from copy
3. Synthesize audio into video
4. Add background audio to video
5. Add subtitles to video
Specific script
import os
import random
import subprocess
import requests
import time
from datetime import datetime
from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip, AudioFileClip, CompositeAudioClip
# 账号配置字典,可根据需要添加更多账号配置
account_configs = {
'zhongzong': {
'wenan_file_path': 'F:\\haidaojuzhen\\cosyvoice\\wenan\\wenan.txt',
'audio_save_folder': 'F:\\haidaojuzhen\\cosyvoice\\yinpin\\zhongzong',
'video_folders': {
'kaitou': 'F:\\haidaojuzhen\\cosyvoice\\shipin\\zhongzong\\kaitou',
'zhongjian': 'F:\\haidaojuzhen\\cosyvoice\\shipin\\zhongzong\\zhongjian',
'jiewei': 'F:\\haidaojuzhen\\cosyvoice\\shipin\\zhongzong\\jiewei'
},
'output_video_folder': 'F:\\haidaojuzhen\\cosyvoice\\chengpian\\zhongzong',
'background_audio_path': 'F:\\haidaojuzhen\\cosyvoice\\music\\qingkuai.mp3',
'subtitle_path': 'F:\\CosyVoice-windows\\音频输出\\output.srt',
'speaker': 'denggang'
},
'jiaotongjie': {
'wenan_file_path': 'F:\\haidaojuzhen\\cosyvoice\\wenan\\wenan1.txt',
'audio_save_folder': 'F:\\haidaojuzhen\\cosyvoice\\yinpin\\jiaotongjie',
'video_folders': {
'kaitou': 'F:\\haidaojuzhen\\cosyvoice\\shipin\\jiaotongjie\\kaitou',
'zhongjian': 'F:\\haidaojuzhen\\cosyvoice\\shipin\\jiaotongjie\\zhongjian',
'jiewei': 'F:\\haidaojuzhen\\cosyvoice\\shipin\\jiaotongjie\\jiewei'
},
'output_video_folder': 'F:\\haidaojuzhen\\cosyvoice\\chengpian\\jiaotongjie',
'background_audio_path': 'F:\\haidaojuzhen\\cosyvoice\\music\\beijing1.mp3',
'subtitle_path': 'F:\\CosyVoice-windows\\音频输出\\output.srt',
'speaker': 'lidamao'
}
}
def get_audio_duration(audio_path):
"""获取音频时长"""
cmd = f'ffprobe -i "{audio_path}" -show_entries format=duration -v quiet -of csv="p=0"'
try:
return float(subprocess.check_output(cmd, shell=True).decode().strip())
except subprocess.CalledProcessError:
print("获取音频时长失败")
return None
def select_video_paths(account, audio_duration):
"""根据音频时长选择视频片段"""
config = account_configs[account]
video_paths = []
if audio_duration <= 10:
video_paths.append(os.path.join(config['video_folders']['jiewei'], random.choice(os.listdir(config['video_folders']['jiewei']))))
elif 11 <= audio_duration <= 20:
video_paths.extend([
os.path.join(config['video_folders']['kaitou'], random.choice(os.listdir(config['video_folders']['kaitou']))),
os.path.join(config['video_folders']['jiewei'], random.choice(os.listdir(config['video_folders']['jiewei'])))
])
elif 21 <= audio_duration <= 30:
video_paths.extend([
os.path.join(config['video_folders']['kaitou'], random.choice(os.listdir(config['video_folders']['kaitou']))),
os.path.join(config['video_folders']['zhongjian'], random.choice(os.listdir(config['video_folders']['zhongjian']))),
os.path.join(config['video_folders']['jiewei'], random.choice(os.listdir(config['video_folders']['jiewei'])))
])
else:
video_paths.extend([
os.path.join(config['video_folders']['kaitou'], random.choice(os.listdir(config['video_folders']['kaitou']))),
*[os.path.join(config['video_folders']['zhongjian'], random.choice(os.listdir(config['video_folders']['zhongjian']))) for _ in range(int(audio_duration // 10))],
os.path.join(config['video_folders']['jiewei'], random.choice(os.listdir(config['video_folders']['jiewei'])))
])
return video_paths
def concat_videos(account, video_paths, output_path):
"""拼接视频片段"""
config = account_configs[account]
concat_file = output_path + '_concat.txt'
with open(concat_file, 'w') as f:
for video_path in video_paths:
f.write(f"file '{video_path}'\n")
temp_video_path = output_path + '_temp.mp4'
ffmpeg_concat_cmd = f'ffmpeg -f concat -safe 0 -i "{concat_file}" -vf "scale=720:1280" "{temp_video_path}"'
try:
subprocess.check_call(ffmpeg_concat_cmd, shell=True)
return temp_video_path
except subprocess.CalledProcessError:
print("视频拼接失败")
return None
def combine_audio_video(account, video_path, audio_path, output_path, audio_duration):
"""合并音频和视频"""
config = account_configs[account]
ffmpeg_cmd = (
f'ffmpeg -i "{video_path}" '
f'-i "{audio_path}" -t {audio_duration} -c:a aac -vf "scale=720:1280" '
f'-map 0:v -map 1:a -shortest "{output_path}"'
)
try:
subprocess.check_call(ffmpeg_cmd, shell=True)
print(f"音频与视频合成成功,输出视频路径:{output_path}")
except subprocess.CalledProcessError:
print("音频与视频合成失败")
def process_text_file(account):
"""读取文案文件并拆分文案"""
config = account_configs[account]
with open(config['wenan_file_path'], 'r', encoding='utf-8') as file:
content = file.read()
return content.split('|')
def add_subtitles_and_audio_to_video(account, video_path, subtitle_path, audio_path, output_path):
width = 720
height = 1280
video = VideoFileClip(video_path, target_resolution=(height, width))
# 加载背景音频文件
background_audio = AudioFileClip(audio_path)
# 调整背景音频长度与视频匹配
background_audio = background_audio.subclip(0, video.duration)
# 调整音量
background_audio = background_audio.volumex(0.1) # 将背景音量调整为原始的20%
# 调整音量
original_audio = video.audio.volumex(1.8) # 将原视频音量调整为原始的80%
mixed_audio = CompositeAudioClip([original_audio, background_audio])
with open(subtitle_path, 'r', encoding='utf-8') as f:
subtitles = f.read().strip().split('\n\n')
clips = []
for subtitle in subtitles:
lines = subtitle.split('\n')
if len(lines) >= 3:
# 解析时间戳
time_str = lines[1]
start_time_str, end_time_str = time_str.split(' --> ')
start_time_obj = datetime.strptime(start_time_str, '%H:%M:%S,%f')
end_time_obj = datetime.strptime(end_time_str, '%H:%M:%S,%f')
start_time = start_time_obj.hour * 3600 + start_time_obj.minute * 60 + start_time_obj.second + start_time_obj.microsecond / 1e6
end_time = end_time_obj.hour * 3600 + end_time_obj.minute * 60 + end_time_obj.second + end_time_obj.microsecond / 1e6
duration = end_time - start_time
# 创建字幕剪辑并设置字体为仿宋
text = ' '.join(lines[2:]) # 合并所有字幕内容行
text_clip = TextClip(text.replace('\n', ' '), fontsize=34, color='white', bg_color='black', method='caption', font='FangSong', size=(400, None))
text_clip = text_clip.set_duration(duration).set_start(start_time).set_position(('center', 800))
clips.append(text_clip)
# 将字幕和视频合成
final_clip = CompositeVideoClip([video] + clips)
# 将混合音频添加到视频中
final_clip = final_clip.set_audio(mixed_audio)
# 输出最终视频
final_clip.write_videofile(output_path, codec='libx264', audio_codec='aac')
def main():
accounts = list(account_configs.keys())
for i, account in enumerate(accounts):
# 读取并拆分文案
wenan_list = process_text_file(account)
for wenan in wenan_list:
# 设置请求参数
params = {
'text': wenan.strip(), # 去除首尾空格
'speaker': account_configs[account]['speaker'],
'new': 1
}
# 生成基于 speaker和当前时间的唯一文件名
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
base_filename = f"{params['speaker']}{timestamp}"
# 发送请求到API
try:
response = requests.get('http://localhost:9880/', params=params)
response.raise_for_status()
except requests.RequestException as e:
print(f"请求失败: {e}")
continue
# 保存音频文件
audio_filename = f'{base_filename}.wav'
save_path = os.path.join(account_configs[account]['audio_save_folder'], audio_filename)
with open(save_path, 'wb') as f:
f.write(response.content)
# 获取音频时长
audio_duration = get_audio_duration(save_path)
if audio_duration is None:
continue
# 选择视频片段
selected_video_paths = select_video_paths(account, audio_duration)
# 视频拼接处理
if len(selected_video_paths) == 1:
video_path = selected_video_paths[0]
else:
video_path = concat_videos(account, selected_video_paths, os.path.join(account_configs[account]['output_video_folder'], base_filename))
if video_path is None:
continue
# 合并音频和视频
output_video_path = os.path.join(account_configs[account]['output_video_folder'], f'{base_filename}.mp4')
combine_audio_video(account, video_path, save_path, output_video_path, audio_duration)
# 添加字幕和背景音到视频
subtitle_path = account_configs[account]['subtitle_path']
background_audio_path = account_configs[account]['background_audio_path']
final_output_path = os.path.join(account_configs[account]['output_video_folder'], f'{base_filename}_final.mp4')
add_subtitles_and_audio_to_video(account, output_video_path, subtitle_path, background_audio_path, final_output_path)
if i!= len(accounts) - 1:
time.sleep(10)
if __name__ == "__main__":
main() |