ishworrsubedii's picture
add: outro video functionality instead of text
126385a
"""
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}")