大模型高级工程师实践 - 将课程内容转为视频

devtools/2025/2/6 18:34:44/

通过整合之前生成的文字、音频、PPT,我们能够制作出引人入胜的科普课程视频,使表达更加生动且多样化。本节课程将介绍如何利用音视频处理工具ffmpeg和moviepy,快速将课程内容转化为视频。

1. 原理介绍

当前的大模型文生视频或图生视频方案还不足以直接生成符合我们预期的科普课程视频,因此,我们会采用传统的方案,使用音视频处理工具进行合成。 本次课程除了上次课程用到的 moviepy 外,你还将用到以下工具:

ffmpeg:一个开源的跨平台音视频处理工具,它提供了强大的音视频编解码功能、转换格式、录制和流媒体功能。FFmpeg 包含了丰富的命令行工具和库,使用户能够灵活地处理各种媒体文件。

使用 ffmpeg 和 moviepy 将课程内容转换为视频的过程如下:
在这里插入图片描述

2. 代码实践

接下来,让我们执行以下代码,将第一节课生成的内容转换为音频,并生成字幕。

2.1. 环境准备

安装 ffmpeg。
可以取消这里的注释来安装 ffmpeg库,如果是 Windows 系统,请参考ffmpeg 官网 安装。

python">!sudo apt update
!sudo apt install ffmpeg
!ffmpeg -version

安装 python 库。

python">! pip install -r requirements.txt -q
python">pdf2image==1.17.0
openai==1.40.8
python-dotenv==1.0.1
requests==2.32.3
dash==2.18.1
dashscope==1.20.12
moviepy==1.0.3
ffmpeg-python==0.2.0
pydub==0.25.1
natsort==8.4.0

导入必要的模块。

python">import os
import json
import re
import time
import traceback
from pydub import AudioSegment
from typing import List
from moviepy.editor import *
from PIL import Image
import natsort
import math
import numpy as np
from glob import glob
import subprocess
from utils import create_directory,read_text_from_file, save_file,load_config

2.2. 剪辑视频

首先,我们将 PPT 剪辑为视频。

python">project_config = load_config("config.json")
title = project_config["title"]

定义一个 calculate_durations_for_each_image 函数,用于计算每一张演示文稿在视频中的持续时间。

python">def calculate_audio_durations(directory):"""计算指定目录下所有以 audio_for_paragraph_{index} 命名的文件夹中 mp3 文件的总持续时间(以秒为单位)。参数:directory (str): 需要扫描的根目录路径。返回:list: 每个 audio_for_paragraph_{index} 文件夹中 mp3 文件总持续时间(秒)的列表。"""# 初始化结果列表durations = []# 遍历目录下的所有子目录for entry in os.scandir(directory):if entry.is_dir() and entry.name.startswith("audio_for_paragraph_"):# 提取 indexindex = int(entry.name.split("_")[-1])# 初始化当前文件夹的总持续时间为0total_duration_ms = 0# 遍历子目录中的所有文件for file_entry in os.scandir(entry.path):if file_entry.name.endswith(".mp3"):# 加载 mp3 文件并计算持续时间audio = AudioSegment.from_mp3(file_entry.path)delay = 300total_duration_ms += len(audio) + delay# 将当前文件夹的总持续# 时间转换为秒,并添加到结果列表中total_duration_seconds = total_duration_ms / 1000.0durations.append((index, total_duration_seconds))# 按照 index 排序结果列表durations.sort(key=lambda x: x[0])# 只保留持续时间(秒)durations = [duration for _, duration in durations]durations.insert(0, 2)return durations

调用 calculate_durations_for_each_image 函数计算每一张演示文稿在视频中的持续时间。

python"># 计算各段落的所有音频时长
audio_file_folder = project_config["audio_file_folder"].format(title=project_config["title"])# 计算音频时长
durations = calculate_audio_durations(audio_file_folder)durations_file=project_config["durations_folder"].format(title=project_config["title"])create_directory(durations_file)# 打印结果
print("各段落的音频时长(秒):")
with open(durations_file, "w") as f:for index, duration in enumerate(durations):f.write(f"段落 {index + 1}: {duration:.2f} 秒\n")print(f"段落 {index + 1}: {duration:.2f} 秒")print(f"时长信息已保存到 {durations_file}")
python">目标目录:./output
各段落的音频时长(秒):
段落 1: 2.00 秒
段落 2: 30.96 秒
段落 3: 38.33 秒
段落 4: 57.61 秒
段落 5: 38.97 秒
段落 6: 28.35 秒
段落 7: 25.71 秒
时长信息已保存到 ./output/durations

定义一个 images_to_video_with_durations 函数,用于将所有输入演示文稿按顺序剪辑为视频。

python">def images_to_video_with_durations(input_image_path, output_video_path, durations, fps, base_name):# 获取所有符合条件的图片,并按文件名中的数字排序# pattern = r'^' + re.escape(base_name) + r'_(\d+)\.png$'pattern = r".*_(\d+)\.png"image_files = [f"{input_image_path}/{file}"for file in os.listdir(input_image_path)if re.match(pattern, file)]print("Matching files:", image_files)  # 调试输出,查看匹配的文件image_files = natsort.natsorted(image_files, key=lambda x: int(re.match(pattern, os.path.basename(x)).group(1)))# 确定视频的背景尺寸target_width, target_height = 1280, 720background_size = (target_width, target_height)clips = []for i, file in enumerate(image_files):print(f"Processing file: {file}, duration: {durations[i]}")  # 再次调试输出img = Image.open(file)width, height = img.sizeratio = width / heightif width > target_width or height > target_height:if ratio > target_width / target_height:new_width = target_widthnew_height = math.floor(new_width / ratio)else:new_height = target_heightnew_width = math.floor(new_height * ratio)else:new_width, new_height = width, heightimg = img.resize((new_width, new_height), resample=Image.Resampling.LANCZOS)img_clip = ImageClip(np.array(img)).set_duration(durations[i])img_clip = img_clip.set_position('center')bg_clip = ColorClip(size=background_size, color=(255, 255, 255), duration=durations[i])composite_clip = CompositeVideoClip([bg_clip, img_clip])clips.append(composite_clip)# 使用concatenate_videoclips函数将所有剪辑串联在一起final_clip = concatenate_videoclips(clips, method="compose")output_filename = os.path.join(output_video_path, f"{base_name}.mp4")create_directory(output_filename)final_clip.write_videofile(output_filename, fps=fps)

调用 images_to_video_with_durations 将 PPT 按顺序剪辑为视频。

python">marp_export_image_folder = project_config["marp_export_image_folder"].format(title=project_config["title"])
srt_and_video_folder = project_config["srt_and_video_folder"]
fps =  project_config["fps"]# 检查输入图像
# pattern = r'^' + re.escape(input_base_name) + r'_(\d+)\.png$'
pattern = r".*_(\d+)\.png"image_files = [f"{marp_export_image_folder}/{file}"for file in os.listdir(marp_export_image_folder)if re.match(pattern, file)
]print("Matching files:", image_files)  # 输出匹配的文件if not image_files:raise ValueError("No matching image files found.")# 检查 durations 的数量
if len(durations) != len(image_files):raise ValueError("The number of durations must match the number of image files.")# 调用函数
images_to_video_with_durations(marp_export_image_folder,srt_and_video_folder,durations,fps,project_config["title"]
)

2.3. 嵌入音频和字幕

接下来,我们将上一课制作的音频和字幕添加到视频中。

定义一个 merge_audio_and_add_to_video 函数,用于合成音频并将音频添加到视频中。

python">def merge_audio_and_add_to_video(video_path, audio_base_dir, output_path):"""合并多个音频文件并添加到视频中。:param video_path: 视频文件的路径。:param audio_base_dir: 包含音频文件夹的基目录。:param output_path: 输出视频的路径。"""# 加载视频文件video_clip = VideoFileClip(video_path)# 初始化音频列表audio_clips = []silent_audio_start = AudioClip(lambda t: [0,0], duration=2)audio_clips.append(silent_audio_start)# 遍历所有子目录,按数字大小排序audio_dirs = glob(os.path.join(audio_base_dir, "audio_for_paragraph_*"))audio_dirs.sort(key=lambda x: int(re.search(r'\d+', os.path.basename(x)).group()))# 遍历所有子目录for audio_dir in audio_dirs:# 获取当前目录的indexindex = int(os.path.basename(audio_dir).split("_")[-1])# 遍历目录中的所有mp3文件mp3_files = glob(os.path.join(audio_dir, f"paragraph_{index}_sentence_*.mp3"))mp3_files.sort(key=lambda x: int(re.search(r'_sentence_(\d+)', os.path.basename(x)).group(1)))# 遍历排序后的mp3文件列表for mp3_file in mp3_files:# 加载音频文件audio_clip = AudioFileClip(mp3_file)# 添加到音频列表if audio_clips:# 如果不是第一个音频,则在前一个音频之后添加0.5秒的静音# 替换原有的 AudioNullClip 代码silent_audio = AudioClip(lambda t: [0,0], duration=0.3)audio_clips.append(silent_audio)audio_clips.append(audio_clip)# 合并所有音频片段final_audio = concatenate_audioclips(audio_clips)# 将音频添加到视频中video_with_audio = video_clip.set_audio(final_audio)# 输出带有新音频的视频文件video_with_audio.write_videofile(output_path, codec='libx264', audio_codec='aac')# 关闭剪辑对象,释放资源video_clip.close()

调用函数 merge_audio_and_add_to_video 添加音频。

python"># 合成路径
video_raw = project_config["video_raw"].format(title=project_config["title"])# 视频文件的路径
audio_file_folder = project_config["audio_file_folder"].format(title=project_config["title"])
# audio_base_dir = "./output/audio/"+title+"_课程脚本_speech_script_plus"  # 音频文件夹的基目录
video_with_audio = project_config["video_with_audio"].format(title=project_config["title"]) # 输出视频的路径# 检查视频和音频路径是否存在
if not os.path.exists(video_raw):raise ValueError(f"Video file not found: {video_raw}")if not os.path.exists(audio_file_folder):raise ValueError(f"Audio directory not found: {audio_file_folder}")# 调用函数
merge_audio_and_add_to_video(video_raw, audio_file_folder, video_with_audio)

定义一个 merge_video_and_subtitle 函数,用于将字幕添加到视频中。

python">def merge_video_and_subtitle(video_path, srt_path, output_path):# 如果输出文件已存在,删除它if os.path.exists(output_path):os.remove(output_path)command = ['ffmpeg','-i', video_path,'-vf', f'subtitles={srt_path}','-c:a', 'copy',output_path]subprocess.run(command, check=True)

调用 merge_video_and_subtitle 函数将字幕添加到视频中。

python"># 构建文件路径
video_with_audio = project_config["video_with_audio"].format(title=project_config["title"])
srt_file_path = project_config["srt_file_path"].format(title=project_config["title"])video_with_audio_and_subtitles = project_config["video_with_audio_and_subtitles"].format(title=project_config["title"])# 调用函数
merge_video_and_subtitle(video_with_audio, srt_file_path, video_with_audio_and_subtitles)

播放生成好的视频

python">from IPython.display import Video# 本地视频文件路径
video_path = project_config["video_with_audio_and_subtitles"].format(title=project_config["title"])
print("视频地址:",video_path)# 播放视频
Video(video_path, width=768, height=512)

本节小结

在本次学习和实践中,我们了解了 ffmpeg,并使用该工具生成了视频。


http://www.ppmy.cn/devtools/156587.html

相关文章

北京门头沟区房屋轮廓shp的arcgis数据建筑物轮廓无偏移坐标测评

在IT行业中,地理信息系统(GIS)是用于处理、分析和展示地理空间数据的重要工具,而ArcGIS则是GIS领域中的一款知名软件。本文将详细解析标题和描述中提及的知识点,并结合“门头沟区建筑物数据”这一标签,深入…

响应式编程_03响应式编程在Netflix Hystrix 、Spring Cloud Gateway、Spring WebFlux中的应用

文章目录 概述响应式编程在主流开源框架中的应用Netflix Hystrix 中的滑动窗口 (基于RxJava框架)HystrixCircuitBreaker 如何动态获取系统运行时的各项数据如何实现滑动窗口? 小结Spring Cloud Gateway 中的过滤器Filter (基于Project Reacto…

SpringSecurity密码编码器:使用BCrypt算法加密、自定义密码编码器

1、Spring Security 密码编码器 Spring Security 作为一个功能完备的安全性框架,一方面提供用于完成加密操作的 PasswordEncoder 组件,另一方面提供一个可以在应用程序中独立使用的密码模块。 1.1 PasswordEncoder 抽象接口 在 Spring Security 中,PasswordEncoder 接口代…

windows环境下如何在PyCharm中安装软件包

windows环境下如何在pyCharm中安装wxPython软件包 在windows环境中,安装软件包可以使用 终端 的方式,在IDE下方的终端中执行pip install wxPython进行安装,安装完毕之后,使用pip show wxPython检查也符合预期。 但是在代码文件中导…

jEasyUI 转换 HTML 表格为数据网格

jEasyUI 转换 HTML 表格为数据网格 引言 随着互联网技术的飞速发展,前端框架和库的应用越来越广泛。jEasyUI 是一款功能强大的 jQuery UI 扩展库,它提供了丰富的 UI 组件,其中数据网格(DataGrid)是 jEasyUI 中一个非常重要的组件。本文将详细介绍如何使用 jEasyUI 将一个…

matlab的.mat文件怎么把表格中的值全部设置为空

在MATLAB中,如果您想要将.mat文件中的表格(table)中的所有值设置为空,您可以先加载该.mat文件,然后修改表格中的数据,最后保存修改后的表格。以下是一个具体的步骤示例: 加载.mat文件&#xff1…

对比DeepSeek、ChatGPT和Kimi的学术写作中搜集参考文献能力

参考文献 列出引用过的文献,按引用顺序排列,并确保格式规范。只列举确实阅读过的文献,包括书籍、期刊文章等,以便读者进一步查阅相关资料。也可以利用endnotes和zotero等文献管理工具插入文献。由于ChatGPT4无法联网进行检索&…

Codeforces Round 1002 (Div. 2)(A-D)

题目链接&#xff1a;Dashboard - Codeforces Round 1002 (Div. 2) - Codeforces A. Milya and Two Arrays 思路 数组a中不同数的数量*数组b的&#xff0c;就是能够组成不同数的数量 代码 void solve(){int n;cin>>n;int cnt10;int cnt20;map<int,bool> mp;ma…