This only includes the video montage/compositing part. The copy and original video footage processing are prepared in advance; combining them together at present is not very reasonable.
All core capabilities involved in the script are built with local programs. There are no additional fees, no updates are needed, and once deployed, it can be used forever.
The computer at the moderator’s company is a GTX 2060; on average, it produces a 1-minute video in about 3 minutes.
During automatic production, the program produces videos one by one according to the copy in 文案.txt that we prepared in advance.
This version is only for a single project. For example, if you use one person as the video protagonist, for now you can only use him for everything, and it supports only his one account.
Script Process
1. Audio cloning
2. Generate audio from the copy
3. Synthesize audio into video
4. Add background audio to the video
5. Add subtitles to the video
Specific Script
```python
import os
import random
import subprocess
import requests
import re
from datetime import datetime
from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip, AudioFileClip, CompositeAudioClip
# 文件路径配置
base_dir = os.path.dirname(os.path.abspath(__file__))
wenan_file_path = os.path.join(base_dir, 'wenan', 'wenan.txt')
audio_save_folder = os.path.join(base_dir, 'yinpin', 'yonghu')
video_folders = {
'kaitou': os.path.join(base_dir, 'shipin', 'yonghu', 'kaitou'),
'zhongjian': os.path.join(base_dir, 'shipin', 'yonghu', 'zhongjian'),
'jiewei': os.path.join(base_dir, 'shipin', 'yonghu', 'jiewei')
}
output_video_folder = os.path.join(base_dir, 'zuopin', 'yonghu')
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(target_duration):
"""根据目标时长选择视频片段,拼接至总时长大于等于目标时长"""
video_paths = []
current_duration = 0
# 添加开头片段
video_paths.append(os.path.join(video_folders['kaitou'], random.choice(os.listdir(video_folders['kaitou']))))
current_duration += get_audio_duration(video_paths[-1])
# 添加中间片段直到时长超过目标
while current_duration < target_duration:
video_paths.append(os.path.join(video_folders['zhongjian'], random.choice(os.listdir(video_folders['zhongjian']))))
current_duration += get_audio_duration(video_paths[-1])
# 添加结尾片段
video_paths.append(os.path.join(video_folders['jiewei'], random.choice(os.listdir(video_folders['jiewei']))))
return video_paths, current_duration
def concat_and_trim_videos(video_paths, target_duration, output_path):
"""拼接视频片段并截取到目标时长"""
print(f"正在拼接视频片段,总计 {len(video_paths)} 个片段。")
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)
if os.path.exists(temp_video_path):
# 截取视频到目标时长
trimmed_video_path = output_path + '_trimmed.mp4'
ffmpeg_trim_cmd = f'ffmpeg -i "{temp_video_path}" -t {target_duration} -c copy "{trimmed_video_path}"'
subprocess.check_call(ffmpeg_trim_cmd, shell=True)
return trimmed_video_path
else:
print("拼接视频生成失败,文件不存在。")
return None
except subprocess.CalledProcessError:
print("视频拼接失败")
return None
def combine_audio_video(video_path, audio_path, output_path):
"""合并音频和视频,确保音视频时长一致"""
audio_duration = get_audio_duration(audio_path)
if audio_duration is None:
print("无法获取音频时长")
return None
print(f"正在合成视频和音频,时长: {audio_duration}")
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 -af "volume=2.0" "{output_path}"'
)
try:
subprocess.check_call(ffmpeg_cmd, shell=True)
print(f"音频与视频合成成功,输出视频路径:{output_path}")
except subprocess.CalledProcessError:
print("音频与视频合成失败")
def process_text_file():
"""读取文案文件并拆分文案"""
print(f"正在读取文案文件,文案位置:{wenan_file_path}")
try:
with open(wenan_file_path, 'r', encoding='utf-8') as file:
content = file.read()
return content.split('|')
except FileNotFoundError:
print(f"文案文件 {wenan_file_path} 未找到")
return []
def preprocess_subtitles(subtitle_path, video_duration):
"""读取并拆分字幕内容,按字符比例分配时间,仅保留问号"""
processed_subtitles = []
with open(subtitle_path, 'r', encoding='utf-8') as f:
subtitles = f.read().strip().split('\n\n')
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
total_duration = end_time - start_time
# 按逗号、句号、问号和叹号拆分为小句
sentences = re.split(r'[,。?!]', lines[2]) # 按标点分割
sentences = [s.strip() for s in sentences if s.strip()] # 去除空句
total_characters = sum(len(sentence) for sentence in sentences)
# 为每一句分配时间,并确保显示时仅保留问号
for sentence in sentences:
# 移除除问号以外的标点符号
sentence_display = re.sub(r'[,。!]', '', sentence) # 删除逗号、句号、叹号,仅保留问号
# 计算句子的显示时长
sentence_duration = (len(sentence) / total_characters) * total_duration
if start_time + sentence_duration > video_duration:
sentence_duration = video_duration - start_time # 限制最后一个字幕时间不超过视频时长
# 添加处理后的句子到字幕列表
processed_subtitles.append((sentence_display, start_time, sentence_duration))
start_time += sentence_duration # 更新下一个句子的起始时间
if start_time >= video_duration: # 如果字幕时间已超出视频时长,停止
break
return processed_subtitles
def add_subtitles_and_audio_to_video(video_path, subtitle_path, audio_path, output_path):
print(f"正在为视频添加背景音和字幕。")
width = 720
height = 1280
video = VideoFileClip(video_path, target_resolution=(height, width))
video_duration = video.duration # 获取视频总时长
# 加载背景音频文件
background_audio = AudioFileClip(audio_path).subclip(0, video_duration).volumex(1.5)
# 混合背景音频与原视频音频
original_audio = video.audio.volumex(1)
mixed_audio = CompositeAudioClip([original_audio, background_audio])
# 预处理字幕文件
processed_subtitles = preprocess_subtitles(subtitle_path, video_duration)
clips = []
for text, start_time, duration in processed_subtitles:
# 创建逐句字幕
text_clip = TextClip(
text, fontsize=40, color='white', bg_color='rgba(0, 0, 0, 0.5)', method='caption', font='Microsoft-YaHei-Bold-&-Microsoft-YaHei-UI-Bold', size=(450, None)
).set_duration(duration).set_start(start_time).set_position(('center', 800))
clips.append(text_clip)
# 将字幕和视频合成
final_clip = CompositeVideoClip([video] + clips).set_audio(mixed_audio)
# 输出最终视频
final_clip.write_videofile(output_path, codec='libx264', audio_codec='aac')
def main():
wenan_list = process_text_file()
print(f"正在处理字段,总共 {len(wenan_list)} 个字段。")
for wenan in wenan_list:
params = {
'text': wenan.strip(),
'speaker': 'lulaoshi',
'new': 1
}
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
base_filename = f"{params['speaker']}{timestamp}"
print(f"正在使用克隆声音,生成音频,文案:{wenan}")
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(audio_save_folder, audio_filename)
with open(save_path, 'wb') as f:
f.write(response.content)
adjusted_audio_path = os.path.join(audio_save_folder, f'{base_filename}_adjusted.wav')
ffmpeg_volume_adjust_cmd = f'ffmpeg -i "{save_path}" -filter:a "volume=4" "{adjusted_audio_path}"'
try:
subprocess.check_call(ffmpeg_volume_adjust_cmd, shell=True)
save_path = adjusted_audio_path
except subprocess.CalledProcessError:
print("音频音量调整失败,继续使用原始音频文件。")
audio_duration = get_audio_duration(save_path)
if audio_duration is None:
continue
selected_video_paths, total_video_duration = select_video_paths(audio_duration)
video_path = concat_and_trim_videos(selected_video_paths, audio_duration, os.path.join(output_video_folder, base_filename))
output_video_path = os.path.join(output_video_folder, f'{base_filename}.mp4')
combine_audio_video(video_path, save_path, output_video_path)
subtitle_path = r'D:\CosyVoice-win\音频输出\output.srt'
background_audio_path = os.path.join(base_dir, 'music', 'xinghe.mp3')
final_output_path = os.path.join(output_video_folder, f'{base_filename}_final.mp4')
add_subtitles_and_audio_to_video(output_video_path, subtitle_path, background_audio_path, final_output_path)
print(f"视频生成完成,请在{output_video_folder} 文件夹中查看视频名称为: {base_filename}_final.mp4")
if __name__ == "__main__":
main()
``` |