Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from PIL import Image, ImageDraw | |
| import io | |
| import numpy as np | |
| from math import cos, radians, sin | |
| import os | |
| import time | |
| import json | |
| import random | |
| import imageio | |
| # Load emojis from JSON file | |
| with open("emojis.json") as f: | |
| emojis = json.load(f)["emojis"] | |
| # Directory where emoji PNGs are stored | |
| emoji_dir = "emoji_pngs" | |
| def preprocess_image(image, emoji_name=None, enable_emoji=True): | |
| # Ensure the image is square by padding it | |
| size = max(image.size) | |
| new_image = Image.new("RGBA", (size, size), (255, 255, 255, 0)) | |
| new_image.paste(image, ((size - image.width) // 2, (size - image.height) // 2)) | |
| # Create resized images for GIF and Video | |
| new_image_gif = new_image.resize((256, 256), Image.LANCZOS) # Resize for GIF | |
| new_image_video = new_image.resize((720, 720), Image.LANCZOS) # Resize for Video | |
| # Only add emoji if enabled | |
| if enable_emoji: | |
| # Use the provided emoji or select a random one | |
| if not emoji_name: | |
| emoji_name = random.choice(list(emojis.keys())) | |
| emoji_path = os.path.join(emoji_dir, f"{emoji_name}.png") | |
| if os.path.exists(emoji_path): | |
| emoji_image = Image.open(emoji_path).convert("RGBA") | |
| emoji_image_gif = emoji_image.resize((256, 256), Image.LANCZOS) | |
| emoji_image_video = emoji_image.resize((720, 720), Image.LANCZOS) | |
| new_image_gif.paste(emoji_image_gif, (0, 0), emoji_image_gif) | |
| new_image_video.paste(emoji_image_video, (0, 0), emoji_image_video) | |
| else: | |
| emoji_name = "none" # Use a placeholder when emojis are disabled | |
| return new_image_gif, new_image_video, emoji_name | |
| def generate_media(editor1_output, editor2_output, transition_type, fps, transition_speed, color, thickness, enable_emoji): | |
| frames = [] | |
| gif_duration = 100 # Duration for each frame in the GIF in milliseconds | |
| total_frames_gif = 18 # Total number of frames for GIF | |
| total_frames_mp4 = int(3 * fps) # Calculate number of frames based on speed | |
| try: | |
| # Open images | |
| img1 = editor1_output["composite"].convert('RGBA') | |
| img2 = editor2_output["composite"].convert('RGBA') | |
| # Preprocess images to make them square and same size, select emojis once | |
| img1_gif, img1_video, random_emoji1 = preprocess_image(img1, enable_emoji=enable_emoji) | |
| img2_gif, img2_video, random_emoji2 = preprocess_image(img2, enable_emoji=enable_emoji) | |
| # Set size for the GIF | |
| gif_size = (256, 256) | |
| # Set size for the MP4 | |
| video_size = (720, 720) # Locked to 1:1 aspect ratio | |
| # Initialize video frames list | |
| video_frames = [] | |
| # Generate frames for GIF and video | |
| if transition_type == "slide": | |
| # Calculate step size for consistent speed | |
| full_width_gif = gif_size[0] | |
| full_width_video = video_size[0] | |
| step_gif = full_width_gif // (total_frames_gif // 2) | |
| step_video = int(full_width_video / (total_frames_mp4 / transition_speed)) | |
| # Generate frames from left to right for GIF | |
| for i in range(0, full_width_gif, step_gif): | |
| frame = Image.new('RGBA', gif_size) | |
| frame.paste(img1_gif, (0, 0)) | |
| frame.paste(img2_gif.crop((i, 0, full_width_gif, gif_size[1])), (i, 0), | |
| mask=img2_gif.crop((i, 0, full_width_gif, gif_size[1]))) | |
| draw = ImageDraw.Draw(frame) | |
| draw.line((i, 0, i, gif_size[1]), fill=color, width=thickness // 2) # Reduced line width for GIF | |
| frame = frame.convert('P', palette=Image.ADAPTIVE) | |
| frames.append(frame) | |
| # Generate frames for video from left to right | |
| for i in range(0, full_width_video, step_video): | |
| frame = Image.new('RGBA', video_size) | |
| frame.paste(img1_video, (0, 0)) | |
| frame.paste(img2_video.crop((i, 0, full_width_video, video_size[1])), (i, 0), | |
| mask=img2_video.crop((i, 0, full_width_video, video_size[1]))) | |
| # Add the green divider line | |
| draw = ImageDraw.Draw(frame) | |
| draw.line((i, 0, i, video_size[1]), fill=color, width=thickness) # Thicker line for video | |
| video_frames.append(np.array(frame)) | |
| # Generate frames from right to left for GIF | |
| for i in range(full_width_gif, step_gif, -step_gif): | |
| frame = Image.new('RGBA', gif_size) | |
| frame.paste(img1_gif, (0, 0)) | |
| frame.paste(img2_gif.crop((i, 0, full_width_gif, gif_size[1])), (i, 0), | |
| mask=img2_gif.crop((i, 0, full_width_gif, gif_size[1]))) | |
| draw = ImageDraw.Draw(frame) | |
| draw.line((i, 0, i, gif_size[1]), fill=color, width=thickness // 2) # Reduced line width for GIF | |
| frame = frame.convert('P', palette=Image.ADAPTIVE) | |
| frames.append(frame) | |
| # Generate frames for video from right to left | |
| for i in range(full_width_video, step_video, -step_video): | |
| frame = Image.new('RGBA', video_size) | |
| frame.paste(img1_video, (0, 0)) | |
| frame.paste(img2_video.crop((i, 0, full_width_video, video_size[1])), (i, 0), | |
| mask=img2_video.crop((i, 0, full_width_video, video_size[1]))) | |
| # Add the green divider line | |
| draw = ImageDraw.Draw(frame) | |
| draw.line((i, 0, i, video_size[1]), fill=color, width=thickness) # Thicker line for video | |
| video_frames.append(np.array(frame)) | |
| else: # rotate transition | |
| mask_size_gif = (gif_size[0] * 2, gif_size[1] * 2) | |
| mask_size_video = (video_size[0] * 2, video_size[1] * 2) | |
| # Prepare mask and draw objects | |
| mask_gif = Image.new('L', mask_size_gif, 0) | |
| draw_gif = ImageDraw.Draw(mask_gif) | |
| draw_gif.rectangle([gif_size[0], 0, mask_size_gif[0], mask_size_gif[1]], fill=255) | |
| mask_video = Image.new('L', mask_size_video, 0) | |
| draw_video = ImageDraw.Draw(mask_video) | |
| draw_video.rectangle([video_size[0], 0, mask_size_video[0], mask_size_video[1]], fill=255) | |
| # Rotate transition for GIF | |
| for angle in range(0, 360, 360 // total_frames_gif): | |
| rotated_mask = mask_gif.rotate(angle, center=(mask_size_gif[0] // 2, mask_size_gif[1] // 2), expand=False) | |
| cropped_mask = rotated_mask.crop( | |
| (gif_size[0] // 2, gif_size[1] // 2, gif_size[0] // 2 + gif_size[0], gif_size[1] // 2 + gif_size[1])) | |
| frame = Image.composite(img1_gif, img2_gif, cropped_mask) | |
| draw = ImageDraw.Draw(frame) | |
| reverse_angle = -angle + 90 | |
| center_x, center_y = gif_size[0] // 2, gif_size[1] // 2 | |
| end_x1 = center_x + int(gif_size[0] * 1.5 * cos(radians(reverse_angle))) | |
| end_y1 = center_y + int(gif_size[1] * 1.5 * sin(radians(reverse_angle))) | |
| end_x2 = center_x - int(gif_size[0] * 1.5 * cos(radians(reverse_angle))) | |
| end_y2 = center_y - int(gif_size[1] * 1.5 * sin(radians(reverse_angle))) | |
| draw.line([center_x, center_y, end_x1, end_y1], fill=color, width=thickness // 2) # Reduced line width for GIF | |
| draw.line([center_x, center_y, end_x2, end_y2], fill=color, width=thickness // 2) # Reduced line width for GIF | |
| frame = frame.convert('P', palette=Image.ADAPTIVE) | |
| frames.append(frame) | |
| # Rotate transition for video | |
| for angle in range(0, 360, int(360 / (total_frames_mp4 / transition_speed))): | |
| rotated_mask = mask_video.rotate(angle, center=(mask_size_video[0] // 2, mask_size_video[1] // 2), | |
| expand=False) | |
| cropped_mask = rotated_mask.crop( | |
| (video_size[0] // 2, video_size[1] // 2, video_size[0] // 2 + video_size[0], | |
| video_size[1] // 2 + video_size[1])) | |
| frame = Image.composite(img1_video, img2_video, cropped_mask) | |
| draw = ImageDraw.Draw(frame) | |
| reverse_angle = -angle + 90 | |
| center_x, center_y = video_size[0] // 2, video_size[1] // 2 | |
| end_x1 = center_x + int(video_size[0] * 1.5 * cos(radians(reverse_angle))) | |
| end_y1 = center_y + int(video_size[1] * 1.5 * sin(radians(reverse_angle))) | |
| end_x2 = center_x - int(video_size[0] * 1.5 * cos(radians(reverse_angle))) | |
| end_y2 = center_y - int(video_size[1] * 1.5 * sin(radians(reverse_angle))) | |
| draw.line([center_x, center_y, end_x1, end_y1], fill=color, width=thickness) # Thicker line for video | |
| draw.line([center_x, center_y, end_x2, end_y2], fill=color, width=thickness) # Thicker line for video | |
| video_frames.append(np.array(frame)) | |
| # Save as GIF | |
| output_gif = io.BytesIO() | |
| frames[0].save(output_gif, format='GIF', save_all=True, append_images=frames[1:], duration=gif_duration, loop=0, | |
| optimize=True) | |
| output_gif.seek(0) | |
| # Save the GIF to a file with a unique name | |
| os.makedirs("outputs", exist_ok=True) | |
| timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())) | |
| nonce = int((time.time() * 2) % 2) # create a nonce to the 1/2 second | |
| output_gif_path = f"outputs/{random_emoji1}-{random_emoji2}-{timestamp}{nonce}.gif" | |
| with open(output_gif_path, "wb") as f: | |
| f.write(output_gif.getvalue()) | |
| # Save as MP4 | |
| output_mp4_path = f"outputs/{random_emoji1}-{random_emoji2}-{timestamp}{nonce}.mp4" | |
| imageio.mimsave(output_mp4_path, video_frames, fps=fps, macro_block_size=None) # Frame rate | |
| # Return paths for download | |
| return output_gif_path, output_mp4_path | |
| except Exception as e: | |
| print(f"Error creating media: {e}") # Debugging output | |
| raise ValueError(f"Error creating media: {e}") | |
| # Gradio interface | |
| with gr.Blocks() as iface: | |
| gr.Markdown("# 2GIF and MP4 Transition Generator") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| image_editor1 = gr.ImageEditor( | |
| label="Edit Image 1", | |
| brush=gr.Brush(colors=["#ff0000", "#00ff00", "#0000ff"]), | |
| eraser=gr.Eraser(default_size=10), | |
| height=400, | |
| width=400, | |
| crop_size=(720, 720), # Specify exact crop size | |
| layers=True, | |
| type="pil" | |
| ) | |
| with gr.Column(scale=2): | |
| image_editor2 = gr.ImageEditor( | |
| label="Edit Image 2", | |
| brush=gr.Brush(colors=["#ff0000", "#00ff00", "#0000ff"]), | |
| eraser=gr.Eraser(default_size=10), | |
| height=400, | |
| width=400, | |
| crop_size=(720, 720), # Specify exact crop size | |
| layers=True, | |
| type="pil" | |
| ) | |
| with gr.Row(): | |
| transition_type = gr.Radio(["slide", "rotate"], label="Transition Type", value="slide") | |
| enable_emoji = gr.Checkbox(label="Enable Emojis", value=True, info="Toggle emoji overlays on/off") | |
| generate_button = gr.Button("Generate GIF & MP4") | |
| with gr.Accordion("Advanced Settings", open=False): | |
| with gr.Row(): | |
| fps = gr.Slider(label="Frame Rate (fps)", minimum=1, maximum=60, value=18, step=1) | |
| transition_speed = gr.Slider(label="Transition Speed", minimum=1, maximum=10, value=1.42069, step=0.1) | |
| color = gr.ColorPicker(label="Divider Line Color", value="#00ff00") | |
| thickness = gr.Slider(label="Line Thickness", minimum=1, maximum=20, value=6, step=1) | |
| with gr.Row(): | |
| gif_display = gr.Image(label="Generated GIF", elem_id="gif_display", visible=True) | |
| gif_download = gr.File(label="Download GIF", elem_id="gif_download", visible=True) | |
| mp4_download = gr.File(label="Download MP4", elem_id="mp4_download", visible=True) | |
| def handle_generation(editor1_output, editor2_output, transition_type, fps, transition_speed, color, thickness, enable_emoji): | |
| try: | |
| gif_path, mp4_path = generate_media(editor1_output, editor2_output, transition_type, fps, transition_speed, color, thickness, enable_emoji) | |
| return gif_path, gif_path, mp4_path | |
| except Exception as e: | |
| print(f"Error in handle_generation: {e}") | |
| return None, None, None | |
| generate_button.click( | |
| handle_generation, | |
| inputs=[image_editor1, image_editor2, transition_type, fps, transition_speed, color, thickness, enable_emoji], | |
| outputs=[gif_display, gif_download, mp4_download] | |
| ) | |
| # Launch the interface | |
| iface.launch(share=False) | |