This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/fixtures/media.py

183 lines
5.5 KiB
Python
Raw Normal View History

"""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