Open mowshon opened 3 years ago
The wobbling effect might be caused by the use of math.ceil
and evening for new_size
. This may result in one or both of the following:
640 638 638 336 334 334
, alternating between 1 and 2 frames for a given quantity.t | w | h |
---|---|---|
0 | 640 | 480 |
1 | 638 | 480 |
2 | 638 | 478 |
3 | 636 | 478 |
4 | 634 | 476 |
5 | 634 | 476 |
It's likely a combination of both, depending on the circumstance. Given the out-of-phase nature of the hypothesized causes, I think wobbling occurs whenever the number of frames that the effect lasts (duration * FPS
) does not share a common factor with the width and height of the image (or something along those lines).
resize
might be rounding the width and height under the hood. So compared to the width and height modulation for your solution (shown in the earlier table), resize
by itself modulates more erratically.
t | w | h |
---|---|---|
0 | 640 | 480 |
1 | 639 | 479 |
2 | 637 | 478 |
3 | 636 | 477 |
4 | 635 | 476 |
5 | 633 | 475 |
By evening the width and height, you're essentially forcing the ratio between the width and height to fluctuate less.
Looking at the various resizer
s in moviepy/video/fx/resize.py
it looks like they are in fact casting the width and height to int
s. I'm wondering if the aliasing algorithms can account for fractions of pixels. But that may be beyond the scope of what moviepy
s contract covers, as it's using OpenCV
, Pillow
, and SciPy
to resize.
Always maintain the ratio of width-to-height by first calculating the new width, then calculating the height based on the width. So replace
new_size = [
math.ceil(img.size[0] * (1 + (zoom_ratio * t))),
math.ceil(img.size[1] * (1 + (zoom_ratio * t)))
]
# The new dimensions must be even.
new_size[0] = new_size[0] + (new_size[0] % 2)
new_size[1] = new_size[1] + (new_size[1] % 2)
with something like
w, h = img.size
new_w = w * (1 + (zoom_ratio * t))
new_h = new_w * (h / w)
# Determine the height based on the new width to maintain the aspect ratio.
new_size = (new_w, new_h)
Problem: resize
can't handle floating numbers where a smooth zoom would need it.
Solution: Only increase the width & height with the aspect ratio (or multiple of it). I.E. if the aspect ratio is 4:3, increase the width by 4 and height by 3. This might be too fast for a zoom effect but won't be wobbling.
An ideal aspect ratio would be a 1:1 ratio and place your content in it masking the space left.
I.E. If your image is 640x480, you can create an empty image of 640x640 and place your image centered inside of it. This way every time you increase the aspect ratio, it will be an integer, which will be digestible by the resize
function
I'm about to try out the theory. If anybody's still interested in an example code let me know
@kalloszsolty I'm interested!! :)
Use this function to create a perfectly square image with even width and height:
def expand2square(img_path):
pil_img = Image.open(img_path)
width, height = pil_img.size
if width == height and width % 2 == 0:
return pil_img
if width % 2 != 0:
width += 1
if height % 2 != 0:
height += 1
elif width > height:
result = Image.new('RGBA', (width, width))
result.paste(pil_img, (0, (width - height) // 2))
return result
else:
result = Image.new('RGBA', (height, height))
result.paste(pil_img, ((height - width) // 2, 0))
return result
Then just use a lambda function to increase the width and height with the same integer:
starting_scale = 400
scale_speed = 2
expanded_img_1 = expand2square(img_path)
expanded_img_1 = numpy.array(expanded_img_1)
img_clip = (ImageClip(expanded_img_1)
.set_fps(25)
.set_duration(4)
.set_position(('center', 'center'))
.resize(width=lambda t: starting_scale + round(t * 25 * scale_speed),
height=lambda t: starting_scale + round(t * 25 * scale_speed)))
@kalloszsolty I'm interested!! :)
At the end, I ended up generating separate videos of zooming with command line ffmpeg (credits to: https://superuser.com/a/1112680)
import subprocess
def run_ffmpeg_zoom(image_path, output_file, screensize, duration=5, fps=25, zoom_ratio=0.0015, zoom_smooth=5):
ffmpeg_command = f"""./ffmpeg -framerate {fps} -loop 1 -i {image_path} -filter_complex "[0:v]scale={screensize[0] * zoom_smooth}x{screensize[1] * zoom_smooth},
zoompan=z='min(zoom+{zoom_ratio},1.5)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d={duration * fps},trim=duration={duration}[v1];[
v1]scale={screensize[0]}:{screensize[1]}[v]" -map "[v]" -y {output_file}"""
process = subprocess.Popen(ffmpeg_command, shell=True, stdout=subprocess.PIPE)
process.wait()
increase the zoom_smooth parameter for example to 10 to get a smoother zoom (it will also take you more time/resources)
For anyone wanting a periodic zoom-in and out effect. I've used a sin wave to replicate that.
# Zoom In and Out effect
def zoom_in_out(t):
return 2 + 0.5*np.sin(t/6)
clip.resize(zoom_in_out)
I am getting AttributeError: 'ImageClip' object has no attribute 'resize'. Did you mean: 'size'?
and I couldn't understand why. Could you help?
I am getting
AttributeError: 'ImageClip' object has no attribute 'resize'. Did you mean: 'size'?
and I couldn't understand why. Could you help?
Maybe this will help.
Just curious which is the solution? Because the output video still seems to wobble...
the input is 1 video, what to do! Looking forward to your support
i made this one (pretty fast and almost without wobbling) with opencv warpAffine plus some additional options :
def Zoom(clip,mode='in',position='center',speed=1): fps = clip.fps duration = clip.duration total_frames = int(duration*fps) def main(getframe,t): frame = getframe(t) h,w = frame.shape[:2] i = t*fps if mode == 'out': i = total_frames-i zoom = 1+(i*((0.1*speed)/total_frames)) positions = {'center':[(w-(w*zoom))/2,(h-(h*zoom))/2], 'left':[0,(h-(h*zoom))/2], 'right':[(w-(w*zoom)),(h-(h*zoom))/2], 'top':[(w-(w*zoom))/2,0], 'topleft':[0,0], 'topright':[(w-(w*zoom)),0], 'bottom':[(w-(w*zoom))/2,(h-(h*zoom))], 'bottomleft':[0,(h-(h*zoom))], 'bottomright':[(w-(w*zoom)),(h-(h*zoom))]} tx,ty = positions[position] M = np.array([[zoom,0,tx], [0,zoom,ty]]) frame = cv2.warpAffine(frame,M,(w,h)) return frame return clip.fl(main)
you can use this way:
from moviepy.editor import * import cv2 import numpy as np img = 'https://www.colorado.edu/cumuseum/sites/default/files/styles/widescreen/public/slider/coachwhip2_1.jpg' #using the image link above clip = ImageClip(img).set_fps(30).set_duration(5) clip = Zoom(clip,mode='in',position='center',speed=1.2) #zoom function above clip.write_videofile('test.mp4',preset='superfast')
sorry my english.
Worked!
At the end, I ended up generating separate videos of zooming with command line ffmpeg (credits to: https://superuser.com/a/1112680)
import subprocess def run_ffmpeg_zoom(image_path, output_file, screensize, duration=5, fps=25, zoom_ratio=0.0015, zoom_smooth=5): ffmpeg_command = f"""./ffmpeg -framerate {fps} -loop 1 -i {image_path} -filter_complex "[0:v]scale={screensize[0] * zoom_smooth}x{screensize[1] * zoom_smooth}, zoompan=z='min(zoom+{zoom_ratio},1.5)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d={duration * fps},trim=duration={duration}[v1];[ v1]scale={screensize[0]}:{screensize[1]}[v]" -map "[v]" -y {output_file}""" process = subprocess.Popen(ffmpeg_command, shell=True, stdout=subprocess.PIPE) process.wait()
increase the zoom_smooth parameter for example to 10 to get a smoother zoom (it will also take you more time/resources)
Thanks! This is a command version that also works based on this.
ffmpeg -y -loop 1 -t 17 -i /PATH_TO_IMAGE/image-0.png -vf "scale=iw5:ih5,zoompan=z='min(zoom+0.0015,1.5)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=510,scale=1344:768,trim=duration=17,fps=30" -c:v prores /PATH_TO_OUTPUT/output.mov
'ImageClip' object has no attribute 'fl'
im using the updated version of moviepy
@steinathan thanks for your comment, I'll see what changes have been made and update the code.
Thanks @mowshon I was able to update it and it works perfectly
the old “.fl” is now “transform” and it works
I am trying to create a video from slides. Each slide has a zoom-in effect. All the code examples for creating the zoom-in effect gave poor results if the zoom speed is very slow. The side effect was a slight twitching.
Below I will provide examples that the documentation offers and the function that I offer.
The problem I faced too: https://github.com/Zulko/moviepy/issues/183
Example from documentation
https://zulko.github.io/moviepy/ref/videofx/moviepy.video.fx.all.resize.html
Result: https://youtu.be/qlU_4hVFm6I
My function
Slideshow example: https://gist.github.com/mowshon/2a0664fab0ae799734594a5e91e518d5
Result: https://youtu.be/U-A54E00sC8
In my example, there is also a slight wobble, but not as obvious as in the first example. Below is a link to a video where you can compare both options.
Comparation video: https://www.youtube.com/watch?v=UPyYdrwWE14
I would be glad to get advice on improving the code.