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

news/2025/2/5 21:45:20/

通过整合之前生成的文字、音频、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/news/1569614.html

相关文章

基于“蘑菇书”的强化学习知识点(五):条件期望

条件期望 摘要一、条件期望的定义二、条件期望的关键性质三、条件期望的直观理解四、条件期望的应用场景五、简单例子离散情况连续情况 摘要 本系列知识点讲解基于蘑菇书EasyRL中的内容进行详细的疑难点分析!具体内容请阅读蘑菇书EasyRL! 对应蘑菇书Eas…

Linux_线程同步生产者消费者模型

同步的相关概念 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。 同步的…

CentOs9新手教程

CentOS 9是基于RHEL的CentOS Stream版本,主要用于开发和测试环境,不适合作为生产环境的稳定系统。它提供了最新的软件和功能,但可能存在不稳定性和兼容性问题。如果你需要一个稳定的生产环境,建议使用CentOS Linux版本。 安装环境…

4 前端前置技术(中):node.js环境

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 前言

使用PaddlePaddle实现逻辑回归:从训练到模型保存与加载

1. 引入必要的库 首先,需要引入必要的库。PaddlePaddle用于构建和训练模型,pandas和numpy用于数据处理,matplotlib用于结果的可视化。 import paddle import pandas as pd import numpy as np import matplotlib.pyplot as plt 2. 加载自定…

C语言内存之旅:从静态到动态的跨越

大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 本文目录 引言正文一 动态内存管理的必要性二 动态…

《手札·开源篇》从开源到商业化:中小企业的低成本数字化转型路径 ——SKF轴承贸易商的十年信息化演进启示

一、战略驱动的数字化演进逻辑 在轴承行业利润持续走低的背景下,我们选择了一条"开源筑基-场景突破-数据驱动"的演进路径。从2013年金蝶EAS的基础供应链管理,到2023年实现车间设备全要素数字化,系统建设始终遵循"业务场景驱动…

ASP.NET Core Filter

目录 什么是Filter? Exception Filter 实现 注意 ActionFilter 注意 案例:自动启用事务的筛选器 事务的使用 TransactionScopeFilter的使用 什么是Filter? 切面编程机制,在ASP.NET Core特定的位置执行我们自定义的代码。…