将 template/layout 中的多个视频与 Python FFMPEG 合并?
Merging multiple videos in a template/layout with Python FFMPEG?
我目前正在尝试使用 Python library of FFMPEG 编辑视频。我正在使用多种文件格式,准确地说是 .mp4
、.png
和文本输入 (.txt
)。目标是在“布局”中嵌入不同的视频文件 - 出于演示目的,我尝试设计一个 示例图片 :
输出应该是一个 1920x1080 .mp4
文件,包含以下元素:
- 元素 3 是视频本身(由于它是移动 phone 屏幕录制,它大约是那里显示的大小)
- Element 1 和 2 是“寄宿生”,即静态图片 (? )
- Element 4 表示定期更改的文本 - 通过 python 脚本输入(可能从
.txt
文件)
- Element 5 描绘了
.png
、.svg
或类似的;一般是广义的“图片”。
我想要实现的是创建一种模板文件,我“只”需要输入不同的 .mp4
和 .png
文件,以及文本和最后我会收到一个 .mp4
文件,而我的 Python 脚本作为导航器将数据包发送到 FFMPEG 以处理视频本身。
我深入研究了 FFMPEG 库以及 python 特定的存储库,但找不到这样的选项。有很多文章解释了“频道布局”的用法(虽然这些似乎不符合我的需要)。
如果有人想尝试相同的版本:
python --version
:
Python3.7.3
pip show ffmpeg
: Version: 1.4 (这是最新的;在题外话中:使用 FFMPEG 不是必须的,我我更喜欢使用这个库,但如果它不提供我正在寻找的功能,如果有人提出其他建议,我将不胜感激)
ffmpeg -i left.jpg -i video.mp4 -i right.png -i logo.png -filter_complex "[0]scale=(1920-1080*($width/$height))/2:1080:force_original_aspect_ratio=increase,crop=(1920-1080*($width/$height))/2:1080[left];[1]scale=-2:1080[main];[2]scale=(1920-1080*($width/$height))/2:1080:force_original_aspect_ratio=increase,crop=(1920-1080*($width/$height))/2:1080[right];[left][main][right]hstack=inputs=3[bg];[bg][3]overlay=5:main_h-overlay_h-5:format=auto,drawtext=textfile=text.txt:reload=1:x=w-tw-10:y=h-th-10,format=yuv420p" -movflags +faststart output.mp4
它所做的是缩放 video.mp4
所以它是 1080 像素高并自动缩放宽度。 left.jpg
和 right.png
被缩放以占据余数,因此结果为 1920x1080。
这是一个简单的示例,不适用于所有输入,例如 video.mp4
自动缩放宽度大于 1920,但您可以使用 arithmetic expressions.
$width
和$height
指的是video.mp4
的大小。请参阅 Getting video dimension / resolution / width x height from ffmpeg 了解使用 JSON 或 XML 的 Python 友好方法。
请参阅 scale, crop, hstack, drawtext, overlay, and format 过滤器的文档。
一个更简单的方法是只添加彩色条而不是任意图像。参见 Resizing videos with ffmpeg to fit a specific size。
通过自动更新 text.txt
即时更改文本。或者,如果您希望文本在某些时间戳上发生变化(您没有指定您需要什么),请使用 subtitles 过滤器而不是 drawtext。
相关回答:
- How to position overlay/watermark/logo with ffmpeg?
- How to position drawtext text?
- Vertically or horizontally stack (mosaic) several videos using ffmpeg?
- Getting video dimension / resolution / width x height from ffmpeg
我做到了。该代码可以用作命令行程序或模块。要了解有关命令行用法的更多信息,请使用 --help 选项调用它。对于模块使用,在您的代码中导入 make_video
函数(或复制粘贴它),并将适当的参数传递给它。我附上了我的脚本使用一些示例 material 生成的屏幕截图,当然还有代码。
代码:
#!/usr/bin/python3
#-*-coding: utf-8-*-
import sys, argparse, ffmpeg, os
def make_video(video, left_boarder, right_boarder, picture_file, picture_pos, text_file, text_pos, output):
videoprobe = ffmpeg.probe(video)
videowidth, videoheight = videoprobe["streams"][0]["width"], videoprobe["streams"][0]["height"] # get width of main video
scale = (1080 / videoheight)
videowidth *= scale
videoheight *= scale
videostr = ffmpeg.input(video) # open main video
audiostr = videostr.audio
videostr = ffmpeg.filter(videostr, "scale", "%dx%d" %(videowidth, videoheight))
videostr = ffmpeg.filter(videostr, "pad", 1920, 1080, "(ow-iw)/2", "(oh-ih)/2")
videostr = videostr.split()
boarderwidth = (1920 - videowidth) / 2 # calculate width of boarders
left_boarderstr = ffmpeg.input(left_boarder) # open left boarder and scale it
left_boarderstr = ffmpeg.filter(left_boarderstr, "scale", "%dx%d" % (boarderwidth, 1080))
right_boarderstr = ffmpeg.input(right_boarder) # open right boarder
right_boarderstr = ffmpeg.filter(right_boarderstr, "scale", "%dx%d" % (boarderwidth, 1080))
picturewidth = boarderwidth - 100 # calculate width of picture
pictureheight = (1080 / 3) - 100 # calculate height of picture
picturestr = ffmpeg.input(picture_file) # open picture and scale it (there will be a padding of 100 px around it)
picturestr = ffmpeg.filter(picturestr, "scale", "%dx%d" % (picturewidth, pictureheight))
videostr = ffmpeg.overlay(videostr[0], left_boarderstr, x=0, y=0) # add left boarder
videostr = ffmpeg.overlay(videostr, right_boarderstr, x=boarderwidth + videowidth, y=0) #add right boarder
picture_y = (((1080 / 3) * 2) + 50) # calculate picture y position for bottom alignment
if picture_pos == "top":
picture_y = 50
elif picture_pos == "center":
picture_y = (1080 / 3) + 50
videostr = ffmpeg.overlay(videostr, picturestr, x=50, y=picture_y)
text_x = (1920 - boarderwidth) + 50
text_y = ((1080 / 3) * 2) + 50
if text_pos == "center":
text_y = (1080 / 3) + 50
elif text_pos == "top":
text_y = 50
videostr = ffmpeg.drawtext(videostr, textfile=text_file, reload=1, x=text_x, y=text_y, fontsize=50)
videostr = ffmpeg.output(videostr, audiostr, output)
ffmpeg.run(videostr)
def main():
#create ArgumentParser and add options to it
argp = argparse.ArgumentParser(prog="ffmpeg-template")
argp.add_argument("--videos", help="paths to main videos (default: video.mp4)", nargs="*", default="video.mp4")
argp.add_argument("--left-boarders", help="paths to images for left boarders (default: left_boarder.png)", nargs="*", default="left_boarder.png")
argp.add_argument("--right-boarders", help="paths to images for right boarders (default: right_boarder.png)", nargs="*", default="right_boarder.png")
argp.add_argument("--picture-files", nargs="*", help="paths to pictures (default: picture.png)", default="picture.png")
argp.add_argument("--picture-pos", help="where to put the pictures (default: bottom)", choices=["top", "center", "bottom"], default="bottom")
argp.add_argument("--text-files", nargs="*", help="paths to files with text (default: text.txt)", default="text.txt")
argp.add_argument("--text-pos", help="where to put the texts (default: bottom)", choices=["top", "center", "bottom"], default="bottom")
argp.add_argument("--outputs", nargs="*", help="paths to outputfiles (default: out.mp4)", default="out.mp4")
args = argp.parse_args()
# if only one file was provided, put it into a list (else, later, every letter of the filename will be treated as a filename)
if type(args.videos) == str:
args.videos = [args.videos]
if type(args.left_boarders) == str:
args.left_boarders = [args.left_boarders]
if type(args.right_boarders) == str:
args.right_boarders = [args.right_boarders]
if type(args.picture_files) == str:
args.picture_files = [args.picture_files]
if type(args.text_files) == str:
args.text_files = [args.text_files]
if type(args.outputs) == str:
args.outputs = [args.outputs]
for i in (range(0, min(len(args.videos), len(args.left_boarders), len(args.right_boarders), len(args.picture_files), len(args.text_files), len(args.outputs))) or [0]):
print("Info : merging video %s, boarders %s %s, picture %s and textfile %s into %s" % (args.videos[i], args.left_boarders[i], args.right_boarders[i], args.picture_files[i], args.text_files[i], args.outputs[i]))
# check if all files provided with the options exist
if not os.path.isfile(args.videos[i]):
print("Error : video %s was not found" % args.videos[i])
continue
if not os.path.isfile(args.left_boarders[i]):
print("Error : left boarder %s was not found" % args.left_boarders[i])
continue
if not os.path.isfile(args.right_boarders[i]):
print("Error : rightt boarder %s was not found" % args.right_boarders[i])
continue
if not os.path.isfile(args.picture_files[i]):
print("Error : picture %s was not found" % args.picture_files[i])
continue
if not os.path.isfile(args.text_files[i]):
print("Error : textfile %s was not found" % args.text_files[i])
continue
try:
make_video(args.videos[i], args.left_boarders[i], args.right_boarders[i], args.picture_files[i], args.picture_pos, args.text_files[i], args.text_pos, args.outputs[i])
except Exception as e:
print(e)
if __name__ == "__main__":
main()
直接用作脚本的示例:
$ ./ffmpeg-template --videos input1.mp4 inout2.mp4 --left-boarders left_boarder1.png left_boarder2.png --right-boarders right_boarder1.png right_boarder2.png --picture-files picture1.png picture2.png --text-files text1.txt text2.png --outputs out1.mp4 out2.mp4 --picture-pos bottom --text-pos bottom
作为选项的值,我采用了默认值。如果省略选项,将使用这些默认值,如果未找到其中一个文件,将显示一条错误消息。
图片:
我目前正在尝试使用 Python library of FFMPEG 编辑视频。我正在使用多种文件格式,准确地说是 .mp4
、.png
和文本输入 (.txt
)。目标是在“布局”中嵌入不同的视频文件 - 出于演示目的,我尝试设计一个 示例图片 :
输出应该是一个 1920x1080 .mp4
文件,包含以下元素:
- 元素 3 是视频本身(由于它是移动 phone 屏幕录制,它大约是那里显示的大小)
- Element 1 和 2 是“寄宿生”,即静态图片 (? )
- Element 4 表示定期更改的文本 - 通过 python 脚本输入(可能从
.txt
文件) - Element 5 描绘了
.png
、.svg
或类似的;一般是广义的“图片”。
我想要实现的是创建一种模板文件,我“只”需要输入不同的 .mp4
和 .png
文件,以及文本和最后我会收到一个 .mp4
文件,而我的 Python 脚本作为导航器将数据包发送到 FFMPEG 以处理视频本身。
我深入研究了 FFMPEG 库以及 python 特定的存储库,但找不到这样的选项。有很多文章解释了“频道布局”的用法(虽然这些似乎不符合我的需要)。
如果有人想尝试相同的版本:
python --version
: Python3.7.3pip show ffmpeg
: Version: 1.4 (这是最新的;在题外话中:使用 FFMPEG 不是必须的,我我更喜欢使用这个库,但如果它不提供我正在寻找的功能,如果有人提出其他建议,我将不胜感激)
ffmpeg -i left.jpg -i video.mp4 -i right.png -i logo.png -filter_complex "[0]scale=(1920-1080*($width/$height))/2:1080:force_original_aspect_ratio=increase,crop=(1920-1080*($width/$height))/2:1080[left];[1]scale=-2:1080[main];[2]scale=(1920-1080*($width/$height))/2:1080:force_original_aspect_ratio=increase,crop=(1920-1080*($width/$height))/2:1080[right];[left][main][right]hstack=inputs=3[bg];[bg][3]overlay=5:main_h-overlay_h-5:format=auto,drawtext=textfile=text.txt:reload=1:x=w-tw-10:y=h-th-10,format=yuv420p" -movflags +faststart output.mp4
它所做的是缩放 video.mp4
所以它是 1080 像素高并自动缩放宽度。 left.jpg
和 right.png
被缩放以占据余数,因此结果为 1920x1080。
这是一个简单的示例,不适用于所有输入,例如 video.mp4
自动缩放宽度大于 1920,但您可以使用 arithmetic expressions.
$width
和$height
指的是video.mp4
的大小。请参阅 Getting video dimension / resolution / width x height from ffmpeg 了解使用 JSON 或 XML 的 Python 友好方法。
请参阅 scale, crop, hstack, drawtext, overlay, and format 过滤器的文档。
一个更简单的方法是只添加彩色条而不是任意图像。参见 Resizing videos with ffmpeg to fit a specific size。
通过自动更新 text.txt
即时更改文本。或者,如果您希望文本在某些时间戳上发生变化(您没有指定您需要什么),请使用 subtitles 过滤器而不是 drawtext。
相关回答:
- How to position overlay/watermark/logo with ffmpeg?
- How to position drawtext text?
- Vertically or horizontally stack (mosaic) several videos using ffmpeg?
- Getting video dimension / resolution / width x height from ffmpeg
我做到了。该代码可以用作命令行程序或模块。要了解有关命令行用法的更多信息,请使用 --help 选项调用它。对于模块使用,在您的代码中导入 make_video
函数(或复制粘贴它),并将适当的参数传递给它。我附上了我的脚本使用一些示例 material 生成的屏幕截图,当然还有代码。
代码:
#!/usr/bin/python3
#-*-coding: utf-8-*-
import sys, argparse, ffmpeg, os
def make_video(video, left_boarder, right_boarder, picture_file, picture_pos, text_file, text_pos, output):
videoprobe = ffmpeg.probe(video)
videowidth, videoheight = videoprobe["streams"][0]["width"], videoprobe["streams"][0]["height"] # get width of main video
scale = (1080 / videoheight)
videowidth *= scale
videoheight *= scale
videostr = ffmpeg.input(video) # open main video
audiostr = videostr.audio
videostr = ffmpeg.filter(videostr, "scale", "%dx%d" %(videowidth, videoheight))
videostr = ffmpeg.filter(videostr, "pad", 1920, 1080, "(ow-iw)/2", "(oh-ih)/2")
videostr = videostr.split()
boarderwidth = (1920 - videowidth) / 2 # calculate width of boarders
left_boarderstr = ffmpeg.input(left_boarder) # open left boarder and scale it
left_boarderstr = ffmpeg.filter(left_boarderstr, "scale", "%dx%d" % (boarderwidth, 1080))
right_boarderstr = ffmpeg.input(right_boarder) # open right boarder
right_boarderstr = ffmpeg.filter(right_boarderstr, "scale", "%dx%d" % (boarderwidth, 1080))
picturewidth = boarderwidth - 100 # calculate width of picture
pictureheight = (1080 / 3) - 100 # calculate height of picture
picturestr = ffmpeg.input(picture_file) # open picture and scale it (there will be a padding of 100 px around it)
picturestr = ffmpeg.filter(picturestr, "scale", "%dx%d" % (picturewidth, pictureheight))
videostr = ffmpeg.overlay(videostr[0], left_boarderstr, x=0, y=0) # add left boarder
videostr = ffmpeg.overlay(videostr, right_boarderstr, x=boarderwidth + videowidth, y=0) #add right boarder
picture_y = (((1080 / 3) * 2) + 50) # calculate picture y position for bottom alignment
if picture_pos == "top":
picture_y = 50
elif picture_pos == "center":
picture_y = (1080 / 3) + 50
videostr = ffmpeg.overlay(videostr, picturestr, x=50, y=picture_y)
text_x = (1920 - boarderwidth) + 50
text_y = ((1080 / 3) * 2) + 50
if text_pos == "center":
text_y = (1080 / 3) + 50
elif text_pos == "top":
text_y = 50
videostr = ffmpeg.drawtext(videostr, textfile=text_file, reload=1, x=text_x, y=text_y, fontsize=50)
videostr = ffmpeg.output(videostr, audiostr, output)
ffmpeg.run(videostr)
def main():
#create ArgumentParser and add options to it
argp = argparse.ArgumentParser(prog="ffmpeg-template")
argp.add_argument("--videos", help="paths to main videos (default: video.mp4)", nargs="*", default="video.mp4")
argp.add_argument("--left-boarders", help="paths to images for left boarders (default: left_boarder.png)", nargs="*", default="left_boarder.png")
argp.add_argument("--right-boarders", help="paths to images for right boarders (default: right_boarder.png)", nargs="*", default="right_boarder.png")
argp.add_argument("--picture-files", nargs="*", help="paths to pictures (default: picture.png)", default="picture.png")
argp.add_argument("--picture-pos", help="where to put the pictures (default: bottom)", choices=["top", "center", "bottom"], default="bottom")
argp.add_argument("--text-files", nargs="*", help="paths to files with text (default: text.txt)", default="text.txt")
argp.add_argument("--text-pos", help="where to put the texts (default: bottom)", choices=["top", "center", "bottom"], default="bottom")
argp.add_argument("--outputs", nargs="*", help="paths to outputfiles (default: out.mp4)", default="out.mp4")
args = argp.parse_args()
# if only one file was provided, put it into a list (else, later, every letter of the filename will be treated as a filename)
if type(args.videos) == str:
args.videos = [args.videos]
if type(args.left_boarders) == str:
args.left_boarders = [args.left_boarders]
if type(args.right_boarders) == str:
args.right_boarders = [args.right_boarders]
if type(args.picture_files) == str:
args.picture_files = [args.picture_files]
if type(args.text_files) == str:
args.text_files = [args.text_files]
if type(args.outputs) == str:
args.outputs = [args.outputs]
for i in (range(0, min(len(args.videos), len(args.left_boarders), len(args.right_boarders), len(args.picture_files), len(args.text_files), len(args.outputs))) or [0]):
print("Info : merging video %s, boarders %s %s, picture %s and textfile %s into %s" % (args.videos[i], args.left_boarders[i], args.right_boarders[i], args.picture_files[i], args.text_files[i], args.outputs[i]))
# check if all files provided with the options exist
if not os.path.isfile(args.videos[i]):
print("Error : video %s was not found" % args.videos[i])
continue
if not os.path.isfile(args.left_boarders[i]):
print("Error : left boarder %s was not found" % args.left_boarders[i])
continue
if not os.path.isfile(args.right_boarders[i]):
print("Error : rightt boarder %s was not found" % args.right_boarders[i])
continue
if not os.path.isfile(args.picture_files[i]):
print("Error : picture %s was not found" % args.picture_files[i])
continue
if not os.path.isfile(args.text_files[i]):
print("Error : textfile %s was not found" % args.text_files[i])
continue
try:
make_video(args.videos[i], args.left_boarders[i], args.right_boarders[i], args.picture_files[i], args.picture_pos, args.text_files[i], args.text_pos, args.outputs[i])
except Exception as e:
print(e)
if __name__ == "__main__":
main()
直接用作脚本的示例:
$ ./ffmpeg-template --videos input1.mp4 inout2.mp4 --left-boarders left_boarder1.png left_boarder2.png --right-boarders right_boarder1.png right_boarder2.png --picture-files picture1.png picture2.png --text-files text1.txt text2.png --outputs out1.mp4 out2.mp4 --picture-pos bottom --text-pos bottom
作为选项的值,我采用了默认值。如果省略选项,将使用这些默认值,如果未找到其中一个文件,将显示一条错误消息。
图片: