import gradio as gr import numpy as np from PIL import Image, ImageDraw, ImageFont import math import dlib import tempfile import requests import os from transformers import pipeline import cv2 import io import json import re import time detector = dlib.get_frontal_face_detector() try: predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") except RuntimeError: print("Downloading shape_predictor_68_face_landmarks.dat...") landmarks_url = "http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2" landmarks_compressed = requests.get(landmarks_url).content import bz2 landmarks_data = bz2.decompress(landmarks_compressed) with open("shape_predictor_68_face_landmarks.dat", "wb") as f: f.write(landmarks_data) predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") IMAGE_GEN_API = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell" HF_TOKEN = os.getenv("HF_TOKEN") LLM_API = "https://api-inference.huggingface.co/models/lmsys/fastchat-t5-3b-v1.0" def query_hf_image_generation(prompt): headers = {"Authorization": f"Bearer {HF_TOKEN}"} payload = {"inputs": prompt} for _ in range(3): try: response = requests.post(IMAGE_GEN_API, headers=headers, json=payload) response.raise_for_status() image_bytes = response.content image = Image.open(io.BytesIO(image_bytes)) return image except requests.exceptions.RequestException as e: print(f"Image generation attempt failed: {e}") time.sleep(2) raise Exception("Image generation failed after multiple attempts") def query_llm(prompt, system_prompt): headers = {"Authorization": f"Bearer {HF_TOKEN}"} prompt_template = f"<|system|>\n{system_prompt}\n<|user|>\n{prompt}\n<|assistant|>\n" payload = {"inputs": prompt_template, "max_new_tokens": 200} for _ in range(3): try: response = requests.post(LLM_API, headers=headers, json=payload) response.raise_for_status() return response.json()[0]['generated_text'] except requests.exceptions.RequestException as e: print(f"LLM query attempt failed: {e}") time.sleep(2) raise Exception("LLM query failed after multiple attempts") def segment_script(script): system_prompt = "You are a helpful assistant. Given a script, divide it into segments suitable for generating images, ensuring each segment is less than 500 characters." llm_response = query_llm(script, system_prompt) segments = llm_response.split('\n') segments = [seg.strip() for seg in segments if seg.strip()] return segments def generate_image_prompts(script_segments): image_prompts = [] for segment in script_segments: system_prompt = "You are a helpful assistant. Create a concise image prompt based on the following script segment:" prompt = f"Script Segment: {segment}" image_prompt = query_llm(prompt, system_prompt) image_prompts.append(image_prompt) return image_prompts def extract_motion_params(llm_output): try: start_index = llm_output.find('{') end_index = llm_output.rfind('}') + 1 json_string = llm_output[start_index:end_index] params = json.loads(json_string) return params except: return { "motion_type": "none", "intensity": 0.25, "text_overlay": "", "text_color": "white", "start_time": 0, "end_time": 5 } def detect_face_landmarks(image): gray = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2GRAY) rects = detector(gray, 1) if len(rects) > 0: shape = predictor(gray, rects[0]) shape = np.array([(shape.part(i).x, shape.part(i).y) for i in range(68)]) return shape else: return None def apply_color_grading(frame, color_preset, intensity): if color_preset == "sepia": sepia_matrix = np.array([[0.393, 0.769, 0.189], [0.349, 0.686, 0.168], [0.272, 0.534, 0.131]]) frame_float = frame.astype(np.float32) / 255.0 sepia_effect = cv2.transform(frame_float, sepia_matrix) blended_frame = (1 - intensity) * frame_float + intensity * sepia_effect return (np.clip(blended_frame, 0, 1) * 255).astype(np.uint8) elif color_preset == "vintage": frame_float = frame.astype(np.float32) / 255.0 frame_float[:, :, 0] *= (1 - intensity * 0.6) frame_float[:, :, 2] *= (1 + intensity * 0.3) grayscale = cv2.cvtColor(frame_float, cv2.COLOR_RGB2GRAY) grayscale_rgb = cv2.cvtColor(grayscale, cv2.COLOR_GRAY2RGB) blended_frame = (1 - intensity * 0.5) * frame_float + intensity * 0.5 * grayscale_rgb return (np.clip(blended_frame, 0, 1) * 255).astype(np.uint8) elif color_preset == "black_and_white": gray_frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) return cv2.cvtColor(gray_frame, cv2.COLOR_GRAY2RGB) elif color_preset == "cold": frame_float = frame.astype(np.float32) / 255.0 frame_float[:, :, 0] *= (1 + intensity * 0.7) frame_float[:, :, 2] *= (1 - intensity * 0.2) return (np.clip(frame_float, 0, 1) * 255).astype(np.uint8) elif color_preset == "warm": frame_float = frame.astype(np.float32) / 255.0 frame_float[:, :, 2] *= (1 + intensity * 0.7) frame_float[:, :, 0] *= (1 - intensity * 0.2) return (np.clip(frame_float, 0, 1) * 255).astype(np.uint8) elif color_preset == "neon": frame_float = frame.astype(np.float32) / 255.0 lab = cv2.cvtColor(frame_float, cv2.COLOR_RGB2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8)) l = clahe.apply(l) lab = cv2.merge((l, a, b)) frame_float = cv2.cvtColor(lab, cv2.COLOR_LAB2RGB) frame_float[:, :, 0] *= (1 - intensity * 0.4) frame_float[:, :, 1] *= (1 + intensity * 0.8) frame_float[:, :, 2] *= (1 - intensity * 0.4) return (np.clip(frame_float, 0, 1) * 255).astype(np.uint8) return frame def apply_vignette(frame, intensity): width, height = frame.shape[1], frame.shape[0] x = np.linspace(-1, 1, width) y = np.linspace(-1, 1, height) X, Y = np.meshgrid(x, y) radius = np.sqrt(X**2 + Y**2) vignette = 1 - intensity * radius**2 vignette = np.clip(vignette, 0, 1) vignette = np.stack([vignette] * 3, axis=-1) frame_float = frame.astype(np.float32) / 255.0 result = frame_float * vignette return (np.clip(result, 0, 1) * 255).astype(np.uint8) def apply_bokeh(frame, intensity, t): frame_float = frame.astype(np.float32) / 255.0 gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) circles = [] for _ in range(int(intensity * 30)): radius = np.random.randint(5, 30) x = np.random.randint(radius, frame.shape[1] - radius) y = np.random.randint(radius, frame.shape[0] - radius) color = frame_float[y, x] brightness = np.random.uniform(0.5, 1.0) circles.append((x, y, radius, color, brightness)) bokeh_effect = np.zeros_like(frame_float) for x, y, radius, color, brightness in circles: y_grid, x_grid = np.ogrid[-y:frame.shape[0]-y, -x:frame.shape[1]-x] mask = x_grid*x_grid + y_grid*y_grid <= radius*radius bokeh_effect[mask] += np.array(color) * brightness * (0.5 + 0.5 * np.sin(t * 2 * math.pi)) blended_frame = frame_float + intensity * bokeh_effect return (np.clip(blended_frame, 0, 1) * 255).astype(np.uint8) def apply_advanced_motion(image, motion_type, intensity, duration, fps, text_overlay, text_color, font_size, start_time, end_time, color_preset, vignette_intensity): frames = [] width, height = image.size landmarks = detect_face_landmarks(image) for i in range(int(duration * fps)): t = i / (duration * fps) frame = image.copy() if landmarks is not None: if motion_type == "head_nod": top_head = landmarks[27] bottom_head = landmarks[8] angle = math.sin(t * 2 * math.pi) * intensity * 8 center_x = (top_head[0] + bottom_head[0]) // 2 center_y = (top_head[1] + bottom_head[1]) // 2 M = cv2.getRotationMatrix2D((center_x, center_y), angle, 1) rotated_image = cv2.warpAffine(np.array(image), M, (width, height), flags=cv2.INTER_LANCZOS4) frame = Image.fromarray(rotated_image) elif motion_type == "head_shake": top_head = landmarks[27] left_head = landmarks[0] right_head = landmarks[16] angle = math.sin(t * 3 * math.pi) * intensity * 6 center_x = top_head[0] center_y = top_head[1] M = cv2.getRotationMatrix2D((center_x, center_y), angle, 1) rotated_image = cv2.warpAffine(np.array(image), M, (width, height), flags=cv2.INTER_LANCZOS4) frame = Image.fromarray(rotated_image) elif motion_type == "eye_blink": left_eye_top = landmarks[37] left_eye_bottom = landmarks[41] right_eye_top = landmarks[43] right_eye_bottom = landmarks[47] blink_progress = abs(math.sin(t * 2 * math.pi)) if blink_progress > 0.9: draw = ImageDraw.Draw(frame) draw.line([tuple(landmarks[36]), tuple(landmarks[39])], fill=text_color, width=2) draw.line([tuple(landmarks[42]), tuple(landmarks[45])], fill=text_color, width=2) else: frame = image.copy() elif motion_type == "smile": mouth_left = landmarks[48] mouth_right = landmarks[54] mouth_top = landmarks[51] mouth_bottom = landmarks[57] smile_progress = intensity * t draw = ImageDraw.Draw(frame) curve_points = [ tuple(mouth_left), (mouth_left[0] + (mouth_right[0] - mouth_left[0]) // 4, mouth_left[1] + int(20 * smile_progress)), (mouth_left[0] + 3 * (mouth_right[0] - mouth_left[0]) // 4, mouth_right[1] + int(20 * smile_progress)), tuple(mouth_right) ] draw.line(curve_points, fill=text_color, width=4) if motion_type == "zoom": scale = 1 + intensity * t new_size = (int(width * scale), int(height * scale)) resized_image = image.resize(new_size, Image.Resampling.LANCZOS) x_offset = (new_size[0] - width) // 2 y_offset = (new_size[1] - height) // 2 frame = resized_image.crop((x_offset, y_offset, x_offset + width, y_offset + height)) elif motion_type == "pan": x_offset = int(intensity * t * (width - width)) y_offset = int(intensity * t * (height - height)) frame = Image.new("RGB", (width, height)) frame.paste(image, (-x_offset, -y_offset)) elif motion_type == "rotate": angle = intensity * t * 360 rotated_image = image.rotate(angle, expand=True, resample=Image.Resampling.BICUBIC) x_offset = (rotated_image.width - width) // 2 y_offset = (rotated_image.height - height) // 2 frame = Image.new("RGB", (width, height)) frame.paste(rotated_image, (-x_offset, -y_offset)) elif motion_type == "move_right": x_offset = int(intensity * t * width) frame = Image.new("RGB", (width, height), "black") frame.paste(image, (x_offset, 0)) elif motion_type == "move_left": x_offset = -int(intensity * t * width) frame = Image.new("RGB", (width, height), "black") frame.paste(image, (x_offset, 0)) elif motion_type == "move_up": y_offset = -int(intensity * t * height) frame = Image.new("RGB", (width, height), "black") frame.paste(image, (0, y_offset)) elif motion_type == "move_down": y_offset = int(intensity * t * height) frame = Image.new("RGB", (width, height), "black") frame.paste(image, (0, y_offset)) elif motion_type == "shake": shake_intensity = intensity * 10 x_offset = int(shake_intensity * math.sin(t * 2 * math.pi * 5)) y_offset = int(shake_intensity * math.cos(t * 2 * math.pi * 3)) frame = Image.new("RGB", (width, height)) frame.paste(image, (x_offset, y_offset)) elif motion_type == "fade_in": alpha = t frame = Image.blend(Image.new("RGB", (width, height), "black"), image, alpha) elif motion_type == "fade_out": alpha = 1 - t frame = Image.blend(Image.new("RGB", (width, height), "black"), image, alpha) elif motion_type == "rain": draw = ImageDraw.Draw(frame) for _ in range(int(intensity * 5)): x = np.random.randint(0, width) y = np.random.randint(0, height) length = np.random.randint(5, 15) speed = intensity * 3 y_end = y + length + i * speed draw.line([(x, y), (x, y_end)], fill="lightblue", width=1) elif motion_type == "bokeh": frame_np = np.array(frame) frame_np = apply_bokeh(frame_np, intensity, t) frame = Image.fromarray(frame_np) frame_np = np.array(frame) if color_preset: frame_np = apply_color_grading(frame_np, color_preset, intensity) if vignette_intensity > 0: frame_np = apply_vignette(frame_np, vignette_intensity) frame = Image.fromarray(frame_np) draw = ImageDraw.Draw(frame) if text_overlay and start_time <= t <= end_time: try: font = ImageFont.truetype("arial.ttf", font_size) except IOError: font = ImageFont.load_default() text_width, text_height = draw.textsize(text_overlay, font=font) x = (width - text_width) // 2 y = (height - text_height) // 2 draw.text((x, y), text_overlay, font=font, fill=text_color) frames.append(np.array(frame)) return frames def create_video_from_frames(frames, output_filename, fps=30): writer = imageio_ffmpeg.write_frames(output_filename, frames[0].shape[:2], pix_fmt_out='yuv420p', fps=fps, codec='libx264', preset="veryslow") writer.send(None) for frame in frames: writer.send(frame) writer.close() def generate_video_from_script(script, duration_per_segment=5): script_segments = segment_script(script) image_prompts = generate_image_prompts(script_segments) all_frames = [] for i, (segment, image_prompt) in enumerate(zip(script_segments, image_prompts)): print(f"Processing segment {i + 1} of {len(script_segments)}") print(f" Segment: {segment}") print(f" Image Prompt: {image_prompt}") image = query_hf_image_generation(image_prompt) image_description = pipeline("image-to-text", model="Salesforce/blip-image-captioning-base")(image)[0]['generated_text'] system_prompt = "You are an expert in image to video creation. Provide the motion type, intensity, text overlay, text color, text start and end times, color preset, and vignette intensity for the following image description and user prompt. Give the response in a JSON format." prompt = f"Image Description: {image_description}\nUser Prompt: {segment}" llm_response = query_llm(prompt, system_prompt) print(f" LLM Response: {llm_response}") motion_params = extract_motion_params(llm_response) print(f" Motion Parameters: {motion_params}") frames = apply_advanced_motion( image, motion_params["motion_type"], motion_params["intensity"], duration=duration_per_segment, fps=30, text_overlay=motion_params["text_overlay"], text_color=motion_params["text_color"], font_size=50, start_time=motion_params["start_time"], end_time=motion_params["end_time"], color_preset=motion_params.get("color_preset", None), vignette_intensity=motion_params.get("vignette_intensity", 0) ) all_frames.extend(frames) with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile: output_filename = tmpfile.name create_video_from_frames(all_frames, output_filename) return output_filename def generate_and_animate(prompt): try: image = query_hf_image_generation(prompt) image_description = pipeline("image-to-text", model="Salesforce/blip-image-captioning-base")(image)[0]['generated_text'] llm_response = query_llm(prompt, image_description) motion_params = extract_motion_params(llm_response) frames = apply_advanced_motion( image, motion_params["motion_type"], motion_params["intensity"], duration=5, fps=30, text_overlay=motion_params["text_overlay"], text_color=motion_params["text_color"], font_size=50, start_time=motion_params["start_time"], end_time=motion_params["end_time"], color_preset=motion_params.get("color_preset", None), vignette_intensity=motion_params.get("vignette_intensity", 0) ) video_file = create_video_from_frames(frames) return video_file, gr.Image.update(value=image) except Exception as e: return str(e), None motion_types = [ "zoom", "pan", "rotate", "move_right", "move_left", "move_up", "move_down", "shake", "fade_in", "fade_out", "head_nod", "head_shake", "eye_blink", "smile", "rain", "bokeh", "none" ] text_colors = ["white", "black", "red", "green", "blue", "yellow"] color_presets = ["sepia", "vintage", "black_and_white", "cold", "warm", "neon", "none"] iface = gr.Interface( fn=generate_and_animate, inputs=[ gr.Textbox(label="Prompt"), ], outputs=[ gr.Video(label="Generated Video"), gr.Image(label="Generated Image") ], title="AI Video Generator", description="Enter a prompt to generate an image and animate it. Uses Flux 1, an LLM, and advanced video processing techniques." ) video_iface = gr.Interface( fn=generate_video_from_script, inputs=[ gr.Textbox(label="Script (max 1 minute video)", lines=5), gr.Slider(label="Duration per Segment (seconds)", minimum=1, maximum=10, step=1, value=5) ], outputs=gr.Video(label="Generated Video from Script"), title="Story Visualizer", description="Enter a short story script, and this will generate a video visualizing it using multiple images and animations." ) demo = gr.TabbedInterface([iface, video_iface], ["Generate and Animate", "Story to Video"]) if __name__ == "__main__": demo.launch(share=True, debug=True)