| import gradio as gr |
| import numpy as np |
| import cv2 |
| import os |
| import tempfile |
| import time |
| from PIL import Image, ImageDraw, ImageFont |
| import textwrap |
| from typing import Tuple, List |
| import colorsys |
|
|
| class CPUVideoGenerator: |
| def __init__(self): |
| self.temp_dir = tempfile.mkdtemp() |
| |
| def generate_text_video(self, prompt: str, duration: int = 5, fps: int = 24, |
| style: str = "typewriter", width: int = 512, height: int = 512) -> str: |
| """Generate a video with animated text based on the prompt.""" |
| |
| |
| output_path = os.path.join(self.temp_dir, f"video_{int(time.time())}.mp4") |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') |
| out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) |
| |
| total_frames = duration * fps |
| |
| try: |
| if style == "typewriter": |
| self._create_typewriter_animation(out, prompt, total_frames, width, height, fps) |
| elif style == "fade": |
| self._create_fade_animation(out, prompt, total_frames, width, height, fps) |
| elif style == "slide": |
| self._create_slide_animation(out, prompt, total_frames, width, height, fps) |
| elif style == "glitch": |
| self._create_glitch_animation(out, prompt, total_frames, width, height, fps) |
| elif style == "wave": |
| self._create_wave_animation(out, prompt, total_frames, width, height, fps) |
| else: |
| self._create_typewriter_animation(out, prompt, total_frames, width, height, fps) |
| |
| out.release() |
| return output_path |
| |
| except Exception as e: |
| out.release() |
| raise e |
| |
| def _create_typewriter_animation(self, out, prompt: str, total_frames: int, |
| width: int, height: int, fps: int): |
| """Create a typewriter effect animation.""" |
| |
| img = Image.new('RGB', (width, height), color=(20, 20, 30)) |
| draw = ImageDraw.Draw(img) |
| |
| |
| margin = 50 |
| font_size = max(20, min(60, (width - 2 * margin) // 20)) |
| font = self._get_font(font_size) |
| |
| |
| lines = textwrap.wrap(prompt, width=30) |
| line_height = font_size + 10 |
| total_text_height = len(lines) * line_height |
| start_y = (height - total_text_height) // 2 |
| |
| |
| total_chars = sum(len(line) for line in lines) |
| chars_per_frame = max(1, total_chars // (total_frames * 0.7)) |
| |
| char_count = 0 |
| for frame_num in range(total_frames): |
| |
| for y in range(height): |
| color_val = 20 + int(10 * (y / height)) |
| color = (color_val, color_val, color_val + 10) |
| cv2.rectangle(img, (0, y), (width, y + 1), color, -1) |
| |
| |
| current_chars = min(char_count, total_chars) |
| drawn_chars = 0 |
| y_pos = start_y |
| |
| for line in lines: |
| if drawn_chars >= current_chars: |
| break |
| |
| line_end = min(len(line), current_chars - drawn_chars) |
| if line_end > 0: |
| |
| text = line[:line_end] |
| draw.text((margin + 2, y_pos + 2), text, font=font, fill=(0, 0, 0)) |
| draw.text((margin, y_pos), text, font=font, fill=(200, 200, 220)) |
| |
| |
| if drawn_chars + line_end < current_chars and frame_num % 10 < 5: |
| text_width = font.getlength(text) |
| draw.line([(margin + text_width, y_pos), |
| (margin + text_width, y_pos + font_size)], |
| fill=(255, 255, 100), width=2) |
| |
| drawn_chars += len(line) |
| y_pos += line_height |
| |
| |
| if char_count >= total_chars and frame_num % 20 < 10: |
| last_line_y = start_y + (len(lines) - 1) * line_height |
| last_line = lines[-1] |
| text_width = font.getlength(last_line) |
| draw.line([(margin + text_width, last_line_y), |
| (margin + text_width, last_line_y + font_size)], |
| fill=(255, 255, 100), width=2) |
| |
| |
| frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
| out.write(frame) |
| |
| if char_count < total_chars: |
| char_count += chars_per_frame |
| |
| def _create_fade_animation(self, out, prompt: str, total_frames: int, |
| width: int, height: int, fps: int): |
| """Create a fade in/out animation.""" |
| img = Image.new('RGB', (width, height), color=(30, 20, 40)) |
| draw = ImageDraw.Draw(img) |
| |
| |
| margin = 50 |
| font_size = max(20, min(50, (width - 2 * margin) // 15)) |
| font = self._get_font(font_size) |
| |
| lines = textwrap.wrap(prompt, width=25) |
| line_height = font_size + 15 |
| total_text_height = len(lines) * line_height |
| start_y = (height - total_text_height) // 2 |
| |
| for frame_num in range(total_frames): |
| |
| fade_progress = frame_num / total_frames |
| if fade_progress < 0.3: |
| alpha = fade_progress / 0.3 |
| elif fade_progress > 0.7: |
| alpha = (1.0 - fade_progress) / 0.3 |
| else: |
| alpha = 1.0 |
| |
| |
| for y in range(height): |
| hue = (frame_num * 2 + y * 0.5) % 360 |
| rgb = colorsys.hsv_to_rgb(hue / 360, 0.3, 0.2) |
| color = tuple(int(c * 255) for c in rgb) |
| cv2.rectangle(img, (0, y), (width, y + 1), color, -1) |
| |
| |
| text_color = tuple(int(200 * alpha) for _ in range(3)) |
| y_pos = start_y |
| for line in lines: |
| draw.text((margin, y_pos), line, font=font, fill=text_color) |
| y_pos += line_height |
| |
| frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
| out.write(frame) |
| |
| def _create_slide_animation(self, out, prompt: str, total_frames: int, |
| width: int, height: int, fps: int): |
| """Create a sliding text animation.""" |
| img = Image.new('RGB', (width, height), color=(40, 30, 50)) |
| draw = ImageDraw.Draw(img) |
| |
| font_size = max(25, min(60, (width - 2 * 50) // 12)) |
| font = self._get_font(font_size) |
| |
| lines = textwrap.wrap(prompt, width=20) |
| line_height = font_size + 15 |
| |
| for frame_num in range(total_frames): |
| |
| for y in range(height): |
| intensity = 40 + int(20 * np.sin((frame_num + y) * 0.05)) |
| color = (intensity, intensity - 10, intensity + 10) |
| cv2.rectangle(img, (0, y), (width, y + 1), color, -1) |
| |
| |
| slide_progress = (frame_num / total_frames) |
| x_offset = int(width * (1 - 2 * abs(slide_progress - 0.5))) |
| |
| y_pos = (height - len(lines) * line_height) // 2 |
| for line in lines: |
| draw.text((x_offset, y_pos), line, font=font, fill=(220, 200, 180)) |
| y_pos += line_height |
| |
| frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
| out.write(frame) |
| |
| def _create_glitch_animation(self, out, prompt: str, total_frames: int, |
| width: int, height: int, fps: int): |
| """Create a glitch-style animation.""" |
| img = Image.new('RGB', (width, height), color=(10, 10, 20)) |
| draw = ImageDraw.Draw(img) |
| |
| font_size = max(20, min(50, (width - 2 * 50) // 15)) |
| font = self._get_font(font_size) |
| |
| lines = textwrap.wrap(prompt, width=25) |
| line_height = font_size + 10 |
| total_text_height = len(lines) * line_height |
| start_y = (height - total_text_height) // 2 |
| |
| for frame_num in range(total_frames): |
| |
| np_img = np.random.randint(0, 30, (height, width, 3), dtype=np.uint8) |
| img = Image.fromarray(np_img) |
| draw = ImageDraw.Draw(img) |
| |
| |
| if frame_num % 5 == 0: |
| for _ in range(3): |
| y = np.random.randint(0, height) |
| color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255)) |
| cv2.rectangle(np_img, (0, y), (width, y + np.random.randint(1, 5)), color, -1) |
| |
| |
| y_pos = start_y |
| for line in lines: |
| |
| offsets = [(0, 0), (2, 0), (-2, 2), (1, -1)] |
| colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)] |
| |
| for (dx, dy), color in zip(offsets, colors): |
| if frame_num % 2 == 0 or frame_num % 3 == 0: |
| draw.text((50 + dx + np.random.randint(-2, 3), |
| y_pos + dy + np.random.randint(-2, 3)), |
| line, font=font, fill=color) |
| |
| y_pos += line_height |
| |
| frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
| frame = cv2.GaussianBlur(frame, (3, 3), 0) if frame_num % 10 == 0 else frame |
| out.write(frame) |
| |
| def _create_wave_animation(self, out, prompt: str, total_frames: int, |
| width: int, height: int, fps: int): |
| """Create a wave text animation.""" |
| img = Image.new('RGB', (width, height), color=(20, 40, 60)) |
| draw = ImageDraw.Draw(img) |
| |
| font_size = max(25, min(55, (width - 2 * 50) // 12)) |
| font = self._get_font(font_size) |
| |
| lines = textwrap.wrap(prompt, width=25) |
| line_height = font_size + 15 |
| |
| for frame_num in range(total_frames): |
| |
| for y in range(height): |
| wave = np.sin((frame_num + y * 5) * 0.1) * 20 |
| color_val = int(40 + wave) |
| color = (color_val // 2, color_val, color_val + 20) |
| cv2.rectangle(img, (0, y), (width, y + 1), color, -1) |
| |
| |
| y_pos = (height - len(lines) * line_height) // 2 |
| for line_idx, line in enumerate(lines): |
| for char_idx, char in enumerate(line): |
| wave_offset = int(np.sin((frame_num * 0.1 + char_idx * 0.3 + line_idx)) * 10) |
| x_pos = 50 + char_idx * (font_size * 0.6) |
| y_offset = y_pos + line_idx * line_height + wave_offset |
| |
| |
| hue = (frame_num * 2 + char_idx * 20 + line_idx * 50) % 360 |
| rgb = colorsys.hsv_to_rgb(hue / 360, 0.8, 1.0) |
| color = tuple(int(c * 255) for c in rgb) |
| |
| draw.text((x_pos, y_offset), char, font=font, fill=color) |
| |
| frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
| out.write(frame) |
| |
| def _get_font(self, size: int): |
| """Get font, fallback to default if custom font not available.""" |
| try: |
| return ImageFont.truetype("arial.ttf", size) |
| except: |
| try: |
| return ImageFont.truetype("/System/Library/Fonts/Arial.ttf", size) |
| except: |
| try: |
| return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size) |
| except: |
| return ImageFont.load_default() |
| |
| def generate_pattern_video(self, pattern_type: str, duration: int = 5, fps: int = 24, |
| width: int = 512, height: int = 512) -> str: |
| """Generate abstract pattern videos.""" |
| output_path = os.path.join(self.temp_dir, f"pattern_{int(time.time())}.mp4") |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') |
| out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) |
| |
| total_frames = duration * fps |
| |
| try: |
| if pattern_type == "spiral": |
| self._create_spiral_pattern(out, total_frames, width, height) |
| elif pattern_type == "particles": |
| self._create_particle_pattern(out, total_frames, width, height) |
| elif pattern_type == "waves": |
| self._create_wave_pattern(out, total_frames, width, height) |
| elif pattern_type == "fractal": |
| self._create_fractal_pattern(out, total_frames, width, height) |
| else: |
| self._create_spiral_pattern(out, total_frames, width, height) |
| |
| out.release() |
| return output_path |
| |
| except Exception as e: |
| out.release() |
| raise e |
| |
| def _create_spiral_pattern(self, out, total_frames: int, width: int, height: int): |
| """Create animated spiral pattern.""" |
| center_x, center_y = width // 2, height // 2 |
| |
| for frame_num in range(total_frames): |
| frame = np.zeros((height, width, 3), dtype=np.uint8) |
| |
| |
| max_radius = min(width, height) // 2 |
| points = 500 |
| |
| for i in range(points): |
| t = i * 0.1 |
| radius = (i / points) * max_radius |
| angle = t + frame_num * 0.05 |
| |
| x = int(center_x + radius * np.cos(angle)) |
| y = int(center_y + radius * np.sin(angle)) |
| |
| if 0 <= x < width and 0 <= y < height: |
| hue = (i * 2 + frame_num * 5) % 360 |
| rgb = colorsys.hsv_to_rgb(hue / 360, 1.0, 1.0) |
| color = tuple(int(c * 255) for c in rgb) |
| |
| cv2.circle(frame, (x, y), 3, color, -1) |
| |
| out.write(frame) |
| |
| def _create_particle_pattern(self, out, total_frames: int, width: int, height: int): |
| """Create animated particle system.""" |
| |
| num_particles = 100 |
| particles = [] |
| for _ in range(num_particles): |
| particles.append({ |
| 'x': np.random.randint(0, width), |
| 'y': np.random.randint(0, height), |
| 'vx': np.random.uniform(-2, 2), |
| 'vy': np.random.uniform(-2, 2), |
| 'size': np.random.randint(2, 8), |
| 'hue': np.random.randint(0, 360) |
| }) |
| |
| for frame_num in range(total_frames): |
| frame = np.zeros((height, width, 3), dtype=np.uint8) |
| |
| |
| for particle in particles: |
| |
| particle['x'] += particle['vx'] |
| particle['y'] += particle['vy'] |
| |
| |
| if particle['x'] <= 0 or particle['x'] >= width: |
| particle['vx'] *= -1 |
| if particle['y'] <= 0 or particle['y'] >= height: |
| particle['vy'] *= -1 |
| |
| |
| particle['x'] = np.clip(particle['x'], 0, width - 1) |
| particle['y'] = np.clip(particle['y'], 0, height - 1) |
| |
| |
| hue = (particle['hue'] + frame_num * 2) % 360 |
| rgb = colorsys.hsv_to_rgb(hue / 360, 1.0, 1.0) |
| color = tuple(int(c * 255) for c in rgb) |
| |
| cv2.circle(frame, (int(particle['x']), int(particle['y'])), |
| particle['size'], color, -1) |
| |
| |
| for other in particles: |
| dist = np.sqrt((particle['x'] - other['x'])**2 + |
| (particle['y'] - other['y'])**2) |
| if dist < 100 and dist > 0: |
| cv2.line(frame, (int(particle['x']), int(particle['y'])), |
| (int(other['x']), int(other['y'])), |
| (color[0]//3, color[1]//3, color[2]//3), 1) |
| |
| out.write(frame) |
| |
| def _create_wave_pattern(self, out, total_frames: int, width: int, height: int): |
| """Create animated wave pattern.""" |
| for frame_num in range(total_frames): |
| frame = np.zeros((height, width, 3), dtype=np.uint8) |
| |
| |
| for y in range(0, height, 5): |
| for x in range(0, width, 5): |
| |
| wave1 = np.sin((x * 0.02 + frame_num * 0.05)) * 20 |
| wave2 = np.cos((y * 0.02 + frame_num * 0.03)) * 20 |
| wave3 = np.sin((x * 0.01 + y * 0.01 + frame_num * 0.02)) * 10 |
| |
| intensity = int(128 + wave1 + wave2 + wave3) |
| intensity = np.clip(intensity, 0, 255) |
| |
| |
| hue = (intensity + frame_num * 2) % 360 |
| rgb = colorsys.hsv_to_rgb(hue / 360, 0.7, intensity / 255) |
| color = tuple(int(c * 255) for c in rgb) |
| |
| cv2.rectangle(frame, (x, y), (x + 5, y + 5), color, -1) |
| |
| out.write(frame) |
| |
| def _create_fractal_pattern(self, out, total_frames: int, width: int, height: int): |
| """Create animated fractal-like pattern.""" |
| for frame_num in range(total_frames): |
| frame = np.zeros((height, width, 3), dtype=np.uint8) |
| |
| |
| max_depth = 5 |
| self._draw_fractal_tree(frame, width//2, height-50, -90, |
| frame_num * 0.02, max_depth, 80) |
| |
| out.write(frame) |
| |
| def _draw_fractal_tree(self, frame, x, y, angle, time_offset, depth, length): |
| """Recursively draw fractal tree.""" |
| if depth == 0: |
| return |
| |
| |
| end_x = x + int(length * np.cos(np.radians(angle))) |
| end_y = y + int(length * np.sin(np.radians(angle))) |
| |
| |
| hue = (depth * 60 + time_offset * 50) % 360 |
| rgb = colorsys.hsv_to_rgb(hue / 360, 0.8, 0.8) |
| color = tuple(int(c * 255) for c in rgb) |
| |
| |
| cv2.line(frame, (x, y), (end_x, end_y), color, max(1, depth//2)) |
| |
| |
| angle_variation = 20 + 10 * np.sin(time_offset) |
| self._draw_fractal_tree(frame, end_x, end_y, angle - angle_variation, |
| time_offset, depth - 1, length * 0.7) |
| self._draw_fractal_tree(frame, end_x, end_y, angle + angle_variation, |
| time_offset, depth - 1, length * 0.7) |
|
|
| |
| generator = CPUVideoGenerator() |
|
|
| def generate_video(prompt: str, style: str, duration: int, fps: int, |
| width: int, height: int, video_type: str) -> Tuple[str, str]: |
| """Main video generation function.""" |
| try: |
| if video_type == "Text Animation": |
| video_path = generator.generate_text_video( |
| prompt, duration, fps, style, width, height |
| ) |
| else: |
| video_path = generator.generate_pattern_video( |
| style, duration, fps, width, height |
| ) |
| |
| return video_path, "โ
Video generated successfully!" |
| except Exception as e: |
| return None, f"โ Error: {str(e)}" |
|
|
| |
| with gr.Blocks(title="CPU Video Generator", theme=gr.themes.Soft()) as demo: |
| gr.Markdown(""" |
| # ๐ฌ CPU Video Generator |
| Built with [anycoder](https://huggingface.co/spaces/akhaliq/anycoder) |
| |
| Generate animated videos entirely on CPU! Choose from text animations or abstract patterns. |
| """) |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### ๐ Input Settings") |
| |
| video_type = gr.Radio( |
| choices=["Text Animation", "Pattern"], |
| value="Text Animation", |
| label="Video Type", |
| info="Choose between text animations or abstract patterns" |
| ) |
| |
| prompt = gr.Textbox( |
| label="Text Prompt (for Text Animation)", |
| placeholder="Enter your text here...", |
| value="Welcome to CPU Video Generation!", |
| lines=3 |
| ) |
| |
| style = gr.Dropdown( |
| choices=["typewriter", "fade", "slide", "glitch", "wave", |
| "spiral", "particles", "waves", "fractal"], |
| value="typewriter", |
| label="Animation Style", |
| info="Choose the animation style" |
| ) |
| |
| with gr.Row(): |
| duration = gr.Slider( |
| minimum=2, maximum=15, value=5, step=1, |
| label="Duration (seconds)" |
| ) |
| fps = gr.Slider( |
| minimum=12, maximum=30, value=24, step=1, |
| label="FPS" |
| ) |
| |
| with gr.Row(): |
| width = gr.Slider( |
| minimum=256, maximum=1024, value=512, step=64, |
| label="Width" |
| ) |
| height = gr.Slider( |
| minimum=256, maximum=1024, value=512, step=64, |
| label="Height" |
| ) |
| |
| generate_btn = gr.Button("๐ฅ Generate Video", variant="primary", size="lg") |
| |
| with gr.Column(): |
| gr.Markdown("### ๐น Output") |
| |
| video_output = gr.Video( |
| label="Generated Video", |
| autoplay=False, |
| show_download_button=True |
| ) |
| |
| status_output = gr.Textbox( |
| label="Status", |
| interactive=False |
| ) |
| |
| |
| gr.Markdown("### ๐ก Examples") |
| examples = gr.Examples( |
| examples=[ |
| ["Hello World! This is CPU video generation.", "typewriter", 5, 24, 512, 512, "Text Animation"], |
| ["Amazing effects running on CPU only!", "wave", 7, 24, 640, 480, "Text Animation"], |
| ["", "spiral", 5, 24, 512, 512, "Pattern"], |
| ["", "particles", 8, 24, 640, 480, "Pattern"], |
| ["Glitch art with CPU power!", "glitch", 6, 24, 512, 512, "Text Animation"], |
| ["", "fractal", 10, 24, 512, 512, "Pattern"], |
| ], |
| inputs=[prompt, style, duration, fps, width, height, video_type], |
| outputs=[video_output, status_output], |
| fn=generate_video, |
| cache_examples=False |
| ) |
| |
| |
| def update_visibility(video_type): |
| if video_type == "Text Animation": |
| return gr.update(visible=True), gr.update(choices=["typewriter", "fade", "slide", "glitch", "wave"], value="typewriter") |
| else: |
| return gr.update(visible=False), gr.update(choices=["spiral", "particles", "waves", "fractal"], value="spiral") |
| |
| video_type.change( |
| update_visibility, |
| inputs=[video_type], |
| outputs=[prompt, style] |
| ) |
| |
| |
| generate_btn.click( |
| generate_video, |
| inputs=[prompt, style, duration, fps, width, height, video_type], |
| outputs=[video_output, status_output] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=True) |