<audio id="audio" title="05 | 图像处理库：如何实现长图拼接？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1c/41/1c049b4b05ced45b00a5ece105e67941.mp3"></audio>

你好，我是尹会生。

我们在工作中，除了和文字、表格打交道之外，还会经常涉及到批量处理图片和视频的工作。比如：媒体从业者在发微博长图文时，需要把多个图片拼接成一幅长图；作为视频剪辑人员，需要从互联网下载多段视频，再进行合并。

这类工作可以用功能强大的商业软件实现，不过这些软件大都操作繁琐，而且还需要付费。为了降低学习成本和购买软件的成本，我们往往还会使用开源软件替代商业软件来实现图片和视频处理功能。但是开源软件通常都是以命令行方式运行的，所以我们不仅要记住命令，还得记住命令的常用参数。

不过，幸运的是，虽然直接使用开源软件不够友好，但如果通过Python来调用这些开源软件，那实现长图和视频拼接就轻而易举了，而且还能大批量地处理图片和视频。

# Python是如何调用外部命令的

为了让你了解Python是如何操作这些开源软件的，我先来给你介绍一下Python调用外部程序的原理。

我们要想使用Python语言之外的功能，要依靠两大途径：导入函数库和调用外部命令。

在第一讲我使用的xlrd库是通过import xlrd命令导入到Python语言中的，Python语言默认是不支持Excel的。那么通过导入函数库，Python就可以获得对Excel的操作能力。

还有一种情况是，需要操作Python语言之外的功能，但这个功能没有人将它开发成函数库，那如果我们想要使用这些功能，使用的途径就是调用外部命令了，而调用外部命令就需要Python内部函数库的subprocess模块来实现。

这个模块的实现机制是：它的run()函数的参数可以指定一个可以运行的程序的路径，而Python会根据这个路径来运行可执行文件，然后再根据运行结果，以及Python的逻辑判断去进行后续的自动化处理工作。

这个实现机制并不难，我给你写一段简单的程序，帮你理解Python是怎样调用外部命令的。这里以macOS系统为例，我们通过Python获取当前目录下所有文件的功能。

```
from subprocess import run, Popen, PIPE

cmd1 = [&quot;ls&quot;, &quot;.&quot;]
returncode = run(cmd1)

print(returncode)
# CompletedProcess(args=['ls', '.'], returncode=0)
# returncode是“ls .”的退出状态码.
# 通常来说, 一个为 0 的退出码表示进程运行正常

# 使用Popen获取程序运行结果
with Popen(cmd1, shell=True, stdout=PIPE, stderr=PIPE, encoding=&quot;utf-8&quot;) as fs:
    
    # 如果程序在 timeout 秒后未执行完成,会抛出 TimeoutExpired 异常
    fs.wait(2)

    # 从标准输出中读取数据,知道文件结束
    files = fs.communicate()[0]
    
print(files)


```

这段代码中最核心的函数是run()函数和Popen类。subprocess模块就是通过这两个函数实现的外部程序调用。我来为你重点剖析一下它们的功能、参数，以及何时选择run()函数、何时选择Popen类。

为了实现Python调用可执行文件，首先在代码的第一行，我是这样编写的：

```
from subprocess import run, Popen, PIPE

```

这样一行代码，它和我第一讲使用的import方式导入函数库的区别是，这种形式可以让你直接使用模块中的类和方法。

如果你使用 “import subprocess”方式导入subprocess库的话，在调用run()函数的时候，就需要用 “库.函数”的形式在Python中使用库当中的函数，即“subprocess.run()”。在你多次调用run()函数时，代码会较长，那么使用“from import”方式导入，就可以在当前代码文件中直接使用run()函数，为代码的阅读带来更好的体验。

接下来，我定义了一个变量cmd1。这个变量的值是macOS命令行能够运行的“ls .”命令，这个命令的执行结果是显示当前目录下所有文件和文件夹的名称。

run()函数的主要功能就是执行一个新的程序，它的用法非常简单，把第一个参数指定为要执行程序的路径就可以了。如果要执行的程序带有参数，那就可以使用列表数据类型存放可执行程序名称和参数，像是我在程序中定义的cmd1变量一样。如果你需要运行其他命令，把代码中的ls替换为你想要运行的其他程序就行了。

为了让Python自动化处理程序更强大，除了运行程序外，你还可以得到可执行程序的运行结果。在这种情况下，我们就需要使用Popen类替代run()函数实现外部程序的调用。

可以看到，我在代码的第12行先通过Popen类执行了“ls .”命令，接着通过参数stdout=PIPE 将命令的执行结果放入到PIPE对象中, 最后再通过communicate()函数将PIPE中的内容读取出来，存放到files变量中，这样就实现了读取命令执行结果的功能。

这个功能是无法在run()函数实现的，因此在你需要通过Python读取程序执行结果的时候，就可以选择Popen类。不过如果只需要运行可执行程序，那使用run()函数就能满足你的要求了。如果你想更深入地了解它们，我建议你阅读[subprocess库的官方文档](https://docs.python.org/zh-cn/3.7/library/subprocess.html#subprocess.Popen)。

以上就是我用subprocess库实现Python调用可执行程序的方法。Python之所以被我们称作最佳的“胶水语言”，就是因为它能轻易“粘合”可执行程序。利用Python灵活的逻辑判断、循环语法可以实现程序的批量执行和流程管理。

接下来，我们就使用subprocess来实现长图拼接和视频拼接的功能。

# 长图拼接

当我进行微博文案推广的时候，需要将多个图片拼接成一个长图。拼接图片的功能Python本身是不具备的，因此就需要引入外部命令来实现图片拼接功能。

我在macOS平台上找到了一个非常强大的图像处理软件叫做**ImageMagick****，**它能对图片进行编辑、合并、切割、旋转等90多种操作。 ImageMagick软件实现图片拼接的命令格式是这样的：

```
composite 图片1.jpg 图片2.jpg ... 图片n.jpg 最终合成结果.jpg

```

在这段命令格式中，composite命令的参数包含了多个图片文件，每个图片需要对照着文件将图片的路径和文件名写在参数中。如果手工输入图片名称，不仅效率低，而且容易遗漏。另外，如果需要大量重复使用composite，还需要精细调整合并结果，给composite程序增加很多参数。

因此，我就可以通过Python调用可执行程序的subprocess库，对composite拼长图的工作进行脚本化编程。它的核心实现代码如下：

```
p = Path(jpg_path)

# 增加命令
cmd = [&quot;composite&quot;,]

# 增加参数
for x in p.iterdir() if  PurePath(x).match('*.jpg'):
    cmd.append(x)

# 增加结果
cmd.append(result_path)

run(cmd)

```

由于composite可以把长图合成的结果直接输出为文件，因此采用run()函数即可实现程序执行的功能。另外，当你需要调整composite参数时，可以直接修改cmd变量的值，并不需要改动程序其他部分。当你要对新的一组图片进行合成的时候，重新设置jpg_path变量就行了。

总结来说，使用Python调用composite合并的好处就是：你不用记住程序使用的繁杂的命令行参数，也不用记住运行逻辑，因为Python程序已经事先把逻辑编写好了。

# 视频的拆分与合并

在了解了如何使用subprocess调用composite实现长图拼接之后，我再给你讲一下如何使用subprocess库调用可执行程序，来进行视频的拆分与合并。

我们先来学习下视频拆分的原理。

你在电脑本地经常见到的视频格式是MP4，但如果要把视频放在互联网上，为了减少首次播放的加载时间，你就必须把一个MP4切分成多个文件，而且切分之后还需要把格式转换为.TS格式的视频文件。

为什么不直接使用MP4格式，而是要把MP4格式改成.TS格式呢？这是因为.TS格式可以保证多个文件之间的视频无缝播放，而且还会保证视频不会在播放下一个文件的时候，出现破音或画面中断等影响用户体验的情况。

当我们将一个视频切分成多个文件的时候，就要考虑文件的播放顺序问题了。为了记录顺序，我们需要在切分之后引入一个索引文件，这个索引文件不用手动编写，我们直接使FFmpeg命令就行了，它可以实现视频格式的转换、合并和拆分。FFmpeg命令会在切分之后，自动产生一个以.M3U8结尾的索引文件。

我来解释一下这个索引文件。M3U8文件是指UDF-8编码格式下的M3U视频索引，播放器通过这个索引文件就可以找到视频下所有的分段，并依次播放视频。

看到这儿你应该就能明白了，想要使用Python进行视频拆分，我们首先需要FFmpeg命令，然后通过Python设置FFmpeg的参数，最后再指定MP4文件和.TS文件的路径，这样就能实现拆分视频的功能了。因此我使用这样的代码来实现视频拆分：

```
from subprocess import run
input_video = &quot;/Users/edz/Desktop/05/xxx.mp4&quot;
segment_time = 10
m3u8_list = &quot;/Users/edz/Desktop/05/xxx.m3u8&quot;
output_video = &quot;/Users/edz/Desktop/05/video-%04d.ts&quot;

cmd1 = [&quot;ffmpeg&quot;, &quot;-i&quot;, input_video, &quot;-f&quot;, &quot;segment&quot;, &quot;-segment_time&quot;, str(segment_time), &quot;-segment_format&quot;,
    &quot;mpegts&quot;, &quot;-segment_list&quot;, m3u8_list, &quot;-c&quot;, &quot;copy&quot;, &quot;-bsf:v&quot;, &quot;h264_mp4toannexb&quot;, &quot;-map&quot;, &quot;0&quot;, output_video]

run(cmd1)


```

在代码中，我通过FFmpeg把MP4切分成了多段TS文件。你要想实现相同功能，首先需要在电脑中安装FFmpeg命令，它的下载地址为：[https://ffmpeg.org/download.html](https://ffmpeg.org/download.html)。

为了实现MP4文件格式的分割，需要使用ffmpeg非常多的参数。不过使用Python进行调用的好处，就是你不用记住复杂的参数。我们把输入文件路径、切分大小、输出的M3U8和TS文件指定为四个变量，这样只修改这四个变量，就可以实现拆分功能了。

如果你需要离线观看视频，就要将网络上的视频下载到本地，这时你会发现从互联网下载的格式是M3U8和TS文件。那又怎么把它们合并成MP4文件呢？

你同样可以使用FFmpeg命令，但是FFmpeg的参数不同。我将FFmpeg的命令写在这里：

```
ffmpeg -allowed_extensions ALL -protocol_whitelist &quot;file,http,crypto,tcp,https&quot; -i index.m3u8 -c copy out.mp4

```

如果你不想背诵这么长的参数，完全可以仿照Python整合拆分视频的代码来实现合并功能。先FFmpeg命令和参数放入列表，再把M3U8文件和MP4文件放入变量，便于你合并新的视频的时候进行重新赋值。

所以你看，相比直接使用FFmpeg，subprocess调用FFmpeg的优势就在于两点，一是不用记住复杂参数，二是对批量转换视频非常有利。举两个例子。

如果你是视频剪辑的专业工作者，肯定要大量使用FFmpeg更复杂的功能，这些功能对应的参数一般都比较多，而且参数很多都使用了简写和大小写, 很难记忆。但要是使用Python调用的话，你可以直接更改要操作的文件路径，就不必记录大量的参数。

另外需要进行视频的批量转换时，可以通过第一讲的循环操作对视频任务批量处理，这样就避免了手动逐个修改书写文件的操作，从而提高视频转换的效率。

# 小结

最后，我来为你总结一下这节课的主要内容。

通过对subprocess库的讲解，你知道了怎样通过它实现Python加载外部可执行程序，并且能够对程序执行的结果进行处理。

我也为你讲解了长图拼接和视频拆分合并的两个案例，帮你更好地理解Python为什么会被称作“胶水”语言。

我还想强调一下，通过Python调用可执行程序的用法非常常见，特别是在多媒体处理、自然科学、AI等领域里。在这些专业领域，为了加快计算速度，通常会使用C++语言实现专业程序。

这些专业程序参数多、功能单一，且使用命令行执行，当你需要多次执行这些程序，又不想背诵它们的参数的时候，就可以利用Python的判断循环功能，结合C++语言实现的专业程序，来实现批量执行和减少参数手动输入的工作，提高你的工作效率。

最后，我也把这节课的代码附上，你可以查看。[本讲代码](https://github.com/wilsonyin123/python_productivity/blob/main/%E6%96%87%E7%AB%A05%E4%BB%A3%E7%A0%81.zip)

# 思考题

在最后也请你思考一下，你在工作当中是否会使用命令行工具呢？它们能否用Python进行包装，从而避免手写复杂参数呢？

如果你觉得这节课有用，能解决你的办公效率问题，欢迎你点击“请朋友读”，分享给你的朋友或同事。
