How can I concatenate and overlay clips with transparency in Moviepy?

Viewed 353

If I have 6 mp4 files, how could I make a video where 5 of those mp4 files play one after each other, with a 0.5 second transparent transition between each video, all on top of the other (the 6th) mp4 file? (Check the video attached below to get an idea of what I'm talking about, but the video was made in a different and worse way)

Currently, I have code that concatenates the 5 into one video, and then puts that video onto the 6th video. But the problem is then the transition between videos isn't transparent, and there are black bars on top and bottom of all of the smaller concatenated videos.

Does anyone know how I could solve this problem? It doesn't have to be solved the way I think it could be; my end goal is just to have transparent transitions between the clips and no thick black bars anywhere. Any help is greatly appreciated, thank you.

The video my current code makes: https://www.youtube.com/watch?v=_Pe3EVDQV74

My code:

from ctypes import resize
from time import sleep
from os import chdir
from os import walk
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.common.exceptions import NoSuchElementException
from gtts import gTTS
from moviepy.editor import *
from getStockFootage import getStockFootage
from selenium.webdriver.common.action_chains import ActionChains

#Get the audio files
myAudioFiles = next(walk(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\topicAndCommentsAudio'), (None, None, []))[2]
audioClips = []

#Get the picture files
myImageFiles = next(walk(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\topicAndCommentsPictures'), (None, None, []))[2]
imageClips = []

#Put all audio clips into a list
chdir(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\topicAndCommentsAudio')
for audioNumber in range(len(myAudioFiles)):
    audioClips.append(AudioFileClip(myAudioFiles[audioNumber]))

#Put all image clips into a list
chdir(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\topicAndCommentsPictures')
for imageNumber in range(len(myImageFiles)):
    imageClips.append(ImageClip(myImageFiles[imageNumber], transparent=True))

#Make a bunch of small videos with the audio and image
chdir(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo')
for videoNumber in range(len(imageClips)):
    sleep(0.5)
    if videoNumber == (len(imageClips)-1):
        video = imageClips[videoNumber].set_audio(audioClips[videoNumber])
        video.duration = audioClips[videoNumber].duration
        video.write_videofile('aTopicText.mp4', fps = 30)
    else:
        video = imageClips[videoNumber].set_audio(audioClips[videoNumber])
        video.duration = audioClips[videoNumber].duration
        video.write_videofile('comment'+str(videoNumber+1)+'.mp4', fps = 30)

#Get a stock video (.mp4) for the video and puts the video into C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo\stockFootage
sleep(2)
getStockFootage(driver)

#Merge all the smaller videos into one large one
chdir(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo')
myVideoFiles = next(walk(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo'), (None, None, []))[2]
for counter16 in range(len(myVideoFiles)):
    if '.mp4' not in myVideoFiles[counter16]:
        myVideoFiles.remove(myVideoFiles[counter16])
videoClips = []
for videoNumber in range(len(myVideoFiles)):
    videoClips.append(VideoFileClip(myVideoFiles[videoNumber]))

#Make video of comments
sleep(3)
mergedVideos = concatenate_videoclips(videoClips, method='compose', bg_color=None, padding=0.5)
mergedVideos.write_videofile('mergedVideo.mp4', fps = 30)
mergedVideos.close()

#The comment video
commentVideo = VideoFileClip('mergedVideo.mp4')
commentVideo = commentVideo.resize(height=360)
commentVideo = commentVideo.set_position(("center"))

#Delete used files (let's keep the workplace clean)
# sleep(3)
# for counter17 in range(len(myVideoFiles)):
#     os.remove(myVideoFiles[counter17])
sleep(3)

#Get stock footage and make into video
chdir(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo\stockFootage')
myStockFootage = next(walk(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo\stockFootage'), (None, None, []))[2]
stockFootage = VideoFileClip(myStockFootage[0], target_resolution=(1080, 1920), audio=False)
stockFootage = stockFootage.loop(duration = mergedVideos.duration)
stockFootage.write_videofile('loopedStock.mp4', fps = 30)
stockFootage.close()
os.remove(myStockFootage[0])

#Get looped video
loopedVideo = VideoFileClip('loopedStock.mp4', target_resolution=(1080, 1920), audio=False)

#Put commentVideo onto stockFootage
chdir(r'C:\Users\jack_l\Documents\REDDIT_TO_YOUTUBE_PYTHON_SELENIUM\redditVideo\finalVideo')
finalVideo = CompositeVideoClip([loopedVideo, commentVideo])
finalVideo.write_videofile('finalVideo.mp4', fps=30)

print('Finished')
1 Answers

From the interactive help (docstring comment) for concatenate_videoclips:

     - method="compose", if the clips do not have the same resolution, the final
      resolution will be such that no clip has to be resized.
      As a consequence the final clip has the height of the highest clip and the
      width of the widest clip of the list. All the clips with smaller dimensions
      will appear centered. The border will be transparent if mask=True, else it
      will be of the color specified by ``bg_color``.

The method itself is declared as:

def concatenate_videoclips(
    clips, method="chain", transition=None, bg_color=None, is_mask=False, padding=0
):

so presumably mask in the docstring should say is_mask instead.

Setting that parameter should allow for the concatenated clips to appear transparent - i.e., the background will show through during the padding time, and unpainted pixels will be transparent when the current clip is smaller than the overall concatenated clip.

The documentation does also say to use bg_color=None (which is the default anyway) for a transparent clip, but apparently this is not sufficient. From tracing through the code, it looks like the values of bg_color and is_mask will be forwarded to CompositeVideoClip, which in turn will use a ColorClip for the background - and when is_mask is False, it appears that a ColorClip will necessarily be opaque (it represents the colour information internally as RGB, not RGBA).

Thus:

mergedVideos = concatenate_videoclips(
    videoClips, method='compose', is_mask=True, padding=0.5
)

You may also want to call attention to this on the project's issue tracker, if it isn't mentioned there already.

Related