# ADD TEXT THAT IS GOING TO MOVE near CENTER of the video. This is an adaptation # of code zulko.github.io/moviepy/examples/moving_letters.html - The for loops # have been simplified to standard one, some variables have been added for better # control for users. Functions have been added to create animation for different # text effects. By default, the background colour is BLACK. However, the most # appropriate utility of the function is to add text to an existing video. This # code does not check if the text shall fit into the width or duariotn of video # Tested on Ubuntu 20.04 LTS ''' Error Message: This error can be due to the fact that ImageMagick is not installed on your computer, or (for Windows users) that you didn't specify the path to the ImageMagick binary in file conf.py, or that the path you specified is incorrect Solution: sudo vim /etc/ImageMagick-6/policy.xml --> remove the line ''' import cv2 import numpy as np import os, platform, sys from moviepy.editor import * from moviepy.video.tools.segmenting import findObjects #--------------------------USER INPUTS----------------------------------------- vidSize = (360, 202) outVidName = 'textAnimMoviepy.mp4' inVidName = 'blankVid.mp4' fps = 25 #Frame per second for the clip duration = 10 #Duration of the video clip in second txt = 'MOVIEPY' fsz = 50 #Font size fsp = 5 #Font spacing # Specify color of text, works for any RGB except rgb(0, 0, 0) which is black colr = "rgb(255,0,0)" #Allowed names: print(b'\n'.join(TextClip.list('color')).decode()) #---------------No further input is needed except set_position ---------------- # Create an ImageClip originating from a script-generated text image # Requires ImageMagick. kerning: spaces between fonts txtClip = TextClip(txt, color=colr, font="Calibri", kerning=fsp, fontsize=fsz) # Use txtClip.set_position to coordinates specificed by location cvc = CompositeVideoClip([txtClip.set_position("center","top")], size=vidSize) # A variable say loc = ("center","top") does not work # ----------------------------------------------------------------------------- # NEXT 4 FUNCTIONS DEFINE FOUR WAYS OF MOVING THE LETTERS based on rotMatrix rotMatrix = lambda a: np.array( [[np.cos(a),np.sin(a)], [-np.sin(a),np.cos(a)]] ) # screenpos: location on the frame # i: counter for letters in the text # nletters: total number f characters in the text def vortex(screenpos, i, nletters): d = lambda t : 1.0/(0.3+t**8) # damping when a text falls a = i*np.pi/ nletters # angle of the movement v = rotMatrix(a).dot([-1,0]) if i%2 : v[1] = -v[1] return lambda t: screenpos+400*d(t)*rotMatrix(0.5*d(t)*a).dot(v) def cascade(screenpos, i, nletters): v = np.array([0,-1]) d = lambda t : 1 if t<0 else abs(np.sinc(t)/(1+t**4)) return lambda t: screenpos+v*400*d(t-0.15*i) def arrive(screenpos, i, nletters): v = np.array([-1,0]) d = lambda t : max(0, 3-3*t) return lambda t: screenpos-400*v*d(t-0.2*i) def vortexout(screenpos, i, nletters): d = lambda t : max(0,t) # damping a = i*np.pi/ nletters # angle of the movement v = rotMatrix(a).dot([-1,0]) if i%2 : v[1] = -v[1] return lambda t: screenpos+400*d(t-0.1*i)*rotMatrix(-0.2*d(t)*a).dot(v) #------------------------------------------------------------------------------ # USE THE PLUGIN findObjects TO LOCATE AND SEPARATE EACH LETTER letters = findObjects(cvc) # a list of ImageClips # ANIMATE THE LETTERS def moveLetters(letters, funcpos): txtSeq = [] for i,letter in enumerate(letters): seq = letter.set_position(funcpos(letter.screenpos, i, len(letters))) txtSeq.append(seq) return txtSeq #------------------------------------------------------------------------------ # Overlay the text clips, create and save animations def createVortexInEffect(outVidName, fps, duration): frames = [] for funcpos in [vortex]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0,duration) frames.append(clips) vortex_clip = concatenate_videoclips(frames) vortex_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') def vortexInEffectExistingVideo(inVidName, outVidName, fps, duration): backgroundVideo = VideoFileClip(inVidName) frames = [] for funcpos in [vortex]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0, duration) clips = CompositeVideoClip([backgroundVideo, clips]) frames.append(clips) vortex_clip = concatenate_videoclips(frames) vortex_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') #vortexInEffectExistingVideo('blankVid.mp4', outVidName, fps, duration) #------------------------------------------------------------------------------ def createCascadeEffect(outVidName, fps, duration): frames = [] for funcpos in [cascade]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0,duration) frames.append(clips) cascade_clip = concatenate_videoclips(frames) cascade_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') def cascadeEffectExistingVideo(inVidName, outVidName, fps, duration): backgroundVideo = VideoFileClip(inVidName) frames = [] for funcpos in [cascade]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0, duration) clips = CompositeVideoClip([backgroundVideo, clips]) frames.append(clips) cascade_clip = concatenate_videoclips(frames) cascade_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') #cascadeEffectExistingVideo('blankVid.mp4', outVidName, fps, duration) #------------------------------------------------------------------------------ def createEntranceEffect(outVidName, fps, duration): frames = [] for funcpos in [arrive]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0,duration) frames.append(clips) entrance_clip = concatenate_videoclips(frames) entrance_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') def entranceEffectExistingVideo(inVidName, outVidName, fps, duration): backgroundVideo = VideoFileClip(inVidName) frames = [] for funcpos in [arrive]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0, duration) clips = CompositeVideoClip([backgroundVideo, clips]) frames.append(clips) entrance_clip = concatenate_videoclips(frames) entrance_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') entranceEffectExistingVideo('starWarOpening.mp4', outVidName, fps, duration) #------------------------------------------------------------------------------ def createVortexOutffect(outVidName, fps, duration): frames = [] for funcpos in [vortexout]: txtSeq = moveLetters(letters,funcpos) clips = CompositeVideoClip(txtSeq, size=vidSize).subclip(0,duration) frames.append(clips) vortex_clip = concatenate_videoclips(frames) vortex_clip.write_videofile(outVidName, fps=fps, codec='mpeg4') #createVortexInEffect(outVidName, fps, duration) # ----------------------------------------------------------------------------- def createEmptyVideo(vidName, size, fps, duration, bg_color): #Size of video is in order height x width, bg_color in [B, G, R] sequence codec = cv2.VideoWriter_fourcc(*'mp4v') vid = cv2.VideoWriter(vidName, codec, fps, [size[0], size[1]]) nframes = int(duration * fps) img = np.zeros((size[1], size[0], 3), dtype=np. uint8) img[:, :, 0] = bg_color[0] img[:, :, 1] = bg_color[1] img[:, :, 2] = bg_color[2] for i in range(0, nframes+1, 1): vid.write(img) vid.release() #Optionally release the video name to a calling function return vidName #createEmptyVideo("blankVid.mp4", vidSize, fps, duration, [255, 0, 0]) # ------------------------MOVIEPY UTILITIES------------------------------------ # Loading an existing video # vidClip = VideoFileClip("inpVideo.mp4") # Clipping the video for desired time segment say initial 10 seconds # vidClip = vidClip.subclip(0, 10) # Optionally reduce the audio volume (volume x 0.75) # vidClip = vidClip.volumex(0.75) # To overlay the text clips on an existing video clip # clips = CompositeVideoClip([vidClip, txtClip]) # Set Frame Speed # txt_clip = txt_clip.set_duration(10).set_fps(25) # clip.set_position((x0, y0)) # Clip is at 40% of the width, 70% of the height # clip.set_position((0.4, 0.7), relative=True) # Clip horizontally centered, at the top of the picture # clip.set_position(("center","top")) # Clip's position is horizontally centered, and moving up ! # clip.set_position(lambda t: ('center', 50+t) )