python - 将视频与具有额外属性的 ffmpeg(或替代品)合并(叠加)

标签 python selenium video ffmpeg watermark

我将从 twitch 中抓取剪辑并将它们合并以创建单个视频文件。
我已经弄清楚了抽搐剪辑链接的抓取(但我只得到 16-20 个视频,因为我需要用 Selenium 滚动,但我真的不介意,如果你有一个可行的解决方案,然后就它做出一个答案)而且也很简单合并视频。
我正在抓取以下链接:

#!/usr/bin/python3.9
import bs4
import requests
import time
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

# Initialize driver and run it headless
options = Options()
options.headless = True
driver = webdriver.Firefox(options=options)

def extract_source(url):
     agent = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"}
     source=requests.get(url, headers=agent).text
     return source

def extract_data(source):
     soup=bs4.BeautifulSoup(source, 'html.parser')
     names=soup.find_all('a', attrs={'data-a-target':'preview-card-image-link'})
     return names

driver.get('https://www.twitch.tv/directory/game/League%20of%20Legends/clips?range=24hr')

# I wait 3 seconds for the clips to get pulled in
# I'd like here to scroll down a bit so i can scrape more clips, but even after i tried some solutions my firefox(was debugging in GUI mode, not headless as it is now) wasnt scrolling
time.sleep(3)
extract_links=extract_data(driver.page_source)
for a in extract_links:
    print(a.get('href'))

driver.quit()

# I tried scrolling using this but didnt work, not sure why
# this script is supposed to scroll until youre at the end of the page
# SCROLL_PAUSE_TIME = 0.5

# # Get scroll height
# last_height = driver.execute_script("return document.body.scrollHeight")

# for i in range(3):
    # # Scroll down to bottom
    # driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    # # Wait to load page
    # time.sleep(SCROLL_PAUSE_TIME)

    # # Calculate new scroll height and compare with last scroll height
    # new_height = driver.execute_script("return document.body.scrollHeight")
    # if new_height == last_height:
        # break
    # last_height = new_height
使用 ffmpeg 下载(使用 youtube-dl)后,我将视频合并在一起:ffmpeg -safe 0 -f concat -segment_time_metadata 1 -i videos.txt -vf select=concatdec_select -af aselect=concatdec_select,aresample=async=1 out.mp4其中videos.txt如下:
file 'video_file1.mp4'
file 'video_file2.mp4'
...
我真的找不到关于如何添加水印的答案(每个视频都不同,尽管我发现 this 它没有解释如何为单个视频添加唯一的水印,但为两个视频添加相同的水印)而无需渲染每个每个视频两次,但一次完成。
我想我偶然发现了一些制作 videos.txt 的人。如下,目的是为每个视频添加额外的选项:
file 'video_file1.mp4'
option 1(for video_file1.mp4)
option 2(for video_file1.mp4)
file 'video_file2.mp4'
option 1(for video_file2.mp4)
option 2(for video_file2.mp4)
...
这是否适用于每个视频的唯一水印(假设水印被命名为 video_file1.png,...意思与视频相同,水印也是透明的,以防需要更多配置)

最佳答案

您可以通过链接 FFmpeg 过滤器来解决它(使用 filter_complex 描述的 here )。
语法令人困惑但易于管理......
例如,我选择创建合成输入文件,而不是为 WEB 下载视频(它使示例更具重现性)。
首先构建 3 个合成视频文件和 3 个合成水印图像(稍后用作输入):

import subprocess as sp
import shlex

vid1 = 'in1.mp4'
vid2 = 'in2.mp4'
vid3 = 'in3.mp4'
in_videos = [vid1, vid2, vid3]

watermark1 = 'waterMark1.png'
watermark2 = 'waterMark2.png'
watermark3 = 'waterMark3.png'
watermarks = [watermark1, watermark2, watermark3]

out_video = 'output.mp4'

n = len(in_videos)

sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -f lavfi -i sine=frequency=300 -c:v libx264 -c:a aac -ar 22050 -t 5 {vid1}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -f lavfi -i sine=frequency=400 -c:v libx264 -c:a aac -ar 22050 -t 5 {vid2}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -f lavfi -i sine=frequency=500 -c:v libx264 -c:a aac -ar 22050 -t 5 {vid3}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i mandelbrot=rate=1:size=64x64 -t 1 {watermark1}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i cellauto=rate=1:size=64x64 -t 1 {watermark2}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i life=rate=1:size=64x64:mold=10:r=100:ratio=0.1:death_color=blue:life_color=#00ff00 -frames:v 1 {watermark3}'))

现在我们要构建一个命令,如下所示:
sp.run(shlex.split('ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 -i waterMark1.png -i waterMark2.png -i waterMark3.png -filter_complex "'
                   '[0:v:0][3:v]overlay=10:main_h-overlay_h-10[v0];'
                   '[1:v:0][4:v]overlay=10:main_h-overlay_h-10[v1];'
                   '[2:v:0][5:v]overlay=10:main_h-overlay_h-10[v2];'
                   '[v0][0:a:0][v1][1:a:0][v2][2:a:0]concat=n=3:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" '
                   '-vcodec libx264 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 output.mp4'))
该命令覆盖水印并连接带水印的视频。

可以使用几个 for 循环以编程方式构建命令:
cmd = 'ffmpeg -y '

# 'ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 '
for vid in in_videos:
    cmd += '-i ' + vid + ' '

# ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 -i waterMark1.png -i waterMark2.png -i waterMark3.png
for watermark in watermarks:
    cmd += '-i ' + watermark + ' '

# ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 -i waterMark1.png -i waterMark2.png -i waterMark3.png -filter_complex "
cmd += '-filter_complex "'

for i in range(n):
    cmd += f'[{i}:v:0][{i+n}:v]overlay=10:main_h-overlay_h-10[v{i}];'  # [0:v:0][3:v]overlay=10:main_h-overlay_h-10[v0];

for i in range(n):
    cmd += f'[v{i}][{i}:a:0]'  # [v0][0:a:0]

cmd += f'concat=n={n}:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" -vcodec libx264 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 {out_video}'  # concat=n=3:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" output.mp4'

这是一个完整的(可执行的)代码示例:
import subprocess as sp
import shlex

# Input video files
vid1 = 'in1.mp4'
vid2 = 'in2.mp4'
vid3 = 'in3.mp4'
in_videos = [vid1, vid2, vid3]

# Input watermark images
watermark1 = 'waterMark1.png'
watermark2 = 'waterMark2.png'
watermark3 = 'waterMark3.png'
watermarks = [watermark1, watermark2, watermark3]

out_video = 'output.mp4'  # Output file name

n = len(in_videos)

# Build synthetic input files for testing (synthetic video with synthetic audio)
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -f lavfi -i sine=frequency=300 -c:v libx264 -c:a aac -ar 22050 -t 5 {vid1}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -f lavfi -i sine=frequency=400 -c:v libx264 -c:a aac -ar 22050 -t 5 {vid2}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -f lavfi -i sine=frequency=500 -c:v libx264 -c:a aac -ar 22050 -t 5 {vid3}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i mandelbrot=rate=1:size=64x64 -t 1 {watermark1}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i cellauto=rate=1:size=64x64 -t 1 {watermark2}'))
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i life=rate=1:size=64x64:mold=10:r=100:ratio=0.1:death_color=blue:life_color=#00ff00 -frames:v 1 {watermark3}'))

# We want to get to the following command:
#sp.run(shlex.split('ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 -i waterMark1.png -i waterMark2.png -i waterMark3.png -filter_complex "'
#                   '[0:v:0][3:v]overlay=10:main_h-overlay_h-10[v0];'
#                   '[1:v:0][4:v]overlay=10:main_h-overlay_h-10[v1];'
#                   '[2:v:0][5:v]overlay=10:main_h-overlay_h-10[v2];'
#                   '[v0][0:a:0][v1][1:a:0][v2][2:a:0]concat=n=3:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" '
#                   '-vcodec libx264 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 output.mp4'))

# Build ffmpeg command with arguments as a long string
cmd = 'ffmpeg -y '

# 'ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 '
for vid in in_videos:
    cmd += '-i ' + vid + ' '

# ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 -i waterMark1.png -i waterMark2.png -i waterMark3.png
for watermark in watermarks:
    cmd += '-i ' + watermark + ' '

# ffmpeg -y -i in1.mp4 -i in2.mp4 -i in3.mp4 -i waterMark1.png -i waterMark2.png -i waterMark3.png -filter_complex "
cmd += '-filter_complex "'

for i in range(n):
    cmd += f'[{i}:v:0][{i+n}:v]overlay=10:main_h-overlay_h-10[v{i}];'  # [0:v:0][3:v]overlay=10:main_h-overlay_h-10[v0];

for i in range(n):
    cmd += f'[v{i}][{i}:a:0]'  # [v0][0:a:0]

cmd += f'concat=n={n}:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" -vcodec libx264 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 {out_video}'  # concat=n=3:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" output.mp4'

# Execute FFmpeg
sp.run(shlex.split(cmd))

笔记:
  • 构建长字符串并使用 shlex.split将字符串拆分为列表不是最佳选择。
    我使用该解决方案是因为我希望该命令看起来像一个可执行命令行(对于不使用 Python 的 FFmpeg 用户来说很熟悉)。

  • 几个示例视频帧:
    enter image description here
    enter image description here
    enter image description here

    关于python - 将视频与具有额外属性的 ffmpeg(或替代品)合并(叠加),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68167642/

    相关文章:

    python - 使用 os.listdir 时出现 UnicodeEncodeError

    python - 将基于 GMT 的时间转换为 UTC python

    python - 递归字符串 : What does the line 'return s[0] == s[-1] and isPal(s[1:-1])' do?

    python - 为什么 matplotlib 的 Slider 只允许 0-7 的范围?

    html - 为 selenium 测试寻找 CSS 选择器

    selenium - 围绕 do block 的 haskell "withSubprocess"构造

    selenium - 使用 Selenium 在多个版本的浏览器上进行测试

    opengl - ffmpeg:如何从 AVframe 获取 YUV 数据并使用 opengl 绘制它?

    android - Exoplayer - 如何使 exoplayer 只播放当前可见的视频

    android - YouTubeAndroidPlayerAPI 无法播放某些视频