1

I'll be scraping clips from twitch and merging them to create a single video file. I already figured out the scraping of twitch clip links(but i only get 16-20 videos because i need to scroll with selenium but i dont really mind it, if you have a working solution then make an answer about it) and also the simple merging videos.

I'm scraping links with:

#!/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

I'm joining videos together after downloading(with youtube-dl) with ffmpeg:

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

Where videos.txt is as follows:

file 'video_file1.mp4'
file 'video_file2.mp4'
...

I can't really find answers on how to add a watermark(different for each video, although i found this it doesnt explain how to add a unique watermark to individual videos but the same watermark to two videos) without having to render each and every video twice but doing so in one go.

I think I stumbled upon some people who made their videos.txt as follows in purpose of adding extra options to each video:

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)
...

Would this work for unique watermarks for each videos(lets suppose watermarks are named video_file1.png, ... meaning the same as the videos, also the watermark is transparent in case that needs more configuration)

dconixDev
  • 11
  • 2
  • Why not setting the watermark image file name using Python code? – Rotem Jun 28 '21 at 18:52
  • @Rotem Not related, what im trying to do is merge (e.g.) 10 videos and each video to have a unique watermark. The naming is irrelevant I just gave an example (video_file1.png) so its easier for me to understand when used on answers. – dconixDev Jun 28 '21 at 19:59
  • Do you mean, concatenating a video from 10 short videos, but adding a different watermark on each one of the short videos before the concatenation? – Rotem Jun 28 '21 at 20:05
  • @Rotem precisely, but dont forget that I would want to do it in one go. If the option simply doesnt exist or is too hard to implement I guess I'd be fine with creating videos with watermarks first then concatenating them – dconixDev Jun 28 '21 at 20:08
  • How about programmatically building a long command line like: `ffmpeg -i input1.mp4 -i input2.webm -i input3.mov -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0][2:v:0][2:a:0]concat=n=3:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" output.mkv`, but adding `overlay` filter for each part for adding a watermark? – Rotem Jun 28 '21 at 20:24
  • Im new to ffmpeg so im not familiar with the way i can pile up arguments, if i can add overlays for each video as you described then it would be pretty easy, ill give your method a try. If you could start the command for the first two videos and the watermark (in bottom left) I'd appreciate it. – dconixDev Jun 28 '21 at 20:47
  • Hi, don't you have any comment for my answer? I thought that kind of solution is what you are looking for. – Rotem Jul 07 '21 at 07:53

2 Answers2

1

You may solve it by chaining FFmpeg filters (using filter_complex as described here).

The syntax is confusing but manageable...

For the example, I chose to create synthetic input files instead of downloading videos for the WEB (it makes the example more reproducible).

Start by building 3 synthetic video files, and 3 synthetic watermark images (later used as input):

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}'))

Now we want to build a command as follows:

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'))

The command overlays a watermark and concatenate the watermarked videos.


Building the command programmatically can be done using few for loops:

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'

Here is a complete (executable) code sample:

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))

Note:

  • Building a long string and using shlex.split for splitting the string to list is not the best option.
    I used that solution because I wanted the command to look like an executable command line (for familiar for FFmpeg users that don't use Python).

Few sample video frames:

enter image description here

enter image description here

enter image description here

Rotem
  • 30,366
  • 4
  • 32
  • 65
0

The select filter is better for this.

ffmpeg -i video -vf "select='between(t,4,6.5)+between(t,17,26)+between(t,74,91)',
            setpts=N/FRAME_RATE/TB" -af "aselect='between(t,4,6.5)+between(t,17,26)+between(t,74,91)',
            asetpts=N/SR/TB" out.mp4

select and its counterpart filter is applied to the video and audio respectively. Segments selected are times 4 to 6.5 seconds, 17 to 26 seconds and finally 74 to 91 seconds. The timestamps are made continuous with the setpts and its counterpart filter.

  • 2
    Okay how do I set the watermark on the videos (each watermark+video is unique) (while keeping the previous configuration of the ffmpeg command mostly untouched, or if theres a better version of that configuration please suggest it)? – dconixDev Jun 28 '21 at 20:02