写点什么

MoviePy - 中文文档 (一个专业的 python 音视频编辑库) 教程

用户头像
ucsheep
关注
发布于: 2021 年 03 月 24 日
MoviePy - 中文文档(一个专业的python音视频编辑库)教程

MoviePy 是一个用于视频编辑的 python 模块,你可以用它实现一些基本的操作(比如视频剪辑,视频拼接,插入标题),还可以实现视频合成,还有视频处理,抑或用它加入一些自定义的高级的特效。总之,它的功能还是蛮丰富的。此外,MoviePy 可以读写绝大多数常见的视频格式,甚至包括 GIF 格式!是不是很兴奋呢?基于 Python2.7 以上的版本,MoviePy 可以轻松实现跨平台,Mac/Windows/Linux 统统没问题,这也以意味着,MoviePy 项目可以部署到服务端,在服务端进行视频处理。真是福音啊!


【PS:现在好多的自媒体公司频繁在各个平台进行视频搬运,对于视频原创性的要求越来越高,用 MoviePy 可以批量实现视频编辑,结合 MoviePy 的跨平台特性,可以实现在服务端视频采集,自动处理,发布的流水线作业】


MoviePy 开发起来是很简便的,来看一个操作例子(在 IPython notebook 环境下开发)


快速上手:

1.下载和安装

其实还有更简单的办法,让你快速使用 MoviePy,告别繁琐的安装步骤,如果你会使用 docker 的话,直接跳到本文末尾,用 docker 使用 MoviePy

1.1 环境确认

首先,确保当前系统中 Python 版本在 2.7 以上,pip 正常工作

对于 Mac 用户,系统自带 python,有可能大家会自己安装 python,比如我是使用 python3.7,系统中同时存在 python2.7 和 3.7,在下面的过程中,如果使用 python3 来安装 moviepy 的话,需要将命令 python 替换为 python3。

1.2 安装 MoviePy

方法 1:pip 自动安装(推荐)

pip install moviepy
复制代码

运行以上代码,理论上就安装好 MoviePy 了,如果你没有 ez_setup 的话,系统也会自动安装。

但是,如果同时没有 ez_setup 和 setup_tools 的话,会安装失败,这个时候莫慌!

依次执行下面的命令

python setup.py installpip install moviepy
复制代码

此时的你应该已经完工嗑瓜子了

方法 2:手动安装

你可以在后面任意一个地方下载源码。【PyPI】或【Github

将所有源码解压到一个目录下,在该目录下命令行运用

python setup.py install
复制代码

1.3 环境介绍

MoviePy 依赖的 python 库有 NumpyimageioDecorator, 和 tqdm这些都会在安装 MoviePy 的时候自动安装.

MoviePy 的视频读写依赖于我们大名鼎鼎的 FFMPEG,你也无需担心,如果你之前没有安装过的话,FFMPEG 应该在 ImageIO 安装的时候就被自动下载和安装了(当然,这有可能会话费一段时间),有一点值得注意的是,MoviePy 对于 FFMPEG 的版本要求是 3.2.4,一般自己安装会高于这个版本,所以会报错,我们可以参考源码文件 moviepy/configdefaults.py 修改 FFMPEG_BINARY 属性,进而使用自定义的版本,如果害怕出错,让程序帮助你自动安装会是一个不错的选择。

此外 MoviePy 是需要运行在 Python2.7 以上的; 如果你在安装过程中遇到问题,可以联系我。

2.快速上手-MoviePy

2.1 快速预览

快速了解

下面的内容介绍以下 MoviePy 适用于何种场景以及 MoviePy 如何工作。

我需要使用 MoviePy 吗?

出于以下的情景或原因,我们可能会有使用 Python 做视频编辑的需求。

  • 我们有大量的视频需要处理,或者采用复杂的方式将他们拼接。

  • 我们需要在服务端自动地创建大量视频或者 GIF 图。

  • 我们需要在视频中创建视频编辑器中所没有的一些特殊的特效,我们只能敲代码来实现。

  • 为其他 Python 库(例如:Matplotlib, Mayavi, Gizeh, scikit-images)生产的图片创建动画效果。

当然,MoviePy 并不是万能的,下面这样的需求,MoviePy 也无能为力。

  • 当我们需要逐帧的做图像分析时(例如人脸检测),这真的不是 MoviePy 的强项,不如使用 ImageIO,OpenCV,SimpleCV 这样专业的库去处理

  • 我们仅仅是要将一段视频,或者一系列图片接进一个目标视频中时,我们使用 ffmpeg 就搞定了,也不用强行使用 MoviePy

MoviePy 的优点与局限

MoviePy 在开发之初,就秉承着下面的理念

  • 简单直观,基本操作一般一行代码搞定。对于初学者,代码很容易理解和学习。

  • 灵活弹性,开发者拥有对视频或者音频中每一帧的全部控制权,这也使得我们在创建自定义效果时得心应手。

  • 跨平台,使用的 ffmpeg 各个平台都有,可以移植到不同的平台运行。

MoviePy 的局限性如下:

  • 不支持流媒体,它也确实不是为了处理这样的视频而设计的。

  • 当同时使用太多(官网说>100 认为提阿多)的视频,音频,或者图片的时候,我们有可能会遇到内存问题。内存问题亟待优化。

代码示例

在一个 MoviePy 脚本中,我们可以加载视频和音频,然后修改它们,将他们合并,然后把最终结果写入到一个新的文件中。下面的例子,加载视频,在视频中间添加一个标题显示 10 秒钟,然后把结果写入到一个新的文件内。

# 导入需要的库from moviepy.editor import * # 从本地载入视频myHolidays.mp4并截取00:00:50 - 00:00:60部分clip = VideoFileClip("myHolidays.mp4").subclip(50,60) # 调低音频音量 (volume x 0.8)clip = clip.volumex(0.8) # 做一个txt clip. 自定义样式,颜色.txt_clip = TextClip("My Holidays 2013",fontsize=70,color='white') # 文本clip在屏幕正中显示持续10秒txt_clip = txt_clip.set_pos('center').set_duration(10) # 把 text clip 的内容覆盖 video clipvideo = CompositeVideoClip([clip, txt_clip]) # 把最后生成的视频导出到文件内video.write_videofile("myHolidays_edited.webm")
复制代码

MoviePy 如何工作

MoviePy 使用软件 ffmpeg 读取和导出视频和音频文件,使用 ImageMagick 生产文字和 GIF 图。这些处理过程都有赖于 Python 强大的数学处理库,高级特效和软件加强用到了许多的 Python 图像处理库。

基本概念

MoviePy 中最核心的对象就是 clipsAudioClips 或者 VedioClips,开发者可以对 clips 进行修改(剪切,调速度,调亮度...)或者和其他 clip 混合拼接到一起。使用 PyGame 或者 IPython NoteBook 还可以预览。

VedioClips 可以由视频文件,图像,文本或者动画来创建实例。vedio clip 可以拥有一个音频轨道(audio clip) 和一个叠加层的 vedio clip(这是一个特殊的 VedioClip,这意味着,当一个视频和其他 VedioClip 混合的时候,这个叠加层 clip 是隐藏的)

一个 clip 可以被 MoviePy 中多多种效果作用,比如(clip.resize(width="360")clip.subclip(t1,t2), or clip.fx(vfx.black_white)).

当然,也可以被用户自定义的效果作用。MoviePy 实现了许多类似(clip.fl, clip.fx)这样的方法,可以简便地修改效果。

moviepy.video.tools 里面,还可以找到许多好东西,实现很多高级功能,比如跟踪视频中的一个对象,画一些简单的图形,斜线,或者颜色,制作副标题等等效果

最后,即使 moviepy 没有图形化的用户交互界面,但是在我们高质量地加工我们的视频的时候,我们仍然有很多可以允许很好地控制和调节脚本的预览方式。

2.2 视频合成

很久没有更新了。目前使用 moviepy 的项目里各种问题接踵而至,第一批我只采集了 1w 个抖音短视频做视频合成,不得不说,moviepy 还是不少缺陷的,最开始的大部分时候是 ffmpeg 引起的 OOM,是我分配的内存不够,加大资源分配,每次运行完毕之后清除 cache,杀掉已经存在的 ffmpeg,稳定运行时间达到了 8 个小时多,然而在昨晚凌晨又死掉了。现在还在收拾,今天再来更新一下。

Mixing clips(就是视频合成,拼接混合)

视频的排版,众所周知,这是一个非线性编辑的技术活,有时候还要把几段 clip 在一个新的 clip 里面一起播放。

还有一点很重要,一般情况下,每一个 video clip 都带有一个 audio 和一个 mask,分别是 audio clip 和 mask clip,通俗地讲就是每一个视频切片带有一个音频切片和一个蒙版切片。所以,当我们把几个视频排版混合到一起的时候。声音轨道和 mask 也同时被混合在一起形成最终的 clip。所以但多数时候我们是不需要操心声音和 mask 的。

视频叠加和视频拼接

简单地把多个视频合成到一起的两种最简单的办法。1.把视频一个接一个地拼接起来。2.视频叠加在一块,比如一个大的画面同时播几个视频。

视频拼接我们使用 concatenate_videoclips 函数来完成。

from moviepy.editor import VideoFileClip, concatenate_videoclips clip1 = VideoFileClip("myvideo.mp4")clip2 = VideoFileClip("myvideo2.mp4").subclip(50,60)clip3 = VideoFileClip("myvideo3.mp4") finalclip = concatenate_videoclips([clip1, clip2, clip3])finalclip.write_videofile("my_concatenate.mp4")
复制代码

finalclip 会按照 clip1,clip2,clip3 的顺序将这三个 clip 播放。这些 clip 并不需要相同的时长或者大小,仅仅是首尾相连而已。我们还可以通过 transition=my_clip 这个参数来设置一下 clip 之间衔接的过渡动画。

视频叠加我们使用 clip_array 函数来完成

from moviepy.editor import VideoFileClip, clips_array, vfx clip1 = VideoFileClip("myvideo.mp4").margin(10)clip2 = clip1.fx(vfx.mirror_x)#x轴镜像clip3 = clip1.fx(vfx.mirror_y)#y轴镜像clip4 = clip1.resize(0.6)#尺寸等比缩放0.6 final_clip = clips_array([                            [clip1, clip2],                            [clip3, clip3]                        ])final_clip.resize(width=480).write_videofile("my_stack.mp4")
复制代码

举个栗子,大家得到画面差不多和这个相似

CompositeVideoClips

CompositeVideoClip 这个类提供来更加灵活的方式来排版视频,但是它可要比 concatenate_videoclips 和 clips_array 要复杂的多了。

video = CompositeVideoClip([clip1, clip2, clip3])
复制代码

现在,这 3 个 clip 是堆叠起来的,啥情况呢,就是,clip2 压在 clip1 上,clip3 压在最上面。大家可以想像成栈结构,从左往右,依次入栈这么堆叠在一块,对于一个普通的视频来说,这样堆叠实在是没办法看了。你想像一下,如果 clip3 的尺寸是最大的,比 clip1 和 clip2 都大,那我们只能看到 clip3,clip1 和 clip2 都被盖住了。最终要合成的 clip 的尺寸是默认第一个 clip 的尺寸,一般第一个也就是做背景用了,但是我们有时候是需要我们的 clip 浮在一个比较大的合成体的上方的,那这个时候我们就需要声明合成 clip 的尺寸。看看下面的栗子。

video = CompositeVideoClip([clip1, clip2, clip3], size=(720,480))
复制代码

开始和结束时间

在堆叠视频中,每个 clip 会在通过 clip.start 函数声明的时间开始播放,我们可以像下面这样去设置。

clip1 = clip1.set_start(5) #在5秒时开始
复制代码

所以一般情况下,我们代码中的视频堆叠都长这个样子。

video = CompositeVideoClip([                            clip1, #在第0秒开始                            clip2.set_start(5), #在第5秒开始                            clip3.set_start(9)  #第9秒开始                            ])
复制代码

在下面的栗子里,可能 clip2 开始的时候,正好是 clip1 将要结束的时机,这样的情况,我们可以让 clip2 使用在一秒内“渐入”的特效来显示。

video = CompositeVideoClip([                               clip1,                            clip2.set_start(5).crossfadein(1),                            clip3.set_start(9).crossfadein(1.5)                            ])
复制代码

Positioning clips

clip 们的位置设定。


clip2 和 clip3 都比 clip1 要小,那你可以通过设定位置决定 clip2 和 clip3 在画面中的位。下面的栗子就是通过指定坐标的形式(距离左上方的像素距离)把 clip2 和 clip3 在画面中指定位置。

video = CompositeVideoClip([                            clip1,                            clip2.set_pos((45,150)),                            clip3.set_pos((90,100))                            ])
复制代码

在 moviepy 中我们有很多的方法定位 clip 的位置

clip2.set_pos((45,150)) #像素坐标clip2.set_pos("center") #居中clip2.set_pos(("center","top")) #水平方向居中,但是处置方向放置在顶部clip2.set_pos(("left","center")) #水平方向放置在左边,垂直方向居中clip2.set_pos((0.4,0.7), relative=True) #0.4倍宽,0.7倍高处clip2.set_pos(lambda t: ('center', 50+t)) #水平居中,向下移动
复制代码

clip 位置的坐标系,在指定位置时,出现在我们脑海里的坐标系应该是这样子的

Compositing audio clips

合成声音 clips


在混合 vedio clips 时,MoviePy 将会自动地把这些 vedio 自带的 audio clip 按照一定形式混合在一起形成最终的 clip,开发者不需要为了这个操心。


如果你有一些特殊的的定制合成音频的需求,应该使用 CompositeAudioClip 和 concatenate_audioclips 这俩类。

from moviepy.editor imports * concat = concatenate_audioclips([clip1, clip2, clip3])compo = CompositeAudioClip([                            aclip1.volumex(1.2),                            aclip2.set_start(5), # start at t=5s                            aclip3.set_start(9)                            ])
复制代码

2.3 Clips 变换与特效

下面是 MoviePy 中的几种对 clip 的修改:

  • 非常常见的修改 clip 属性的方法有:clip.setduration, clip.setaudio, clip.setmask, clip.setstart 等.

  • 已经实现的特效.clip.subclip(t1,t2):截取 t1 到 t2 时间段内的片段;还有一些高级效果,loop:让 clip 循环播放、timemirror:让 clip 倒播,这些方法位于特殊的模块 moviepy.video.fx, moviepy.audio.fx,应用 clip.fx 方法,比如 clip.fx(timemirror)让视频倒播。

  • 可你自己创造一些需要的特效。

以上的特效其实本质上并不是原地直接修改的(没有对原始视频修改),而是根据修改产生新的 clip。所以,我们如果想让修改生效,需要将修改过的产生的 clip 赋值给某 clip,保存修改。举个栗子。

my_clip = VideoFileClip("some_file.mp4")my_clip.set_start(t=5) #没有做任何改变,修改会丢失my_new_clip = my_clip.set_start(t=5) #这样才对。moviepy中,修改过的clip要重新赋值给变量,修改才会被保存
复制代码

所以,当我们写出 clip.resize(width=640),moviepy 并不是立刻就逐帧修改 clip。一般只会先修改第一帧,其他的左右的帧只有在需要的时候(最后写入文件或者预览)才会被 resize。

另一方面,可以这样讲,创建一个 clip,几乎是不会占用时间和内存的,几乎所有的计算其实发生在最后转换的时刻。

moviepy 中的时间表示

很多方法都接受时间参数,clip.subclip(tstart,tend),截取两个时间点之间的 clip 片段,在这个方法中,时间既可以用(tstart=230.54),以秒的时间来表示,也可以用(tstart=(3,50.54)),以 3 分 50.54 秒的形式来表示,还可以 (tstart=(0,3,50.54))或者 (tstart=(00:03:50.54)),以,小时,分钟,秒的形式老表示。


大多数没有赋值的时间参数会有一个默认值,比如 clip.subclip(tstart=50),tend 的默认值就是视频的长度,clip.subclip(tend=50),那么 tstart 就默认为 0.当时间是负数的时候,代表倒数 n 秒。比如,clip.subclip(-20, -10)会截取倒数 20 秒到倒数 10 秒之间的片段。

改变 clip 属性的方法

clip.fx

假定我们很多修改 clip 的方法。这些方法呢,都是输入一个 clip 和一些参数,输出一个新的 clip。

effect_1(clip, args1) -> new clipeffect_2(clip, args2) -> new clipeffect_3(clip, args3) -> new clip
复制代码

如果我们要按顺序,依次修改 clip,那么你可能会这样写:

newclip =  effect_3( effect_2( effect_1(clip, args3), args2), args1)
复制代码

但是上面的代码可读性不高,我们可以利用 clip.fx 来实现一种更简练的写法。

newclip = (clip.fx( effect_1, args1)               .fx( effect_2, args2)               .fx( effect_3, args3))
复制代码

看上去是不是好多了。在模块 moviepy.video.fx moviepy.audio.fx 中已经实现来一些修改 clip 的方法,这些 fx 的方法会自动的作用到和 clip 相关的声音和 mask 上,所以我们在修改 clip 的时候并不需要去关心声音和 mask 的处理,除非我们确实要对声音或者 mask 做一些特殊的处理。


在实际应用中,当我们使用 from moviepy import.editor *的时候,这两个模块会被加载为 vfx afx,所以我们会像下面这样写

from moviepy.editor import *clip = (VideoFileClip("myvideo.avi")        .fx( vfx.resize, width=460) # 尺寸变化,保持纵横比        .fx( vfx.speedx, 2) # 2倍速        .fx( vfx.colorx, 0.5)) # 画面调暗
复制代码

为方便起见,当我们使用 moviepy.editor 的时候,比如我们使用 resize 的时候,我们经常会使用 clip.resize(...)这样的简便的写法来代替 clip.fx( vfx.resize, ...)的写法。

创建用户自定义特效的方法

clip.fl

我们可以使用 clip.fl_time、clip.fl_image 或者更普遍的 clip.fl 来修改一个 clip。


我们可以使用 clip.fl_time 修改 clip 的时间线,像下面这样

modifiedClip1 = my_clip.fl_time(lambda t: 3*t)modifiedClip2 = my_clip.fl_time(lambda t: 1+sin(t))
复制代码

现在,modifiedClip1 是 my_clip 的三倍速度播放效果。modifiedClip2 只会在 t=0s 和 t=2s 之间播放。在后一个例子中,我们已经创建了一个无限持续的 clip(现在并不会出什么问题)。


我们也可以用 clip.fl_image 来修改一个 clip 的显示。接下来,将会把一个 clip 每一帧的绿色和蓝色通道转换。

def invert_green_blue(image):    return image[:,:,[0,2,1]]modifiedClip = my_clip.fl_image(invert_green_blue)
复制代码

接下来,我没还有可能需要同时对时间和每一帧的画面都做处理,我们用 clip.fl(filter)方法也是可以实现的,这个 filter 必须是一个传入两个参数,返回一个画面的函数。第一个参数是 get_frame 方法(例如:g(t)给定一个时刻,然后返回在那一时刻这个 clip 中对应的一帧),第二个参数就是时间。

def scroll(get_frame, t):    '''    这个函数返回:当前帧的一个‘区域’    上述所说的‘区域’的位置依赖于第二个参数,时间    '''    frame = get_frame(t)    frame_region = frame[int(t):int(t)+360,:]    return frame_regionmodifiedClip = my_clip.fl(scroll)
复制代码

以上代码,将会向下滚动 360 个像素高度。


当我们在编写一些新的效果的时候,应该尽可能地使用 fl_time fl_image,而不是 fl。原因在于,在这些效果被应用于 ImageClip 的时候,MoviePy 会认为这些方法不需要应用于每一帧,渲染速度会更快。

2.4 如何高效率使用 MoviePy

接下来,这一节将会介绍许多全世界 MoviePy 开发者总结的开发经验。


开始学习使用 MoviePy 最好的方式是使用 IPython Notebook:它可以让预览 clip 变得简单,有自动补全的功能,而且可以显示函数库中不同方法的文档。

我们是不是应该使用moviepy.editor

这个文档中的大部分例子都会用到子模块 moviepy.editor,但是这个子模块并不适用于所有需求。所以,我们应该使用他吗?


简短回答:如果我们是使用 moviepy 手动地编辑视频,那就使用他。但是,如果我们使用的是 MoviePy 内置的庞大的库、程序、或者网络服务,最好还是避免它,只需要加载我们需要的方法就够了。


moviepy.editor 模块可以通过下列的 3 种导入方法的任意一中导入

from moviepy.editor import *    #全部导入,快速,但是不干净   import moviepy.editor as mpy    #干净,使用示例:mpy.VideoClipfrom moviepy.editor import VideoFileClip    #只导入需要的
复制代码

无论使用上面哪一种导入方式,moviepy.editor 模块事实上都会在幕后做大量的工作:加载很多 moviepy 常用的类、方法、还有子包、以及如果安装了 PyGame 的话还会加载 PyGame session,这样,才可以预览 vedio clips,实现很多简便的调用(比如:将一个 clip resize 变化)。这样,我们可以使用 clip.resize(width=240)而不是更长的写法 clip.fx(resize, width=240)。简短说,moviepy.editor 提供给我们在各处播放和编辑所需要的所有的东西,但是这也会消耗一部分时间用于加载(大于 1 秒钟)。所以,如果我们需要的仅仅是 library 中一两个特性的话,最好使用的导入方法是:只导入自己需要的,像下面这样


from moviepy.video.io.VedioFileClipe import VideoFileClipfrom moviepy.video.fx.resize import resize
复制代码

很多预览 clip 的方式

当我们使用 MoviePy 编辑视频或者实现一个效果的时候,我们会进行一些试验、和错误的过程。通常情况我没每一次的试验都会话费相对较长的时间,这一节,会告诉大家很多的小窍门,让这些尝试的过程变得快一点。

clip.save_frame

大多情况下,我们只需要得到一帧画面就可以知道程序有没有正确执行,这样,我们只需要保存一帧画面到一个文件,就可以去验证,像下面这样:

my_clip.save_frame("frame.jpeg")        #保存第一帧my_clip.save_frame("frame.png", t=2)    #保存2s时刻的那一帧
复制代码

clip.show and clip.preview

clip.show clip.preview 这两个方法可以在 Pygame 的窗口中展示。这是最快的预览方式,clip 的生成和显示几乎是同时发生的,而且对于获取一个像素点的颜色和坐标也有用。完成后按 Esc 键退出。


一个 clip 可以按照下面的方式来预览

my_clip.preview() # 默认fps=15 预览my_clip.preview(fps=25)my_clip.preview(fps=15, audio=False) # 不 生成/播放 音频my_audio_clip.preview(fps=22000)
复制代码

假如你在预览的画面中点击了某些地方,将会输出被点击像素的位置坐标和颜色。按 Esc 可以终端预览。


如果当 clip 十分复杂而且电脑配置很渣,运行很慢的时候,预览一般会出现比真实画面速度慢的情况,这个时候,我没可以尝试降低帧率,或者使用 clip.resize 减小 clip 的尺寸。

ipython_display

如果不使用 clip.show()或者 clip.preview(),使用 IPython Notebook 显示 clip 也是一个不错的选择.使用的画面一般是这样的。

通过 ipython_display,我们可以将图片、视频、音频等嵌入,既可以来自于一个文件,也可以来自于一个 clip

ipython_display(my_video_clip)ipython_display(my_image_clip)ipython_display(my_audio_clip) ipython_display("my_picture.jpeg")ipython_display("my_video.mp4")ipython_display("my_sound.mp3")
复制代码

只有当 rendering 在 notebook 的最后一行时才有效。我们也可以把 ipython_display 作为一个 clip 方法调用

my_video_clip.ipython_display()
复制代码

如果我们的 clip 需要一个帧率,我们可以指定 ipython_display 的 fps=25。

如果我们仅仅需要显示一下视频 clip 的快照,那么,我们直接指定 time 就行

my_video_clip.ipython_display(t=15)    #将会显示15s处的快照
复制代码

我们也可以提供任何的 HTML5 选项作为关键词参数,例如:当一个 clip 特别大的时候,我们一般会这么写

ipython_display(my_clip, width=400) # HTML5 将会把他的尺寸变成400像素
复制代码

当我们编辑一个 GIF 格式的动画,并且确保他有完好的循环。我们可以让视频自动开始,并且循环播放。

ipython_display(my_clip, autoplay=1, loop=1)
复制代码

注意:事实上,ipython_display 是将 clip 完全地入我们的 notebook。这样做的优点是无论我们移动 nootbook 还是放到线上,视频都会正常的播放。缺点在于,nootbook 文件的大小将会变得很大。取决于你的浏览器,多次重新计算和显示视频不光是花费很多的时间,也会占用特别多的内存(RAM),重新启动浏览器会解决这个问题。

2.5 使用 matplotlib(一个 2D 绘图库)

用户自定义动画

moviepy 允许开发者自定义动画:定义一个方法,以 numpy 数组的形式在动画中给定的时间返回一帧画面。

一个例子如下:

from moviepy.editor import VideoClip def make_frame(t):    '''    返回在t时刻的一帧画面    '''    # 通过其他的任意第三方库,创建一帧画面    return frmae_from_time_t #(height * width * 3) Numpy arrayanimation = VideoClip(make_frame, duration=3)
复制代码

这样的 animation,通常可以按照 moviepy 中的方式导出,如下:

# 导出为一个视频animation.write_videofile("my_animation.mp4", fps=24)# 导出为一个GIF动图animation.write_gif("my_animation.gif", fps=24) # 一般情况,这种方式会慢点
复制代码

简单的 matplotlib 示例

一个使用 matplotlib 操作动画的例子,如下:

import matplotlib.pyplot as pltimport numpy as npfrom moviepy.editor import VideoClipfrom moviepy.video.io.bindings import mplfig_to_npimage x = np.linspace(-2, 2, 200) duration = 2 fig,ax = plt.subplots()def make_frame(t):    ax.clear()    ax.plot(x, sinc(x**2) + np.sin(x + 2*np.pi/duration * t), lw=3)    ax.set_ylim(-1.5, 2.5)    return mplfig_to_npimage(fig) animation = VideoClip(name_frame, duration=duration)
复制代码

使用 Jupyter Notebook

如果我们在 Jupyter Notebook 写代码的话,我们就可以享受到使用 ipython_display 方法将 VideoClips 嵌入 notebook 的 output 部分。下面就是一个实现案例:

import matplotlib.pyplot as pltimport numpy as npfrom moviepy.editor import VideoClipfrom moviepy.vedio.io.bindings import mplfig_to_npimage x = np.linspace(-2, 2, 200) duration = 2 fig, ax = plt.subplots() def make_frame(t):    ax.clear()    ax.plot(x, np.sinc(x**2) + np.sin(x + 2*np.pi/duration * t), lw=3)    ax.set_ylim(-1.5, 2.5)    return mplfig_to_npimage(fig) animation = VideoClip(make_frame, duration=duration)animation.ipython_display(fps=20, loop=True, autoplay=True)
复制代码

 

2.6 moviepy 中的音频

下面主要是演示在 moviepy 中如何创建和编辑 audio clips。

之前曾经说过,在 moviepy 中,当我们剪切,混合,拼接 video clip 的时候,audio clip 并不需要我们去操心,他会自动的随着 video 完成相应的处理。这篇博文提到的对于 audio clip 的操作主要是为了开发者的两种情况:首先,那就是我们有兴趣只对音频做处理,其次就是我们希望为 video 配自定义的音频。

什么是 audio clips

AudioClips 和 moviepy 中 VideoClips 的概念很像:autio clip 有长度,像 video clip 一样可以被剪切,组合等等。一个显著的区别在于 audioclip.get_frame(t)

创建一个新的 audio clip

audio clip 创建可以有两种方式,一种是从一个文件直接创建 autio clip,第二种办法呢,就是从一个视频文件中提取声音的部分。

from moviepy.editor import *audioclip = AudioFileClip("some_audiofile.mp3")audioclip = AudioFileCLip("some_video.avi")
复制代码

另外,也可以从已经创建的 VideoClip 对象中直接获取 audio clip

videoclip = VideoFileClip("some_video.avi")audioclip = videoclip.audio
复制代码

合成音频(待续)

导出和预览 audio clips

我们可以将一个 audio clip 分配到一个 vedio clip 声音轨道上

videoclip2 = videoclip.set_audio(my_audioclip)
复制代码

2.7 创建和导出 vedio clips

video 和 audio clips 是 moviepy 中的核心的对象。这篇博文,我们会介绍不同的短 clip,展示如何创建他们,以及如何将它们导出到文件中。

接下来的代码是总结好的我们可以通过 moviepy 创建的一些基本的 clips。

# VIDEO CLIPSclip = VideoClip(make_frame, duration=4) # 自定义动画clip = VideoFileClip("my_vedio_file.mp4") # 文件格式还可以是avi、webm、 gif等clip = ImageSequenceClip(['imagefile.jpeg', ... ], fps=24)clip = ImageClip('my_picture.png') # 文件格式还可以是 png、tiff等clip = TextClip('Hello!', font="Amiri-Bold", fintsize=70, color='black')clip = ColorClip(size=(460, 380), color=[R,G,B]) # AUDIO CLIPSclip = AudioFileClip("my_audio_file.mp3") #文件格式还可以是ogg、wav或者也可以是一个vedioclip = AudioArrayClip(numpy_array, fps=44100) # 一个numpy数组clip = AudioClip(make_frame, duration=3) # 使用一个方法make_frame(t)
复制代码

video clip 的分类

video clip 好比是较长的 video 的积木块。从技术上讲,这些 clip 都是使用 clip.get_frame(t)方法,在某一时间 t 获得的 HxWx3 numpy 格式的剪辑帧的数组。主要有两种分类,首先是动画形式的 clip(包括 VideoClip、VideoFileClip);其次是非动画形式的 clip,循环显示相同的画面(包括 ImageClip、TextClip、ColorClip 都在此列)。还有一种特殊的 clip,那就是 masks,它属于上面的类别,但是他输出的灰色帧表示在另一个剪辑中哪些部分是可见的还有哪些部分是不可见的。

VideoClip

VideoClip 是 movipy 中其他所有的 vedio clips 的父类。如果你的需求仅仅是编辑视频文件,你是不会用到他的。事实上,一般只有在我们通过一些第三方的库生成动画的时候才会用到这个类。我们所需要做的,就是定义一个 make_frame(t)函数,这个函数在一个给定的时刻 t 返回 HxWx3 形式的 numpy 数组。下面是一个使用第三方图像库 Gizeh 的例子:

import gizehimport moviepy.editor as mpy def make_frame(t):    surface = gizeh.Surface(128, 128) # 宽、高    radius = W*(1+ (t*(2-t))**2 )/6 # 半径随时间变化    circle = gizeh.circle(radius, xy=(64,64), fill(1,0,0))    circle.draw(surface)    return surface.getnpimage() # 返回一个8bit RGB数组 clip = mpy.VideoClip(make_frame, duration=2)clip.write_gif('circle.gif', fps=15)
复制代码

注意:在上面的 make_frame 函数并没有一个默认的帧率,所以我们必须给 write_gif 或者 write_videofile 提供一个 fps(frames per second).

VideoFileClip

VideoFileClip 就是从视频文件(大部分的视频格式都支持)或者 GIF 格式文件读取生成的 clip。我们可以像下面这样去加载一个 video。

myclip = VideoFileClip("some_video.avi")myclip = VideoFileClip("some_animation,gif")
复制代码

每一个 clip 都有一个 fps 参数,当我们使用write_videofilewrite_gif等去将一个clip切分成小的clip去保存的时候,原有的clip的fps会默认地传递给新产生的clip。


myclip = VideoFileClip("some_video.avi")print(myclip.fps) # 输出为30# 剪辑这个clip 的10s-25s,这会保存fpsmyclip2 = myclip.subclip(10, 25)myclip2.write_gif("test.gif") # 这个gif文件的fps=30
复制代码

ImageSequenceClip

顾名思义,就是一系列图片创建的 clip,你可以像下面这样调用:

clip = ImageSequenceClip(images_list, fps=25)
复制代码

images_list 可以有三种形式:


  1. 图片 name list,将会按照 list 顺序播放

  2. 一个目录,这个目录下面的所有的图片都会被播放,按照字母或者数字的顺序播放

  3. 一系列 frames (Numpy arrays),从其他的 clip 剪辑得到的,顺序是按照 list 顺序播放


当我们使用第一种或者第二种的时候,我们可以通过选择 load_images=True 来指定是否将所有的图片直接加载到 RAM 中。当然,这样的操作仅仅是在我们的图片不是很多,且每张图片都被使用超过 1 次的时候。

ImageClip

ImageClip 就是一个一直播同一张图片的 vedio clip,我们可以按照下面的方式创建:

myclip = ImageClip("some_pic.jpeg")myclip = ImageClip(some_array) # 一个(高 × 宽 × 3)RGB numpy 数组myclip = some_video_clip.to_ImageClip(t = '01:00:00') # 在1h时刻的帧画面
复制代码

TextClip

生成一个 TextClip 需要安装 ImageMagick,并且和 moviepy 关联。

下面是一个创建 TextClip 的案例

myclip = TextClip('Hello!', font='Amiri-Bold')
复制代码

注意:这里的 font 可以是任何安装在你的电脑上的 font,如果照抄这里的代码,有可能会有问题,因为你的电脑上并没有这个样式。那么如何查看当前设备可用的样式呢?

TextClip.list('fonts') # 可以获得当前设备所有可用的fonts
复制代码

ImageMagick 对样式也有自己的指定的名称,比如:通常的 normal Amiri 被称为 Amiri-Regular,Impact font 被称为 Impact-Normal

查找所有给定字体相关的所有字体,使用下面的方法:


TextClip.search('Amiri','fonts') # 查找所有包含Amiri的字体
复制代码

注意:使用笔划或者(轮廓)的话,对于较小的字母来说不会有很好的结果。如果我们需要一个小的具有轮廓的 TextClip,我们最好是先生成一个大点的文字,然后再将它缩小,这样的效果才会好。

myclip = TextClip('Hello!', fontsize=70, stroke_width=5).resize(height=15)
复制代码

TextClip 是有很多很多的可选项的,对齐、字距(字母距离)、笔划大小、背景、字体环绕等等。如果需要详细的信息,可以去官网参考手册去找或者等待后续中文文档更新。

Mask clips

mask clip 是一种特殊的 vide clip,当一个带有 mask 的 video clip -- A 和其他的 video clips 合成的时候,可以用来指示哪些像素是可见的,哪些是不显示的。在导出 GIF 或者 PNG 文件的时候,mask 也可以用来定义透明度。

mask clip 和普通的 vedio clip 最基本的不同在于:


  • 标准的 clip 的每一帧中的每一个像素是由 3 个色元即 R-G-B 组合成的,取值范围是 0-255。(0-255, 0-255,0-255)

  • mask clip 每个像素仅一个色元组件, 取值范围只有 0-1。要么着一个像素可见,要么这个像素透明。

另一方面看,mask 只有灰色的通道。

当我们创建或者加载一个带有 mask 的 clip 的时候,我们会像下面这样声明:

maskclip = VideoClip(makeframe, duration=4, ismask=True)maskclip = ImageClip('my_mask.jpeg', ismask=True)maskclip = VideoFileClip("myvideo.mp4", ismask=True)
复制代码

将一个 mask clip 覆盖在一个和他同样尺寸的 clip 时使用

myclip.set_mask(mask_clip)
复制代码

一些通过一个 alpha 层来支持透明度的图片格式,比如 PNG,moviepy 中应该使用 mask 来对应:

myclip = ImageClip("image.png", transparent=True) # 默认为Truemyclip.mask # 图片的alpha层
复制代码

任何一个 video clip 都可以通过 clip.to_mask()转换为一个 mask,任何一个 mask 也可以通过 my_mask_clip.to_RGB()转换为标准的 RGB video clip.

导出 video clips

video 文件(.mp4 , .webm, .ogv...)

将一个 clip 写入一个文件

myclip.write_videofile('movie.mp4') # 默认编码 'libx264', 24 fpsmyclip.write_videofile('movie.mp4', fps=15)myclip.write_videofile('movie.webm')myclip.write_videofile('movie.webm', audio=False) # 不使用音频
复制代码

MoviePy 对于大多数的扩展名都有默认的编解码,如果我们希望使用其他的编解码,或者我们不喜欢默认的编解码的话,我们可以通过 codec='mpeg4'来使用自己指定的编解码。当我们导出为一个文件的时候,也有许多的可选项(比特率,是否将音频也导出,文件大小优化,使用处理器个数,等等)

有些时候,我们使用 moviepy 是无法获得一个 clip 的 duration 这个值的。然后,duration 这个必须通过 clip.set_duration()来指定:

# 创建一个clip,显示一个花的图片5秒钟my_clip = Image('flower.jpeg') # 现在是无限时间,不停地循环my_clip.write_videofile('flower.mp4') # 这一步将会出错,因为没有指定durationmy_clip.set_duration(5).write_videofile('flower.mp4') # 正确执行
复制代码

GIF 动画

将 vedio clip 导出为一个 gif 动画

my_clip.write_gif('test.gif', fps=12)
复制代码

注意:需要安转 ImageMagick。此外,我们还可以通过添加选项 program=‘ffmpeg‘来使用 ffmpeg 生成 gif 动画。使用 ffmpeg 生成 gif 动图,虽然要快一点,但是看起来效果可能会不太好,而且没有办法去优化。

导出图片

我们可以将某一帧导出为图片

myclip.save_frame('frame.png') # 默认保存第一帧画面myclip.save_frame('frame.jpeg', t='01:00:00') # 保存1h时刻的帧画面
复制代码

如果这些 clip 有 mask 的话,mask 将会作为图片的 alpha 层被保存。除非我们使用 withmask=False 指定不保存 mask。

3.效果展示

这里都是一些 moviepy 的实现,包括视频特效、Gif 动画、3D 特效果等等很炫的例子。

传送门:MoviePy画廊

有一些是 youtube 的视频链接,需要上网,还有一些 youtube 链接失效了。

这边放几个炫酷的例子吊吊胃口!




4.MoviePy 实战案例

4.1 炫动的字母特效

这个例子,一定能很好的地说明为什么大家有兴趣基于 MoviePy 脚本来实现一些特效,我们也可以想像一下如果是手工做的话,绝壁手抽筋了。

代码实现 

import numpy as npfrom moviepy.editor import *from moviepy.video.tools.segmenting import findObjects # 目标是创建炫动的文字,先创建TextClip,然后设置它居中 screensize = (720,460)txtClip = TextClip('Cool effect',color='white', font="Amiri-Bold",                   kerning = 5, fontsize=100)cvc = CompositeVideoClip( [txtClip.set_pos('center')],                        size=screensize) # 下面的四个函数,定义了四种移动字母的方式 # helper functionrotMatrix = lambda a: np.array( [[np.cos(a),np.sin(a)],                                  [-np.sin(a),np.cos(a)]] ) def vortex(screenpos,i,nletters):    d = lambda t : 1.0/(0.3+t**8) #damping    a = i*np.pi/ nletters # angle of the movement    v = rotMatrix(a).dot([-1,0])    if i%2 : v[1] = -v[1]    return lambda t: screenpos+400*d(t)*rotMatrix(0.5*d(t)*a).dot(v)    def cascade(screenpos,i,nletters):    v = np.array([0,-1])    d = lambda t : 1 if t<0 else abs(np.sinc(t)/(1+t**4))    return lambda t: screenpos+v*400*d(t-0.15*i) def arrive(screenpos,i,nletters):    v = np.array([-1,0])    d = lambda t : max(0, 3-3*t)    return lambda t: screenpos-400*v*d(t-0.2*i)    def vortexout(screenpos,i,nletters):    d = lambda t : max(0,t) #damping    a = i*np.pi/ nletters # angle of the movement    v = rotMatrix(a).dot([-1,0])    if i%2 : v[1] = -v[1]    return lambda t: screenpos+400*d(t-0.1*i)*rotMatrix(-0.2*d(t)*a).dot(v)   # WE USE THE PLUGIN findObjects TO LOCATE AND SEPARATE EACH LETTER letters = findObjects(cvc) # a list of ImageClips  # 让字母动起来def moveLetters(letters, funcpos):    return [ letter.set_pos(funcpos(letter.screenpos,i,len(letters)))              for i,letter in enumerate(letters)] clips = [ CompositeVideoClip( moveLetters(letters,funcpos),                              size = screensize).subclip(0,5)          for funcpos in [vortex, cascade, arrive, vortexout] ] # 连接,写入文件final_clip = concatenate_videoclips(clips)final_clip.write_videofile('../../coolTextEffects.avi',fps=25,codec='mpeg4')
复制代码


4.2 重新构建 15 世纪舞蹈视频


# -*- coding: utf-8 -*- """Result: https://www.youtube.com/watch?v=Qu7HJrsEYFgThis is how we can imagine knights dancing at the 15th century, based on a veryserious historical study here: https://www.youtube.com/watch?v=zvCvOC2VwDcHere is what we do:0- Get the video of a dancing knight, and a (Creative Commons) audio music file.1- load the audio file and automatically find the tempo2- load the video and automatically find a segment that loops well3- extract this segment, slow it down so that it matches the audio tempo,   and make it loop forever.4- Symmetrize this segment so that we will get two knights instead of one5- Add a title screen and some credits, write to a file.This example has been originally edited in an IPython Notebook, which makes iteasy to preview and fine-tune each part of the editing.""" from moviepy.editor import *from moviepy.video.tools.cuts import find_video_periodfrom moviepy.audio.tools.cuts import find_audio_period  # Next lines are for downloading the required videos from Youtube.# To do this you must have youtube-dl installed, otherwise you will need to# download the videos by hand and rename them, as follows:#     https://www.youtube.com/watch?v=zvCvOC2VwDc => knights.mp4#     https://www.youtube.com/watch?v=lkY3Ek9VPtg => frontier.mp4 import osif not os.path.exists("knights.mp4"):    os.system("youtube-dl zvCvOC2VwDc -o knights.mp4")    os.system("youtube-dl lkY3Ek9VPtg -o frontier.mp4")#==========  # LOAD, EDIT, ANALYZE THE AUDIO  audio = (AudioFileClip("frontier.mp4")         .subclip((4,7), (4,18))         .audio_fadein(1)         .audio_fadeout(1)) audio_period = find_audio_period(audio)print ('Analyzed the audio, found a period of %.02f seconds'%audio_period)  # LOAD, EDIT, ANALYZE THE VIDEO  clip = (VideoFileClip("./knights.mp4", audio=False)        .subclip((1,24.15),(1,26))        .crop(x1=332, x2=910, y2=686)) video_period = find_video_period(clip, tmin=.3)print ('Analyzed the video, found a period of %.02f seconds'%video_period) edited_right = (clip.subclip(0,video_period)                .speedx(final_duration=2*audio_period)                .fx(vfx.loop, duration=audio.duration)                .subclip(.25)) edited_left = edited_right.fx(vfx.mirror_x) dancing_knights = (clips_array([[edited_left, edited_right]])                   .fadein(1).fadeout(1).set_audio(audio).subclip(.3)) # MAKE THE TITLE SCREEN  txt_title = (TextClip("15th century dancing\n(hypothetical)", fontsize=70,               font="Century-Schoolbook-Roman", color="white")             .margin(top=15, opacity=0)             .set_position(("center","top"))) title = (CompositeVideoClip([dancing_knights.to_ImageClip(), txt_title])         .fadein(.5)         .set_duration(3.5))  # MAKE THE CREDITS SCREEN  txt_credits = """CREDITSVideo excerpt: Le combat en armure au XVe siècleBy J. Donzé, D. Jaquet, T. Schmuziger,Université de Genève, Musée National de Moyen AgeMusic: "Frontier", by DOCTOR VOXUnder licence Creative Commonshttps://www.youtube.com/user/DOCTORVOXofficialVideo editing © Zulko 2014 Licence Creative Commons (CC BY 4.0)Edited with MoviePy: http://zulko.github.io/moviepy/""" credits = (TextClip(txt_credits, color='white',            font="Century-Schoolbook-Roman", fontsize=35, kerning=-2,            interline=-1, bg_color='black', size=title.size)          .set_duration(2.5)          .fadein(.5)          .fadeout(.5))  # ASSEMBLE EVERYTHING, WRITE TO FILE final = concatenate_videoclips([title, dancing_knights, credits]) final.write_videofile("dancing_knights.mp4", fps=clip.fps,                      audio_bitrate="1000k", bitrate="4000k")
复制代码


4.3 一个简单的音乐视频

4.4 一个操作音频的例子

4.5 做一个《星战》风格的片头


这种透视效果需要比较复杂的转换,但是这样的练习也挺不错的

"""Description of the video:Mimic of Star Wars' opening title. A text with a (false)perspective effect goes towards the end of space, on abackground made of stars. Slight fading effect on the text.""" import numpy as npfrom skimage import transform as tf from moviepy.editor import *from moviepy.video.tools.drawing import color_gradient  # RESOLUTION w = 720h = w*9/16 # 16/9 screenmoviesize = w,h   # THE RAW TEXTtxt = "\n".join(["A long time ago, in a faraway galaxy,","there lived a prince and a princess","who had never seen the stars, for they","lived deep underground.","","Many years before, the prince's","grandfather had ventured out to the","surface and had been burnt to ashes by","solar winds.","","One day, as the princess was coding","and the prince was shopping online, a","meteor landed just a few megameters","from the couple's flat."])  # Add blankstxt = 10*"\n" +txt + 10*"\n"  # CREATE THE TEXT IMAGE  clip_txt = TextClip(txt,color='white', align='West',fontsize=25,                    font='Xolonium-Bold', method='label')  # SCROLL THE TEXT IMAGE BY CROPPING A MOVING AREA txt_speed = 27fl = lambda gf,t : gf(t)[int(txt_speed*t):int(txt_speed*t)+h,:]moving_txt= clip_txt.fl(fl, apply_to=['mask'])  # ADD A VANISHING EFFECT ON THE TEXT WITH A GRADIENT MASK grad = color_gradient(moving_txt.size,p1=(0,2*h/3),                p2=(0,h/4),col1=0.0,col2=1.0)gradmask = ImageClip(grad,ismask=True)fl = lambda pic : np.minimum(pic,gradmask.img)moving_txt.mask = moving_txt.mask.fl_image(fl)  # WARP THE TEXT INTO A TRAPEZOID (PERSPECTIVE EFFECT) def trapzWarp(pic,cx,cy,ismask=False):    """ Complicated function (will be latex packaged as a fx) """    Y,X = pic.shape[:2]    src = np.array([[0,0],[X,0],[X,Y],[0,Y]])    dst = np.array([[cx*X,cy*Y],[(1-cx)*X,cy*Y],[X,Y],[0,Y]])    tform = tf.ProjectiveTransform()    tform.estimate(src,dst)    im = tf.warp(pic, tform.inverse, output_shape=(Y,X))    return im if ismask else (im*255).astype('uint8') fl_im = lambda pic : trapzWarp(pic,0.2,0.3)fl_mask = lambda pic : trapzWarp(pic,0.2,0.3, ismask=True)warped_txt= moving_txt.fl_image(fl_im)warped_txt.mask = warped_txt.mask.fl_image(fl_mask)  # BACKGROUND IMAGE, DARKENED AT 60% stars = ImageClip('../../videos/stars.jpg')stars_darkened = stars.fl_image(lambda pic: (0.6*pic).astype('int16'))  # COMPOSE THE MOVIE final = CompositeVideoClip([         stars_darkened,         warped_txt.set_pos(('center','bottom'))],         size = moviesize)  # WRITE TO A FILE final.set_duration(8).write_videofile("starworms.avi", fps=5) # This script is heavy (30s of computations to render 8s of video)   """=====================================================================    CODE FOR THE VIDEO TUTORIAL  We will now code the video tutorial for this video.  When you think about it, it is a code for a video explaining how to  make another video using some code (this is so meta !).  This code uses the variables of the previous code (it should be placed  after that previous code to work).====================================================================="""   def annotate(clip,txt,txt_color='white',bg_color=(0,0,255)):    """ Writes a text at the bottom of the clip. """        txtclip = TextClip(txt, fontsize=20, font='Ubuntu-bold',                       color=txt_color)                           txtclip = txtclip.on_color((clip.w,txtclip.h+6), color=(0,0,255),                        pos=(6,'center'))                            cvc =  CompositeVideoClip([clip , txtclip.set_pos((0,'bottom'))])        return cvc.set_duration(clip.duration)  def resizeCenter(clip):    return clip.resize( height=h).set_pos('center')     def composeCenter(clip):    return CompositeVideoClip([clip.set_pos('center')],size=moviesize)   annotated_clips = [ annotate(clip,text) for clip,text in [               (composeCenter(resizeCenter(stars)).subclip(0,3),    "This is a public domain picture of stars"), (CompositeVideoClip([stars],moviesize).subclip(0,3),    "We only keep one part."), (CompositeVideoClip([stars_darkened],moviesize).subclip(0,3),    "We darken it a little."), (composeCenter(resizeCenter(clip_txt)).subclip(0,3),    "We generate a text image."), (composeCenter(moving_txt.set_mask(None)).subclip(6,9),    "We scroll the text by cropping a moving region of it."), (composeCenter(gradmask.to_RGB()).subclip(0,2),    "We add this mask to the clip."), (composeCenter(moving_txt).subclip(6,9),    "Here is the result"), (composeCenter(warped_txt).subclip(6,9),    "We now warp this clip in a trapezoid."), (final.subclip(6,9),    "We finally superimpose with the stars.")]] # Concatenate and write to a file concatenate_videoclips(annotated_clips).write_videofile('tutorial.avi', fps=5)
复制代码


4.6 字幕特效-部分隐藏


from moviepy.editor import *from moviepy.video.tools.credits import credits1 # 加载山背景的clip,截取,变慢,画面变暗clip = (VideoFileClip('../../videos/badl-0001.mov', audio=False)           .subclip(37,46)           .speedx( 0.4)           .fx( vfx.colorx, 0.7)) # 保存第一帧画面,一会使用GIMP处理,增加一个mask#~ clip.save_frame('../../credits/mountainMask2.png')  # 加载mountain maskmountainmask = ImageClip('../../credits/mountainMask2.png',ismask=True) # 用一个文本文件内容生成字幕credits = credits1('../../credits/credits.txt',3*clip.w/4)scrolling_credits = credits.set_pos(lambda t:('center',-10*t))  # 让字幕以10像素每秒的速度滚动起来final = CompositeVideoClip([clip,                            scrolling_credits,                            clip.set_mask(mountainmask)])
复制代码


4.7 使用画笔特效-定格视频中的某一帧

这样的处理手法,会让一帧画面看起来像画一样:


  1. 用 Sobel 算法算出图像中的边缘,我们就获得了像手绘的黑白画面

  2. 图片矩阵相乘,获得比较亮的画面,再叠加第一步获得的轮廓


最终的 clip 包含三级:处理之前的部分,处理的部分,处理过后的部分。处理的部分,是按照以下三步走来获得的。


  1. 定格一帧画面,制作成手绘风格,作为一个 clip

  2. 添加一个写着[Audrey]的 text clip 到第一步的 clip

  3. 把的以上得到的 clip 覆盖在原始的 clip 上,让后让它用渐入和渐出的效果显示和移除显示


下面是代码

""" 需要安装1 scikit-image (for vfx.painting) """ from moviepy.editor import * # WE TAKE THE SUBCLIPS WHICH ARE 2 SECONDS BEFORE & AFTER THE FREEZE charade = VideoFileClip("../../videos/charade.mp4")tfreeze = cvsecs(19.21) # Time of the freeze, 19'21 # when using several subclips of a same clip, it can be faster# to create 'coreaders' of the clip (=other entrance points).clip_before = charade.coreader().subclip(tfreeze -2,tfreeze)clip_after = charade.coreader().subclip(tfreeze ,tfreeze +2)  # 被定格的一帧画面im_freeze = charade.to_ImageClip(tfreeze)painting = (charade.fx( vfx.painting, saturation = 1.6,black = 0.006)                   .to_ImageClip(tfreeze))#签名的TextClip  txt = TextClip('Audrey',font='Amiri-regular',fontsize=35) painting_txt = (CompositeVideoClip([painting,txt.set_pos((10,180))])                   .add_mask()                   .set_duration(3)                   .crossfadein( 0.5)                   .crossfadeout( 0.5)) # FADEIN/FADEOUT EFFECT ON THE PAINTED IMAGE painting_fading = CompositeVideoClip([im_freeze,painting_txt])  final_clip =  concatenate_videoclips([ clip_before,                            painting_fading.set_duration(3),                            clip_after]) final_clip.write_videofile('../../audrey.avi',fps=charade.fps,                        codec = "mpeg4", audio_bitrate="3000k")
复制代码


4.8 把多个 clip 放置在一个画面中(超美)



这么复杂的合成,是怎么做出来的啊?!

在这个脚本中,我们会使用下面这个图:



我们将找到这些区域,然后把不同的 clip 填充到这些区域

from moviepy.editor import *from moviepy.video.tools.segmenting import findObjects # 加载用来指定区域的图像im = ImageClip("../../ultracompositing/motif.png") # 加载这些区域返回一个ImageClip列表regions = findObjects(im)  # 载入美国国家公园的7个clipclips = [VideoFileClip(n, audio=False).subclip(18,22) for n in     [ "../../videos/romo_0004.mov",      "../../videos/apis-0001.mov",      "../../videos/romo_0001.mov",      "../../videos/elma_s0003.mov",      "../../videos/elma_s0002.mov",      "../../videos/calo-0007.mov",      "../../videos/grsm_0005.mov"]] # 把每一个clip都放置在对应的图片中的区域comp_clips =  [c.resize(r.size)                .set_mask(r.mask)                .set_pos(r.screenpos)               for c,r in zip(clips,regions)] cc = CompositeVideoClip(comp_clips,im.size)cc.resize(0.6).write_videofile("../../composition.mp4") # 这个特殊的合成任务要花费很长的时间
复制代码


4.9 给 MoviePy Logo 做一个移动的阴影效果



4.10 追踪人脸,打马赛克


首先,我们需要先追踪到人脸,即,获得两个函数 fx 和 fy 就像(fx(t), fy(t))获得某时刻 t 脸的中心的坐标,通过 manual_tracking,这个功能很快就实现了。然后我们就需要把脸的中心为半径的一部分区域做模糊化处理。

import pickle from moviepy.editor import *from moviepy.video.tools.tracking import manual_tracking, to_fxfy  # 加载clip,截取一个卓别林电影的6‘51-7’01之间的片段clip = VideoFileClip("../../videos/chaplin.mp4").subclip((6,51.7),(7,01.3)) # 手动跟踪标记头部 # 下面的三行代码,手动跟踪,然后把结果保存进文件,应该在一次运行之后就完成量跟踪标记# 注意:我们保存的格式是一个(ti,xi,yi)list,不是函数fx和fy #txy, (fx,fy) = manual_tracking(clip, fps=6)#with open("../../chaplin_txy.dat",'w+') as f:#    pickle.dump(txy)   # 已经完成手动跟踪人脸并标记的情况下# fx(t),fy(t)的形式加载跟踪标记的数据 with open("../../chaplin_txy.dat",'r') as f:    fx,fy = to_fxfy( pickle.load(f) )  # 在clip中,模糊卓别林的头部 clip_blurred = clip.fx( vfx.headblur, fx, fy, 25)  # 生成文本,灰色背景 txt = TextClip("Hey you ! \n You're blurry!", color='grey70',               size = clip.size, bg_color='grey20',               font = "Century-Schoolbook-Italic", fontsize=40)                              # 把卓别林的vedio clip和TextClip连接起来,添加audio clip final = concatenate_videoclips([clip_blurred,txt.set_duration(3)]).\          set_audio(clip.audio) # 将比特率修改为3000k是为了画面不至于太丑 final.write_videofile('../../blurredChaplin.avi', bitrate="3000k")
复制代码

4.11 漂亮的结尾



from moviepy.editor import *from moviepy.video.tools.drawing import circle # 原有视频clip = VideoFileClip("../../videos/badl-0006.mov", audio=False).           subclip(26,31).           add_mask()           w,h = clip.size  # 这里的mask是一个半径按照 r(t) = 800-200*t  根据时间变化消失的圆           clip.mask.get_frame = lambda t: circle(screensize=(clip.w,clip.h),                                       center=(clip.w/2,clip.h/4),                                       radius=max(0,int(800-200*t)),                                       col1=1, col2=0, blur=4)  # 搞一个TextClip来放The Endthe_end = TextClip("The End", font="Amiri-bold", color="white",                   fontsize=70).set_duration(clip.duration) final = CompositeVideoClip([the_end.set_pos('center'),clip],                           size =clip.size)                           final.write_videofile("../../theEnd.avi")
复制代码


5.Docker

之前有不少同学说搭建 moviepy 环境失败了,大部分是卡在 ffmpeg 自动下载的步骤了,原因在于从国外下载 ffmpeg。当然有办法解决:可以手动下载,然后放在~/.imageio/ffmpeg 目录下。

ucsheep@ucsheep-B250M-D2V:~/.imageio/ffmpeg$ ll总用量 44864drwxrwxr-x 2 ucsheep ucsheep     4096 8月  21 10:44 ./drwxrwxr-x 3 ucsheep ucsheep     4096 8月  14 08:54 ../-rwxrw-r-- 1 ucsheep ucsheep 45929032 8月  21 10:44 ffmpeg-linux64-v3.3.1*
复制代码

但是,其实我这边最开始就直接使用的是 moviepy 的 docker 镜像,很方便。也不会遇到上述的情况。那么,在那里可以买的到呢?

有两种办法,一种是通过 Dockerfile 构建,一种是直接在 Docker Hub 寻找有么有现成的镜像。


注意:进行以下操作的首要前提是,您的电脑已经安装 Docker

5.1 使用 Docker 镜像快速搭建 moviepy 环境

首先,查找镜像

ucsheep@ucsheep-B250M-D2V:~$ docker search moviepyNAME                                DESCRIPTION                                     STARS               OFFICIAL            AUTOMATEDdkarchmervue/moviepy                Image for moviepy, with latest Python 3.4, l…   1                                       [OK]jdauphant/moviepy                                                                   0                                       [OK]mentlsve/anaconda3-opencv-moviepy   Anaconda3 (Python 3.5) with opencv and movie…   0                                       srid/moviepy                                                                        0                                       [OK]mbeacom/moviepy                                                                     0                                       jacquesvdm/moviepy                  Image that contains PHP and MoviePy             0                                       hongyusir/python-moviepy            docker image with python, pil and moviepy in…   0                                       ib5k/moviepy                                                                        0                                       screencom/moviepy                                                                   0                                       verynb/moviepy                                                                      0                                       hongyusir/mnpp                      mongo, nodejs and python together, and with …   0                                       lucasrodesg/deepo                   Modified from ufoym/deepo. Incorporates pyth…   0 
复制代码

然后,选择第一个,拉取镜像,创建一个容器

ucsheep@ucsheep-B250M-D2V:~$ docker run -idt --name moviepy-test dkarchmervue/moviepy4e4afde5a3702171ac345f924f4a40596fb081f20cf6984544c0b7855d5585b7
复制代码

查看容器运行情况

ucsheep@ucsheep-B250M-D2V:~$ docker ps -aCONTAINER ID        IMAGE                      COMMAND                  CREATED              STATUS                     PORTS                                        NAMES4e4afde5a370        dkarchmervue/moviepy       "/bin/bash"              About a minute ago   Up About a minute                                                       moviepy-test
复制代码

进入容器,看看 moviepy 环境是否可用

ucsheep@ucsheep-B250M-D2V:~$ docker attach moviepy-testroot@4e4afde5a370:/work# root@4e4afde5a370:/work# python3Python 3.4.4 (default, May 25 2016, 06:34:52) [GCC 4.8.4] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import moviepy>>>
复制代码

没有报错,大功告成。可以将开发目录挂载到容器中的 work 目录,方便运行。

5.2 Dockerfile 构建

首先,clone MoviePy 源码

ucsheep@ucsheep-B250M-D2V:~/git-projects$ git clone https://github.com/Zulko/moviepy.git
复制代码

查看,看到了 Dockerfile

ucsheep@ucsheep-B250M-D2V:~/git-projects$ cd moviepy/ucsheep@ucsheep-B250M-D2V:~/git-projects/moviepy$ lsappveyor      CONTRIBUTING.md  examples     MANIFEST.in  setup.pyappveyor.yml  Dockerfile       ez_setup.py  moviepy      testsCHANGELOG.md  docs             LICENCE.txt  README.rst
复制代码

Dockerfile 的内容如下

ROM python:3 # Install numpy using system package managerRUN apt-get -y update && apt-get -y install libav-tools imagemagick libopencv-dev python-opencv # Install some special fonts we use in testing, etc..RUN apt-get -y install fonts-liberation RUN apt-get install -y locales && \    locale-gen C.UTF-8 && \    /usr/sbin/update-locale LANG=C.UTF-8 ENV LC_ALL C.UTF-8 # do we need all of these, maybe remove some of them?RUN pip install imageio numpy scipy matplotlib pandas sympy nose decorator tqdm pillow pytest requests # install scikit-image after the other deps, it doesn't cause errors this way.RUN pip install scikit-image sklearn ADD . /var/src/moviepy/#RUN git clone https://github.com/Zulko/moviepy.git /var/src/moviepyRUN cd /var/src/moviepy/ && python setup.py install # install ffmpeg from imageio.RUN python -c "import imageio; imageio.plugins.ffmpeg.download()" #add soft link so that ffmpeg can executed (like usual) from command lineRUN ln -s /root/.imageio/ffmpeg/ffmpeg.linux64 /usr/bin/ffmpeg # modify ImageMagick policy file so that Textclips work correctly.RUN cat /etc/ImageMagick-6/policy.xml | sed 's/none/read,write/g'> /etc/ImageMagick-6/policy.xml
复制代码

就是在 Python3 的基础上,搭建量 MoviePy 的运行环境

那么,接下来,开始构建

ucsheep@ucsheep-B250M-D2V:~/git-projects/moviepy$ docker build -t moviepy -f Dockerfile .Sending build context to Docker daemon  7.115MBStep 1/12 : FROM python:3 ---> 825141134528Step 2/12 : RUN apt-get -y update && apt-get -y install libav-tools imagemagick libopencv-dev python-opencv ---> Running in b09eb178cc0aIgn:1 http://deb.debian.org/debian stretch InReleaseGet:2 http://security.debian.org/debian-security stretch/updates InRelease [94.3 kB]Get:3 http://deb.debian.org/debian stretch-updates InRelease [91.0 kB]Get:4 http://deb.debian.org/debian stretch Release [118 kB]Get:5 http://security.debian.org/debian-security stretch/updates/main amd64 Packages [437 kB]Get:6 http://deb.debian.org/debian stretch-updates/main amd64 Packages [5148 B]Get:7 http://deb.debian.org/debian stretch Release.gpg [2434 B]Get:8 http://deb.debian.org/debian stretch/main amd64 Packages [7099 kB]
复制代码

最终执行到 Step 12/12 后会构建完毕

接下来,可以运行容器,测试 moviepy 给出的示例代码:

ucsheep@ucsheep-B250M-D2V:~/git-projects/moviepy$ cd tests/ucsheep@ucsheep-B250M-D2V:~/git-projects/moviepy/tests$ lsdownload_media.py      test_fx.py                   test_resourcerelease.py__init__.py            test_helper.py               test_TextClip.pyREADME.rst             test_ImageSequenceClip.py    test_tools.pytest_AudioClips.py     test_issues.py               test_VideoClip.pytest_compositing.py    test_misc.py                 test_VideoFileClip.pytest_examples.py       test_PR.py                   test_Videos.pytest_ffmpeg_reader.py  test_resourcereleasedemo.py  test_videotools.pyucsheep@ucsheep-B250M-D2V:~/git-projects/moviepy/tests$ docker run -it -v `pwd`:/tests moviepy bash
复制代码


发布于: 2021 年 03 月 24 日阅读数: 51
用户头像

ucsheep

关注

譬如朝露 去日苦多 2021.03.22 加入

还未添加个人简介

评论

发布
暂无评论
MoviePy - 中文文档(一个专业的python音视频编辑库)教程