1+ # Lets import moviepy, lets also import numpy we will use it a some point
2+ from moviepy import *
3+ import numpy as np
4+
5+
6+ #################
7+ # VIDEO LOADING #
8+ #################
9+ # We load our video
10+ video = VideoFileClip ("./example_vids/bbb.mp4" )
11+
12+
13+ #####################
14+ # SCENES EXTRACTION #
15+ #####################
16+ # We extract the scenes we want to use
17+
18+ # First the characters
19+ intro_clip = video .subclipped (1 , 11 )
20+ bird_clip = video .subclipped (16 , 20 )
21+ bunny_clip = video .subclipped (37 , 55 )
22+ rodents_clip = video .subclipped ('00:03:34.75' , '00:03:56' )
23+ # we can also use string notation with format HH:MM:SS.uS
24+ rambo_clip = video .subclipped ('04:41.5' , '04:44.70' )
25+
26+
27+ #####################
28+ # SCENES PREVIEWING #
29+ #####################
30+ # Now, lets have a first look at our clips
31+ # Warning: you need ffplay installed for preview to work
32+ # We set a low fps so our machine can render in real time without slowing down
33+ """ intro_clip.preview(fps=20)
34+ bird_clip.preview(fps=20)
35+ bunny_clip.preview(fps=20)
36+ rodents_clip.preview(fps=20)
37+ rambo_clip.preview(fps=20) """
38+
39+
40+ ##############################
41+ # CLIPS MODIFICATION CUTTING #
42+ ##############################
43+ # Well, looking at the rodent scene it is
44+ # a bit long isn't?
45+ # Let's see how we modify the clip
46+ # with one of the many clip manipulation
47+ # method starting by with_*
48+ # in that case by removing of
49+ # the clip the part between
50+ # 00:06:00 to 00:10:00 of the clip, using
51+ # with_cutout
52+ rodents_clip = rodents_clip .with_section_cut_out (start_time = 4 , end_time = 10 )
53+
54+ # Note: You may have noticed that we have reassign rodents_clip, this is because all with_* methods return a modified *copy* of the
55+ # original clip instead of modifying it directly. In MoviePy any function starting by with_* is out-place instead of in-place
56+ # meaning it does not modify the original data, but instead copy it and modify/return the copy
57+
58+ # Lets check the result
59+ rodents_clip .preview (fps = 10 )
60+
61+ ############################
62+ # TEXT/LOGO CLIPS CREATION #
63+ ############################
64+ # Lets create the texts to put between our clips
65+ font = "./fonts/font.ttf"
66+ intro_text = TextClip (font = font , text = "The Blender Foundation and\n Peach Project presents" , font_size = 50 , color = '#fff' , text_align = "center" )
67+ bird_text = TextClip (font = font , text = "An unlucky bird" , font_size = 50 , color = '#fff' )
68+ bunny_text = TextClip (font = font , text = "A (slightly overweight) bunny" , font_size = 50 , color = '#fff' )
69+ rodents_text = TextClip (font = font , text = "And three rodent pests" , font_size = 50 , color = '#fff' )
70+ revenge_text = TextClip (font = font , text = "Revenge is coming..." , font_size = 50 , color = '#fff' )
71+ made_with_text = TextClip (font = font , text = "Made with" , font_size = 50 , color = '#fff' )
72+
73+ # We will also need the big buck bunny logo, so lets load it and resize it
74+ logo_clip = ImageClip ('./example_vids/logo_bbb.png' ).resized (width = 400 )
75+ moviepy_clip = ImageClip ('./example_vids/logo_moviepy.png' ).resized (width = 300 )
76+
77+
78+ ################
79+ # CLIPS TIMING #
80+ ################
81+ # We have all the clips we need, but if we was to turn all thoses clips into a single one with composition (we will see that during next step)
82+ # all our clips would start at the same time and play on top of each other, which is obviously not what we want.
83+ # To fix that, we need to say when a clip should start and stop in the final clip.
84+ # So, lets start by telling when each clip must start and end with appropriate with_* methods
85+ intro_text = intro_text .with_duration (6 ).with_start (3 ) # Intro for 6 seconds, start after 3 seconds
86+ logo_clip = logo_clip .with_start (intro_text .start + 2 ).with_end (intro_text .end ) # Logo start 2 second after intro text and stop with it
87+ bird_clip = bird_clip .with_start (intro_clip .end ) # Make bird clip start after intro, duration already known
88+ bird_text = bird_text .with_start (bird_clip .start ).with_end (bird_clip .end ) # Make text synchro with clip
89+ bunny_clip = bunny_clip .with_start (bird_clip .end ) # Make bunny clip follow bird clip
90+ bunny_text = bunny_text .with_start (bunny_clip .start + 2 ).with_duration (7 )
91+ rodents_clip = rodents_clip .with_start (bunny_clip .end )
92+ rodents_text = rodents_text .with_start (rodents_clip .start ).with_duration (4 )
93+ rambo_clip = rambo_clip .with_start (rodents_clip .end - 1.5 )
94+ revenge_text = revenge_text .with_start (rambo_clip .start + 1.5 ).with_duration (4 )
95+ made_with_text = made_with_text .with_start (rambo_clip .end ).with_duration (3 )
96+ moviepy_clip = moviepy_clip .with_start (made_with_text .start ).with_duration (3 )
97+
98+
99+ ########################
100+ # CLIPS TIMING PREVIEW #
101+ ########################
102+ # Lets make a first compositing of thoses clips into one single clip and do a quick preview to see if everything is synchro
103+
104+ quick_compo = CompositeVideoClip ([intro_clip , intro_text , logo_clip , bird_clip , bird_text , bunny_clip , bunny_text , rodents_clip ,
105+ rodents_text , rambo_clip , revenge_text , made_with_text , moviepy_clip ])
106+ quick_compo .preview (fps = 10 )
107+
108+
109+ ######################
110+ # CLIPS POSITIONNING #
111+ ######################
112+ # Now that we have set the timing of our different clips, we need to make sure they are in the right position
113+ # We will keep things simple, and almost always set center center for every texts
114+ bird_text = bird_text .with_position (('center' , 'center' ))
115+ bunny_text = bunny_text .with_position (('center' , 'center' ))
116+ rodents_text = rodents_text .with_position (('center' , 'center' ))
117+ revenge_text = revenge_text .with_position (('center' , 'center' ))
118+
119+ # For the logos and intro/end, we will use pixel position instead of center
120+ top = intro_clip .h // 2
121+ intro_text = intro_text .with_position (('center' , 200 ))
122+ logo_clip = logo_clip .with_position (("center" , top ))
123+ made_with_text = made_with_text .with_position (('center' , 300 ))
124+ moviepy_clip = moviepy_clip .with_position (('center' , 360 ))
125+
126+ # Lets take another look to check positions
127+ quick_compo = CompositeVideoClip ([intro_clip , intro_text , logo_clip , bird_clip , bird_text , bunny_clip , bunny_text , rodents_clip ,
128+ rodents_text , rambo_clip , revenge_text , made_with_text , moviepy_clip ])
129+ # quick_compo.preview(fps=10)
130+
131+
132+ ################################
133+ # CLIPS TRANSITION AND EFFECTS #
134+ ################################
135+ # Now that our clip are timed and positionned, lets add some transition to make it more natural
136+ # To do so we use the with_effects method and the video effects in vfx
137+ # We call with_effects on our clip and pass him an array of effect objects to apply
138+ # We'll keep it simple, nothing fancy just cross fading
139+ intro_text = intro_text .with_effects ([vfx .CrossFadeIn (1 ), vfx .CrossFadeOut (1 )])
140+ logo_clip = logo_clip .with_effects ([vfx .CrossFadeIn (1 ), vfx .CrossFadeOut (1 )])
141+ bird_text = bird_text .with_effects ([vfx .CrossFadeIn (0.5 ), vfx .CrossFadeOut (0.5 )])
142+ bunny_text = bunny_text .with_effects ([vfx .CrossFadeIn (0.5 ), vfx .CrossFadeOut (0.5 )])
143+ rodents_text = rodents_text .with_effects ([vfx .CrossFadeIn (0.5 ), vfx .CrossFadeOut (0.5 )])
144+
145+ # Also add cross fading on video clips and video clips audio
146+ # See how video effects are under vfx and audio ones under afx
147+ intro_clip = intro_clip .with_effects ([vfx .FadeIn (1 ), vfx .FadeOut (1 ), afx .AudioFadeIn (1 ), afx .AudioFadeOut (1 )])
148+ bird_clip = bird_clip .with_effects ([vfx .FadeIn (1 ), vfx .FadeOut (1 ), afx .AudioFadeIn (1 ), afx .AudioFadeOut (1 )])
149+ bunny_clip = bunny_clip .with_effects ([vfx .FadeIn (1 ), vfx .FadeOut (1 ), afx .AudioFadeIn (1 ), afx .AudioFadeOut (1 )])
150+ rodents_clip = rodents_clip .with_effects ([vfx .FadeIn (1 ), vfx .CrossFadeOut (1.5 ), afx .AudioFadeIn (1 ), afx .AudioFadeOut (1.5 )]) # Just fade in, rambo clip will do the cross fade
151+ rambo_clip = rambo_clip .with_effects ([vfx .CrossFadeIn (1.5 ), vfx .FadeOut (1 ), afx .AudioFadeIn (1.5 ), afx .AudioFadeOut (1 )])
152+ rambo_clip = rambo_clip .with_effects ([vfx .CrossFadeIn (1.5 ), vfx .FadeOut (1 ), afx .AudioFadeIn (1.5 ), afx .AudioFadeOut (1 )])
153+
154+ # Effects are not only for transition, they can also change a clip timing or apparence
155+ # To show that, lets also modify the Rambo-like part of our clip to be in slow motion
156+ # PS : We do it for effect, but this is one of the few effects that have a direct shortcut, with_multiply_speed
157+ # the others are with_multiply_volume, resized, croped and rotated
158+ rambo_clip = rambo_clip .with_effects ([vfx .MultiplySpeed (0.5 )])
159+
160+ # Because we modified timing of rambo_clip with our MultiplySpeed effect, we must re-assign the following clips timing
161+ made_with_text = made_with_text .with_start (rambo_clip .end ).with_duration (3 )
162+ moviepy_clip = moviepy_clip .with_start (made_with_text .start ).with_duration (3 )
163+
164+ # Let's have a last look at the result to make sure everything is working as expected
165+ quick_comp = CompositeVideoClip ([intro_clip , intro_text , logo_clip , bird_clip , bird_text , bunny_clip , bunny_text ,
166+ rodents_clip , rodents_text , rambo_clip , revenge_text , made_with_text , moviepy_clip ])
167+ quick_comp .preview (fps = 10 )
168+
169+
170+ ###############
171+ # CLIP FILTER #
172+ ###############
173+ # Lets finish by modifying our rambo clip to make it sepia
174+
175+ # We will start by defining a function that turn a numpy image into sepia
176+ # It takes the image as numpy array in entry and return the modified image as output
177+ def sepia_fitler (frame : np .ndarray ):
178+ # Sepia filter transformation matrix
179+ # Sepia transform works by applying to each pixel of the image the following rules
180+ # res_R = (R * .393) + (G *.769) + (B * .189)
181+ # res_G = (R * .349) + (G *.686) + (B * .168)
182+ # res_B = (R * .272) + (G *.534) + (B * .131)
183+ #
184+ # With numpy we can do that very efficiently by multiplying the image matrix by a transformation matrix
185+ sepia_matrix = np .array ([[0.393 , 0.769 , 0.189 ],
186+ [0.349 , 0.686 , 0.168 ],
187+ [0.272 , 0.534 , 0.131 ]])
188+
189+ # Convert the image to float32 format for matrix multiplication
190+ frame = frame .astype (np .float32 )
191+
192+ # Apply the sepia transformation
193+ # .T is needed because multiplying matrix of shape (n,m) * (m,k) result in a matrix of shape (n,k)
194+ # what we want is (n,m), so we must transpose matrix (m,k) to (k,m)
195+ sepia_image = np .dot (frame , sepia_matrix .T )
196+
197+ # Because final result can be > 255, we limit the result to range [0, 255]
198+ sepia_image = np .clip (sepia_image , 0 , 255 )
199+
200+ # Convert the image back to uint8 format, because we need integer not float
201+ sepia_image = sepia_image .astype (np .uint8 )
202+
203+ return sepia_image
204+
205+ # Now, we simply apply the filter to our clip by calling image_transform, which will call our filter on every frame
206+ rambo_clip = rambo_clip .image_transform (sepia_fitler )
207+
208+ # Let's see how our filter look
209+ rambo_clip .preview (fps = 10 )
210+
211+
212+ ##################
213+ # CLIP RENDERING #
214+ ##################
215+ # Everything is good and ready, we can finally render our clip into a file
216+ final_clip = CompositeVideoClip ([intro_clip , intro_text , logo_clip , bird_clip , bird_text , bunny_clip , bunny_text ,
217+ rodents_clip , rodents_text , rambo_clip , revenge_text , made_with_text , moviepy_clip ])
218+ final_clip .write_videofile ("./result.mp4" )
0 commit comments