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

通过整合之前生成的文字、音频、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_"):
            # 提取 index
            index = int(entry.name.split("_")[-1])

            # 初始化当前文件夹的总持续时间为0
            total_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 = 300
                    total_duration_ms += len(audio) + delay

            # 将当前文件夹的总持续
            # 时间转换为秒,并添加到结果列表中
            total_duration_seconds = total_duration_ms / 1000.0
            durations.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, 720
    background_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.size
        ratio = width / height

        if width > target_width or height > target_height:
            if ratio > target_width / target_height:
                new_width = target_width
                new_height = math.floor(new_width / ratio)
            else:
                new_height = target_height
                new_width = math.floor(new_height * ratio)
        else:
            new_width, new_height = width, height

        img = 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:
        # 获取当前目录的index
        index = 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,并使用该工具生成了视频。

相关推荐
破晓之翼7 分钟前
司库建设-融资需求分析与计划制定
大数据·人工智能·信息可视化
QQ274378510913 分钟前
基于python热门歌曲采集分析系统
开发语言·python
冰糖小新新15 分钟前
ELF2开发板(飞凌嵌入式)搭建深度学习环境部署(RKNN环境部署)
人工智能·深度学习
qq40542519718 分钟前
基于python的体育新闻数据可视化及分析
开发语言·python·信息可视化
想成为配环境大佬19 分钟前
流式学习(简易版)
python·学习·信息可视化
MichaelIp38 分钟前
大模型高级工程师实践 - 将课程内容转为音频
人工智能·gpt·ai·语言模型·自然语言处理·aigc·音视频
慕容木木1 小时前
本地部署最强人工智能服务:方案(一)Chatbox+硅基流动(满血DeepSeek R1)
人工智能·华为云·deepseek·硅基流动·chatbox
弥树子1 小时前
使用 PaddlePaddle 实现逻辑回归:从训练到模型保存与加载
人工智能·逻辑回归·paddlepaddle
belldeep1 小时前
python:如何播放 .spx 声音文件
python·ffmpeg·pyaudio
浩浩kids2 小时前
WPS计算机二级•幻灯片的音视频表格与图形
音视频·wps