pythonmp4moviepyvideo-editing

MoviePy: CompositeVideoClip generates blank, unplayable video


I'm trying to generate a video: the image at the top, the text in the center and the video at the bottom. I don't want any gap between them. The image is resized to become the first half, so the height is set to 960 (the final video is 1080 x 1920). Similarly, the video is resized and cropped. The text should be on top of the whole thing. But what it generates is a blank, unplayable video (2570 x 960: why is the width 2570?); no errors, either. I've tried many combinations over the past few days, but none worked.

Here's the code:

import cv2
from moviepy.editor import VideoFileClip, concatenate_videoclips, TextClip, ImageClip, CompositeVideoClip, clips_array
from moviepy.video.fx.crop import crop
from moviepy.video.fx.resize import resize

from moviepy.config import change_settings
change_settings({"IMAGEMAGICK_BINARY": r"E:\User\ImageMagick\ImageMagick-7.1.1-Q16-HDRI\magick.exe"})

def generate_video(image_path, video_path, output_path, text, text_options: dict = None):
    
    image_clip = ImageClip(image_path)
    image_clip = resize(image_clip, width=1080, height=960)
    
    video_clip = VideoFileClip(video_path)
    video_clip = resize(video_clip, height=960)
    video_clip = crop(video_clip, x_center=video_clip.w/2, width=1080)
    
    if text_options is None:
        text_options = {'fontsize': 30, 'color': 'white', 'bg_color': 'black', 'font': 'Arial'}

    text_clip = TextClip(text, text_options)
    text_clip = text_clip.set_position(("center", "center"))
    
    image_clip = image_clip.set_duration(video_clip.duration)
    text_clip = text_clip.set_duration(video_clip.duration)
    
    image_clip = image_clip.set_position((0.0, 0.0), relative=True)
    video_clip = video_clip.set_position((0.0, 0.5), relative=True)

    final_clip = CompositeVideoClip([image_clip, text_clip, video_clip])
    
    final_clip.write_videofile(output_path, codec="libx264", audio=False)
    
    image_clip.close()
    text_clip.close()
    video_clip.close()

It's worked before (so the installation is fine; when the script was smaller), the paths are correct, and I've called the function with valid arguments. Why doesn't this code work? I'm also curious to know how the code would be if clips_array or concatenate_videoclips were used (the output should be exactly the same).


Solution

  • The issue comes from these two lines bellow, resize asumes that only one of the arguments will be specified, meaning that the width argument is ignored (hitting this condition) and because you didn't specify a size when calling CompositeVideoClip, the final clip will be rendered with the size of the first clip in the array (not documented but happens in this line) which is most likely why you can't play the video.

    image_clip = resize(image_clip, width=1080, height=960)
    final_clip = CompositeVideoClip([image_clip, text_clip, video_clip])
    

    Here you have it fixt.

    def generate_video(image_path, video_path, output_path, text, text_options=None):
        video_clip = VideoFileClip(video_path)
        video_clip = resize(video_clip, newsize=(1920, 1080))
        video_clip = crop(video_clip, y1=1920 // 4, height=1920 // 2)
    
        image_clip = ImageClip(image_path, duration=video_clip.duration)
        image_clip = resize(image_clip, newsize=(1920, 1080))
        image_clip = crop(image_clip, height=1920 // 2)
        
        default_text_options = {
            'fontsize': 30, 
            'font': 'Arial',
            'color': 'white', 
            'bg_color': 'black', 
        }
    
        text_clip = TextClip(text, **(text_options or default_text_options))
        text_clip = text_clip.set_duration(video_clip.duration)
    
        final_clip = CompositeVideoClip(
            [
                image_clip.set_position((0.0, 0.0), relative=True), 
                video_clip.set_position((0.0, 0.5), relative=True), 
                text_clip.set_position(("center", "center")),  # the last layer is the top layer
            ], 
            size=(1920, 1080),
        )
        
        final_clip.write_videofile(output_path, codec="libx264", audio=False)
        
        text_clip.close()
        image_clip.close()
        video_clip.close()