Search code examples
pythonmoviepy

Moviepy Transition issues with multiple CompositeVideoClips


I'm trying to create a video slideshow with moviepy with a slide up transition between the slides. I can get the transition to work fine if they are just ImageClips, but when I add text to the images using a CompositeVideoClip it stops working. If I have just one CompositeVideoClip and the rest ImageClips it works fine, but once I have more than one CompositeVideoClips it starts to break.

I'm not sure if this is a bug with moviepy, or with the way I have it setup.

Here is my code:

from moviepy.editor import *

H = 720
W = 1280
SIZE = (W, H)
HX = H + H * .10  # increase size 10%
WX = W + W * .10
bold_font = 'Liberation-Sans-Bold'
plain_font = 'Liberation-Sans'

def slide_out(clip, duration, height, counter):
    def calc(t, counter, duration, h):
        ts = t - (counter * duration)
        val = min(-45, h*(duration-ts))
        return ('center', val)
    return clip.set_pos(lambda t: calc(t, counter, duration, height))

def add_transition(clip_size, counter, clip):
    # reverse the count to get slide number.
    counter = clip_size - 1 - counter
    return slide_out(clip.resize(height=HX, width=WX), 3, HX, counter)

img_1 = ImageClip("/pics/1.jpg").set_duration(4).set_start(0).resize(height=H, width=W)   # 3-8
txt_1 = TextClip("title 1", font=bold_font, color='white', fontsize=64, interline=9).set_duration(2).set_start(1).set_pos(('right', 360)).crossfadein(.3)
stxt_1 = TextClip("sub title 1", font=plain_font, color='white', fontsize=80, interline=9).set_duration(1.5).set_start(1.5).set_pos(('right', 440)).crossfadein(.3)
img_2 = ImageClip("/pics/2.jpg").set_duration(4).set_start(3).resize(height=H, width=W)   # 3-8
txt_2 = TextClip("title 2", font=bold_font, color='white', fontsize=64, interline=9).set_duration(3).set_start(3.5).set_pos(('right', 360)).crossfadein(.3)
stxt_2 = TextClip("sub title 2", font=plain_font, color='white', fontsize=80, interline=9).set_duration(2.5).set_start(3.5).set_pos(('right', 440)).crossfadein(.3)

# slides images with text on top.
slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]) #.set_duration(4)
slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]) #.set_duration(4)

clips = [slide_2, slide_1] # reverse because we want the first slides on top.

slides = [add_transition(len(clips), x, clip) for x, clip in enumerate(clips)]
final_clip = CompositeVideoClip(slides, size=SIZE).set_duration(8)
final_clip.write_videofile("/pics/vids/video.mp4", fps=24, audio_codec="aac")

I have tried a few different things to see if I can figure out what is going on. Any help that you could give, would be great.

Option 1

If if slide_1 has duration set and slide_2 and final_clip does not have duration set. finished video has duration=4, total duration is 4. It will show the full first slide, and only the first second of the second slide.

slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]).set_duration(4) slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]) final_clip = CompositeVideoClip(slides, size=SIZE)

Result

1

Option 2

if slide_1 and slide_2 don't set duration, but final_clip does have duration of 8. it is 8 seconds long but the second image only shows up for 1 second (at t=3 to t=4), then disapears and leaves the text.

slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]) slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]) final_clip = CompositeVideoClip(slides, size=SIZE).set_duration(8)

Result

2

Option 3.

if all three have durations set. slide_1 works fine, but slide 2 only shows up for 1 second, then goes black for rest of the time.

slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]).set_duration(4) slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]).set_duration(4) final_clip = CompositeVideoClip(slides, size=SIZE).set_duration(8)

Result

3

Option 4.

if none of them have durations set. same as 2.

slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]) slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]) final_clip = CompositeVideoClip(slides, size=SIZE)

Result

2

Option 5.

if slide 2 has duration, but slide_1 and final_clip do not. same as 1.

slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]) slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]).set_duration(4) final_clip = CompositeVideoClip(slides, size=SIZE)

Result

1


Solution

  • please test the below code,then you will find the mechanism behind the CompositeVideoClip

    from moviepy.editor import *
    H = 720
    W = 1280
    SIZE = (W, H)
    HX = H + H * .10  # increase size 10%
    WX = W + W * .10
    bold_font = 'Liberation-Sans-Bold'
    plain_font = 'Liberation-Sans'
    img_1 = ImageClip("bbb.jpeg").set_duration(4).set_start(0).resize(height=H, width=W)   # 3-8
    txt_1 = TextClip("title 1", font=bold_font, color='white', fontsize=64, interline=9).set_duration(2).set_start(1).set_pos(('right', 360)).crossfadein(.3)
    stxt_1 = TextClip("sub title 1", font=plain_font, color='white', fontsize=80, interline=9).set_duration(1.5).set_start(1.5).set_pos(('right', 440)).crossfadein(.3)
    img_2 = ImageClip("aaa.jpg").set_duration(8).set_start(4).resize(height=H, width=W)   # 3-8
    # look the img_2's set_duration and set_start,the same as txt_2 and stxt_2
    txt_2 = TextClip("title 2", font=bold_font, color='white', fontsize=64, interline=9).set_duration(7).set_start(4.5).set_pos(('right', 360)).crossfadein(.3)
    stxt_2 = TextClip("sub title 2", font=plain_font, color='white', fontsize=80, interline=9).set_duration(6).set_start(5.5).set_pos(('right', 440)).crossfadein(.3)
    
    slide_1 = CompositeVideoClip([img_1, txt_1, stxt_1]).set_duration(4)
    slide_2 = CompositeVideoClip([img_2, txt_2, stxt_2]).set_duration(8)
    
    clips = [slide_2, slide_1]
    final_clip = CompositeVideoClip(clips, size=SIZE).set_duration(8)
    final_clip.write_videofile("video.mp4", fps=12, audio_codec="aac")
    

    all the weird behave is cause by the code in videopy.video.compositeVideoClip.py

            # compute duration
        ends = [c.end for c in self.clips]
        if not any([(e is None) for e in ends]):
            self.duration = max(ends)
            self.end = max(ends)