"""Real media file fixtures for integration tests. Generates actual PNG images, WAV audio files, and MP4 video clips using numpy, Pillow, and MoviePy — no AI models required. """ from __future__ import annotations import wave from pathlib import Path import numpy as np from PIL import Image, ImageDraw # ── Color palettes for visual variety ───────────────────────────────────────── SCENE_COLORS = [ (30, 60, 120), # dark blue — "night sky" (200, 100, 30), # warm orange — "sunrise" (50, 150, 50), # forest green — "mountain forest" (20, 120, 180), # teal blue — "river" (180, 60, 60), # crimson — "sunset" (40, 40, 80), # deep purple — "twilight" ] def make_storyboard_frame( path: Path, label: str, color: tuple[int, int, int] = (60, 60, 60), width: int = 320, height: int = 180, ) -> Path: """Create a real PNG image with a colored background and text label. Returns the path to the written file. """ img = Image.new("RGB", (width, height), color=color) draw = ImageDraw.Draw(img) # Draw label text in white, centered bbox = draw.textbbox((0, 0), label) tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1] x = (width - tw) // 2 y = (height - th) // 2 draw.text((x, y), label, fill=(255, 255, 255)) # Add a border draw.rectangle([2, 2, width - 3, height - 3], outline=(255, 255, 255), width=2) path.parent.mkdir(parents=True, exist_ok=True) img.save(path) return path def make_storyboard( output_dir: Path, scene_labels: list[str], width: int = 320, height: int = 180, ) -> list[Path]: """Generate a full storyboard — one PNG per scene.""" frames = [] for i, label in enumerate(scene_labels): color = SCENE_COLORS[i % len(SCENE_COLORS)] path = output_dir / f"frame_{i:03d}.png" make_storyboard_frame(path, label, color=color, width=width, height=height) frames.append(path) return frames def make_audio_track( path: Path, duration_seconds: float = 10.0, sample_rate: int = 44100, frequency: float = 440.0, fade_in: float = 0.5, fade_out: float = 0.5, ) -> Path: """Create a real WAV audio file — a sine wave tone with fade in/out. Good enough to verify audio overlay, mixing, and codec encoding. """ n_samples = int(sample_rate * duration_seconds) t = np.linspace(0, duration_seconds, n_samples, endpoint=False) # Generate a sine wave with slight frequency variation for realism signal = np.sin(2 * np.pi * frequency * t) # Add a second harmonic for richness signal += 0.3 * np.sin(2 * np.pi * frequency * 2 * t) # Fade in/out fade_in_samples = int(sample_rate * fade_in) fade_out_samples = int(sample_rate * fade_out) if fade_in_samples > 0: signal[:fade_in_samples] *= np.linspace(0, 1, fade_in_samples) if fade_out_samples > 0: signal[-fade_out_samples:] *= np.linspace(1, 0, fade_out_samples) # Normalize and convert to 16-bit PCM signal = (signal / np.max(np.abs(signal)) * 32767 * 0.8).astype(np.int16) path.parent.mkdir(parents=True, exist_ok=True) with wave.open(str(path), "w") as wf: wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(sample_rate) wf.writeframes(signal.tobytes()) return path def make_video_clip( path: Path, duration_seconds: float = 3.0, fps: int = 12, width: int = 320, height: int = 180, color_start: tuple[int, int, int] = (30, 30, 80), color_end: tuple[int, int, int] = (80, 30, 30), label: str = "", ) -> Path: """Create a real MP4 video clip with a color gradient animation. Frames transition smoothly from color_start to color_end, producing a visible animation that's easy to visually verify. """ from moviepy import ImageSequenceClip n_frames = int(duration_seconds * fps) frames = [] for i in range(n_frames): t = i / max(1, n_frames - 1) r = int(color_start[0] + (color_end[0] - color_start[0]) * t) g = int(color_start[1] + (color_end[1] - color_start[1]) * t) b = int(color_start[2] + (color_end[2] - color_start[2]) * t) img = Image.new("RGB", (width, height), color=(r, g, b)) if label: draw = ImageDraw.Draw(img) draw.text((10, 10), label, fill=(255, 255, 255)) # Frame counter draw.text((10, height - 20), f"f{i}/{n_frames}", fill=(200, 200, 200)) frames.append(np.array(img)) path.parent.mkdir(parents=True, exist_ok=True) clip = ImageSequenceClip(frames, fps=fps) clip.write_videofile(str(path), codec="libx264", audio=False, logger=None) return path def make_scene_clips( output_dir: Path, scene_labels: list[str], duration_per_clip: float = 3.0, fps: int = 12, width: int = 320, height: int = 180, ) -> list[Path]: """Generate one video clip per scene, each with a distinct color animation.""" clips = [] for i, label in enumerate(scene_labels): c1 = SCENE_COLORS[i % len(SCENE_COLORS)] c2 = SCENE_COLORS[(i + 1) % len(SCENE_COLORS)] path = output_dir / f"clip_{i:03d}.mp4" make_video_clip( path, duration_seconds=duration_per_clip, fps=fps, width=width, height=height, color_start=c1, color_end=c2, label=label, ) clips.append(path) return clips