Spaces:
Sleeping
Sleeping
""" | |
project @ images_to_video | |
created @ 2024-12-12 | |
author @ github.com/ishworrsubedii | |
""" | |
from moviepy.audio.io.AudioFileClip import AudioFileClip | |
from moviepy.video.VideoClip import ImageClip, ColorClip, TextClip | |
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip | |
from moviepy.video.compositing.concatenate import concatenate_videoclips | |
from moviepy.video.fx.all import resize | |
from moviepy.video.io.VideoFileClip import VideoFileClip | |
class VideoCreator: | |
def __init__(self, intro_video_path, necklace_image, nto_outputs: list, nto_cto_outputs: list, makeup_outputs: list, | |
font_path, | |
output_path, audio_path, image_display_duration=2.5, box_color: tuple = (131, 42, 48), box_opacity=0.8, | |
font_size=28, necklace_display_duration=2.0, text_color="white", fps=1, | |
necklace_image_title="Necklace Preview", nto_image_title="Necklace Try-On", | |
nto_cto_image_title="Clothing Try-On", makeup_image_title="Makeup Try-On", outro_video_path=""): | |
self.intro_video_path = intro_video_path | |
self.nto_images_dir = nto_outputs | |
self.nto_cto_images_dir = nto_cto_outputs | |
self.makeup_images_dir = makeup_outputs | |
self.output_video_path = output_path | |
self.font_path = font_path | |
self.necklace_image = necklace_image | |
self.audio_path = audio_path | |
self.image_display_duration = image_display_duration | |
self.text_color = text_color | |
self.box_color = box_color | |
self.box_opacity = box_opacity | |
self.font_size = font_size | |
self.category_font_size = font_size | |
self.necklace_display_duration = necklace_display_duration | |
self.fps = fps | |
self.necklace_image_title = necklace_image_title | |
self.makeup_image_title = makeup_image_title | |
self.nto_image_title = necklace_image_title | |
self.nto_cto_image_title = nto_cto_image_title | |
self.outro_video_path = outro_video_path | |
def create_necklace_clips(self, necklace_image_path, backgrounds=None): | |
if backgrounds is None: | |
backgrounds = [ | |
# Add to your configurations | |
(245, 245, 245), # Soft White (Perfect for Gold) | |
(220, 245, 245), # Rich Black (Premium look) | |
(230, 230, 235), # Pearl Gray (Elegant) | |
# Alternative premium colors: | |
# (25, 25, 112), # Midnight Blue | |
# (44, 49, 51), # Charcoal | |
# (189, 172, 152), # Champagne | |
# (241, 235, 218), # Ivory | |
] | |
necklace_clips = [] | |
# Create a clip for each background color | |
for bg_color in backgrounds: | |
# Create background | |
# bg_clip = ColorClip((1080, 1080), col=bg_color) | |
# bg_clip = bg_clip.set_duration(self.image_display_duration) | |
# | |
# # Create necklace clip | |
# necklace = ImageClip(necklace_image_path) | |
# w, h = necklace.size | |
# new_size = (w * 0.5, h * 0.15) | |
bg_clip = ColorClip((1080, 1080), col=bg_color) | |
bg_clip = bg_clip.set_duration(self.image_display_duration) | |
# Create necklace clip | |
necklace = ImageClip(necklace_image_path) | |
w, h = necklace.size | |
# Calculate the scaling factor based on the desired width of 1080 | |
scaling_factor = 1080 / w | |
# Calculate the new size to maintain the aspect ratio, then reduce it by 20% | |
new_size = (1080, int(h * scaling_factor)) | |
new_size = (int(new_size[0] * 0.6), int(new_size[1] * 0.6)) | |
necklace = resize(necklace, (new_size)) # Adjust size as needed | |
necklace = necklace.set_duration(self.image_display_duration) | |
# Center the necklace | |
necklace = necklace.set_position('center') | |
# Composite necklace over background | |
final_clip = CompositeVideoClip([bg_clip, necklace]) | |
# Add text overlay | |
txt_overlay = self.create_text_overlay("Necklace Preview", (1080, 80), self.image_display_duration) | |
txt_overlay = txt_overlay.set_position(('center', 'bottom')) | |
final_clip = CompositeVideoClip([final_clip, txt_overlay]) | |
necklace_clips.append(final_clip) | |
return necklace_clips | |
def create_text_overlay(self, text, size, duration, is_category=False): | |
"""Create a professional text overlay with background box""" | |
# Create background box | |
w, h = 1080, 120 if is_category else 80 | |
box = ColorClip((w, h), col=self.box_color, duration=duration) | |
print("box_opacity", self.box_opacity) | |
box = box.set_opacity(self.box_opacity) | |
# Create text using TextClip with method='label' instead of default | |
txt = TextClip( | |
text, | |
font=self.font_path, | |
fontsize=self.category_font_size if is_category else self.font_size, | |
color=self.text_color, | |
size=(w, h), | |
method='label' # Use 'label' method instead of default | |
).set_position('center').set_duration(duration) | |
return CompositeVideoClip([box, txt]) | |
def add_text_to_image(self, image_path, text, font_path, output_path): | |
# Create image clip | |
img_clip = ImageClip(image_path).resize((1080, 1080)) | |
# Create text overlay | |
txt_overlay = self.create_text_overlay(text, (1080, 80), img_clip.duration) | |
txt_overlay = txt_overlay.set_position(('center', 'bottom')) | |
# Composite the clips | |
final_clip = CompositeVideoClip([img_clip, txt_overlay]) | |
# Save as image | |
final_clip.save_frame(output_path, t=0) | |
return output_path | |
def create_image_clip(self, image_path, text, duration): | |
img_clip = ImageClip(image_path) | |
img_clip = resize(img_clip, (1080, 1080)) # Using resize from fx.all | |
img_clip = img_clip.set_duration(duration) | |
txt_overlay = self.create_text_overlay(text, (1080, 80), duration) | |
txt_overlay = txt_overlay.set_position(('center', 'bottom')) | |
final_clip = CompositeVideoClip([img_clip, txt_overlay]) | |
return final_clip | |
def process_images_in_directory(self, directory, duration, text): | |
clips = [] | |
print(directory) | |
for image_file in sorted(directory): | |
if image_file.lower().endswith((".png", ".jpg", ".jpeg", ".webp")): | |
print(f"Processing image: {image_file}") | |
# image_path = os.path.join(directory, image_file) | |
clip = self.create_image_clip(image_file, text, duration) | |
clips.append(clip) | |
return clips | |
def create_final_video(self): | |
print("Loading and processing main videos...") | |
intro_clip = resize(VideoFileClip(self.intro_video_path), (1080, 1080)) | |
outro_clip = resize(VideoFileClip(self.outro_video_path), (1080, 1080)) | |
# outro_clip = resize(VideoFileClip(outro_video_p/ath), (1080, 1080)) | |
# outro_clip_1 = resize(VideoFileClip(outro_video_path1), (1080, 1080)) | |
# Create necklace preview clips with different backgrounds | |
necklace_clips = self.create_necklace_clips(self.necklace_image) | |
# Process images with categories | |
nto_image_clips = self.process_images_in_directory( | |
self.nto_images_dir, self.image_display_duration, self.nto_image_title) | |
nto_cto_image_clips = self.process_images_in_directory( | |
self.nto_cto_images_dir, self.image_display_duration, self.nto_cto_image_title) | |
makeup_image_clips = self.process_images_in_directory( | |
self.makeup_images_dir, self.image_display_duration, self.makeup_image_title) | |
# Combine all clips | |
all_clips = [intro_clip] | |
all_clips.extend(necklace_clips) | |
all_clips.extend(nto_image_clips) | |
all_clips.extend(nto_cto_image_clips) | |
all_clips.extend(makeup_image_clips) | |
all_clips.append(outro_clip) | |
# all_clips.append(outro_clip_1) | |
# Create final temp_video without transitions | |
final_video = concatenate_videoclips(all_clips, method="compose") | |
# Add audio | |
try: | |
print("Adding audio...") | |
audio = AudioFileClip(self.audio_path) | |
video_duration = final_video.duration | |
if audio.duration > video_duration: | |
audio = audio.subclip(0, video_duration) | |
final_video = final_video.set_audio(audio) | |
print("Audio added successfully") | |
except Exception as e: | |
print(f"Error adding audio: {str(e)}") | |
# Write the final temp_video with progress bar | |
print("Rendering final temp_video...") | |
final_video.write_videofile( | |
self.output_video_path, | |
fps=self.fps, | |
codec="libx264", | |
audio_codec="aac", | |
bitrate="8000k", | |
threads=4, | |
preset='ultrafast' # ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow | |
) | |
print(f"Video saved to: {self.output_video_path}") | |