|
|
|
|
|
import cv2 |
|
import numpy as np |
|
import torch |
|
import moviepy.editor as mp |
|
from transformers import pipeline |
|
from PIL import Image, ImageDraw |
|
import os |
|
|
|
class AnimationGenerator: |
|
def __init__(self): |
|
self.pose_analyzer = pipeline("text2text-generation", |
|
model="google/pegasus-x-base") |
|
self.character_db = {} |
|
self.resolution = (720, 480) |
|
self.fps = 24 |
|
|
|
def create_animation(self, script, character, bg, camera_effect, sound, tmp_dir): |
|
|
|
keyframes = self.parse_script(script, character) |
|
|
|
|
|
frames = self.render_frames(keyframes, character, bg) |
|
|
|
|
|
frames = self.apply_camera_effects(frames, camera_effect) |
|
|
|
|
|
video_path = os.path.join(tmp_dir, "animation.mp4") |
|
self.save_video(frames, video_path) |
|
return self.add_audio_track(video_path, sound, tmp_dir) |
|
|
|
def parse_script(self, script, character): |
|
|
|
prompt = f"Convert this story into animation keyframes: {script}" |
|
analysis = self.pose_analyzer(prompt, max_length=400) |
|
return self.extract_motion_data(analysis[0]['generated_text'], character) |
|
|
|
def extract_motion_data(self, text, character): |
|
|
|
return [{ |
|
'position': (100 + i*20, 200), |
|
'pose': 'walking' if i%2 ==0 else 'standing', |
|
'expression': 'neutral' |
|
} for i in range(24)] |
|
|
|
def render_frames(self, keyframes, character, bg): |
|
|
|
if character not in self.character_db: |
|
self.character_db[character] = { |
|
'color': (0, 0, 0), |
|
'scale': 1.0, |
|
'last_position': (100, 200) |
|
} |
|
|
|
frames = [] |
|
for frame_data in keyframes: |
|
canvas = self.create_background(bg) |
|
self.draw_character(canvas, frame_data, character) |
|
frames.append(cv2.cvtColor(np.array(canvas), cv2.COLOR_RGB2BGR)) |
|
return frames |
|
|
|
def create_background(self, bg_name): |
|
|
|
if bg_name == "Dark Forest": |
|
return Image.new('RGB', self.resolution, (34, 139, 34)) |
|
elif bg_name == "Haunted House": |
|
return Image.new('RGB', self.resolution, (28, 28, 28)) |
|
return Image.new('RGB', self.resolution, (255, 255, 255)) |
|
|
|
def draw_character(self, canvas, data, character): |
|
draw = ImageDraw.Draw(canvas) |
|
|
|
x, y = data['position'] |
|
|
|
draw.ellipse((x-15, y-40, x+15, y-10), outline=(0,0,0), width=2) |
|
|
|
draw.line((x, y, x, y+60), fill=(0,0,0), width=3) |
|
|
|
draw.line((x-30, y+30, x+30, y+30), fill=(0,0,0), width=3) |
|
|
|
draw.line((x-20, y+90, x, y+60), fill=(0,0,0), width=3) |
|
draw.line((x+20, y+90, x, y+60), fill=(0,0,0), width=3) |
|
|
|
def apply_camera_effects(self, frames, effect): |
|
|
|
if effect == "Dynamic Shake": |
|
return [self.apply_shake(frame) for frame in frames] |
|
elif effect == "Cinematic Zoom": |
|
return self.create_zoom_effect(frames) |
|
return frames |
|
|
|
def apply_shake(self, frame): |
|
dx, dy = np.random.randint(-7,7), np.random.randint(-5,5) |
|
M = np.float32([[1,0,dx], [0,1,dy]]) |
|
return cv2.warpAffine(frame, M, self.resolution) |
|
|
|
def create_zoom_effect(self, frames): |
|
zoomed = [] |
|
for i, frame in enumerate(frames): |
|
scale = 1.0 + (i/len(frames))*0.3 |
|
new_frame = cv2.resize(frame, None, fx=scale, fy=scale) |
|
y_start = int((new_frame.shape[0] - self.resolution[1])/2) |
|
x_start = int((new_frame.shape[1] - self.resolution[0])/2) |
|
zoomed.append(new_frame[y_start:y_start+self.resolution[1], |
|
x_start:x_start+self.resolution[0]]) |
|
return zoomed |
|
|
|
def save_video(self, frames, path): |
|
clip = mp.ImageSequenceClip(frames, fps=self.fps) |
|
clip.write_videofile(path, codec='libx264', audio=False) |
|
|
|
def add_audio_track(self, video_path, sound, tmp_dir): |
|
|
|
final_path = os.path.join(tmp_dir, "final.mp4") |
|
video = mp.VideoFileClip(video_path) |
|
video.write_videofile(final_path) |
|
return final_path |