Open stefanodvx opened 2 years ago
I fixed images position using set_start() but still slide out effect shows a black screen for the duration of the transition (it should display the next Image instead). also, images after transition are totally shifted to left. they should be centered like normally and be full screen
this is what i got so far. as you can see images are shifted to left and not centered, also they are not resized well (see 3rd slide( https://user-images.githubusercontent.com/69367859/175087198-674bc797-036a-4d53-8742-00da8cd1a347.mp4
You probably need to do this:
slided_clips = [CompositeVideoClip([clip.fx(transfx.slide_out, duration=0.4, side="left")])
for clip in clips]
final_clip = concatenate( slided_clips, padding=-1)
@MohamedAbdultawab mh, you sure? i thought CompositeVideoClip arleady make a Video, you sure i can pass it in concatenate?? Ill try soon tho. Thanks man
yes pretty sure, because slide_out won't work unless clip is in a composition, so you create a composition from it, and then concatenate those.
this is the result. a bit funny XD. it doesnt work tho :( https://user-images.githubusercontent.com/69367859/175133209-5818ac73-6837-4ed4-a2ae-f0c9588570c9.mp4
Code:
video = concatenate([
CompositeVideoClip([
clip.fx(transfx.slide_out, duration=0.3, side="left")
]) for clip in clips
], padding=-1)
Can you please check this Thread
@MohamedAbdultawab ok so, i checked it. but what about HX and WX? i never know the images i use so size couldnt be a constant.
You can always assign them dynamically by getting clip.h and clip.w You can focus on getting the effect done right manually first, won't hurt to add those values manually just to get the effect working then aftdr that, you can look into automating it.
@MohamedAbdultawab i cant make it working even manually, i tried a lot :/
clips = [ImageClip(img).set_duration(1) for img in ["1.jpeg","2.jpeg","3.jpeg"]]
videos= [CompositeVideoClip([
clip.fx(transfx.slide_out, duration=0.4, side="left")])
for clip in clips
]
video = concatenate_videoclips(videos, method="compose")
video.write_videofile("output.mp4",
codec="libx264", audio_codec="aac",
preset="ultrafast", fps=24,
ffmpeg_params=[
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2",
"-pix_fmt", "yuv420p"
]
)
can you confirm if the behavior in the attached video is what you want?
```python clips = [ImageClip(img).set_duration(1) for img in ["1.jpeg","2.jpeg","3.jpeg"]] videos= [CompositeVideoClip([ clip.fx(transfx.slide_out, duration=0.4, side="left")]) for clip in clips ] video = concatenate_videoclips(videos, method="compose") video.write_videofile("output.mp4", codec="libx264", audio_codec="aac", preset="ultrafast", fps=24, ffmpeg_params=[ "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-pix_fmt", "yuv420p" ] )
moviepy_concatenate.mp4
can you confirm if the behavior in the attached video is what you want?
sorry for late reply. btw, not really. if you see, there's a black screen moving in the fx. instead, the next image should move to left, not a black screen
I believe this is what you want, and you can achieve by this:
# Create image clips and resize them to same size so no black borders gets added
EFFECT_DURATION = 0.4
CLIP_DURATION = 1
clip1 = ImageClip("../experiments/1.jpeg").set_duration(CLIP_DURATION)
clip2 = (
ImageClip("../experiments/2.jpeg").resize(clip1.size).set_duration(CLIP_DURATION)
)
clip3 = (
ImageClip("../experiments/3.jpeg").resize(clip1.size).set_duration(CLIP_DURATION)
)
clips = [clip1, clip2, clip3]
# For the first clip we will need it to start from the beginning and only add
# slide out effect to the end of it
first_clip = CompositeVideoClip(
[clips[0].fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")]
).set_start((CLIP_DURATION - EFFECT_DURATION) * 0)
# For the last video we only need it to start entring the screen from the left going right
# but not slide out at the end so the end clip exits on a full image not a partial image or black screen
last_clip = CompositeVideoClip(
[clips[-1].fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
# -1 because we start with index 0 so we go all the way up to array length - 1
).set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips) - 1))
videos = (
[first_clip]
# For all other clips in the middle, we need them to slide in to the previous clip and out for the next one
+ [
(
CompositeVideoClip(
[clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
)
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
)
# set start to 1 since we start from second clip in the original array
for idx, clip in enumerate(clips[1:-1], start=1)
]
+ [last_clip]
)
video = CompositeVideoClip(videos)
video.write_videofile(
"final_clip.mp4",
codec="libx264",
audio_codec="aac",
preset="ultrafast",
fps=24,
threads=24,
ffmpeg_params=["-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-pix_fmt", "yuv420p"],
)
Note: you will need to tweak the effect duration and clip duration for the first and last clips and maybe the middle clips so that all clips runs for the same time.
that's exactly what i need. ill try in some hourd and let you know. thanks!
def create_clip(self, urls, audio_name, file_name):
EFFECT_DURATION = 0.4
CLIP_DURATION = 3
clips = []
for _, name in urls:
img = name.replace("webp", "jpg")
Image.open(name).convert("RGB").save(img)
clip = ImageClip(img).set_duration(CLIP_DURATION)
if len(clips) > 0:
clip = clip.resize(clips[0].size)
clips.append(clip)
# For the first clip we will need it to start from the beginning and only add
# slide out effect to the end of it
first_clip = CompositeVideoClip(
[clips[0].fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")]
).set_start((CLIP_DURATION - EFFECT_DURATION) * 0)
# For the last video we only need it to start entring the screen from the left going right
# but not slide out at the end so the end clip exits on a full image not a partial image or black screen
last_clip = CompositeVideoClip(
[clips[-1].fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
# -1 because we start with index 0 so we go all the way up to array length - 1
).set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips) - 1))
videos = (
[first_clip]
# For all other clips in the middle, we need them to slide in to the previous clip and out for the next one
+ [
(
CompositeVideoClip(
[clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
)
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
)
# set start to 1 since we start from second clip in the original array
for idx, clip in enumerate(clips[1:-1], start=1)
]
+ [last_clip]
)
video = CompositeVideoClip(videos)
audio = AudioFileClip(audio_name)
if audio.duration > video.duration:
video = vfx.loop(video, duration=audio.duration)
elif audio.duration < video.duration:
audio = afx.audio_loop(audio, duration=video.duration)
video.audio = audio
video.audio.duration = video.duration
video.write_videofile(file_name,
codec="libx264", audio_codec="aac", threads=24,
preset="ultrafast", fps=24, logger=None,
ffmpeg_params=[
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2",
"-pix_fmt", "yuv420p"
]
)
video.close()
audio.close()
ok currently this is the code im using, the one you made, and then this is the result i get:
as you can see, when the loops end, there's no animation, and it should have it (since the slideshow loops for the duration of the audio). also the images are stretched. they should fit properly in the screen (lets suppose its a phone screen resolution) but not get stretched. like the slideshow of TikTok @MohamedAbdultawab
You can add a slide out effect to the last clip if you want it, I guessed you didn't need it so didn't add it. images are stretched because they are not of the same size, ypu need to pre procese those images or set their size explicitly so moviepy doesn't guess it for you
what if i want images fit good in a 1792×828 video? (screen phone)
You can make a small function that takes an image6clip and video width and height, and check the size of the image, if it matches the video width and height then ok return the clip as is, if bigger then resize it down and fix the apsect ratio by, adding a margin to the image, if smaller then you can resize up or add margin to match the size
aight okok
@MohamedAbdultawab tried something to resize keeping good aspect ratio (using most high image as referer). works fine but some images are not centered to the screen:
Code:
def resize_image(clip: ImageClip, ideal: ImageClip):
width, height = clip.size
ratio = width / height
new_h = ideal.h
new_w = int(ratio * new_h)
return clip.resize((new_w, new_h))
EFFECT_DURATION = 0.3
CLIP_DURATION = 3.3
clip1 = ImageClip("imgs/1.jpg").set_duration(CLIP_DURATION)
clip2 = ImageClip("imgs/2.png").set_duration(CLIP_DURATION)
clip3 = ImageClip("imgs/3.jpg").set_duration(CLIP_DURATION)
clips = [clip1, clip2, clip3]
most_high_clip = max(clips, key=lambda x: x.h)
clips = [resize_image(clip, most_high_clip) for clip in clips]
first_clip = CompositeVideoClip(
[clips[0].fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")]
).set_start((CLIP_DURATION - EFFECT_DURATION) * 0)
# For the last video we only need it to start entring the screen from the left going right
# but not slide out at the end so the end clip exits on a full image not a partial image or black screen
last_clip = CompositeVideoClip(
[clips[-1].fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
# -1 because we start with index 0 so we go all the way up to array length - 1
).set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips) - 1))
videos = (
[first_clip]
# For all other clips in the middle, we need them to slide in to the previous clip and out for the next one
+ [
(
CompositeVideoClip(
[clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
)
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
)
# set start to 1 since we start from second clip in the original array
for idx, clip in enumerate(clips[1:-1], start=1)
]
+ [last_clip]
)
video = CompositeVideoClip(videos)
video.write_videofile(
"test.mp4",
codec="libx264", audio_codec="aac", threads=48,
preset="ultrafast", fps=24,
ffmpeg_params=[
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2",
"-pix_fmt", "yuv420p"
]
)
video.close()
Call set_position("center")
on all resized clips (or all clips maybe)
same result with
return clip.set_position("center").resize((new_w, new_h))
and even with
return clip.resize((new_w, new_h)).set_position("center")
Ok then put it in here
CompositeVideoClip(
[clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
)
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left").set_position("center")
)
it works like this, thanks!
You're welcome
last thing i need to do is loop the video for a duration i want, lets suppose its 15 seconds. how i can do it? the slide effect should be looped too, so vfx.loop its not a good choice ig
The whole video?
You can use moviepy.video.fx.all.loop
function on the final clip and set only the duration you want
yea, loop the whole video. until it reaches the time i set
if i use moviepy.video.fx.all.loop
on last clip, only last clip would be looped right?
Yes, if you used it on the last clip only it will be looped, but you need to take into consideration how this affect the audio clip attached, it won't be applied the same way you expect, you will need to preprocess it differently. Also you will need to subtract the clips duration from the desired duration you added on the last clip If you want to loop the whole video then it's easy.
yea i want to loop the whole video :D
i used moviepy.video.fx.all.loop
function but last clip has no animation to the looped first clip, so i guess i have to do something like this
videos = [
first_clip,
*[(
CompositeVideoClip([clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")])
.set_position(("center", "center"))
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
) for idx, clip in enumerate(clips[1:-1], start=1)
], CompositeVideoClip([last_clip]).fx(transfx.slide_in, duration=EFFECT_DURATION, side="left")
]
Yes and then loop the whole vidoe after
doesnt work. last clip has still no slide to the first one :(
Can you please send ghe code used and the output file
Code:
first_clip = CompositeVideoClip(
[clips[0].fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")]
).set_start((CLIP_DURATION - EFFECT_DURATION) * 0).set_position(("center", "center"))
last_clip = CompositeVideoClip(
[clips[-1].fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]
).set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips) - 1)).set_position(("center", "center"))
videos = [
first_clip,
*[(
CompositeVideoClip([clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")])
.set_position(("center", "center"))
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
) for idx, clip in enumerate(clips[1:-1], start=1)
], CompositeVideoClip([last_clip]).fx(transfx.slide_in, duration=EFFECT_DURATION, side="left")
]
video = CompositeVideoClip(videos)
video = loop(video, duration=20)
video.write_videofile(
"test.mp4",
codec="libx264", audio_codec="aac", threads=48,
preset="ultrafast", fps=24,
ffmpeg_params=[
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2",
"-pix_fmt", "yuv420p"
]
)
Output:
You're missing the set_start
call on last clip
.set_start((CLIP_DURATION - EFFECT_DURATION) * len(clips))
?
set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips) - 1))
videos = [
first_clip,
*[(
CompositeVideoClip([clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")])
.set_position(("center", "center"))
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
) for idx, clip in enumerate(clips[1:-1], start=1)
], CompositeVideoClip([last_clip]).set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips)-1)).fx(transfx.slide_in, duration=EFFECT_DURATION, side="left")
]
Output:
@MohamedAbdultawab any idea on how to fix it?
You need to make the fx inside the composite video, and also you ne3d to make it slide in from the right and slide out from left
videos = [
first_clip,
*[(
CompositeVideoClip([clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")])
.set_position(("center", "center"))
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
) for idx, clip in enumerate(clips[1:-1], start=1)
], CompositeVideoClip([last_clip.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")]).set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips)-1)).fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
]
Tried this code, this is the output: last clip its not showing well, it appears on last second
I believe the problem is with your ordering of the operations you are performing on each clip Check the following snippet and the following output
PS. I've also added a set_position call to fix the positioning of the unfit images
# Create image clips and resize them to same size so no black borders gets added
EFFECT_DURATION = 0.3
CLIP_DURATION = 3.3
clip1 = ImageClip("../experiments/1.jpeg").set_duration(CLIP_DURATION)
clip2 = ImageClip("../experiments/2.jpeg").set_duration(CLIP_DURATION)
clip3 = ImageClip("../experiments/3.jpeg").set_duration(CLIP_DURATION)
clips = [clip1, clip2, clip3]
# For the first clip we will need it to start from the beginning and only add
# slide out effect to the end of it
first_clip = CompositeVideoClip(
[
clips[0]
.set_pos("center")
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
]
).set_start((CLIP_DURATION - EFFECT_DURATION) * 0)
# For the last video we only need it to start entring the screen from the left going right
# but not slide out at the end so the end clip exits on a full image not a partial image or black screen
last_clip = (
CompositeVideoClip(
[
clips[-1]
.set_pos("center")
.fx(transfx.slide_in, duration=EFFECT_DURATION, side="right")
]
# -1 because we start with index 0 so we go all the way up to array length - 1
)
.set_start((CLIP_DURATION - EFFECT_DURATION) * (len(clips) - 1))
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
)
videos = (
[first_clip]
# For all other clips in the middle, we need them to slide in to the previous clip and out for the next one
+ [
(
CompositeVideoClip(
[
clip.set_pos("center").fx(
transfx.slide_in, duration=EFFECT_DURATION, side="right"
)
]
)
.set_start((CLIP_DURATION - EFFECT_DURATION) * idx)
.fx(transfx.slide_out, duration=EFFECT_DURATION, side="left")
)
# set start to 1 since we start from second clip in the original array
for idx, clip in enumerate(clips[1:-1], start=1)
]
+ [last_clip]
)
video = CompositeVideoClip(videos)
video.write_videofile(
"2_final_clip.mp4",
codec="libx264",
audio_codec="aac",
preset="ultrafast",
fps=24,
threads=24,
ffmpeg_params=["-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-pix_fmt", "yuv420p"],
)
used your code, works pretty well but middle image is not centered to the screen even with set_pos("center")
. Result:
Please send me the image and the code you used
The problem is with your resizing logic, you can't just take the clip with largest height as reference and make others follow it. The problem with this is if you have horizontal images(width is way bigger than height) and vertical images (height is way bigger than width) then resizing the horizontal image to the height of the vertical ones will lead to even bigger horizontal because aspect ratio is preserved, so when you do so the vertical image still can't be positioned in the middle of the screen on its own because it's not actually overlayed on something else so it can be centered, instead it's just displayed at the left of the screen and then the res of the canvas is empty so it appears black.
What you need to do instead is make sure all images be of the same size by either cropping them all or fit them all in one specific size and add margins accordingly.
I prefer adding margins, but you will need to adjust for all the possible cases that might happen which is tedious, so cropping is easier since you can crop horizontal rectangle from horizontal or vertical images.
i guess i'll crop to a specific size, or cant i just crop to the highest clip? not resizing according to it, i just mean cropping it so it has black bands oj the top / sides
I think you can't make an image bigger by cropping it, you will need combination of cropping resizing and adding margins.
this is my code. im trying to make a slideshow from these images, but the issue is that the result is not what it should be. every clip is on top of each other and trasnition is not working, it just do the effect to a black screen: here's the result i get:
https://user-images.githubusercontent.com/69367859/175075544-e9b56cb8-5af6-40ed-b2a4-97714941ef65.mp4