用Python和FFmpeg将音频变成可分享的视频

2,141 阅读13分钟

在本教程中,我们将学习如何用Python和FFmpeg构建一个应用程序,使我们能够将语音记录变成很酷的视频,并能轻松地在社交媒体上分享。

在本教程结束时,我们将把一个语音记录变成一个看起来与下面类似的视频。

Project demo

教程要求

要学习本教程,你将需要以下组件。

  • 一个或多个你想转换为视频的语音记录。存储在你的Twilio账户中的可编程的语音记录对这个教程非常有用。
  • 安装了Python3.6+。
  • 安装FFmpeg4.3.1版或更新的版本。

创建项目结构

在这一节中,我们将创建我们的项目目录,在这个目录中,我们将创建子目录,存放本教程中要用到的录音、图片、字体和视频。最后,我们将创建Python文件,该文件将包含允许我们使用FFmpeg来创建和编辑视频的代码。

打开一个终端窗口,输入以下命令,创建项目目录移动到其中。

mkdir twilio-turn-recording-to-video
cd twilio-turn-recording-to-video

使用下面的命令创建四个子目录。

mkdir images
mkdir fonts
mkdir videos
mkdir recordings

images 目录是我们将存储视频的背景图像的地方。下载这张图片,并将其存储在images 目录中,名称为bg.png 。这张图片最初是从Freepik.com下载的。

fonts 目录中,我们将存储用于在我们的视频中书写文本的字体文件。下载这个字体,并将其存储在fonts 目录中,名称为LeagueGothic-CondensedRegular.otf 。这个字体最初是从fontsquirrel.com下载的。

videos 目录将包含视频和动画,它们将被添加到背景图片之上。下载这个中间有Twilio标志的旋转唱片的视频,并将其保存在videos 目录中,名称为spinningRecord.mp4 。这个视频中使用的源图片是从flaticon.com下载的。

recordings 目录是我们将储存将变成视频的语音记录的地方。在这个目录中添加一个或多个你自己的语音记录。

现在我们已经创建了所有需要的目录,打开你最喜欢的代码编辑器,在项目的顶级目录中创建一个名为main.py 的文件。这个文件将包含负责将我们的录音变成视频的代码。

如果你不想跟随教程的每一步,你可以在这里获得完整的项目源代码。

将一个音频文件变成一个视频

在本节中,我们将添加代码,使我们能够把录音变成视频,显示录音的声波。

我们将使用FFmpeg ,从一个音频文件生成一个视频。因此,为了从Python中调用FFmpeg和相关程序,我们将使用Python的subprocess 模块。

运行一个命令

main.py 文件内添加以下代码。

import subprocess


def run_command(command):
    p = subprocess.run(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
    )
    print('Done!!!')
    print('stdout:\n{}'.format(p.stdout.decode()))

    return p.stdout.decode().strip()

在上面的代码块中,我们已经导入了subprocess 模块并创建了一个run_command() 函数。顾名思义,这个函数负责运行一个被传入参数的命令。当命令完成后,我们会打印输出,同时将其返回给调用者。

获取一个录音的持续时间

run_command() 函数下面添加以下代码。

def get_rec_duration(rec_name):
    rec_path = "./recordings/{}".format(rec_name)

    command = "ffprobe -i {rec_path} -show_entries format=duration -v quiet \
    -of csv=\"p=0\"".format(rec_path=rec_path)

    rec_duration = run_command(command)
    print("rec duration", rec_duration)

    return rec_duration

在这里,我们创建了一个名为get_rec_duration() 的函数。这个函数负责检索一个录音的持续时间。该函数接收一个录音名称(rec_name)作为参数,该参数以录音目录的名称为前缀,并存储在rec_path 本地变量中。

ffprobe 程序是FFmpeg的一部分,它被用来创建一个命令字符串以获得录音的持续时间。我们用这个命令调用run_command() 函数,并将返回的值存储在rec_duration

最后,我们打印,然后返回我们获得的录音时间。

录音持续时间是需要的,以指定将由其生成的视频的持续时间是相同的。

将音频转换为视频

get_rec_duration() 函数下面添加以下代码。

def turn_audio_to_video(rec_name, rec_duration):
    rec_path = "./recordings/{}".format(rec_name)
    bg_image_path = "./images/bg.png"
    video_name = "video_with_sound_waves.mp4"

    command = 'ffmpeg -y -i {rec_path} -loop 1 -i {bg_image_path} -t {rec_duration} \
    -filter_complex "[0:a]showwaves=s=1280x150:mode=cline:colors=00e5ff[fg];  \
    drawbox=x=0:y=285:w=1280:h=150:color=black@0.8:t=fill[bg]; \
    [bg][fg]overlay=format=auto:x=(W-w)/2:y=(H-h)/2 " \
    -map 0:a -c:v libx264 -preset fast -crf 18 -c:a aac \
    -shortest ./videos/{video_name}'.format(
        rec_path=rec_path,
        bg_image_path=bg_image_path,
        rec_duration=rec_duration,
        video_name=video_name,
    )

    print(video_name)
    run_command(command)
    return video_name

turn_audio_to_video() 函数将把录音变成显示录音声波的视频。该函数以录音名称(rec_name)和录音时间(rec_duration)作为参数。

从音频生成视频的FFmpeg命令使用录音路径(rec_path),背景图片的路径(bg_image_path),以及视频的输出文件名(video_name)。

让我们仔细看看FFmpeg命令。

ffmpeg -y -i {rec_path} -loop 1 -i {bg_image_path} -t {rec_duration} \
-filter_complex \"[0:a]showwaves=s=1280x150:mode=cline:colors=00e5ff[fg];  \
drawbox=x=0:y=285:w=1280:h=150:color=black@0.8:t=fill[bg]; \
[bg][fg]overlay=format=auto:x=(W-w)/2:y=(H-h)/2 \" \
-map 0:a -c:v libx264 -preset fast -crf 18 -c:a aac -shortest ./videos/{video_name}

[-y](https://ffmpeg.org/ffmpeg-all.html#Main-options)告诉ffmpeg覆盖输出文件,如果它存在于磁盘上。

[-i](https://ffmpeg.org/ffmpeg-all.html#Main-options)选项指定了输入的内容。在这个例子中,我们有2个输入文件,录音文件,rec_path ,我们使用的图像有一个背景,它存储在bg_image_path

[-loop](https://ffmpeg.org/ffmpeg-all.html#loop)选项通过重复(循环)输入文件来生成一个视频。在这里,我们正在循环播放我们在bg_image_path 中输入的图像。 默认值是0 (不循环),所以我们把它设置为1 (循环),以便在所有视频帧中重复这个图像。

[-t](https://ffmpeg.org/ffmpeg-all.html#Main-options)选项指定了一个以秒为单位的持续时间,或者使用"hh:mm:ss[.xxx]" 语法。这里我们使用录制持续时间(rec_duration)值来设置我们输出视频的持续时间。

[-filter_complex](https://ffmpeg.org/ffmpeg.html#filter_005fcomplex_005foption)滤波器:允许我们定义一个复杂的滤波器图,一个具有任意数量的输入和/或输出的滤波器。这是一个复杂的选项,需要一些参数,下面讨论。

首先,我们使用showwaves 过滤器来转换语音记录,引用为[0:a] ,以视频输出。s 参数用于指定输出的视频尺寸,我们将其设置为1280x150。mode 参数定义了音频波的绘制方式。可用的值是。point,line,p2p, 和clinecolors 参数指定了波形的颜色。波形的绘制被指定为标签[fg]

我们使用drawbox 过滤器在我们的背景图像上画一个彩色的方框,以帮助波形的突出。x and y 参数指定了方框的左上角坐标,而w and h则设置了它的宽度和高度。color 参数将方框的颜色配置为black ,不透明度为80%。t 参数设置方框边界的厚度。通过设置该值为fill ,我们创建了一个实心的盒子。

为了完成这个滤波器的定义,我们用overlay ,把波形图放在黑框的上面。overlay 过滤器的配置是:format ,它自动设置像素格式;x and y ,它指定覆盖物在视频帧中的坐标。我们用一些数学方法来指定xy 应该放在我们视频的中心。

[-map](https://ffmpeg.org/ffmpeg.html#Stream-selection)选项用来选择哪些输入流应该被包括在或排除在输出中。我们选择将我们的录像中的所有数据流加入到我们的输出视频中。

[-c:v](https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1)选项用来对视频流进行编码,并使用特定的编解码器。我们告诉FFmpeg使用libx264 编码器。

[-preset](https://trac.ffmpeg.org/wiki/Encode/H.264)选项可以选择一系列的选项,提供一定的编码速度和压缩率。我们在这里使用的是fast 选项,但如果你喜欢,可以随意将预设值改为较慢(质量较好)或较快(质量较差)的。

[-crf](https://trac.ffmpeg.org/wiki/Encode/H.264)选项代表恒定速率因子。速率控制决定了每一帧将使用多少比特。这将决定文件的大小和输出视频的质量。为了获得视觉上的无损质量,建议使用18的数值。

-[c:a](https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1)选项用于用某种编解码器对音频流进行编码。我们正在用AAC编解码器对音频进行编码。

[-shortest](https://ffmpeg.org/ffmpeg.html#toc-Advanced-options)选项告诉FFmpeg在最短的输入流结束时停止写入输出。

命令末尾的./videos/{video_name} 选项指定了我们输出文件的路径。

如果你感到好奇,下面是上面讨论的所有FFmpeg波形模式的作用和它们的外观。

Point 为每个样本画一个点。

Point waveform mode

Line ,为每个样本画一条垂直线。

Line waveform mode

P2p 为每个样本画一个点,在它们之间画一条线。

P2p waveform mode

Cline 为每个样本画一条居中的垂直线。这就是我们在本教程中使用的。

Cline waveform mode

turn_audio_to_video() 函数的下面添加以下代码。

def main():
    rec_name = "rec_1.mp3"
    rec_duration = get_rec_duration(rec_name)
    turn_audio_to_video(rec_name,rec_duration)


main()

在这个新引入的代码中,我们有一个名为main() 的函数。在其中,我们将录音名称存储在一个名为rec_name 的变量中。你应该更新这一行,以包括你自己的录音文件的名称。

之后,我们调用get_rec_duration() 函数来获得录音时间。

然后,我们用录音名称和持续时间调用turn_audio_to_video 函数,并将返回的值存储在一个名为video_with_sound_waves 的变量中。

最后,我们调用main() 函数来运行整个过程。记住用你想处理的录音的名字替换rec_name 变量的值。

回到你的终端,运行以下命令来生成视频。

python main.py

videos 目录中寻找一个名为video_with_sound_waves.mp4 的文件,打开它,你应该看到与下面类似的东西。

Waveform rendering

在背景之上添加视频

在本节中,我们将在生成的视频的左下角添加一个旋转的唱片视频。我们要添加的视频存储在videos 目录中名为spinningRecord.mp4 的文件中。

Spinning record animation

回到你的代码编辑器,打开main.py 文件,并在turn_audio_to_video() 函数下面添加以下代码。

def add_spinning_record(video_name, rec_duration):
    video_path = "./videos/{}".format(video_name)
    spinning_record_video_path = "./videos/spinningRecord.mp4"
    new_video_name = "video_with_spinning_record.mp4"

    command = 'ffmpeg -y -i {video_path} -stream_loop -1 -i {spinning_record_video_path} \
    -t {rec_duration} -filter_complex "[1:v]scale=w=200:h=200[fg]; \
    [0:v] scale=w=1280:h=720[bg], [bg][fg]overlay=x=25:y=(H-225)" \
    -c:v libx264 -preset fast -crf 18 -c:a copy \
    ./videos/{new_video_name}'.format(
        video_path=video_path,
        spinning_record_video_path=spinning_record_video_path,
        rec_duration=rec_duration,
        new_video_name=new_video_name,
    )

    print(new_video_name)
    run_command(command)
    return new_video_name

在这里,我们已经创建了一个名为add_spinning_record() 的函数。这个函数将负责在显示声波的视频上添加spinningRecord.mp4 视频。它的参数是先前生成的视频的名称(video_name )和录制的时间(rec_duration )。

这个函数也运行FFmpeg。下面是该命令的详细内容。

$ ffmpeg -y -i {video_path} -stream_loop -1 -i {spinning_record_video_path} \
-t {rec_duration} -filter_complex \"[1:v]scale=w=200:h=200[fg]; \
 [0:v] scale=w=1280:h=720[bg], [bg][fg]overlay=x=25:y=(H-225)\" \
-c:v libx264 -preset fast -crf 18 -c:a copy ./videos/{new_video_name}

上面的命令有以下选项。

-y,-t,-c:v,-preset, 和-crf 选项与生成音波的FFmpeg命令相同。

-i 选项之前也用过,但在这种情况下,我们有2个视频作为输入文件,即上一步生成的视频文件,以及旋转的记录视频文件。

[-stream_loop](https://ffmpeg.org/ffmpeg.html#Stream-copy)选项允许我们设置一个输入流被循环的次数。值为0意味着禁止循环,而-1意味着无限循环。我们将旋转的记录视频设置为无限循环。这将使FFmpeg无限制地对输出视频进行编码,但由于我们也指定了输出视频的持续时间,当视频达到这个持续时间时,FFmpeg将停止编码。

-filter_complex 选项:也与之前的功能相同,但这里我们有2个视频作为输入文件,即上一节中生成的视频[0:v] ,以及旋转记录的视频[1:v]

该过滤器首先使用 [scale](https://ffmpeg.org/ffmpeg-filters.html#scale)来调整旋转记录视频的大小,使其具有200x200的尺寸,并给它分配了[fg] 的标签。然后,我们再次使用scale 过滤器,将上一节中创建的视频设置为1280x720的尺寸,并给它加上[bg] 的标签。最后,我们使用 [overlay](https://ffmpeg.org/ffmpeg-filters.html#overlay)过滤器将旋转的记录视频放在上一节创建的视频上面,坐标为x=25 ,和y=H-225 (H代表视频高度)。

-c:a 选项也是在上一节中介绍的,但在本例中,我们使用特殊值copy ,告诉ffmpeg从源视频中复制音频流而不重新编码。

命令的最后部分,./videos/{new_video_name} ,设置我们的输出文件的路径。

用下面的内容替换main() 函数内的代码,它增加了对add_spinning_record() 函数的调用。

def main():
    rec_name = "rec_1.mp3"
    rec_duration = get_rec_duration(rec_name)
    video_with_sound_waves = turn_audio_to_video(rec_name, rec_duration)
    add_spinning_record(video_with_sound_waves, rec_duration)

在你的终端中运行以下命令来生成一个视频。

python main.py

videos 目录中寻找一个名为video_with_spinning_record.mp4 的文件,打开它,你应该看到与下面类似的内容。

Waveform rendering with spinning record

为视频添加文本

在本节中,我们将在视频的顶部部分添加一个标题。作为其中的一部分,我们将学习如何使用FFmpeg来绘制文本,改变颜色、大小、字体和位置。

回到你的代码编辑器,打开main.py 文件,并在add_spinning_record 函数下面添加以下代码。

def add_text_to_video(video_name):
    video_path = "./videos/{}".format(video_name)
    new_video_name = "video_with_text.mp4"
    font_path = "./fonts/LeagueGothic-CondensedRegular.otf"

    command = "ffmpeg -y -i {video_path} -vf \"drawtext=fontfile={font_path}:  \
    text='Turning your Twilio voice recordings into videos':fontcolor=black: \
    fontsize=90:box=1:boxcolor=white@0.5 \
    :boxborderw=5:x=((W/2)-(tw/2)):y=100\" \
    -c:a copy ./videos/{new_video_name}".format(
        video_path=video_path,
        font_path=font_path,
        new_video_name=new_video_name
    )

    print(new_video_name)
    run_command(command)
    return new_video_name

在这个函数中,我们创建了一个名为add_text_to_video() 的函数,它调用了一个新的FFmpeg命令来绘制文本。让我们仔细看看这个FFmpeg命令。

ffmpeg -y -i {video_path} -vf \"drawtext=fontfile={font_path}:  \
text='Turning your Twilio voice recordings into videos':fontcolor=black: \
fontsize=90:box=1:boxcolor=white@0.5:boxborderw=5:x=((W/2)-(tw/2)):y=100\" \
-c:a copy ./videos/{new_video_name}

-y ,和-c:a 选项的使用与之前完全一样。

-i 选项,定义了输入,现在只有一个输入文件,即上一节中生成的视频文件。

-[vf](https://ffmpeg.org/ffmpeg.html#Stream-copy)选项允许我们创建一个简单的filtergraph ,并使用它来过滤流。这里我们用这个 [drawtext](https://ffmpeg.org/ffmpeg-filters.html#drawtext-1)过滤器在视频顶部绘制文本,有一些参数:fontfile 是用于绘制文本的字体文件,text 定义了要绘制的文本(可以根据你的喜好自由改变),fontcolor 设置文本颜色为黑色,fontsize 设置文本大小,box 在文本周围启用一个框,boxcolor 设置这个框的颜色为white ,不透明度为50%,boxborderw 设置边界框的宽度,x and y 设置文本在视频中要打印的位置。我们用一点数学方法把文字画在中间。

最后的./videos/{new_video_name} 选项设置输出文件,就像以前的FFmpeg命令一样。

用下面的版本替换main() 函数中的代码,其中增加了标题步骤。

def main():
    rec_name = "rec_1.mp3"
    rec_duration = get_rec_duration(rec_name)
    video_with_sound_waves = turn_audio_to_video(rec_name, rec_duration)
    video_with_spinning_record = add_spinning_record(video_with_sound_waves, rec_duration)
    video_with_text = add_text_to_video(video_with_spinning_record)

回到你的终端,运行以下命令来生成一个带标题的视频。

python main.py

videos 目录中寻找一个名为video_with_text.mp4 的文件,打开它,你应该看到与下面类似的内容。

Waveform rendering with spinning record and title

结语

在本教程中,我们学习了如何使用FFmpeg中的一些高级选项,将一段语音录制成可以在社交媒体上分享的视频。我希望这能鼓励你学习更多关于FFmpeg的知识。