From 4e56f76ad8baff34bfb6ed27d8462820b4569bb4 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Fri, 24 Jul 2026 23:31:34 +0545 Subject: [PATCH 01/18] docs: add movielite docs --- backend/movielite_docs/advanced.md | 619 ++++++++++++++++ backend/movielite_docs/api.md | 1077 ++++++++++++++++++++++++++++ 2 files changed, 1696 insertions(+) create mode 100644 backend/movielite_docs/advanced.md create mode 100644 backend/movielite_docs/api.md diff --git a/backend/movielite_docs/advanced.md b/backend/movielite_docs/advanced.md new file mode 100644 index 0000000..e760c85 --- /dev/null +++ b/backend/movielite_docs/advanced.md @@ -0,0 +1,619 @@ +# Advanced Usage Guide + +This guide covers advanced topics for using movielite effectively. + +## Table of Contents + +- [Custom Effect Development](#custom-effect-development) +- [Performance Optimization](#performance-optimization) +- [Memory Management](#memory-management) +- [Advanced Audio Processing](#advanced-audio-processing) +- [Complex Compositions](#complex-compositions) +- [Masking and Compositing](#masking-and-compositing) +- [Integration with Other Libraries](#integration-with-other-libraries) + +--- + +## Custom Effect Development + +### Creating Custom Visual Effects + +You can create custom visual effects by subclassing `GraphicEffect`: + +```python +from movielite.vfx.base import GraphicEffect +from movielite.core import GraphicClip +import cv2 +import numpy as np + +class CustomVignetteEffect(GraphicEffect): + """Custom vignette effect with adjustable parameters.""" + + def __init__(self, intensity: float = 0.7, color: tuple = (0, 0, 0)): + """ + Args: + intensity: Darkness intensity (0.0 to 1.0) + color: RGB color tuple for vignette (default: black) + """ + self.intensity = intensity + self.color = color + + def apply(self, clip: GraphicClip) -> None: + """Apply the vignette effect to the clip.""" + + # Cache the vignette mask + cache = {} + + def vignette_transform(frame: np.ndarray, t: float) -> np.ndarray: + h, w = frame.shape[:2] + cache_key = (w, h) + + if cache_key not in cache: + # Create radial gradient + center_x, center_y = w // 2, h // 2 + Y, X = np.ogrid[:h, :w] + dist = np.sqrt(((X - center_x) / w) ** 2 + ((Y - center_y) / h) ** 2) + + # Create mask + mask = np.clip(1.0 - dist, 0, 1) + mask = 1.0 - (1.0 - mask) * self.intensity + mask = np.stack([mask] * 3, axis=2) + + cache[cache_key] = mask + + # Apply vignette + result = frame.astype(np.float32) * cache[cache_key] + return result.astype(np.uint8) + + clip.add_transform(vignette_transform) + +# Usage +from movielite import VideoClip, VideoWriter + +clip = VideoClip("video.mp4") +clip.add_effect(CustomVignetteEffect(intensity=0.8, color=(20, 20, 30))) + +writer = VideoWriter("output.mp4", fps=clip.fps, size=clip.size) +writer.add_clip(clip) +writer.write() +clip.close() +``` + +### Creating Custom Audio Effects + +```python +from movielite.afx.base import AudioEffect +from movielite.audio import AudioClip +import numpy as np + +class EchoEffect(AudioEffect): + """Add echo effect to audio.""" + + def __init__(self, delay: float = 0.3, decay: float = 0.5): + """ + Args: + delay: Echo delay in seconds + decay: Echo decay factor (0.0 to 1.0) + """ + self.delay = delay + self.decay = decay + + def apply(self, clip: AudioClip) -> None: + """Apply echo effect to the audio clip.""" + + def echo_transform(samples: np.ndarray, t: float, sr: int) -> np.ndarray: + # Calculate delay in samples + delay_samples = int(self.delay * sr) + + # Create echo buffer + result = samples.copy() + + # Add delayed and decayed samples + if len(samples) > delay_samples: + result[delay_samples:] += samples[:-delay_samples] * self.decay + + # Normalize to prevent clipping + max_val = np.abs(result).max() + if max_val > 1.0: + result = result / max_val + + return result + + clip.add_transform(echo_transform) + +# Usage +from movielite import AudioClip + +audio = AudioClip("audio.mp3") +audio.add_effect(EchoEffect(delay=0.5, decay=0.4)) +``` + +### Creating Custom Transitions + +```python +from movielite.vtx.base import Transition +from movielite.core import GraphicClip + +class SlideTransition(Transition): + """Slide transition between two clips.""" + + def __init__(self, duration: float, direction: str = "left"): + """ + Args: + duration: Duration of the transition + direction: Slide direction ("left", "right", "up", "down") + """ + self.duration = duration + self.direction = direction + + def apply(self, clip1: GraphicClip, clip2: GraphicClip) -> None: + """Apply slide transition.""" + + # Validate overlap + self._validate_clips_have_overlap(clip1, clip2, self.duration) + + # Get overlap region + overlap_start = clip2.start + overlap_end = min(clip1.end, clip2.start + self.duration) + + # Modify clip positions + original_pos1 = clip1._position + original_pos2 = clip2._position + + def position_func_clip1(t): + base_pos = original_pos1(t) + + if clip1.start <= t < overlap_end: + # During transition, slide out + progress = (t - overlap_start) / self.duration + + if self.direction == "left": + offset_x = int(-clip1.size[0] * progress) + return (base_pos[0] + offset_x, base_pos[1]) + elif self.direction == "right": + offset_x = int(clip1.size[0] * progress) + return (base_pos[0] + offset_x, base_pos[1]) + + return base_pos + + def position_func_clip2(t): + base_pos = original_pos2(t) + + if overlap_start <= t < overlap_end: + # During transition, slide in + progress = (t - overlap_start) / self.duration + + if self.direction == "left": + offset_x = int(clip2.size[0] * (1 - progress)) + return (base_pos[0] + offset_x, base_pos[1]) + elif self.direction == "right": + offset_x = int(-clip2.size[0] * (1 - progress)) + return (base_pos[0] + offset_x, base_pos[1]) + + return base_pos + + clip1.set_position(position_func_clip1) + clip2.set_position(position_func_clip2) +``` + +--- + +## Performance Optimization + +### Using Multiprocessing + +For long videos, use multiprocessing to parallelize rendering: + +```python +from movielite import VideoClip, VideoWriter, VideoQuality +import multiprocessing + +clip = VideoClip("long_video.mp4") + +writer = VideoWriter("output.mp4", fps=clip.fps, size=clip.size) +writer.add_clip(clip) + +# Use all available CPU cores +num_processes = multiprocessing.cpu_count() +writer.write(processes=num_processes, video_quality=VideoQuality.HIGH) + +clip.close() +``` + +### Caching Expensive Computations + +For static transformations, cache results: + +```python +import cv2 +import numpy as np + +class CachedGrayscaleEffect: + def __init__(self): + self.cache = {} + + def __call__(self, frame: np.ndarray, t: float) -> np.ndarray: + # Use frame memory address as cache key + frame_id = id(frame) + + if frame_id not in self.cache: + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + self.cache[frame_id] = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) + + # Warning: you should have a maximum amount of frames stored in your cache + return self.cache[frame_id] + +clip.add_transform(CachedGrayscaleEffect()) +``` + +--- + +## Advanced Audio Processing + +### Complex Audio Mixing + +```python +from movielite import AudioClip, VideoClip, VideoWriter, afx +import numpy as np + +# Load multiple audio tracks +video = VideoClip("video.mp4") +music = AudioClip("background_music.mp3", start=0, volume=0.3) +narration = AudioClip("voiceover.mp3", start=5, volume=1.0) +sfx1 = AudioClip("explosion.wav", start=10, volume=0.8) +sfx2 = AudioClip("applause.wav", start=15, volume=0.6) + +# Apply effects to each track +music.add_effect(afx.FadeIn(2.0)).add_effect(afx.FadeOut(3.0)) +narration.add_effect(afx.FadeIn(0.5)) + +# Duck music during narration (custom volume curve) +def ducking_curve(t): + if 5 <= t < 25: # Narration period + return 0.2 # Reduce music to 20% + return 0.5 # Normal music at 50% + +music.set_volume_curve(ducking_curve) + +# Mix all tracks +writer = VideoWriter("output.mp4", fps=video.fps, size=video.size) +writer.add_clip(video) +writer.add_clip(music) +writer.add_clip(narration) +writer.add_clip(sfx1) +writer.add_clip(sfx2) +writer.write() + +video.close() +``` + +### Custom Audio Filters + +```python +import numpy as np +from scipy import signal + +def apply_lowpass_filter(cutoff_freq: float = 1000, order: int = 5): + """Create a lowpass filter transform.""" + + def lowpass_transform(samples: np.ndarray, t: float, sr: int) -> np.ndarray: + # Design Butterworth lowpass filter + nyquist = sr / 2 + normal_cutoff = cutoff_freq / nyquist + b, a = signal.butter(order, normal_cutoff, btype='low', analog=False) + + # Apply filter to each channel + filtered = np.zeros_like(samples) + for ch in range(samples.shape[1]): + filtered[:, ch] = signal.lfilter(b, a, samples[:, ch]) + + return filtered + + return lowpass_transform + +# Usage +audio = AudioClip("audio.mp3") +audio.add_transform(apply_lowpass_filter(cutoff_freq=2000, order=4)) +``` + +--- + +## Complex Compositions + +### Picture-in-Picture Effect + +```python +from movielite import VideoClip, VideoWriter, vfx + +# Main video +main_video = VideoClip("main.mp4", start=0) + +# Small overlay video +pip_video = VideoClip("overlay.mp4", start=0) +pip_video.set_size(width=320, height=180) # Resize to small size +pip_video.set_position((main_video.size[0] - 340, 20)) # Top-right corner +pip_video.add_effect(vfx.FadeIn(0.5)) +pip_video.add_effect(vfx.FadeOut(0.5)) + +# Compose +writer = VideoWriter("output.mp4", fps=30, size=main_video.size) +writer.add_clip(main_video) +writer.add_clip(pip_video) +writer.write() + +main_video.close() +pip_video.close() +``` + +### Split Screen + +```python +from movielite import VideoClip, VideoWriter, ImageClip + +# Create black background +bg = ImageClip.from_color((0, 0, 0), size=(1920, 1080), duration=10) + +# Left video +left_video = VideoClip("left.mp4") +left_video.set_size(width=960, height=1080) +left_video.set_position((0, 0)) + +# Right video +right_video = VideoClip("right.mp4") +right_video.set_size(width=960, height=1080) +right_video.set_position((960, 0)) + +# Compose +writer = VideoWriter("split_screen.mp4", fps=30, size=(1920, 1080)) +writer.add_clip(bg) +writer.add_clip(left_video) +writer.add_clip(right_video) +writer.write() + +left_video.close() +right_video.close() +``` + +### Animated Text + +```python +from movielite import VideoClip, TextClip, VideoWriter +from pictex import Canvas +import math + +video = VideoClip("background.mp4") + +canvas = Canvas().font_size(80).color("white").background_color("transparent") +text = TextClip("Animated Title", start=0, duration=5, canvas=canvas) + +# Animate position (bounce effect) +def animated_position(t): + # Bounce in from top + if t < 1.0: + y = int(-100 + (video.size[1] // 2) * (1 - math.cos(t * math.pi))) + else: + y = video.size[1] // 2 + + x = video.size[0] // 2 - text.size[0] // 2 + return (x, y) + +text.set_position(animated_position) + +# Animate opacity +text.set_opacity(lambda t: min(1.0, t / 0.5)) + +writer = VideoWriter("output.mp4", fps=video.fps, size=video.size) +writer.add_clip(video) +writer.add_clip(text) +writer.write() + +video.close() +``` + +--- + +## Integration with Other Libraries + +### Using OpenCV Filters + +```python +import cv2 +import numpy as np +from movielite import VideoClip, VideoWriter + +clip = VideoClip("video.mp4") + +def apply_bilateral_filter(frame: np.ndarray, t: float) -> np.ndarray: + """Apply bilateral filter for edge-preserving smoothing.""" + return cv2.bilateralFilter(frame, d=9, sigmaColor=75, sigmaSpace=75) + +clip.add_transform(apply_bilateral_filter) + +writer = VideoWriter("filtered.mp4", fps=clip.fps, size=clip.size) +writer.add_clip(clip) +writer.write() +clip.close() +``` + +### Using NumPy for Advanced Effects + +```python +import numpy as np +from movielite import VideoClip, VideoWriter + +clip = VideoClip("video.mp4") + +def color_temperature_shift(frame: np.ndarray, t: float) -> np.ndarray: + """Shift color temperature (warmer).""" + result = frame.copy().astype(np.float32) + + # Increase red channel + result[:, :, 2] = np.clip(result[:, :, 2] * 1.1, 0, 255) + + # Decrease blue channel + result[:, :, 0] = np.clip(result[:, :, 0] * 0.9, 0, 255) + + return result.astype(np.uint8) + +clip.add_transform(color_temperature_shift) + +writer = VideoWriter("warm.mp4", fps=clip.fps, size=clip.size) +writer.add_clip(clip) +writer.write() +clip.close() +``` + +### Using Pillow for Image Processing + +```python +from PIL import Image, ImageEnhance +import numpy as np +from movielite import VideoClip, VideoWriter +import cv2 + +clip = VideoClip("video.mp4") + +def enhance_colors(frame: np.ndarray, t: float) -> np.ndarray: + """Use Pillow to enhance colors.""" + # Convert BGR to RGB + rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Convert to PIL Image + pil_image = Image.fromarray(rgb_frame) + + # Enhance color + enhancer = ImageEnhance.Color(pil_image) + enhanced = enhancer.enhance(1.5) # 1.5x color saturation + + # Convert back to numpy array (BGR) + rgb_array = np.array(enhanced) + return cv2.cvtColor(rgb_array, cv2.COLOR_RGB2BGR) + +clip.add_transform(enhance_colors) + +writer = VideoWriter("enhanced.mp4", fps=clip.fps, size=clip.size) +writer.add_clip(clip) +writer.write() +clip.close() +``` + +### Using scipy for Audio Processing + +```python +from scipy.signal import butter, filtfilt +import numpy as np +from movielite import AudioClip + +def bandpass_filter(lowcut: float, highcut: float, order: int = 5): + """Create a bandpass filter for audio.""" + + def filter_transform(samples: np.ndarray, t: float, sr: int) -> np.ndarray: + nyquist = sr / 2 + low = lowcut / nyquist + high = highcut / nyquist + + b, a = butter(order, [low, high], btype='band') + + filtered = np.zeros_like(samples) + for ch in range(samples.shape[1]): + filtered[:, ch] = filtfilt(b, a, samples[:, ch]) + + return filtered + + return filter_transform + +audio = AudioClip("audio.mp3") +audio.add_transform(bandpass_filter(lowcut=200, highcut=3000)) +``` + +--- + +## Masking and Compositing + +Masking is a powerful feature for creating advanced compositing effects where one clip's visibility is controlled by another clip's luminance values. + +### Basic Masking + +```python +from movielite import VideoClip, ImageClip, VideoWriter + +video = VideoClip("waves.mp4") +mask = ImageClip("mask.png", duration=video.duration) + +# Apply mask - white areas of mask = visible, black = transparent +video.set_mask(mask) + +writer = VideoWriter("masked.mp4", fps=video.fps, size=video.size) +writer.add_clip(video) +writer.write() + +video.close() +``` + +### Text Masking + +```python +from movielite import VideoClip, TextClip, VideoWriter +from pictex import Canvas + +video = VideoClip("colorful.mp4", duration=5) + +# Create text as mask +canvas = Canvas().font_size(200).color("white").background_color("transparent") +text = TextClip("MASKED", duration=5, canvas=canvas) +text.set_position((video.size[0] // 2 - text.size[0] // 2, + video.size[1] // 2 - text.size[1] // 2)) + +# Video only visible through text +video.set_mask(text) + +writer = VideoWriter("text_masked.mp4", fps=video.fps, size=video.size) +writer.add_clip(video) +writer.write() + +video.close() +``` + +### Animated Masks + +```python +import numpy as np +from movielite import VideoClip, TextClip, VideoWriter +from pictex import Canvas + +video = VideoClip("waves.mp4", duration=10) + +# Create animated text mask +canvas = Canvas().font_size(200).color("white").background_color("transparent") +text = TextClip("Hello World!", duration=10, canvas=canvas) + +# Animate position (wave motion) +text.set_position(lambda t: ( + 960 - text.size[0] // 2, + 500 + int(20 * np.sin(2 * np.pi * (t / 10.0))) +)) + +# Animate scale (grow) +text.set_scale(lambda t: 1.0 + 0.4 * (t / 10.0)) + +video.set_mask(text) +video.set_size(1920, 1080) + +writer = VideoWriter("animated_mask.mp4", fps=30, size=(1920, 1080)) +writer.add_clip(video) +writer.write() + +video.close() +``` + +--- + +## Best Practices + +1. **Profile your effects**: Time-intensive effects should be optimized or cached. + +2. **Use appropriate data types**: Keep frames as uint8 until final processing to save memory. + +3. **Leverage multiprocessing**: For videos longer than 60 seconds, use multiple processes if is possible. + +4. **Cache static transformations**: If a transformation doesn't depend on time, cache its result. diff --git a/backend/movielite_docs/api.md b/backend/movielite_docs/api.md new file mode 100644 index 0000000..e0b9343 --- /dev/null +++ b/backend/movielite_docs/api.md @@ -0,0 +1,1077 @@ +# API Reference + +Complete API reference for movielite library. + +## Table of Contents + +- [Core Classes](#core-classes) + - [MediaClip](#mediaclip) + - [GraphicClip](#graphicclip) + - [VideoClip](#videoclip) + - [AlphaVideoClip](#alphavideoclip) + - [ImageClip](#imageclip) + - [TextClip](#textclip) + - [AudioClip](#audioclip) + - [VideoWriter](#videowriter) +- [Visual Effects (vfx)](#visual-effects-vfx) + - [Fade Effects](#fade-effects) + - [Blur Effects](#blur-effects) + - [Color Effects](#color-effects) + - [Zoom Effects](#zoom-effects) + - [Glitch Effects](#glitch-effects) + - [Other Effects](#other-effects) +- [Audio Effects (afx)](#audio-effects-afx) +- [Transitions (vtx)](#transitions-vtx) +- [Enumerations](#enumerations) +- [Utilities](#utilities) + +--- + +## Core Classes + +### MediaClip + +Base class for all media clips (visual and audio). + +**Constructor:** +```python +MediaClip(start: float, duration: float) +``` + +**Parameters:** +- `start` (float): Start time in the composition (seconds) +- `duration` (float): Duration of the clip (seconds) + +**Properties:** +- `start` (float): Start time in seconds +- `duration` (float): Duration in seconds +- `end` (float): End time in seconds (start + duration) + +**Methods:** + +#### set_start(start: float) -> Self +Set the start time of this clip in the composition. + +**Parameters:** +- `start` (float): Start time in seconds (must be >= 0) + +**Returns:** Self for chaining + +**Raises:** ValueError if start is negative + +--- + +#### set_duration(duration: float) -> Self +Set the duration of this clip. + +**Parameters:** +- `duration` (float): Duration in seconds (must be > 0) + +**Returns:** Self for chaining + +**Raises:** ValueError if duration is not positive + +--- + +#### set_end(end: float) -> Self +Set the end time of this clip in the composition. Adjusts duration to match. + +**Parameters:** +- `end` (float): End time in seconds (must be > start) + +**Returns:** Self for chaining + +**Raises:** ValueError if end is not greater than start + +--- + +### GraphicClip + +Base class for all visual/graphic clips (video, image, text). + +Inherits from [MediaClip](#mediaclip). + +**Constructor:** +```python +GraphicClip(start: float, duration: float) +``` + +**Properties:** +- `position` (Callable[[float], Tuple[int, int]]): Position function +- `opacity` (Callable[[float], float]): Opacity function +- `scale` (Callable[[float], float]): Scale function +- `size` (Tuple[int, int]): Size of the clip (width, height) +- `has_any_transform` (bool): Whether any transformations are applied + +**Methods:** + +#### set_position(value: Union[Callable[[float], Tuple[int, int]], Tuple[int, int]]) -> Self +Set the position of the clip. + +**Parameters:** +- `value`: Either a tuple (x, y) or a function that takes time and returns (x, y) + +**Returns:** Self for chaining + +**Example:** +```python +# Static position +clip.set_position((100, 100)) + +# Animated position +clip.set_position(lambda t: (int(100 + t * 50), 100)) +``` + +--- + +#### set_opacity(value: Union[Callable[[float], float], float]) -> Self +Set the opacity of the clip. + +**Parameters:** +- `value`: Either a float (0-1) or a function that takes time and returns opacity + +**Returns:** Self for chaining + +**Example:** +```python +# Static opacity +clip.set_opacity(0.5) + +# Animated opacity +clip.set_opacity(lambda t: min(1.0, t / 2.0)) +``` + +--- + +#### set_scale(value: Union[Callable[[float], float], float]) -> Self +Set the scale of the clip. + +**Parameters:** +- `value`: Either a float or a function that takes time and returns scale + +**Returns:** Self for chaining + +**Example:** +```python +# Static scale +clip.set_scale(0.5) + +# Animated scale +clip.set_scale(lambda t: 1.0 + t * 0.1) +``` + +--- + +#### set_size(width: Optional[int] = None, height: Optional[int] = None) -> Self +Set the size of the clip, maintaining aspect ratio if only one dimension is provided. + +**Parameters:** +- `width` (Optional[int]): Target width +- `height` (Optional[int]): Target height + +**Returns:** Self for chaining + +**Raises:** ValueError if both width and height are None or invalid + +**Example:** +```python +# Explicit size +clip.set_size(width=1280, height=720) + +# Maintain aspect ratio (width only) +clip.set_size(width=1280) + +# Maintain aspect ratio (height only) +clip.set_size(height=720) +``` + +--- + +#### set_mask(mask: GraphicClip) -> Self +Set a mask for this clip. The mask determines which pixels are visible. + +The mask is converted to grayscale, where white (255) means fully visible and black (0) means fully transparent. Gray values create partial transparency. + +**Parameters:** +- `mask` (GraphicClip): A GraphicClip to use as mask + +**Returns:** Self for chaining + +**Examples:** + +Simple image mask: +```python +image = ImageClip("photo.png") +mask = ImageClip("mask.png") +image.set_mask(mask) +``` + +Animated text mask: +```python +import numpy as np +from movielite import VideoClip, TextClip +from pictex import Canvas + +# Video to be masked +video = VideoClip("waves.mp4", start=0, duration=10) + +# Create animated text mask +canvas = Canvas().font_size(200).color("white").background_color("transparent") +text = TextClip("Hello World!", start=0, duration=10, canvas=canvas) + +# Animate the mask +text.set_position(lambda t: ( + 960 - text.size[0] // 2, + 500 + int(20 * np.sin(2 * np.pi * (t / text.duration))) +)) +text.set_scale(lambda t: 1.0 + 0.4 * (t / text.duration)) + +# Apply mask - video only visible through text shape +video.set_mask(text) +``` + +--- + +#### add_transform(callback: Callable[[np.ndarray, float], np.ndarray]) -> Self +Apply a custom transformation to each frame at render time. + +**Parameters:** +- `callback` (Callable): Function that takes (frame, time) and returns transformed frame + +**Returns:** Self for chaining + +**Note:** The frame must be in BGR or BGRA format and uint8 type. + +**Example:** +```python +def make_grayscale(frame, t): + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + return cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) + +clip.add_transform(make_grayscale) +``` + +--- + +#### add_effect(effect: GraphicEffect) -> Self +Apply a visual effect to this clip. + +**Parameters:** +- `effect` (GraphicEffect): A GraphicEffect instance to apply + +**Returns:** Self for chaining + +**Example:** +```python +from movielite import vfx +clip.add_effect(vfx.FadeIn(2.0)).add_effect(vfx.FadeOut(1.5)) +``` + +--- + +#### add_transition(next_clip: GraphicClip, transition: Transition) -> Self +Apply a transition effect between this clip and another clip. + +**Parameters:** +- `next_clip` (GraphicClip): The clip to transition to +- `transition` (Transition): A Transition instance to apply + +**Returns:** Self for chaining + +**Example:** +```python +from movielite import vtx +clip1.add_transition(clip2, vtx.CrossFade(0.5)) +``` + +--- + +#### get_frame(t_rel: float) -> np.ndarray +Get the frame at a relative time within the clip (abstract method). + +**Parameters:** +- `t_rel` (float): Relative time within the clip (0 to duration) + +**Returns:** Frame as numpy array (BGR or BGRA, uint8) + +--- + +#### render(bg: np.ndarray, t_global: float) -> np.ndarray +Render this clip onto a background at a given global time. + +**Parameters:** +- `bg` (np.ndarray): Background frame (BGR or BGRA format, float32 type) +- `t_global` (float): Global time in seconds + +**Returns:** Background with this clip rendered on top + +**Note:** Modifies the background in-place. + +--- + +#### close() +Closes the graphic clip and releases any resources. + +--- + +### VideoClip + +A video clip that loads and processes frames in BGR format (no alpha channel). + +Inherits from [GraphicClip](#graphicclip). + +**Constructor:** +```python +VideoClip( + path: str, + start: float = 0, + duration: Optional[float] = None, + offset: float = 0 +) +``` + +**Parameters:** +- `path` (str): Path to the video file +- `start` (float): Start time in the composition (seconds) +- `duration` (Optional[float]): Duration to use from the video (if None, uses full duration) +- `offset` (float): Start offset within the video file (seconds) + +**Properties:** +- `fps` (float): Frames per second of the video +- `audio` (AudioClip): Audio track associated with this video + +**Methods:** + +#### subclip(start: float, end: float) -> VideoClip +Extract a subclip from this video. + +**Parameters:** +- `start` (float): Start time within this clip (seconds) +- `end` (float): End time within this clip (seconds) + +**Returns:** New VideoClip instance + +**Raises:** ValueError if range is invalid + +**Example:** +```python +clip = VideoClip("video.mp4") +segment = clip.subclip(10, 20) # Extract seconds 10-20 +``` + +--- + +#### loop(enabled: bool = True) -> Self +Enable or disable looping for this video clip. + +**Parameters:** +- `enabled` (bool): Whether to enable looping + +**Returns:** Self for chaining + +**Example:** +```python +clip.loop(True) # Video will restart when it reaches the end +``` + +--- + +#### set_offset(offset: float) -> Self +Set the offset within the source video file. + +**Parameters:** +- `offset` (float): Offset in seconds + +**Returns:** Self for chaining + +--- + +### AlphaVideoClip + +A video clip that loads and processes frames in BGRA format (with alpha channel). + +Inherits from [VideoClip](#videoclip). + +**Note:** AlphaVideoClip has a performance penalty (~33% more memory per frame) compared to VideoClip. Only use this when you need transparency support. + +**Constructor:** +```python +AlphaVideoClip( + path: str, + start: float = 0, + duration: Optional[float] = None, + offset: float = 0 +) +``` + +**Parameters:** Same as [VideoClip](#videoclip) + +--- + +### ImageClip + +An image clip that displays a static image for a given duration. + +Inherits from [GraphicClip](#graphicclip). + +**Constructor:** +```python +ImageClip( + source: Union[str, np.ndarray], + start: float = 0, + duration: float = 5.0 +) +``` + +**Parameters:** +- `source` (Union[str, np.ndarray]): Either a file path or a numpy array (RGB/RGBA) +- `start` (float): Start time in the composition (seconds) +- `duration` (float): How long to display the image (seconds) + +**Class Methods:** + +#### from_color(color: tuple, size: tuple, start: float = 0, duration: float = 5.0) -> ImageClip +Create a solid color image clip. + +**Parameters:** +- `color` (tuple): RGB or RGBA tuple (0-255) +- `size` (tuple): (width, height) +- `start` (float): Start time in seconds +- `duration` (float): Duration in seconds + +**Returns:** ImageClip instance + +**Example:** +```python +# Create a red background +red_bg = ImageClip.from_color( + color=(255, 0, 0), + size=(1920, 1080), + duration=10 +) +``` + +--- + +### TextClip + +A text clip that renders text using the pictex library. + +Inherits from [GraphicClip](#graphicclip). + +**Constructor:** +```python +TextClip( + text: str, + start: float = 0, + duration: float = 5.0, + canvas: Optional[Canvas] = None +) +``` + +**Parameters:** +- `text` (str): The text to render +- `start` (float): Start time in the composition (seconds) +- `duration` (float): How long to display the text (seconds) +- `canvas` (Optional[Canvas]): A pictex Canvas instance with styling configured + +**Properties:** +- `text` (str): The text content + +**Example:** +```python +from pictex import Canvas, LinearGradient, Shadow + +canvas = ( + Canvas() + .font_family("Arial") + .font_size(60) + .color("white") + .padding(20) + .background_color(LinearGradient(["#2C3E50", "#FD746C"])) + .border_radius(10) +) + +text = TextClip("Hello World", duration=3, canvas=canvas) +``` + +--- + +### AudioClip + +An audio clip that can be overlaid on video. + +Inherits from [MediaClip](#mediaclip). + +**Constructor:** +```python +AudioClip( + path: str, + start: float = 0, + duration: Optional[float] = None, + volume: float = 1.0, + offset: float = 0 +) +``` + +**Parameters:** +- `path` (str): Path to the audio file +- `start` (float): Start time in the composition (seconds) +- `duration` (Optional[float]): Duration to use (if None, uses full audio duration) +- `volume` (float): Volume multiplier (0.0 to 1.0+) +- `offset` (float): Start offset within the audio file (seconds) + +**Properties:** +- `path` (str): Path to the audio file +- `volume` (float): Volume multiplier +- `offset` (float): Offset within the source audio file +- `sample_rate` (int): Sample rate in Hz +- `channels` (int): Number of audio channels (1=mono, 2=stereo) +- `has_audio` (bool): Whether this clip has actual audio + +**Methods:** + +#### get_samples(start: float = 0, end: Optional[float] = None) -> np.ndarray +Get audio samples as numpy array. + +**Parameters:** +- `start` (float): Start time relative to this clip's offset (seconds) +- `end` (Optional[float]): End time relative to this clip's offset (seconds) + +**Returns:** Numpy array of shape (n_samples, n_channels) with float32 values in [-1, 1] + +**Note:** For memory-efficient processing of long audio, use `iter_chunks()` instead. + +--- + +#### iter_chunks(chunk_duration: float = 5.0) -> Iterator[Tuple[np.ndarray, float]] +Iterate over audio chunks sequentially. + +**Parameters:** +- `chunk_duration` (float): Duration of each chunk in seconds + +**Yields:** Tuple of (processed_samples, chunk_start_time) + +**Example:** +```python +for samples, start_time in audio.iter_chunks(chunk_duration=10.0): + # Process each chunk + process_audio(samples) +``` + +--- + +#### add_transform(callback: Callable[[np.ndarray, float, int], np.ndarray]) -> Self +Apply a custom transformation to audio samples at render time. + +**Parameters:** +- `callback` (Callable): Function that takes (samples, time, sample_rate) and returns transformed samples + +**Returns:** Self for chaining + +**Example:** +```python +def apply_reverb(samples, t, sr): + # Apply custom reverb effect + return reverb_filter(samples, sr) + +audio.add_transform(apply_reverb) +``` + +--- + +#### fade_in(duration: float) -> Self +Apply a linear fade-in effect. + +**Parameters:** +- `duration` (float): Fade duration in seconds + +**Returns:** Self for chaining + +--- + +#### fade_out(duration: float) -> Self +Apply a linear fade-out effect. + +**Parameters:** +- `duration` (float): Fade duration in seconds + +**Returns:** Self for chaining + +--- + +#### set_volume(volume: float) -> Self +Set the volume of this audio clip. + +**Parameters:** +- `volume` (float): Volume multiplier (0.0 to 1.0+) + +**Returns:** Self for chaining + +--- + +#### set_volume_curve(curve: Union[Callable[[float], float], float]) -> Self +Set a volume curve that changes over time. + +**Parameters:** +- `curve`: Either a float (constant volume) or a function that takes time and returns volume + +**Returns:** Self for chaining + +**Example:** +```python +# Gradual volume increase +audio.set_volume_curve(lambda t: min(1.0, t / 5.0)) +``` + +--- + +#### subclip(start: float, end: float) -> AudioClip +Extract a subclip from this audio. + +**Parameters:** +- `start` (float): Start time within this clip (seconds) +- `end` (float): End time within this clip (seconds) + +**Returns:** New AudioClip instance + +--- + +#### loop(enabled: bool = True) -> Self +Enable or disable looping for this audio clip. + +**Parameters:** +- `enabled` (bool): Whether to enable looping + +**Returns:** Self for chaining + +--- + +#### add_effect(effect: AudioEffect) -> Self +Apply an audio effect to this clip. + +**Parameters:** +- `effect` (AudioEffect): An AudioEffect instance to apply + +**Returns:** Self for chaining + +**Example:** +```python +from movielite import afx +clip.add_effect(afx.FadeIn(2.0)).add_effect(afx.FadeOut(1.5)) +``` + +--- + +### VideoWriter + +Write clips to a video file. + +**Constructor:** +```python +VideoWriter( + output_path: str, + fps: float = 30, + size: Optional[Tuple[int, int]] = None, + duration: Optional[float] = None +) +``` + +**Parameters:** +- `output_path` (str): Path where the final video will be saved +- `fps` (float): Frames per second for the output video +- `size` (Optional[Tuple[int, int]]): Video dimensions (width, height). If None, auto-calculated from clips +- `duration` (Optional[float]): Total duration in seconds. If None, auto-calculated from clips + +**Methods:** + +#### add_clip(clip: MediaClip) -> VideoWriter +Add a media clip to the composition. + +**Parameters:** +- `clip` (MediaClip): MediaClip to add (VideoClip, AudioClip, ImageClip, TextClip, etc.) + +**Returns:** Self for chaining + +--- + +#### add_clips(clips: List[MediaClip]) -> VideoWriter +Add multiple media clips to the composition. + +**Parameters:** +- `clips` (List[MediaClip]): List of MediaClip instances to add + +**Returns:** Self for chaining + +--- + +#### write(processes: int = 1, video_quality: VideoQuality = VideoQuality.MIDDLE) -> None +Render and write the final video. + +**Parameters:** +- `processes` (int): Number of processes to use for parallel rendering +- `video_quality` (VideoQuality): Quality preset for encoding + +**Example:** +```python +from movielite import VideoWriter, VideoQuality + +writer = VideoWriter("output.mp4", fps=30, size=(1920, 1080)) +writer.add_clip(clip) +writer.write(processes=8, video_quality=VideoQuality.HIGH) +``` + +--- + +## Visual Effects (vfx) + +All visual effects inherit from `GraphicEffect` and are applied using `clip.add_effect(effect)`. + +### Fade Effects + +#### FadeIn +Gradually increases opacity from 0 to the clip's original opacity. + +```python +from movielite import vfx +clip.add_effect(vfx.FadeIn(duration=2.0)) +``` + +**Parameters:** +- `duration` (float): Duration of the fade in seconds + +--- + +#### FadeOut +Gradually decreases opacity from the clip's original opacity to 0. + +```python +clip.add_effect(vfx.FadeOut(duration=1.5)) +``` + +**Parameters:** +- `duration` (float): Duration of the fade in seconds + +--- + +### Blur Effects + +#### Blur +Apply Gaussian blur to the clip. + +```python +clip.add_effect(vfx.Blur(intensity=5.0, animated=False)) +``` + +**Parameters:** +- `intensity` (float): Blur intensity (kernel size). Higher = more blur +- `animated` (bool): If True, blur increases from 0 to intensity over duration +- `duration` (Optional[float]): Duration of the blur animation (required if animated=True) + +--- + +#### BlurIn +Starts blurred and gradually becomes sharp. + +```python +clip.add_effect(vfx.BlurIn(duration=2.0, max_intensity=15.0)) +``` + +**Parameters:** +- `duration` (float): Duration of the blur-in effect +- `max_intensity` (float): Maximum blur intensity at the start + +--- + +#### BlurOut +Starts sharp and gradually becomes blurred. + +```python +clip.add_effect(vfx.BlurOut(duration=2.0, max_intensity=15.0)) +``` + +**Parameters:** +- `duration` (float): Duration of the blur-out effect +- `max_intensity` (float): Maximum blur intensity at the end + +--- + +### Color Effects + +#### Saturation +Adjust saturation of the clip. + +```python +clip.add_effect(vfx.Saturation(factor=1.5)) +``` + +**Parameters:** +- `factor` (float): Saturation multiplier (0.0=grayscale, 1.0=no change, >1.0=more saturated) + +--- + +#### Brightness +Adjust brightness of the clip. + +```python +clip.add_effect(vfx.Brightness(factor=1.2)) +``` + +**Parameters:** +- `factor` (float): Brightness multiplier (1.0=no change, >1.0=brighter, <1.0=darker) + +--- + +#### Contrast +Adjust contrast of the clip. + +```python +clip.add_effect(vfx.Contrast(factor=1.3)) +``` + +**Parameters:** +- `factor` (float): Contrast multiplier (1.0=no change, >1.0=more contrast, <1.0=less contrast) + +--- + +#### BlackAndWhite / Grayscale +Convert clip to black and white. + +```python +clip.add_effect(vfx.BlackAndWhite()) +# or +clip.add_effect(vfx.Grayscale()) +``` + +--- + +#### Sepia +Apply sepia tone effect. + +```python +clip.add_effect(vfx.Sepia(intensity=1.0)) +``` + +**Parameters:** +- `intensity` (float): Intensity of the sepia effect (0.0 to 1.0) + +--- + +### Zoom Effects + +#### ZoomIn +Gradually scales up the clip from a smaller size. + +```python +clip.add_effect(vfx.ZoomIn(duration=3.0, from_scale=0.5, to_scale=1.0)) +``` + +**Parameters:** +- `duration` (float): Duration of the zoom effect +- `from_scale` (float): Starting scale (0.5 = 50% size) +- `to_scale` (float): Ending scale (1.0 = 100% size) + +--- + +#### ZoomOut +Gradually scales down the clip. + +```python +clip.add_effect(vfx.ZoomOut(duration=3.0, from_scale=1.0, to_scale=0.5)) +``` + +**Parameters:** +- `duration` (float): Duration of the zoom effect +- `from_scale` (float): Starting scale +- `to_scale` (float): Ending scale + +--- + +#### KenBurns +Slow zoom and pan animation for cinematic effect. + +```python +clip.add_effect(vfx.KenBurns( + duration=None, # Uses entire clip duration + start_scale=1.0, + end_scale=1.2, + start_position=(0, 0), + end_position=(100, 50) +)) +``` + +**Parameters:** +- `duration` (Optional[float]): Duration of the effect (None = entire clip duration) +- `start_scale` (float): Starting zoom level +- `end_scale` (float): Ending zoom level +- `start_position` (Tuple[int, int]): Starting position (x, y) +- `end_position` (Tuple[int, int]): Ending position (x, y) + +--- + +### Glitch Effects + +#### Glitch +Digital distortion artifacts simulating VHS glitches. + +```python +clip.add_effect(vfx.Glitch( + intensity=0.5, + rgb_shift=True, + horizontal_lines=True, + scan_lines=False +)) +``` + +**Parameters:** +- `intensity` (float): Intensity of the glitch effect (0.0 to 1.0) +- `rgb_shift` (bool): Enable RGB channel shifting +- `horizontal_lines` (bool): Enable horizontal displacement lines +- `scan_lines` (bool): Enable scan line artifacts + +--- + +#### ChromaticAberration +RGB channel separation for lens distortion effect. + +```python +clip.add_effect(vfx.ChromaticAberration(intensity=5.0)) +``` + +**Parameters:** +- `intensity` (float): Intensity of the aberration in pixels + +--- + +#### Pixelate +Reduce resolution to create a blocky appearance. + +```python +clip.add_effect(vfx.Pixelate(block_size=10)) +``` + +**Parameters:** +- `block_size` (int): Size of pixelation blocks in pixels + +--- + +### Other Effects + +#### Vignette +Darken the edges of the frame. + +```python +clip.add_effect(vfx.Vignette(intensity=0.5, radius=0.8)) +``` + +**Parameters:** +- `intensity` (float): Darkness intensity at the edges (0.0 to 1.0) +- `radius` (float): Radius of the bright center area (0.0 to 1.0) + +--- + +## Audio Effects (afx) + +All audio effects inherit from `AudioEffect` and are applied using `audio.add_effect(effect)`. + +### FadeIn +Gradually increases volume from 0 to the clip's original volume. + +```python +from movielite import afx +audio.add_effect(afx.FadeIn(duration=2.0)) +``` + +**Parameters:** +- `duration` (float): Duration of the fade in seconds + +--- + +### FadeOut +Gradually decreases volume from the clip's original volume to 0. + +```python +audio.add_effect(afx.FadeOut(duration=1.5)) +``` + +**Parameters:** +- `duration` (float): Duration of the fade in seconds + +--- + +## Transitions (vtx) + +All transitions inherit from `Transition` and are applied using `clip1.add_transition(clip2, transition)`. + +### CrossFade +Smooth opacity-based transition between two clips. + +```python +from movielite import vtx +clip1.add_transition(clip2, vtx.CrossFade(duration=0.5)) +``` + +**Parameters:** +- `duration` (float): Duration of the crossfade in seconds + +**Note:** Requires clips to overlap by at least `duration` seconds. + +--- + +### BlurDissolve +Transition with blur effect during the dissolve. + +```python +clip1.add_transition(clip2, vtx.BlurDissolve(duration=1.0, max_blur=15.0)) +``` + +**Parameters:** +- `duration` (float): Duration of the transition in seconds +- `max_blur` (float): Maximum blur intensity at the midpoint + +--- + +## Enumerations + +### VideoQuality + +Quality presets for video encoding. + +```python +from movielite import VideoQuality + +# Available values: +VideoQuality.LOW # Fastest encoding, lowest quality +VideoQuality.MIDDLE # Balanced (default) +VideoQuality.HIGH # Slower encoding, better quality +VideoQuality.VERY_HIGH # Slowest encoding, best quality +``` + +--- + +## Utilities + +### get_logger() +Get the movielite logger instance. + +```python +from movielite import get_logger + +logger = get_logger() +logger.info("Processing video...") +``` + +--- + +### set_log_level(level: int) +Set logging level. + +```python +from movielite import set_log_level +import logging + +set_log_level(logging.DEBUG) +``` + +**Parameters:** +- `level` (int): Logging level (e.g., logging.DEBUG, logging.INFO, logging.WARNING) From 8cc072de1be365d506c8e9b550178830710be04e Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Fri, 24 Jul 2026 23:34:22 +0545 Subject: [PATCH 02/18] feat(docs,ci): implement render presets and update test script --- backend/.gitignore | 1 + backend/README.md | 37 +++++++++++++++++++++++++++++++++++++ backend/test.sh | 8 +++++--- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/backend/.gitignore b/backend/.gitignore index ed26d05..eb79a35 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -19,6 +19,7 @@ output/reels/*.mp4 output/temp/*.mp4 output/temp/*.mp3 output/temp/*.wav +output/render_profile_calibration.json # Keep directory structure with .gitkeep files !output/audio/.gitkeep diff --git a/backend/README.md b/backend/README.md index 502adc4..5dd2f2f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -52,6 +52,43 @@ export PEXELS_API_KEY='...' python reel_generator.py ``` +### Render presets + +`normal` is the default and is intended for background generation while the +browser and desktop remain responsive. It renders at 720x1280/24fps with the +balanced encoder setting. On Linux, it also runs in a 60% CPU cgroup and at a +lower scheduling priority, so an idle laptop does not keep a core fully busy. + +Normal intentionally uses one MovieLite worker. Its 60% CPU quota and lower +scheduling priority keep it background-friendly, while Linux is free to place +that work on any available CPU rather than pinning it to CPU 0. On the +reference laptop, two- and three-worker exports were slower under moderate +aggregate CPU quotas and increased peak memory substantially because MovieLite +renders and merges one encoded part per worker. + +`fast` starts with up to two workers when current RAM headroom permits it. +MovieLite renders one encoded part per process and then merges them, so the +calibration reserves a physical core for the desktop and only accepts +additional workers when they make a meaningful measured improvement. Linux +places the selected workers naturally; no preset pins them to particular CPU +IDs. +It keeps the same 720x1280/24fps output as Normal but uses MovieLite's faster +x264 profile and up to two measured-useful encoder threads. That is a modest +compression trade-off for faster generation, not a resolution or frame-rate +reduction. +On its first Fast reel, MindStream benchmarks safe worker counts against a +short sample of the downloaded footage, caches the fastest meaningful result, +and reuses it until the hardware or output profile changes. + +```bash +./test.sh --preset normal +./test.sh --preset fast +./test.sh --preset fast --recalibrate-presets + +# When using the Express server, Normal remains the default. +MINDSTREAM_REEL_PRESET=fast npm start +``` + **What happens:** 1. Loads `data/sample_emotion_result.json` (frustrated emotion) 2. Generates philosophical script with Gemini diff --git a/backend/test.sh b/backend/test.sh index 35ca219..cfd6d23 100755 --- a/backend/test.sh +++ b/backend/test.sh @@ -3,6 +3,8 @@ # # Usage: # ./test.sh # Run with default sample (data/sample_emotion_result.json) +# ./test.sh --preset fast # Run the same sample with the Fast preset +# ./test.sh --preset fast --recalibrate-presets # Re-measure Fast worker scaling # ./test.sh --emotion anxious --context '{"active_tab_category":"social_media",...}' # # How it works: @@ -28,9 +30,9 @@ fi source venv/bin/activate -echo "Checking environment..." +echo "Preparing backend..." pip install -q -r requirements.txt 2>/dev/null || pip install -r requirements.txt 2>/dev/null -echo -e "\033[92m✓\033[0m Environment ready" +echo -e "\033[92m✓\033[0m Ready" # # --- API keys (set via env or use defaults) --- # if [ -z "$GEMINI_API_KEY" ]; then @@ -48,7 +50,7 @@ echo -e "\033[92m✓\033[0m Environment ready" # --- Run --- if [ $# -gt 0 ]; then # Custom args passed — forward to reel_generator.py - echo "Running with custom args: $@" + # echo "Running sample with selected options..." python reel_generator.py "$@" else # No args — run with default sample data From 911e258107fb478d2caae02c47b64d03905b6956 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Fri, 24 Jul 2026 23:37:15 +0545 Subject: [PATCH 03/18] feat: introduce hardware-aware render presets --- backend/benchmark_render_presets.py | 199 +++++ backend/reel_generator.py | 1139 ++++++++++++++++++++------- backend/server.js | 10 +- backend/test_presets.py | 122 +++ 4 files changed, 1189 insertions(+), 281 deletions(-) create mode 100644 backend/benchmark_render_presets.py create mode 100644 backend/test_presets.py diff --git a/backend/benchmark_render_presets.py b/backend/benchmark_render_presets.py new file mode 100644 index 0000000..b25533c --- /dev/null +++ b/backend/benchmark_render_presets.py @@ -0,0 +1,199 @@ +"""Benchmark MovieLite worker/quota combinations with a fixed local export. + +Run from backend after selecting an existing video and narration file: + venv/bin/python benchmark_render_presets.py --video path/to/clip.mp4 --audio path/to/audio.mp3 +""" + +import argparse +import contextlib +import json +import os +import shutil +import subprocess +import sys +import time +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + + +PROFILES: Tuple[Tuple[str, int, int], ...] = ( + ("one-worker-50", 1, 50), + ("two-workers-50", 2, 50), + ("two-workers-60", 2, 60), + ("two-workers-75", 2, 75), + ("three-workers-75", 3, 75), +) + + +def _read_cpu_usage(cgroup_path: Path) -> int: + values = dict(line.split() for line in (cgroup_path / "cpu.stat").read_text().splitlines()) + return int(values["usage_usec"]) + + +def _cgroup_path(unit: str) -> Optional[Path]: + result = subprocess.run( + ["systemctl", "--user", "show", unit, "--property=ControlGroup", "--value"], + capture_output=True, + text=True, + check=False, + ) + group = result.stdout.strip() + return Path("/sys/fs/cgroup") / group.lstrip("/") if group else None + + +def _unit_is_active(unit: str) -> bool: + return ( + subprocess.run( + ["systemctl", "--user", "is-active", "--quiet", unit], check=False + ).returncode + == 0 + ) + + +def _run_child(args: argparse.Namespace) -> int: + os.chdir(Path(__file__).parent) + from reel_generator import ReelGenerator + + generator = ReelGenerator(preset="normal") + generator._spinner = lambda _: contextlib.nullcontext() + # Benchmark Linux scheduling without affinity: let the kernel place workers. + generator.preset_cfg["writer_processes"] = args.workers + generator.preset_cfg["cpu_affinity"] = () + generator.composite_reel( + video_paths=[args.video], + tts_path=args.audio, + output_path=args.output, + subtitle_list=["A short representative export", "for preset comparison"], + tts_duration=args.duration, + ) + return 0 + + +def _benchmark_profile( + name: str, + workers: int, + quota: int, + args: argparse.Namespace, + output_dir: Path, +) -> Dict[str, Any]: + unit = f"mindstream-benchmark-{os.getpid()}-{name}.service" + output_path = output_dir / f"{name}.mp4" + command = [ + "systemd-run", + "--user", + "--quiet", + "--no-block", + "--unit", + unit, + "-p", + f"CPUQuota={quota}%", + sys.executable, + str(Path(__file__).resolve()), + "--child", + "--workers", + str(workers), + "--duration", + str(args.duration), + "--video", + str(Path(args.video).resolve()), + "--audio", + str(Path(args.audio).resolve()), + "--output", + str(output_path), + ] + subprocess.run(command, check=True) + + cgroup = None + for _ in range(50): + cgroup = _cgroup_path(unit) + if cgroup and (cgroup / "cpu.stat").exists(): + break + time.sleep(0.05) + if not cgroup or not (cgroup / "cpu.stat").exists(): + raise RuntimeError(f"Could not inspect benchmark cgroup for {name}") + + start_time = time.monotonic() + last_time = start_time + start_usage = _read_cpu_usage(cgroup) + last_usage = start_usage + peak_cpu_percent = 0.0 + peak_memory_bytes = 0 + + while _unit_is_active(unit): + time.sleep(0.1) + now = time.monotonic() + try: + usage = _read_cpu_usage(cgroup) + except FileNotFoundError: + # The scope exited between the active-state check and this sample. + break + elapsed = now - last_time + if elapsed > 0: + peak_cpu_percent = max( + peak_cpu_percent, (usage - last_usage) / 1_000_000 / elapsed * 100 + ) + memory_peak = cgroup / "memory.peak" + if memory_peak.exists(): + peak_memory_bytes = max(peak_memory_bytes, int(memory_peak.read_text())) + last_time = now + last_usage = usage + + total_seconds = time.monotonic() - start_time + # A transient scope may be collected immediately after it stops. The final + # active-state sample is therefore the authoritative value to retain. + final_usage = last_usage + memory_peak = cgroup / "memory.peak" + if memory_peak.exists(): + peak_memory_bytes = max(peak_memory_bytes, int(memory_peak.read_text())) + + if not output_path.exists() or output_path.stat().st_size == 0: + raise RuntimeError(f"{name} did not produce an output file") + + return { + "profile": name, + "workers": workers, + "quota_percent": quota, + "wall_seconds": round(total_seconds, 2), + "average_cpu_percent": round( + (final_usage - start_usage) / 1_000_000 / total_seconds * 100, 1 + ), + "peak_cpu_percent": round(peak_cpu_percent, 1), + "peak_memory_mib": round(peak_memory_bytes / 1024 / 1024, 1), + "output": str(output_path), + } + + +def _run_benchmark(args: argparse.Namespace) -> int: + if not shutil.which("systemd-run"): + raise RuntimeError("This benchmark requires systemd-run and cgroup v2") + + output_dir = Path(args.output_dir).resolve() + output_dir.mkdir(parents=True, exist_ok=True) + results: List[Dict[str, Any]] = [] + + for name, workers, quota in PROFILES: + print(f"Benchmarking {name}: {workers} worker(s), {quota}% CPU quota...") + results.append(_benchmark_profile(name, workers, quota, args, output_dir)) + + print(json.dumps(results, indent=2)) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Benchmark MovieLite render profiles") + parser.add_argument("--video", required=True) + parser.add_argument("--audio", required=True) + parser.add_argument("--output-dir", default="/tmp/mindstream-render-benchmarks") + parser.add_argument("--duration", type=float, default=8.0) + parser.add_argument("--workers", type=int) + parser.add_argument("--output") + parser.add_argument("--child", action="store_true") + args = parser.parse_args() + + if args.child: + return _run_child(args) + return _run_benchmark(args) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/backend/reel_generator.py b/backend/reel_generator.py index a351af2..661521c 100644 --- a/backend/reel_generator.py +++ b/backend/reel_generator.py @@ -5,16 +5,28 @@ # Suppress tqdm progress bars from MovieLite (must be before imports) import os -os.environ['TQDM_DISABLE'] = '1' + +os.environ["TQDM_DISABLE"] = "1" + +# Limit BLAS/OpenMP threading at import time so numpy doesn't saturate all cores. +# The preset system overrides ffmpeg threads at runtime, but BLAS threads must be +# set before OpenBLAS initializes (i.e. before numpy is imported). +os.environ.setdefault("OPENBLAS_NUM_THREADS", "1") +os.environ.setdefault("OPENBLAS64_NUM_THREADS", "1") # scipy_openblas64 uses this +os.environ.setdefault("MKL_NUM_THREADS", "1") +os.environ.setdefault("OMP_NUM_THREADS", "1") +os.environ.setdefault("NUMEXPR_NUM_THREADS", "1") import re import json import asyncio import requests import base64 +import shutil import sys import time import threading +from dataclasses import dataclass from typing import List, Dict, Any, Optional, Tuple from concurrent.futures import ThreadPoolExecutor, as_completed from dotenv import load_dotenv @@ -28,27 +40,232 @@ print("Run: pip install movielite pictex google-generativeai python-dotenv") exit(1) -# Suppress third-party warnings for cleaner CLI output +# Suppress third-party warnings for cleaner ux import warnings -warnings.filterwarnings('ignore', category=UserWarning) -warnings.filterwarnings('ignore', message='.*FontNotFoundWarning.*') -warnings.filterwarnings('ignore', message='.*fontconfig.*') -warnings.filterwarnings('ignore', message='.*resource_tracker.*') + +warnings.filterwarnings("ignore", category=UserWarning) +warnings.filterwarnings("ignore", message=".*FontNotFoundWarning.*") +warnings.filterwarnings("ignore", message=".*fontconfig.*") +warnings.filterwarnings("ignore", message=".*resource_tracker.*") import logging -logging.getLogger('movielite').setLevel(logging.ERROR) -logging.getLogger('movielite').propagate = False + +logging.getLogger("movielite").setLevel(logging.ERROR) +logging.getLogger("movielite").propagate = False import contextlib -# Load environment variables from .env file load_dotenv() + +@dataclass(frozen=True) +class HardwareProfile: + """CPU topology available to this process, respecting cpuset limits.""" + + logical_cpus: Tuple[int, ...] + physical_core_groups: Tuple[Tuple[int, ...], ...] + + @property + def logical_core_count(self) -> int: + return len(self.logical_cpus) + + @property + def physical_core_count(self) -> int: + return len(self.physical_core_groups) + + +def _detect_hardware_profile() -> HardwareProfile: + """Return available logical CPUs grouped by physical core where possible.""" + try: + logical_cpus = tuple(sorted(os.sched_getaffinity(0))) + except (AttributeError, OSError): + logical_cpus = tuple(range(max(1, os.cpu_count() or 1))) + if not logical_cpus: + logical_cpus = (0,) + + # Linux exposes sibling threads through sysfs. Keeping siblings together lets + # Fast use a small number of complete physical cores instead of all threads. + groups: Dict[Tuple[str, str], List[int]] = {} + try: + for cpu in logical_cpus: + topology_dir = f"/sys/devices/system/cpu/cpu{cpu}/topology" + with open(os.path.join(topology_dir, "physical_package_id")) as fh: + package_id = fh.read().strip() + with open(os.path.join(topology_dir, "core_id")) as fh: + core_id = fh.read().strip() + groups.setdefault((package_id, core_id), []).append(cpu) + except OSError: + groups = {} + + if groups: + physical_core_groups = tuple( + tuple(cpus) for _, cpus in sorted(groups.items(), key=lambda item: min(item[1])) + ) + else: + # Without topology information, treat each available CPU as a core. This + # remains safe because Fast is deliberately capped below all-core usage. + physical_core_groups = tuple((cpu,) for cpu in logical_cpus) + + return HardwareProfile(logical_cpus, physical_core_groups) + +# --------------------------------------------------------------------------- +# Speed / resource presets +# +# Each preset defines a user experience, not a fixed resource allocation. +# Render process counts are derived from detected hardware at init time. +# --------------------------------------------------------------------------- +PRESETS = { + # Background-friendly. Resource limits are resolved from the CPU topology. + "normal": { + "frame_size": (720, 1280), + "target_fps": 24, + "video_quality": "middle", + "cpu_quota_percent": 60, + "download_workers": 1, + "download_chunk_size": 131072, + }, + # Faster while intentionally leaving most of the machine to the user. + "fast": { + "frame_size": (720, 1280), + "target_fps": 24, + # MovieLite maps this to x264 ultrafast / CRF 23. At 720p it is a + # practical Fast trade-off, while Normal retains the balanced encoder. + "video_quality": "low", + "cpu_quota_percent": None, + "download_workers": 2, + "download_chunk_size": 262144, + }, +} + +CALIBRATION_VERSION = 2 +CALIBRATION_SAMPLE_SECONDS = 6.0 +CALIBRATION_MIN_SPEEDUP = 0.08 +CALIBRATION_WORKER_MEMORY_BYTES = 768 * 1024 * 1024 + + +def _available_memory_bytes() -> Optional[int]: + """Return available RAM on Linux without treating cached memory as unavailable.""" + try: + values = {} + with open("/proc/meminfo") as fh: + for line in fh: + key, value = line.split(":", 1) + values[key] = int(value.strip().split()[0]) * 1024 + return values.get("MemAvailable") + except (OSError, ValueError, IndexError): + return None + + +def _candidate_worker_counts(hardware: HardwareProfile) -> Tuple[int, ...]: + """Return safe worker candidates without consuming every physical core.""" + # Reserve one physical core for the desktop whenever the machine has more + # than one. MovieLite's short-reel split/merge overhead also makes more than + # four candidates counterproductive to benchmark at generation time. + cpu_limit = max(1, hardware.physical_core_count - 1) + memory_available = _available_memory_bytes() + memory_limit = ( + max(1, memory_available // CALIBRATION_WORKER_MEMORY_BYTES) + if memory_available is not None + else 2 + ) + max_workers = min(4, cpu_limit, memory_limit) + return tuple(range(1, max_workers + 1)) + + +def _resolve_preset(preset: str, hardware: HardwareProfile) -> Dict[str, Any]: + """Resolve a preset into limits appropriate for this machine. + + MovieLite creates one Python compositor and one libx264 encoder per render + process. Its implementation also merges every part afterwards, so benchmarked + scaling is useful through two workers but additional workers mainly add memory + pressure and encoder contention for MindStream's short portrait reels. + """ + if preset not in PRESETS: + raise ValueError(f"Unknown preset '{preset}'. Choose from: {', '.join(PRESETS)}") + + config = dict(PRESETS[preset]) + if preset == "normal": + # A single compositor plus the cgroup quota keeps Normal lightweight. + # Leave placement to the kernel scheduler rather than pinning CPU 0. + config.update( + writer_processes=1, + ffmpeg_threads=1, + cpu_affinity=(), + niceness=10, + ) + return config + + # This is a safe first-run fallback. Calibration replaces it with a measured + # result once real footage is available. Memory headroom is included here so + # Fast never starts more workers than the calibrator considers safe. + fast_worker_limit = min(2, max(_candidate_worker_counts(hardware))) + config.update( + writer_processes=fast_worker_limit, + # Measured scaling improves through two encoder threads but not beyond. + ffmpeg_threads=min(2, hardware.physical_core_count), + cpu_affinity=(), + niceness=0, + ) + return config + + +def _run_in_cpu_limited_scope(preset: str) -> Optional[int]: + """Re-exec Normal in a user cgroup so its average CPU use is truly capped. + + Affinity and niceness are useful scheduling hints, but neither prevents an + idle machine from running one core at 100%. systemd's CPUQuota covers the + Python renderer and every MovieLite/FFmpeg child process on Linux. + """ + quota_percent = PRESETS[preset].get("cpu_quota_percent") + if ( + not quota_percent + or sys.platform != "linux" + or os.getenv("MINDSTREAM_CPU_QUOTA_APPLIED") == "1" + ): + return None + + systemd_run = shutil.which("systemd-run") + if not systemd_run: + print("WARNING: CPU quota unavailable; continuing with affinity limits only.") + return None + + command = [ + systemd_run, + "--user", + "--scope", + "--quiet", + "-p", + f"CPUQuota={quota_percent}%", + "env", + "MINDSTREAM_CPU_QUOTA_APPLIED=1", + sys.executable, + os.path.abspath(__file__), + *sys.argv[1:], + ] + print(f"Normal preset: applying a {quota_percent}% CPU quota.") + try: + import subprocess + + return subprocess.run(command, check=False).returncode + except OSError as exc: + print(f"WARNING: Could not apply CPU quota ({exc}); continuing normally.") + return None + + +# TODO: ambient audio class ReelGenerator: - """Complete reel generation pipeline with multi-source video search and ambient audio.""" + """Complete generation pipeline with multi-source video search and ambient audio.""" - def __init__(self, gemini_key: str = None, pexels_key: str = None, - pixabay_key: str = None, coverr_key: str = None, mimo_key: str = None): - # Load API keys from environment + def __init__( + self, + gemini_key: str = None, + pexels_key: str = None, + pixabay_key: str = None, + coverr_key: str = None, + mimo_key: str = None, + preset: str = "normal", + recalibrate_presets: bool = False, + ): + # load API keys if not provided self.gemini_key = gemini_key or os.getenv("GEMINI_API_KEY") self.groq_key = os.getenv("GROQ_API_KEY") self.pexels_key = pexels_key or os.getenv("PEXELS_API_KEY") @@ -60,6 +277,17 @@ def __init__(self, gemini_key: str = None, pexels_key: str = None, self.script_provider = os.getenv("SCRIPT_MODEL_PROVIDER", "gemini").lower() self.script_model = os.getenv("SCRIPT_MODEL_NAME", "gemini-2.0-flash-exp") + # Speed / resource preset + self.preset = preset + self.hardware = _detect_hardware_profile() + self.physical_cores = self.hardware.physical_core_count + self.preset_cfg = _resolve_preset(preset, self.hardware) + self._niceness_applied = False + self.recalibrate_presets = recalibrate_presets + + # Reuse a single HTTP session across all API calls (connection pooling) + self._http = requests.Session() + # Validate required keys if self.script_provider == "gemini" and not self.gemini_key: raise ValueError("GEMINI_API_KEY required (set in .env)") @@ -80,28 +308,34 @@ def __init__(self, gemini_key: str = None, pexels_key: str = None, os.makedirs(os.path.join(self.output_dir, "audio"), exist_ok=True) os.makedirs(os.path.join(self.output_dir, "reels"), exist_ok=True) os.makedirs(self.temp_dir, exist_ok=True) - + self.calibration_path = os.path.join( + self.output_dir, "render_profile_calibration.json" + ) + # Ambient audio mapping (placeholder files - replace with real audio later) self.ambient_music = { "frustrated": "assets/audio/frustrated.mp3", # Dark ambient, subtle rain - "fatigued": "assets/audio/fatigued.mp3", # Soft piano, gentle pads + "fatigued": "assets/audio/fatigued.mp3", # Soft piano, gentle pads "distracted": "assets/audio/distracted.mp3", # Calm waves, subtle wind - "anxious": "assets/audio/anxious.mp3", # Breathing sounds, soft hum - "neutral": "assets/audio/neutral.mp3", # White noise, minimal drone + "anxious": "assets/audio/anxious.mp3", # Breathing sounds, soft hum + "neutral": "assets/audio/neutral.mp3", # White noise, minimal drone } @contextlib.contextmanager def _spinner(self, message: str): """Context manager that shows a braille spinner with the given message.""" stop = False + def _run(): idx = 0 while not stop: - sys.stdout.write(f'\r{self.BRAILLE_CHARS[idx % len(self.BRAILLE_CHARS)]} {message}') + sys.stdout.write( + f"\r{self.BRAILLE_CHARS[idx % len(self.BRAILLE_CHARS)]} {message}" + ) sys.stdout.flush() idx += 1 time.sleep(0.1) - sys.stdout.write('\r' + ' ' * 50 + '\r') + sys.stdout.write("\r" + " " * 50 + "\r") sys.stdout.flush() thread = threading.Thread(target=_run, daemon=True) @@ -123,12 +357,12 @@ def generate_script(self, emotion: str, context: Dict[str, Any]) -> Dict[str, An - "subtitles": list of short phrases (4-6 words each) that together cover the whole script in order """ - activity = context.get("active_tab_category", "browsing") - time_of_day = context.get("time_of_day", "the day") - duration = context.get("session_duration_minutes", 0) - idle_time = context.get("idle_minutes_since_last_activity", 0) - user_name = context.get("user_name", "friend") - local_weather = context.get("local_weather", "calm") + activity = context.get("active_tab_category", "browsing") + time_of_day = context.get("time_of_day", "the day") + duration = context.get("session_duration_minutes", 0) + idle_time = context.get("idle_minutes_since_last_activity", 0) + user_name = context.get("user_name", "friend") + local_weather = context.get("local_weather", "calm") # Build activity description more generically activity_desc = activity @@ -181,30 +415,31 @@ def generate_script(self, emotion: str, context: Dict[str, Any]) -> Dict[str, An # Call the appropriate LLM based on provider if self.script_provider == "groq": - response = requests.post( + response = self._http.post( "https://api.groq.com/openai/v1/chat/completions", headers={ "Authorization": f"Bearer {self.groq_key}", - "Content-Type": "application/json" + "Content-Type": "application/json", }, json={ "model": self.script_model, "messages": [ - {"role": "system", "content": "You are a wise, warm elder who creates mindfulness scripts in JSON format."}, - {"role": "user", "content": prompt} + { + "role": "system", + "content": "You are a wise, warm elder who creates mindfulness scripts in JSON format.", + }, + {"role": "user", "content": prompt}, ], "temperature": 0.8, - "response_format": {"type": "json_object"} + "response_format": {"type": "json_object"}, }, - timeout=30 + timeout=30, ) response.raise_for_status() raw = response.json()["choices"][0]["message"]["content"] else: response = self.client.models.generate_content( - model=self.script_model, - contents=prompt, - config={"temperature": 0.9} + model=self.script_model, contents=prompt, config={"temperature": 0.9} ) raw = response.text.strip() # Strip markdown fences if the model ignores the instruction @@ -216,9 +451,11 @@ def generate_script(self, emotion: str, context: Dict[str, Any]) -> Dict[str, An data = json.loads(raw) if "script" not in data or "subtitles" not in data: - raise ValueError(f"LLM JSON missing required keys. Got: {list(data.keys())}") + raise ValueError( + f"LLM JSON missing required keys. Got: {list(data.keys())}" + ) - script = data["script"].strip() + script = data["script"].strip() subtitles = [p.strip() for p in data["subtitles"] if p.strip()] if not script: @@ -234,7 +471,7 @@ def generate_script(self, emotion: str, context: Dict[str, Any]) -> Dict[str, An def extract_video_keywords(self, script: str, emotion: str) -> List[str]: """Extract 3-5 cinematic/moody video search terms from the script.""" - + prompt = f"""From this mindfulness script about the emotion "{emotion}", extract 3-5 search terms to find matching stock video footage. Script: @@ -250,15 +487,18 @@ def extract_video_keywords(self, script: str, emotion: str) -> List[str]: try: if self.script_provider == "groq": - response = requests.post( + response = self._http.post( "https://api.groq.com/openai/v1/chat/completions", - headers={"Authorization": f"Bearer {self.groq_key}", "Content-Type": "application/json"}, + headers={ + "Authorization": f"Bearer {self.groq_key}", + "Content-Type": "application/json", + }, json={ "model": self.script_model, "messages": [{"role": "user", "content": prompt}], - "temperature": 0.7 + "temperature": 0.7, }, - timeout=20 + timeout=20, ) response.raise_for_status() text = response.json()["choices"][0]["message"]["content"] @@ -266,10 +506,10 @@ def extract_video_keywords(self, script: str, emotion: str) -> List[str]: response = self.client.models.generate_content( model=self.script_model, contents=prompt, - config={"temperature": 0.9} + config={"temperature": 0.9}, ) text = response.text.strip() - + text = text.replace("```json", "").replace("```", "").strip() keywords = json.loads(text) if isinstance(keywords, list) and keywords: @@ -284,24 +524,35 @@ def extract_video_keywords(self, script: str, emotion: str) -> List[str]: # Step 3 — Multi-source video search + download # ----------------------------------------------------------------------- - def search_pexels_videos(self, keyword: str, orientation: str = "portrait") -> Optional[str]: + def search_pexels_videos( + self, keyword: str, orientation: str = "portrait" + ) -> Optional[str]: """Search Pexels and return a direct download URL for the best match.""" try: - resp = requests.get( + resp = self._http.get( "https://api.pexels.com/videos/search", headers={"Authorization": self.pexels_key}, - params={"query": keyword, "orientation": orientation, "size": "medium", "per_page": 20}, + params={ + "query": keyword, + "orientation": orientation, + "size": "medium", + "per_page": 20, + }, timeout=10, ) resp.raise_for_status() videos = resp.json().get("videos", []) - + if not videos: return None # Prefer HD (height >= 1080) portrait files for video in videos: - for f in sorted(video.get("video_files", []), key=lambda x: x.get("height", 0), reverse=True): + for f in sorted( + video.get("video_files", []), + key=lambda x: x.get("height", 0), + reverse=True, + ): if f.get("height", 0) >= 720: return f.get("link") @@ -315,21 +566,21 @@ def search_pixabay_videos(self, keyword: str) -> Optional[str]: """Search Pixabay (fallback source) and return a direct download URL.""" if not self.pixabay_key: return None - + try: - resp = requests.get( + resp = self._http.get( "https://pixabay.com/api/videos/", params={ "key": self.pixabay_key, "q": keyword, "video_type": "all", - "per_page": 20 + "per_page": 20, }, timeout=10, ) resp.raise_for_status() videos = resp.json().get("hits", []) - + if not videos: return None @@ -339,7 +590,7 @@ def search_pixabay_videos(self, keyword: str) -> Optional[str]: return video["videos"]["medium"]["url"] elif "small" in video.get("videos", {}): return video["videos"]["small"]["url"] - + return None except Exception as e: return None @@ -348,10 +599,10 @@ def search_coverr_videos(self, keyword: str) -> Optional[str]: """Search Coverr (fallback source) and return a direct download URL.""" if not self.coverr_key: return None - + try: # Coverr API endpoint (based on common API patterns) - resp = requests.get( + resp = self._http.get( "https://api.coverr.co/videos", headers={"Authorization": f"Bearer {self.coverr_key}"}, params={"query": keyword, "per_page": 20}, @@ -359,7 +610,7 @@ def search_coverr_videos(self, keyword: str) -> Optional[str]: ) resp.raise_for_status() videos = resp.json().get("videos", []) - + if not videos: return None @@ -367,7 +618,7 @@ def search_coverr_videos(self, keyword: str) -> Optional[str]: for video in videos: if "url" in video: return video["url"] - + return None except Exception as e: # Coverr API might have different structure, fail gracefully @@ -375,11 +626,12 @@ def search_coverr_videos(self, keyword: str) -> Optional[str]: def _download_video(self, url: str, dest: str) -> bool: try: - r = requests.get(url, stream=True, timeout=60) + r = self._http.get(url, stream=True, timeout=60) r.raise_for_status() - + + chunk_size = self.preset_cfg["download_chunk_size"] with open(dest, "wb") as fh: - for chunk in r.iter_content(chunk_size=65536): + for chunk in r.iter_content(chunk_size=chunk_size): fh.write(chunk) return True except Exception as e: @@ -393,48 +645,50 @@ def download_videos_for_script(self, keywords: List[str], job_id: str) -> List[s if not keywords: print("No keywords extracted — cannot download videos") return [] - + def download_single_keyword(i: int, kw: str) -> Optional[str]: """Try all sources for a keyword: Pexels → Pixabay → Coverr""" url = None - + # Try Pexels first url = self.search_pexels_videos(kw) if url: source = "Pexels" - + # Fallback to Pixabay if not url and self.pixabay_key: url = self.search_pixabay_videos(kw) if url: source = "Pixabay" - + # Fallback to Coverr if not url and self.coverr_key: url = self.search_coverr_videos(kw) if url: source = "Coverr" - + if not url: return None - + dest = os.path.join(self.temp_dir, f"{job_id}_clip_{i}.mp4") if self._download_video(url, dest): return dest return None - + # Download videos in parallel with DNF-style progress paths = [] completed = 0 total = len(keywords) width = 20 - - with ThreadPoolExecutor(max_workers=min(4, len(keywords))) as executor: + + with ThreadPoolExecutor( + max_workers=min(self.preset_cfg["download_workers"], len(keywords)) + ) as executor: futures = { - executor.submit(download_single_keyword, i, kw): (i, kw) + executor.submit(download_single_keyword, i, kw): (i, kw) for i, kw in enumerate(keywords) } - + for future in as_completed(futures): i, kw = futures[future] try: @@ -447,26 +701,40 @@ def download_single_keyword(i: int, kw: str) -> Optional[str]: # DNF-style progress bar matching the format of other steps pct = int(100 * completed / total) if total > 0 else 0 filled = int(width * completed / total) if total > 0 else 0 - bar = '━' * filled + ' ' * (width - filled) + bar = "━" * filled + " " * (width - filled) color = self.CYAN if completed < total else self.GREEN # Format exactly like _progress_bar_dnf with green checkmark prefix desc = f"{'Downloading footage':<26}" if completed >= total: # Final line with green checkmark - print(f"\r{self.GREEN}✓{self.RESET} {desc}{color}{pct:3d}% |{bar}| {completed}/{total}{self.RESET}", end='', flush=True) + print( + f"\r{self.GREEN}✓{self.RESET} {desc}{color}{pct:3d}% |{bar}| {completed}/{total}{self.RESET}", + end="", + flush=True, + ) else: # In-progress with spinner (updates per item) - spinner_char = self.BRAILLE_CHARS[completed % len(self.BRAILLE_CHARS)] - print(f"\r{self.CYAN}{spinner_char}{self.RESET} {desc}{color}{pct:3d}% |{bar}| {completed}/{total}{self.RESET}", end='', flush=True) - + spinner_char = self.BRAILLE_CHARS[ + completed % len(self.BRAILLE_CHARS) + ] + print( + f"\r{self.CYAN}{spinner_char}{self.RESET} {desc}{color}{pct:3d}% |{bar}| {completed}/{total}{self.RESET}", + end="", + flush=True, + ) + print() # newline after progress # Sort paths by clip number to maintain order - paths.sort(key=lambda p: int(re.search(r'clip_(\d+)', p).group(1)) if 'clip_' in p else 999) - + paths.sort( + key=lambda p: ( + int(re.search(r"clip_(\d+)", p).group(1)) if "clip_" in p else 999 + ) + ) + if not paths: print("No videos could be downloaded from any source") return [] - + return paths # ----------------------------------------------------------------------- @@ -475,11 +743,10 @@ def download_single_keyword(i: int, kw: str) -> Optional[str]: async def _generate_tts_async(self, script: str, output_path: str) -> str: loop = asyncio.get_event_loop() - - + response = await loop.run_in_executor( None, - lambda: requests.post( + lambda: self._http.post( "https://api.xiaomimimo.com/v1/chat/completions", headers={ "Authorization": f"Bearer {self.mimo_key}", @@ -493,7 +760,7 @@ async def _generate_tts_async(self, script: str, output_path: str) -> str: timeout=90, ), ) - + if response.status_code != 200: raise RuntimeError( f"MiMo TTS API error {response.status_code}: {response.text[:300]}" @@ -501,7 +768,7 @@ async def _generate_tts_async(self, script: str, output_path: str) -> str: audio_b64 = response.json()["choices"][0]["message"]["audio"]["data"] with open(output_path, "wb") as fh: fh.write(base64.b64decode(audio_b64)) - + return output_path def generate_tts(self, script: str, output_path: str) -> str: @@ -517,9 +784,12 @@ def generate_tts(self, script: str, output_path: str) -> str: def _srt_ts(seconds: float) -> str: """Convert float seconds → SRT timestamp string HH:MM:SS,mmm.""" ms = max(0, int(round(seconds * 1000))) - h = ms // 3_600_000; ms %= 3_600_000 - m = ms // 60_000; ms %= 60_000 - s = ms // 1_000; ms %= 1_000 + h = ms // 3_600_000 + ms %= 3_600_000 + m = ms // 60_000 + ms %= 60_000 + s = ms // 1_000 + ms %= 1_000 return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}" @staticmethod @@ -538,31 +808,31 @@ def build_srt(self, subtitles: List[str], audio_duration: float) -> str: if not subtitles: return "" - PAUSE_BONUS = 0.20 # Add 20% extra time for phrases ending with punctuation - + PAUSE_BONUS = 0.20 # Add 20% extra time for phrases ending with punctuation + # Use character count (better proxy for speech duration than word count) weights = [] for phrase in subtitles: # Count characters (excluding spaces) as base weight w = max(1, len(phrase.replace(" ", ""))) - + # Add pause time for sentence endings if self._phrase_has_pause(phrase): w += PAUSE_BONUS * w - + weights.append(w) total_weight = sum(weights) - available = audio_duration + available = audio_duration - lines = [] - cursor = 0.0 + lines = [] + cursor = 0.0 for idx, (phrase, weight) in enumerate(zip(subtitles, weights), start=1): phrase_dur = available * (weight / total_weight) - start = cursor - end = cursor + phrase_dur - cursor = end + start = cursor + end = cursor + phrase_dur + cursor = end lines.append(str(idx)) lines.append(f"{self._srt_ts(start)} --> {self._srt_ts(end)}") @@ -580,65 +850,322 @@ def build_srt(self, subtitles: List[str], audio_duration: float) -> str: # ----------------------------------------------------------------------- @staticmethod - def parse_srt(srt_path: str) -> List[Tuple[float, float, str]]: - """ - Parse SRT file and return list of (start_time, end_time, text) tuples. - Times are in seconds (float). - """ - with open(srt_path, 'r', encoding='utf-8') as f: - content = f.read().strip() - - # Split by double newlines (subtitle blocks) - blocks = re.split(r'\n\n+', content) + def _parse_srt_content(content: str) -> List[Tuple[float, float, str]]: + """Parse SRT content string and return list of (start, end, text) tuples.""" + content = content.strip() + blocks = re.split(r"\n\n+", content) subtitles = [] - + for block in blocks: - lines = block.strip().split('\n') + lines = block.strip().split("\n") if len(lines) < 3: continue - - # Line 0: sequence number (ignore) - # Line 1: timestamp (00:00:01,234 --> 00:00:03,456) - # Line 2+: subtitle text - + timestamp_line = lines[1] - match = re.match(r'(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2}),(\d{3})', timestamp_line) + match = re.match( + r"(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2}),(\d{3})", + timestamp_line, + ) if not match: continue - - # Parse start time + h1, m1, s1, ms1, h2, m2, s2, ms2 = map(int, match.groups()) start_time = h1 * 3600 + m1 * 60 + s1 + ms1 / 1000.0 end_time = h2 * 3600 + m2 * 60 + s2 + ms2 / 1000.0 - - # Join remaining lines as text - text = ' '.join(lines[2:]) - + text = " ".join(lines[2:]) subtitles.append((start_time, end_time, text)) - + return subtitles - def _create_subtitle_canvas(self) -> Canvas: + @staticmethod + def parse_srt(srt_path: str) -> List[Tuple[float, float, str]]: + """Parse SRT file and return list of (start_time, end_time, text) tuples.""" + with open(srt_path, "r", encoding="utf-8") as f: + content = f.read() + return ReelGenerator._parse_srt_content(content) + + def _create_subtitle_canvas(self, frame_width: int) -> Canvas: """Create styled canvas for subtitle text (MovieLite/pictex).""" + scale = frame_width / 1080 return ( Canvas() .font_family("Poppins") - .font_size(50) + .font_size(round(50 * scale)) .color("#FFFF00") # Yellow - .text_shadows(Shadow(offset=(2, 2), blur_radius=3, color="black")) - .padding(20) + .text_shadows( + Shadow( + offset=(round(2 * scale), round(2 * scale)), + blur_radius=round(3 * scale), + color="black", + ) + ) + .padding(round(20 * scale)) ) - def _resize_to_portrait(self, clip: ml.VideoClip) -> ml.VideoClip: + def _resize_to_portrait( + self, clip: ml.VideoClip, frame_size: Tuple[int, int] + ) -> ml.VideoClip: """ - Resize to exactly 1080×1920 (9:16) using MovieLite. + Resize to the preset's portrait output size using MovieLite. MovieLite's set_size() maintains aspect ratio and crops/pads automatically. """ # MovieLite's set_size will resize maintaining aspect ratio # If the source aspect ratio doesn't match, it will crop center - clip.set_size(width=1080, height=1920) + clip.set_size(width=frame_size[0], height=frame_size[1]) return clip + def _calibration_signature(self) -> Dict[str, Any]: + """Describe the machine and output profile that affect worker scaling.""" + return { + "logical_cpus": list(self.hardware.logical_cpus), + "physical_core_groups": [ + list(group) for group in self.hardware.physical_core_groups + ], + "frame_size": list(self.preset_cfg["frame_size"]), + "target_fps": self.preset_cfg["target_fps"], + "video_quality": self.preset_cfg["video_quality"], + "movielite_version": getattr(ml, "__version__", "unknown"), + } + + def _affinity_for_workers(self, workers: int) -> Tuple[int, ...]: + # Process count is the resource control. The kernel can then place work + # naturally instead of permanently reserving arbitrary CPU IDs. + return () + + def _load_calibrated_workers(self) -> Optional[int]: + if self.recalibrate_presets or not os.path.exists(self.calibration_path): + return None + try: + with open(self.calibration_path, encoding="utf-8") as fh: + cached = json.load(fh) + profile = cached.get("profiles", {}).get(self.preset) + if ( + cached.get("version") != CALIBRATION_VERSION + or cached.get("signature") != self._calibration_signature() + or not profile + ): + return None + workers = int(profile["workers"]) + return workers if workers in _candidate_worker_counts(self.hardware) else None + except (OSError, ValueError, TypeError, KeyError, json.JSONDecodeError): + return None + + def _save_calibrated_workers( + self, workers: int, timings: Dict[int, float] + ) -> None: + payload: Dict[str, Any] = { + "version": CALIBRATION_VERSION, + "signature": self._calibration_signature(), + "profiles": {}, + } + try: + if os.path.exists(self.calibration_path): + with open(self.calibration_path, encoding="utf-8") as fh: + existing = json.load(fh) + if existing.get("signature") == payload["signature"]: + payload["profiles"] = existing.get("profiles", {}) + except (OSError, ValueError, TypeError, json.JSONDecodeError): + pass + + payload["profiles"][self.preset] = { + "workers": workers, + "timings_seconds": {str(key): round(value, 3) for key, value in timings.items()}, + } + temporary_path = f"{self.calibration_path}.tmp" + try: + with open(temporary_path, "w", encoding="utf-8") as fh: + json.dump(payload, fh, indent=2) + os.replace(temporary_path, self.calibration_path) + except OSError: + try: + os.remove(temporary_path) + except OSError: + pass + + def _benchmark_render_workers(self, video_path: str, workers: int) -> float: + """Render a short clip using a candidate worker count and return wall time.""" + frame_width, frame_height = self.preset_cfg["frame_size"] + output_path = os.path.join( + self.temp_dir, f"calibration_{self.preset}_{workers}_{os.getpid()}.mp4" + ) + clip = None + started = time.perf_counter() + try: + with self._limit_render_resources( + self.preset_cfg["ffmpeg_threads"], + self._affinity_for_workers(workers), + self.preset_cfg["niceness"], + ): + clip = ml.VideoClip(video_path) + sample_duration = min(CALIBRATION_SAMPLE_SECONDS, clip.duration) + if sample_duration <= 0: + raise RuntimeError("Video has no usable duration for calibration") + self._resize_to_portrait(clip, (frame_width, frame_height)) + clip.set_duration(sample_duration) + writer = ml.VideoWriter( + output_path, + fps=self.preset_cfg["target_fps"], + size=(frame_width, frame_height), + duration=sample_duration, + ) + writer.add_clip(clip) + writer.write( + processes=workers, + video_quality=( + ml.VideoQuality.HIGH + if self.preset_cfg["video_quality"] == "high" + else ml.VideoQuality.MIDDLE + ), + ) + return time.perf_counter() - started + finally: + if clip is not None and hasattr(clip, "close"): + clip.close() + try: + os.remove(output_path) + except OSError: + pass + + def _configure_calibrated_workers(self, video_path: str) -> None: + """Select a measured Fast worker count once per hardware/output profile.""" + if self.preset != "fast": + return + + cached_workers = self._load_calibrated_workers() + if cached_workers is not None: + self.preset_cfg["writer_processes"] = cached_workers + self.preset_cfg["cpu_affinity"] = self._affinity_for_workers(cached_workers) + return + + candidates = _candidate_worker_counts(self.hardware) + if len(candidates) == 1: + self.preset_cfg["writer_processes"] = candidates[0] + self.preset_cfg["cpu_affinity"] = () + print(" Fast: using one render worker while memory headroom is limited.") + return + + print("Calibrating Fast for this machine (one-time)...") + timings: Dict[int, float] = {} + for workers in candidates: + try: + timings[workers] = self._benchmark_render_workers(video_path, workers) + except Exception: + continue + + if not timings: + print(" Calibration skipped; using the safe Fast fallback.") + return + + selected_workers = min(timings) + selected_time = timings[selected_workers] + for workers in sorted(timings): + if workers == selected_workers: + continue + candidate_time = timings[workers] + if candidate_time <= selected_time * (1 - CALIBRATION_MIN_SPEEDUP): + selected_workers = workers + selected_time = candidate_time + + self.preset_cfg["writer_processes"] = selected_workers + self.preset_cfg["cpu_affinity"] = self._affinity_for_workers(selected_workers) + self._save_calibrated_workers(selected_workers, timings) + print(f" Fast calibrated: {selected_workers} render worker(s).") + + @contextlib.contextmanager + def _limit_render_resources( + self, + ffmpeg_threads: int, + cpu_affinity: Tuple[int, ...], + niceness: int, + ): + """Apply the preset's CPU budget to MovieLite and its child processes. + + MovieLite forks render workers, so the affinity, niceness, and Popen patch + are inherited by frame rendering, libx264 encoding, audio work, and merging. + """ + import subprocess + + original_popen = subprocess.Popen + + _patch_count = 0 # track how many calls we intercepted + + def _patched_popen(args, *posargs, **kwargs): + nonlocal _patch_count + if isinstance(args, (list, tuple)) and args: + # Match "ffmpeg" by basename (handles /usr/bin/ffmpeg too) + prog = os.path.basename(str(args[0])) + if prog == "ffmpeg" and "-threads" not in args: + args = list(args) + # Find last occurrence of "-i" to place -threads as an output option (after input) + last_i = -1 + for idx, arg in enumerate(args): + if str(arg) == "-i": + last_i = idx + insert_idx = ( + (last_i + 2) + if (last_i != -1 and last_i + 1 < len(args)) + else (len(args) - 1) + ) + args.insert(insert_idx, "-threads") + args.insert(insert_idx + 1, str(ffmpeg_threads)) + _patch_count += 1 + return original_popen(args, *posargs, **kwargs) + + # MovieLite's compositor calls OpenCV for every frame. Keep it single + # threaded so one render worker cannot exceed its preset CPU budget. + try: + import cv2 + + saved_cv2_threads = cv2.getNumThreads() + cv2.setNumThreads(1) + except Exception: + saved_cv2_threads = None + + saved_affinity = None + if cpu_affinity and hasattr(os, "sched_setaffinity"): + try: + saved_affinity = os.sched_getaffinity(0) + os.sched_setaffinity(0, set(cpu_affinity)) + except (OSError, PermissionError): + saved_affinity = None + + subprocess.Popen = _patched_popen + if niceness > 0 and not self._niceness_applied: + # This is intentionally applied only to Normal. Linux niceness cannot + # be raised again without elevated privileges, but this worker exits + # after generation and all MovieLite children inherit the lower priority. + try: + os.nice(niceness) + self._niceness_applied = True + except (OSError, PermissionError): + pass + + try: + yield + finally: + subprocess.Popen = original_popen + + if saved_cv2_threads is not None: + try: + import cv2 + + cv2.setNumThreads(saved_cv2_threads) + except Exception: + pass + + if saved_affinity is not None and hasattr(os, "sched_setaffinity"): + try: + os.sched_setaffinity(0, saved_affinity) + except (OSError, PermissionError): + pass + + if _patch_count > 0: + print( + f" (patched {_patch_count} ffmpeg calls → threads={ffmpeg_threads})" + ) + else: + print(f" (WARNING: ffmpeg patch did not fire — threads not limited)") + def composite_reel( self, video_paths: List[str], @@ -646,155 +1173,170 @@ def composite_reel( output_path: str, subtitle_list: List[str], ambient_path: Optional[str] = None, + tts_duration: float = 0.0, ) -> str: """ - Composite reel using MovieLite (4x faster than MoviePy). + Composite reel using MovieLite. Concatenates video clips, mixes audio, and overlays subtitles. + Wrapped entirely in _limit_render_resources so every MovieLite stage + respects the selected CPU budget. """ - # Get TTS audio duration to determine video length - tts_audio = ml.AudioClip(tts_path) - duration = tts_audio.duration - - # --- Phase 1: Process video clips with smooth playback optimization --- - time_per_clip = duration / len(video_paths) - processed_clips = [] - current_time = 0.0 # Track cumulative time to avoid gaps - - TARGET_FPS = 30 # Standardize all clips to 30fps for smooth playback - - for i, path in enumerate(video_paths): - clip = ml.VideoClip(path) - - # CRITICAL FIX 1: Resize BEFORE setting duration/fps to avoid frame inconsistencies - clip = self._resize_to_portrait(clip) - - # CRITICAL FIX 2: Get the actual source FPS and standardize to TARGET_FPS - # This prevents jitter from FPS mismatches between clips - source_fps = getattr(clip, 'fps', 30) - - # CRITICAL FIX 3: If clip is too short, use looping instead of freezing last frame - # This creates smoother transitions - if clip.duration < time_per_clip: - clip.loop(True) # Enable looping for short clips - - # CRITICAL FIX 4: Set exact duration to prevent gaps/overlaps - clip.set_duration(time_per_clip) - clip.set_start(current_time) - - # Move to next clip's start time - current_time += time_per_clip - - processed_clips.append(clip) - - print(f"{self.GREEN}✓{self.RESET} Processing video clips") - - # --- Phase 2: Setup audio --- - print(f"{self.GREEN}✓{self.RESET} Synchronizing audio") - - # Add TTS audio - tts_audio.set_start(0) - - # Add ambient audio if provided - audio_clips = [tts_audio] - if ambient_path and os.path.exists(ambient_path): - ambient_audio = ml.AudioClip(ambient_path, start=0, volume=0.12) - ambient_audio.set_duration(duration) - ambient_audio.loop(True) # Loop if shorter than TTS - audio_clips.append(ambient_audio) - - # --- Phase 3: Generate and parse subtitles --- - srt_content = self.build_srt(subtitle_list, duration) - srt_path = tts_path.replace(".mp3", ".srt") - with open(srt_path, "w", encoding="utf-8") as fh: - fh.write(srt_content) - - # Parse SRT and create TextClip for each subtitle - subtitle_clips = [] - canvas = self._create_subtitle_canvas() - - for start_time, end_time, text in self.parse_srt(srt_path): - adjusted_start = max(0, start_time - 0.2) - adjusted_end = max(adjusted_start + 0.1, end_time - 0.2) - - text_clip = ml.TextClip( - text, - start=adjusted_start, - duration=adjusted_end - adjusted_start, - canvas=canvas + ffmpeg_threads = self.preset_cfg["ffmpeg_threads"] + cpu_affinity = self.preset_cfg["cpu_affinity"] + niceness = self.preset_cfg["niceness"] + + with self._limit_render_resources(ffmpeg_threads, cpu_affinity, niceness): + # Use pre-computed duration to avoid re-loading the TTS audio + if tts_duration <= 0: + tts_audio_tmp = ml.AudioClip(tts_path) + tts_duration = tts_audio_tmp.duration + tts_audio_tmp.close() + duration = tts_duration + + target_fps = self.preset_cfg["target_fps"] + writer_procs = self.preset_cfg["writer_processes"] + frame_width, frame_height = self.preset_cfg["frame_size"] + quality_str = self.preset_cfg["video_quality"] + video_quality = ( + ml.VideoQuality.HIGH + if quality_str == "high" + else ( + ml.VideoQuality.LOW + if quality_str == "low" + else ml.VideoQuality.MIDDLE + ) ) - text_width = text_clip.size[0] - text_clip.set_position(((1080 - text_width) // 2, 1650)) - subtitle_clips.append(text_clip) - - print(f"{self.GREEN}✓{self.RESET} Rendering {len(subtitle_clips)} subtitle segments\n") - - # --- Phase 5: Export --- - self._print_step(5, 5, "Exporting final video") - - with self._spinner("Exporting..."): - # Create writer with optimized settings for smooth playback - writer = ml.VideoWriter( - output_path, - fps=30, - size=(1080, 1920), - duration=duration + # --- Phase 1: Process video clips --- + time_per_clip = duration / len(video_paths) + processed_clips = [] + current_time = 0.0 + + for i, path in enumerate(video_paths): + clip = ml.VideoClip(path) + clip = self._resize_to_portrait(clip, (frame_width, frame_height)) + + if clip.duration < time_per_clip: + clip.loop(True) + + clip.set_duration(time_per_clip) + clip.set_start(current_time) + current_time += time_per_clip + processed_clips.append(clip) + + print(f"{self.GREEN}✓{self.RESET} Processing video clips") + + # --- Phase 2: Setup audio --- + print(f"{self.GREEN}✓{self.RESET} Synchronizing audio") + + tts_audio = ml.AudioClip(tts_path) + tts_audio.set_start(0) + + audio_clips = [tts_audio] + if ambient_path and os.path.exists(ambient_path): + ambient_audio = ml.AudioClip(ambient_path, start=0, volume=0.12) + ambient_audio.set_duration(duration) + ambient_audio.loop(True) + audio_clips.append(ambient_audio) + + # --- Phase 3: Generate subtitles in memory (no disk write) --- + srt_content = self.build_srt(subtitle_list, duration) + subtitle_entries = self._parse_srt_content(srt_content) + + subtitle_clips = [] + canvas = self._create_subtitle_canvas(frame_width) + + for start_time, end_time, text in subtitle_entries: + adjusted_start = max(0, start_time - 0.2) + adjusted_end = max(adjusted_start + 0.1, end_time - 0.2) + + text_clip = ml.TextClip( + text, + start=adjusted_start, + duration=adjusted_end - adjusted_start, + canvas=canvas, + ) + + text_width = text_clip.size[0] + text_clip.set_position( + ((frame_width - text_width) // 2, int(frame_height * 0.86)) + ) + subtitle_clips.append(text_clip) + + print( + f"{self.GREEN}✓{self.RESET} Rendering {len(subtitle_clips)} subtitle segments\n" ) - - for clip in processed_clips: - writer.add_clip(clip) - for audio_clip in audio_clips: - writer.add_clip(audio_clip) - for sub_clip in subtitle_clips: - writer.add_clip(sub_clip) - - writer.write(processes=4, video_quality=ml.VideoQuality.HIGH) - - print(f"{self.GREEN}✓{self.RESET} Reel generated successfully") - - # Cleanup - MovieLite clips have close() method, but it's optional - # They auto-cleanup when garbage collected - try: - for clip in processed_clips: - if hasattr(clip, 'close'): - clip.close() - except Exception: - pass # Ignore cleanup errors - - return output_path + + # --- Phase 4: Export --- + self._print_step(5, 5, "Exporting final video") + + with self._spinner("Exporting..."): + writer = ml.VideoWriter( + output_path, + fps=target_fps, + size=(frame_width, frame_height), + duration=duration, + ) + + for clip in processed_clips: + writer.add_clip(clip) + for audio_clip in audio_clips: + writer.add_clip(audio_clip) + for sub_clip in subtitle_clips: + writer.add_clip(sub_clip) + + writer.write(processes=writer_procs, video_quality=video_quality) + + print(f"{self.GREEN}✓{self.RESET} Reel generated successfully") + + # Cleanup all held resources + all_clips = processed_clips + audio_clips + subtitle_clips + for clip in all_clips: + try: + if hasattr(clip, "close"): + clip.close() + except Exception: + pass + + return output_path # ----------------------------------------------------------------------- # Main pipeline # ----------------------------------------------------------------------- # ANSI color codes - CYAN = '\033[96m' - GREEN = '\033[92m' - RED = '\033[91m' - RESET = '\033[0m' + CYAN = "\033[96m" + GREEN = "\033[92m" + RED = "\033[91m" + RESET = "\033[0m" # Braille spinner for multi-stage loading - BRAILLE_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] + BRAILLE_CHARS = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] def _print_step(self, step: int, total: int, desc: str): """Print step header in DNF style.""" print(f"[{step}/{total}] {desc}") - def _progress_bar_dnf(self, current: int, total: int, desc: str, color: str = '') -> str: + def _progress_bar_dnf( + self, current: int, total: int, desc: str, color: str = "" + ) -> str: """Generate DNF-style progress bar with proper vertical alignment.""" width = 20 percentage = (current / total * 100) if total > 0 else 0 filled = int(width * current / total) if total > 0 else 0 - bar = '━' * filled + ' ' * (width - filled) + bar = "━" * filled + " " * (width - filled) return f"{desc:<25} {color}{percentage:3.0f}% |{bar}| {current}/{total}{self.RESET}" - - def generate_reel(self, job_id: str, emotion: str, context: Dict[str, Any], header: bool = True) -> Dict[str, Any]: + + def generate_reel( + self, job_id: str, emotion: str, context: Dict[str, Any], header: bool = True + ) -> Dict[str, Any]: result: Dict[str, Any] = { "success": False, "job_id": job_id, "reel_path": None, "script": None, "keywords": None, + "preset": self.preset, "error": None, } @@ -802,31 +1344,34 @@ def generate_reel(self, job_id: str, emotion: str, context: Dict[str, Any], head # Phase 1: Script generation self._print_step(1, 5, "Generating personalized script") print(f"Provider : Gemini ({self.script_model})") - + with self._spinner("Generating script..."): - script_data = self.generate_script(emotion, context) - script = script_data["script"] + script_data = self.generate_script(emotion, context) + script = script_data["script"] subtitle_list = script_data["subtitles"] result["script"] = script - + desc = f"{'Script generated':<26}" - print(f"{self.GREEN}✓{self.RESET} {desc}{self.GREEN}100% |{'━' * 20}| 1/1{self.RESET}\n") + print( + f"{self.GREEN}✓{self.RESET} {desc}{self.GREEN}100% |{'━' * 20}| 1/1{self.RESET}\n" + ) # Phase 2: Video search self._print_step(2, 5, "Finding supporting visuals") print(f"Provider : Pexels") - + with self._spinner("Searching videos..."): keywords = self.extract_video_keywords(script, emotion) result["keywords"] = keywords if not keywords: raise RuntimeError("Could not extract video keywords from script") - + def _truncate(kw, maxlen=20): - return kw if len(kw) <= maxlen else kw[:maxlen-1] + "…" + return kw if len(kw) <= maxlen else kw[: maxlen - 1] + "…" + keywords_display = " • ".join(_truncate(kw) for kw in keywords[:4]) print(f"Keywords : {keywords_display}") - + video_paths = self.download_videos_for_script(keywords, job_id) if not video_paths: raise RuntimeError("No videos could be downloaded") @@ -835,36 +1380,46 @@ def _truncate(kw, maxlen=20): # Phase 3: TTS generation self._print_step(3, 5, "Generating narration") print(f"Provider : MiMo (Dean)") - + with self._spinner("Generating narration..."): tts_path = os.path.join(self.output_dir, "audio", f"{job_id}.mp3") self.generate_tts(script, tts_path) - - # Calculate audio duration for display + + # Compute TTS duration once (avoids reloading in composite_reel) + tts_duration = 0.0 try: audio = ml.AudioClip(tts_path) - duration = int(audio.duration) + tts_duration = audio.duration + duration_display = int(tts_duration) audio.close() desc = f"{'Narration generated':<26}" - print(f"{self.GREEN}✓{self.RESET} {desc}{self.GREEN}100% |{'━' * 20}| 1/1{self.RESET} ({duration}s)\n") + print( + f"{self.GREEN}✓{self.RESET} {desc}{self.GREEN}100% |{'━' * 20}| 1/1{self.RESET} ({duration_display}s)\n" + ) except: desc = f"{'Narration generated':<26}" - print(f"{self.GREEN}✓{self.RESET} {desc}{self.GREEN}100% |{'━' * 20}| 1/1{self.RESET}\n") + print( + f"{self.GREEN}✓{self.RESET} {desc}{self.GREEN}100% |{'━' * 20}| 1/1{self.RESET}\n" + ) # Phase 4: Timeline preparation self._print_step(4, 5, "Preparing final composition") - reel_path = os.path.join(self.output_dir, "reels", f"{job_id}.mp4") - ambient_path = self.ambient_music.get(emotion, self.ambient_music.get("neutral")) + self._configure_calibrated_workers(video_paths[0]) + reel_path = os.path.join(self.output_dir, "reels", f"{job_id}.mp4") + ambient_path = self.ambient_music.get( + emotion, self.ambient_music.get("neutral") + ) self.composite_reel( video_paths=video_paths, tts_path=tts_path, output_path=reel_path, subtitle_list=subtitle_list, ambient_path=ambient_path, + tts_duration=tts_duration, ) result["reel_path"] = reel_path - result["success"] = True + result["success"] = True # Clean up temp video clips for p in video_paths: @@ -876,6 +1431,7 @@ def _truncate(kw, maxlen=20): except Exception as exc: result["error"] = str(exc) import traceback + traceback.print_exc() return result @@ -885,23 +1441,41 @@ def _truncate(kw, maxlen=20): # CLI entry point # --------------------------------------------------------------------------- + def main() -> int: import argparse - print("MindStream — Phase 3: Reel Generation\n") - print("Checking environment...") - parser = argparse.ArgumentParser(description="MindStream Reel Generator") - parser.add_argument("--job-id", required=False, help="Job ID") - parser.add_argument("--emotion", required=False, help="Emotion label") - parser.add_argument("--context", required=False, help="Context JSON string") + parser.add_argument("--job-id", required=False, help="Job ID") + parser.add_argument("--emotion", required=False, help="Emotion label") + parser.add_argument("--context", required=False, help="Context JSON string") + parser.add_argument( + "--preset", + required=False, + default="normal", + choices=list(PRESETS.keys()), + help="Speed/resource preset (default: normal)", + ) + parser.add_argument( + "--recalibrate-presets", + action="store_true", + help="Ignore cached Fast calibration and measure worker scaling again", + ) args = parser.parse_args() + scoped_exit_code = _run_in_cpu_limited_scope(args.preset) + if scoped_exit_code is not None: + return scoped_exit_code + + print("MindStream — Reel Generation\n") + try: - gen = ReelGenerator() - print(f"{gen.GREEN}✓{gen.RESET} Environment ready\n") + gen = ReelGenerator( + preset=args.preset, recalibrate_presets=args.recalibrate_presets + ) + print(f"Preset: {args.preset.title()}\n") except ValueError as e: - print(f"{gen.RED}✗{gen.RESET} Environment check failed\n") + print("\033[91m✗\033[0m Environment check failed\n") print(f"Error: {e}", file=sys.stderr) return 1 @@ -913,8 +1487,12 @@ def main() -> int: except json.JSONDecodeError as e: print(f"Invalid --context JSON: {e}", file=sys.stderr) return 1 - print(f"Job : {args.job_id} | {args.emotion.title()} | {ctx.get('user_name', 'User')}\n") - result = gen.generate_reel(job_id=args.job_id, emotion=args.emotion, context=ctx, header=False) + print( + f"Job : {args.job_id} | {args.emotion.title()} | {ctx.get('user_name', 'User')}\n" + ) + result = gen.generate_reel( + job_id=args.job_id, emotion=args.emotion, context=ctx, header=False + ) else: sample = "data/sample_emotion_result.json" if not os.path.exists(sample): @@ -923,12 +1501,14 @@ def main() -> int: print(f"Input : {sample}") with open(sample) as fh: data = json.load(fh) - print(f"Job : {data['job_id']} | {data['emotion']['label'].title()} | {data['context'].get('user_name', 'User')}\n") + print( + f"Job : {data['job_id']} | {data['emotion']['label'].title()} | {data['context'].get('user_name', 'User')}\n" + ) result = gen.generate_reel( job_id=data["job_id"], emotion=data["emotion"]["label"], context=data["context"], - header=False + header=False, ) elapsed = time.time() - start_time @@ -944,5 +1524,6 @@ def main() -> int: print(f"Error: {result['error']}", file=sys.stderr) return 1 + if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/backend/server.js b/backend/server.js index 0560889..5263930 100644 --- a/backend/server.js +++ b/backend/server.js @@ -13,6 +13,11 @@ app.use(express.json()); // In-memory job state tracker const jobs = {}; +const REEL_PRESET = process.env.MINDSTREAM_REEL_PRESET || 'normal'; +if (!['normal', 'fast'].includes(REEL_PRESET)) { + throw new Error("MINDSTREAM_REEL_PRESET must be 'normal' or 'fast'"); +} + // Cache for results written before check-in call finishes (to avoid race conditions) const pendingResults = {}; @@ -29,7 +34,7 @@ try { // Helper to run reel generator Python script function triggerReelGeneration(jobId, emotion, context) { - console.log(`[server] Spawning reel worker for Job ${jobId} (Emotion: ${emotion})...`); + console.log(`[server] Spawning ${REEL_PRESET} reel worker for Job ${jobId} (Emotion: ${emotion})...`); jobs[jobId].status = 'processing_reel'; const contextStr = JSON.stringify(context); @@ -40,7 +45,8 @@ function triggerReelGeneration(jobId, emotion, context) { scriptPath, '--job-id', jobId, '--emotion', emotion, - '--context', contextStr + '--context', contextStr, + '--preset', REEL_PRESET ], { cwd: __dirname }); diff --git a/backend/test_presets.py b/backend/test_presets.py new file mode 100644 index 0000000..ec9ed56 --- /dev/null +++ b/backend/test_presets.py @@ -0,0 +1,122 @@ +"""Regression tests for MindStream's hardware-aware render presets.""" + +import unittest +from unittest.mock import patch + +from reel_generator import ( + CALIBRATION_WORKER_MEMORY_BYTES, + HardwareProfile, + PRESETS, + _candidate_worker_counts, + _resolve_preset, +) + + +class PresetResolutionTests(unittest.TestCase): + def test_only_normal_and_fast_are_available(self): + self.assertEqual(set(PRESETS), {"normal", "fast"}) + + def test_normal_is_scheduler_managed_background_work(self): + hardware = HardwareProfile( + logical_cpus=(0, 1, 2, 3, 4, 5, 6, 7), + physical_core_groups=((0, 4), (1, 5), (2, 6), (3, 7)), + ) + + normal = _resolve_preset("normal", hardware) + + self.assertEqual(normal["writer_processes"], 1) + self.assertEqual(normal["ffmpeg_threads"], 1) + self.assertEqual(normal["cpu_affinity"], ()) + self.assertGreater(normal["niceness"], 0) + self.assertEqual(normal["cpu_quota_percent"], 60) + self.assertEqual(normal["frame_size"], (720, 1280)) + self.assertEqual(normal["target_fps"], 24) + + def test_fast_starts_with_two_workers_when_hardware_has_headroom(self): + hardware = HardwareProfile( + logical_cpus=tuple(range(16)), + physical_core_groups=tuple((i, i + 8) for i in range(8)), + ) + + with patch( + "reel_generator._available_memory_bytes", + return_value=3 * CALIBRATION_WORKER_MEMORY_BYTES, + ): + fast = _resolve_preset("fast", hardware) + + self.assertEqual(fast["writer_processes"], 2) + self.assertEqual(fast["ffmpeg_threads"], 2) + self.assertEqual(fast["cpu_affinity"], ()) + self.assertEqual(fast["niceness"], 0) + self.assertIsNone(fast["cpu_quota_percent"]) + self.assertEqual(fast["frame_size"], (720, 1280)) + + def test_fast_degrades_safely_on_one_core(self): + hardware = HardwareProfile(logical_cpus=(0,), physical_core_groups=((0,),)) + + with patch( + "reel_generator._available_memory_bytes", + return_value=3 * CALIBRATION_WORKER_MEMORY_BYTES, + ): + fast = _resolve_preset("fast", hardware) + + self.assertEqual(fast["writer_processes"], 1) + self.assertEqual(fast["ffmpeg_threads"], 1) + self.assertEqual(fast["cpu_affinity"], ()) + + def test_fast_leaves_a_core_available_on_two_core_hardware(self): + hardware = HardwareProfile( + logical_cpus=(0, 1, 2, 3), physical_core_groups=((0, 2), (1, 3)) + ) + + with patch( + "reel_generator._available_memory_bytes", + return_value=3 * CALIBRATION_WORKER_MEMORY_BYTES, + ): + fast = _resolve_preset("fast", hardware) + + self.assertEqual(fast["writer_processes"], 1) + self.assertEqual(fast["cpu_affinity"], ()) + + def test_fast_reduces_its_first_run_fallback_when_memory_is_tight(self): + hardware = HardwareProfile( + logical_cpus=tuple(range(8)), + physical_core_groups=((0, 4), (1, 5), (2, 6), (3, 7)), + ) + + with patch( + "reel_generator._available_memory_bytes", + return_value=CALIBRATION_WORKER_MEMORY_BYTES, + ): + fast = _resolve_preset("fast", hardware) + + self.assertEqual(fast["writer_processes"], 1) + self.assertEqual(fast["cpu_affinity"], ()) + + def test_calibration_candidates_reserve_a_core_and_respect_memory(self): + hardware = HardwareProfile( + logical_cpus=tuple(range(8)), + physical_core_groups=((0, 4), (1, 5), (2, 6), (3, 7)), + ) + + with patch( + "reel_generator._available_memory_bytes", + return_value=3 * CALIBRATION_WORKER_MEMORY_BYTES, + ): + self.assertEqual(_candidate_worker_counts(hardware), (1, 2, 3)) + + with patch( + "reel_generator._available_memory_bytes", + return_value=CALIBRATION_WORKER_MEMORY_BYTES, + ): + self.assertEqual(_candidate_worker_counts(hardware), (1,)) + + def test_presets_use_the_same_output_profile(self): + self.assertEqual(PRESETS["fast"]["frame_size"], PRESETS["normal"]["frame_size"]) + self.assertEqual(PRESETS["fast"]["target_fps"], PRESETS["normal"]["target_fps"]) + self.assertEqual(PRESETS["normal"]["video_quality"], "middle") + self.assertEqual(PRESETS["fast"]["video_quality"], "low") + + +if __name__ == "__main__": + unittest.main() From e89797f3f3333282630301ea3e2ea6a6bcc6064c Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 18:28:14 +0545 Subject: [PATCH 04/18] feat: implement dynamic cpu quota scaling --- backend/reel_generator.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/reel_generator.py b/backend/reel_generator.py index 661521c..9ec3eb1 100644 --- a/backend/reel_generator.py +++ b/backend/reel_generator.py @@ -119,7 +119,6 @@ def _detect_hardware_profile() -> HardwareProfile: "frame_size": (720, 1280), "target_fps": 24, "video_quality": "middle", - "cpu_quota_percent": 60, "download_workers": 1, "download_chunk_size": 131072, }, @@ -171,6 +170,14 @@ def _candidate_worker_counts(hardware: HardwareProfile) -> Tuple[int, ...]: return tuple(range(1, max_workers + 1)) +def _normal_cpu_quota_percent(hardware: HardwareProfile) -> int: + """Choose a responsive aggregate CPU budget from available core topology.""" + # A compositor and encoder can use CPU concurrently. On a four-core machine, + # 150% lets that pipeline make progress while still leaving most capacity to + # the desktop; smaller machines scale down instead of using the same quota. + return min(150, max(100, 50 + 25 * hardware.physical_core_count)) + + def _resolve_preset(preset: str, hardware: HardwareProfile) -> Dict[str, Any]: """Resolve a preset into limits appropriate for this machine. @@ -191,6 +198,7 @@ def _resolve_preset(preset: str, hardware: HardwareProfile) -> Dict[str, Any]: ffmpeg_threads=1, cpu_affinity=(), niceness=10, + cpu_quota_percent=_normal_cpu_quota_percent(hardware), ) return config @@ -215,7 +223,11 @@ def _run_in_cpu_limited_scope(preset: str) -> Optional[int]: idle machine from running one core at 100%. systemd's CPUQuota covers the Python renderer and every MovieLite/FFmpeg child process on Linux. """ - quota_percent = PRESETS[preset].get("cpu_quota_percent") + quota_percent = ( + _normal_cpu_quota_percent(_detect_hardware_profile()) + if preset == "normal" + else PRESETS[preset].get("cpu_quota_percent") + ) if ( not quota_percent or sys.platform != "linux" From 25d3a45e68533684e4478820f7a447e57ea75da1 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 18:29:58 +0545 Subject: [PATCH 05/18] feat: update normal preset CPU quota and scaling --- backend/benchmark_render_presets.py | 2 +- backend/test_presets.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/benchmark_render_presets.py b/backend/benchmark_render_presets.py index b25533c..6c793b0 100644 --- a/backend/benchmark_render_presets.py +++ b/backend/benchmark_render_presets.py @@ -54,7 +54,7 @@ def _run_child(args: argparse.Namespace) -> int: os.chdir(Path(__file__).parent) from reel_generator import ReelGenerator - generator = ReelGenerator(preset="normal") + generator = ReelGenerator(preset=" ") generator._spinner = lambda _: contextlib.nullcontext() # Benchmark Linux scheduling without affinity: let the kernel place workers. generator.preset_cfg["writer_processes"] = args.workers diff --git a/backend/test_presets.py b/backend/test_presets.py index ec9ed56..d4f6471 100644 --- a/backend/test_presets.py +++ b/backend/test_presets.py @@ -8,6 +8,7 @@ HardwareProfile, PRESETS, _candidate_worker_counts, + _normal_cpu_quota_percent, _resolve_preset, ) @@ -28,7 +29,7 @@ def test_normal_is_scheduler_managed_background_work(self): self.assertEqual(normal["ffmpeg_threads"], 1) self.assertEqual(normal["cpu_affinity"], ()) self.assertGreater(normal["niceness"], 0) - self.assertEqual(normal["cpu_quota_percent"], 60) + self.assertEqual(normal["cpu_quota_percent"], 150) self.assertEqual(normal["frame_size"], (720, 1280)) self.assertEqual(normal["target_fps"], 24) @@ -111,6 +112,15 @@ def test_calibration_candidates_reserve_a_core_and_respect_memory(self): ): self.assertEqual(_candidate_worker_counts(hardware), (1,)) + def test_normal_quota_scales_down_for_smaller_hardware(self): + one_core = HardwareProfile(logical_cpus=(0,), physical_core_groups=((0,),)) + two_cores = HardwareProfile( + logical_cpus=(0, 1, 2, 3), physical_core_groups=((0, 2), (1, 3)) + ) + + self.assertEqual(_normal_cpu_quota_percent(one_core), 100) + self.assertEqual(_normal_cpu_quota_percent(two_cores), 100) + def test_presets_use_the_same_output_profile(self): self.assertEqual(PRESETS["fast"]["frame_size"], PRESETS["normal"]["frame_size"]) self.assertEqual(PRESETS["fast"]["target_fps"], PRESETS["normal"]["target_fps"]) From 119b192a2af46477bc89a64910423d4d0914b819 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 18:31:06 +0545 Subject: [PATCH 06/18] docs: update linux cpu cgroup resource documentation & update icon sizes --- backend/README.md | 9 ++++++--- public/manifest.json | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/backend/README.md b/backend/README.md index 5dd2f2f..3c19cf2 100644 --- a/backend/README.md +++ b/backend/README.md @@ -56,10 +56,13 @@ python reel_generator.py `normal` is the default and is intended for background generation while the browser and desktop remain responsive. It renders at 720x1280/24fps with the -balanced encoder setting. On Linux, it also runs in a 60% CPU cgroup and at a -lower scheduling priority, so an idle laptop does not keep a core fully busy. +balanced encoder setting. On Linux, it also runs in a hardware-aware CPU cgroup +and at a lower scheduling priority. A four-physical-core machine receives a +150% aggregate budget (up to one and a half CPUs), which leaves substantial +desktop capacity without throttling the compositor and encoder into a long +serial export. -Normal intentionally uses one MovieLite worker. Its 60% CPU quota and lower +Normal intentionally uses one MovieLite worker. Its aggregate CPU quota and lower scheduling priority keep it background-friendly, while Linux is free to place that work on any available CPU rather than pinning it to CPU 0. On the reference laptop, two- and three-worker exports were slower under moderate diff --git a/public/manifest.json b/public/manifest.json index 7ef070a..9150ac9 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -7,9 +7,8 @@ "action": { "default_title": "Mind Stream", "default_icon": { - "16": "icons/icon-16.png", - "32": "icons/icon-32.png", "48": "icons/icon-48.png", + "96": "icons/icon-96.png", "128": "icons/icon-128.png" } }, @@ -17,7 +16,14 @@ "service_worker": "background.js", "type": "module" }, - "permissions": ["sidePanel", "storage", "alarms", "notifications", "tabs", "downloads"], + "permissions": [ + "sidePanel", + "storage", + "alarms", + "notifications", + "tabs", + "downloads" + ], "host_permissions": [ "http://localhost/*", "http://localhost:*/*", @@ -28,11 +34,8 @@ "default_path": "index.html" }, "icons": { - "16": "icons/icon-16.png", - "32": "icons/icon-32.png", "48": "icons/icon-48.png", - "128": "icons/icon-128.png", - "256": "icons/icon-256.png", - "512": "icons/icon-512.png" + "96": "icons/icon-96.png", + "128": "icons/icon-128.png" } } From fc11a5526be390eddeb07334581fe47086848978 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 18:31:33 +0545 Subject: [PATCH 07/18] chore: update application icons --- public/icons/icon-128.png | Bin 9138 -> 7162 bytes public/icons/icon-16.png | Bin 632 -> 0 bytes public/icons/icon-256.png | Bin 19737 -> 0 bytes public/icons/icon-32.png | Bin 1629 -> 0 bytes public/icons/icon-48.png | Bin 2673 -> 3089 bytes public/icons/icon-96.png | Bin 0 -> 5381 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 public/icons/icon-16.png delete mode 100644 public/icons/icon-256.png delete mode 100644 public/icons/icon-32.png create mode 100644 public/icons/icon-96.png diff --git a/public/icons/icon-128.png b/public/icons/icon-128.png index 12036dbfd9910f90423e90fe16caa4e881752212..2b255ec07617acc4dbaaafda3b6a318823785ff5 100644 GIT binary patch literal 7162 zcmbtZc{tQ>*Pp>KD0}u88T&T&b;iz+Bsdamb>_j=#&b=`B8`*S|`cFy@{ZWtNro~B}_0ssJ~^^jV|L>YEGz~sc= zb_A-6C@L|Qdd_-!!T>PQAO|o2NdZ8jc0AaDpc83M)H#0Zc0^t9H-?0$Gm`up?-omT zf_EnRk3R?EIOh46B(9o70U#rKU{aDW;GJJOkjMbY`|X#6!GL+c7>|ws0Fu04jA6tW z(tqeg6ELFgQa}`x-bgE70D$J@@c^cZ&~N|%AOkm3ORVJu10}SVhd9c~%Ml}v^YA{7 z0;s6syiw?@7%apQ5elORJ=bkJ3_qLY9s zf=UIaL?rOQU{Mg9hr6e*5)LkK!mC8okKGaiM39e@vy!ov&Tk0Of(y7}vEE7&5&;1L z;sMg)UOp}ol8TCo5-=$VDJd}`Ld-YN6N|!$dHV7a8K@v999kG(w2zxN*3HWka?FWx z^zy^P1q7%dzp2%+SRXgXKkS~q;;0jk-*JdI@z4?|CtnFkaoF*;AP|*bcqMfw-(&OF zA|{w?C&n+!-R)#0Zbh3;p8A0(KbOCNjy@<)r@xRUmYR?YC^xK|i!16k$MruuJlV_N+tbEj^_&pSGKw;2 zl$@g&T22-vCL=8^FQy&;G#-;eeu;@6A*0{$aw{`Z;W zg#MF7-hMvrCmHI5mcY1UJTRWv;~bLwokLD&C9E6P9rN!DQg_Gxok7IXP;y6ky1)f+ zVon%ml%G3R0Ac2N66OSP!uYzmctXS=lKx*yin%Lid(tZsr-H#CK)Ya#W{s~hX79Ap1AODxQj`~N@PF|l*}x#Yh# zgF|`$qezJ`rIX_F@-g-Daz|*QJpEA+V#UD!Q6-S$%7Fi)8XzYX0RJoNRV4nC>%Z>* z*O7<~i`cp({xmqE@ux*%Jc)hLhu8qkJUXfYfHNt2TI!~_tgp7zwNBdCns&_!B2Q(QalN3UKUME= zZXV4W4syhKsPVEQUr&E~`zapbiWTH<=I(tDDr8KV@61VsZGl0m&bcm^OAj&z*wc8s zOx!GcQ0s4C);eGJaw61Y#3g{Ew;uV$=qUf6U5j~koEu#>9c z$y`SoOXviTq-EH(qQ#=$rKg}s^Dmc9>ViV+DfJADX2Sbf`$P_mmdFd6P4`mf;r4^^ zsU_wwE-VSOHui*tVf0tclrF6v34(J|X~Hy&ZGE*buyXS8_6>-oGT&FTMWVbw=IWM@ zp)6N|q6SLzD;{$d4cA;!0`CZpFYrdBjwRPRV$#bORP(UuAt9tfSLXD2^U0CBrKWqH zgn;TrdCP6?OBoL%aF+<8_8)uvtzTf5L|GULjbiSa(R1aOZ!GLFp=XC1xUO#QS;&lc zz125q%Y#g{zup(xsy1)gtxrifg|B1M87qGG^gc{Gjs4TtObY6D!Q&+C=;f1S7Ym)-e(cHZq!$OVS z>va_U(TMrne7busB~;{ZNl*yFW@SjI@nERgo^s(%Z5}69f@!WawZurtdKLeq$jem8 zbcyWDy2hJZ$b2O^kC%XwLODLZ;s_@4_dIVG7iwLsO6{M#(S>tIRXVhiu zi#n$%k3_k(-*#TENWZi_4vJnVv90&FsF$}O$+Z4R1Ich6@n37e3t!Lt@=YjZMX+@L zy^lY87;A0nzz)*k!g6-s8aBT?G?Z8qku*4hNj7`SK4h-?#?OI4heCVU`h3e7Zc zD#kWAHHCD4Rh0*hKd8Jt&V5;IR_ELSr>AE9I!eEGGBvCyLUnGN|5nQVZRa;X*C#|8 zYjWI&t#&;h#gce~Y9UNb)9QtR{_xx}KN^MLhS6;*e{e3|pD^)!1nAi}wHM5du>r2O zgXs}2ccYm!RXL@9^smBo>Z)ItESMiYf5lbL*7MzR;A{z5DYsmdr;KiQ1#rgV%DRXh zGq~W!1wM}v@WAs`hskRmx+(da<*%z;)ovQv*4#Nqa{mg=_kI5O(n0d~mkrN_2PQ`2 z{Vvx}IG0g!b!U$g6U>x+l6S5LLV*W|Ct4|ftB_#3m9LwGSS{JS_GTK)WuktVR+K1= zl9;x+Fzz5K1gu(~Mb`Ijn)cpdW`z{Tmc2t$Np`W2^qE)Bc0Y}C7>~{fuuyn( zSwNGuxo$A=ez=|Gau>9ftSxS29A@&>x~WsHW@YD*{PVV_gID8b>of;3#{8FmhT#m_IEa7v4A|CYKEl4y>IjjLZb`Z z3X^)yRrlpB30MK49Q2<;VP^Ve*R*9#R&h1bO4D*Zk#O1Z3*|qC@LXkA8Wu1dUWLXT+Bs}05WzOvvt<$FxPu6By_@An>1c(J_ih&}SvQ#-uX zT&_}A)0cH=*MquHTZu&M!zOEf5A^Y?A^juuz5qi)TIul@<{wIF0LUs$&0q?l9Yl;I7pGYfJz@JhPmEX-qye9f==)u;dgUkkz= zO1D&Awphp({%}tVp2!=JgIR6b`|uUVv0-Z?oD7SdE5`TV=(gAd|GKWWosnw6ZvkmRtFtd%I z+8_XYy#=F46xPqGEtL~^`F?1opz1^46&9pucBJE=YxL8zc{D8xU#*4g0NXd6n&@%z z_U*%apDO|?v!T>+a5LBUDdObc36W&=H9J%p4@ZqQg7+T*a%q`9JBnY08imoDkO-Y> zDhPT>2D1T%umZHsuj&M|tho8#9zIgeq?U8Zn%LQ~8A1leLHPuQ2IBdYh5c_2(57%n0$b*O zqAh5Q%R%b4md~^5Mp)E}cORyRd&rGt8gVDJX>*nsCRFcj$gIiL%Z&|0Z|_szWAO-8 zOtZM#r$Q1XQv3=^o!8Tq`Y3}P2`RO(_OPMN7Mg;wZ)dp>;u>ymX~|{ij1GJ4v_!m` zr_ty{J>nK*_B8aX8(&`D8OWm92QBaG7OFg`Jqw)dq!5!BlWrF7&`GVCQ+Yf*B)oP8 zD#|iaFY@u@?YHN^Ft*f5etIy*jO?+cdx4XlXl|h{i0bo{qop3C zoZ?meBxb4Vwhn2hAudP4+AU~k7zsdJ)HGV#L%jf>wdn27?5S;gR}s58`i;O&ZEOJS zBg5=g%3DaOf~j-xiwS2u9$hg>C7HcR_cSWUyeUNIOCtCnJSqs22Ux31a|A?m+Gut^ z`B?lt#$m(ti0c*_*c0Fk(g%EgJzA^%W7n(rmF`E5^6n6rRB%WV|^64qXxQDV^{GmpB#5pOKm0(uGld_=I^>AZIdHeo|)?Zh}M z5U_~9{K)j~YUuhKfdwPQcXl&<(mc;6!y5zgOFuJwLhH=Wf1{huGZRVy_N^(9Ti^s; z$2f`$bZY7OT6d)PjR5}fs|*VHU-Gam9W+x}&CWd;*?RQkml4Jt^N+p1oo2t0-IA@q zJ*kV816g|)v~5HP?oY&Crfn|P0Q#I0;qDyaH4PDYK*maMx1>QYw9nOG@KO15ErrFd zbn*3JiiV`m`({sz!x>D!2d0TPJdPZQ%Uj<|cUs`~XSJj!uzL1w#v1W9b21)+PhZ6z zi9joCR-fl3G8A|7si*s;I#3i+zi~wug|X3A-_$I&g-6%M(IYnwE5=vN5!ETzsX4md z3anGeWL~{J`eOrnIWXnc4CI^=w?_onZOtvUqA-(yhuFy0K7Sn&HQ@qzWDF|~F^E=1 zzxbMTjSbbp?|-;$u4GW|@wvaTLA2PikffFY`7Y8sVQrXCT4rPKV}czQBU0apJ;rVK z=z=i`couf0-HWWDMW}FaS6=y9H@q}?^yM;q%Tv`@wU|Uf-z3sWAD_@~b@Oe|rNR<9 zemSJkFpkIUbV0e&xd5@op|=|-X7YZnECD9mEkYd2nh&ih56(1k{~Q7}EY}`>CTWy( zY9cJ{d7aZ>SJU=&>hnjPU%pfW?X0UPs(@j13;P?Q(F5o&E$J~{mxHJuB&TD%WL8LHRk^tri@YtGogPL?W0<-lLxynD&DF>W0;)B|MAEi#9CP#TF(b zgV0~T`%=U4gp@Nw(Y2+0?_Q=cu6!(LU48ZL0G}Km`~Z$OisB`4RIEv)qI{%1@M*l{ zS|EN~!C4=2-#W?hb;u1%7&tYN%CYxmqh=nqDKUq`B*ObcGMq5_^5-c$ng8jZbS|t1 zIRnbjc-M@xj|7TDbN8=bX)WF*pBbL~C_BkgoAssXb;&bnyBG3O&>BU4(gwHaRB}g= z`Oc`B<HQ}nD+Be*t=~Ssgu%ijpsX5EL=x>ehhYo+V_E_4w`oS)C_otLk zqCR?opIxx&>KY}$|)(Q-rZrNneFpL?9hW{qma_Om%3 z{;)E5oK@J)hrq!)h9nI^d28O0wR%F)+I~9K>pd~;)tHS`UU?^ICV%yuPput+%xHcr z2v*tgmM1;DCZ{QJaO=~8#GHJpBjrK~I&`SjSfTxss}1v%{6$&Dvtqbn-R$OvORM^* zmz~#v18jwv9L_gG@Al3NG(O!3Nd4am%f+pkW({jROu>`9KuI5tXI+<{ zA;1wWKjQ*GDmu44?=a-OtDE854qif|wceJjB*-BbfF`9oOQxzU=jw}^-~MP!AOObP zCUW03`hjR2YM9@B_#p{j8k!Md?=7_^9om(OO}EgbB#$7Y;XNE5@ZcLu?M}Q)@7qi- zB*ov5JcCq<)UF@0f6<*w0C**@G-wH`9R-ujNd)qI4ao9akp)H7hgmLh3NTeqJUMI2 zla6ZNpk$g7m}id0pJ7KNF&6g)MQKn?QsaUh#9|RQ!)w&|77sSNg*#rS zo24|{gIvi?sC(@lme|N|(?D_Z_rxpOhC#G5RIPnqy;koJ7-3Wr{a1Q@I+2pE&LL+y z{I3r%Rh}QWiJQD4{bVpQ$d_O^CG4>On#o8>mPV6PcSos9b&i6WaNl=_G2Z-Mn`d44m~@oVSKfIV{t(pN_!*x13&4?~ zZJ`$3sx!iys`q-YAB2nzk0Ge17wIp>%e%Ktt+(eH-w&mojkzdGxL=KU1vgyYZhPUk zY^VIekGBaWv>*1p>vUV6Pqyw05x^KAQI`Gmh)qL6B4v|)@pX>HAIiaB^K(K}9HfL5 zmpUEJ6nlF4L?*@9CLUxCxP@_EWEm)?%ybLbhrhUT4m2{5r3+r~o~$?G*}ZQhFq`>C zL<@eZ_eShf$!}TTIZZ3XQ=dmD|I4us-? z57SIApQ1^g&D?+T5CAE!o?LBLRkSNhmkh9M3!p1#dQ0_@eeKo+xduf+WRNLk;X%6Y z)J;5jVNj~>%UfoL?;UV{EyD080?PdK1OR?CL}fx5J4g5MVKA9Cg>|j>)JjG~cWK4h zp_v@LOJSTZZQKbq3k6|?oL5yVKZJ$H_U9)cd1c8|oW7LuFhLcEvqVeCO2FPh-ke89 zkm&eo^o9$VwK_=vx@$}tvGe^|o*nJyJgvFO3Xcb@)nyb6L6tz`r(`q_@+V_MwVIk< zmCzntm*Ze5%I+^neMk>D--@B}@74F2Yv0D-(clB!r-6h|Y2WVB zTl1r}k)q>^)GX5V8KvYXVyHG)3)=9EYCXZtc5h}^*D~^(kaPm z`jgS%X!!1_qSTPH-C5cG+0-4dxsjXTYSIJ*@2&k_hdn?-jnAMU%ofsdBgMK)QRpY7 z@_BkE_Cpg(c3=^2uvW1;mkTbrhZI2qX)2o;!M2@>WYU?v`d%EDkdO)IR*ueswL_v+ zffCvw{H&n13SUZTw&a87WC>X_+9|u2YY$d-WI#}8Hx8L*QOOVeKYZ`Cm6GZxXO4jU X^T1L%v4NK3f4B6s4Yf)%93uV)k?DAb literal 9138 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 S_*Lit0000>eu38;Wn0TBX-3P=$FK`DYrNdQFw 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 diff --git a/public/icons/icon-32.png b/public/icons/icon-32.png deleted file mode 100644 index 051d4c2061372cf4926b6a77d50cf2505a010fea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1629 zcmV-j2BP_iP)FhXcD z$;u*vB1|fqptNkTvNXiVLQB!=-gEZq_pP<}KKq_~rN#QMZ~A}V8qYpKrreP9T2~#= zsAdg+Q}4PBz5`MBUR$!#6%>^lGL}vy1xJpI8l$>?*Q=DS+vARATGRFnQ%rM6=>)9e z);w2gZxtO^mUZN8N0ZdFySmq|AQpg5PkEL`rDE!CN&9V{P$s)@G`Z0$Ij3~G>Tokj z?ja<_Jx)%tXX-AYj5b7U=;NpybS_2>r&g$`F)GA*V!zQklob;H?;xC#$$7N8UYZFp$m!q1<-E8J(iI5qUC&ZX>o3oCod$cQeO` zWM3ess<)9)yC6{e6mZeXl`H*(Q;2It5@bvZK>AT0k8879ty5GKJ6jeW=0sMpTAzF# zN!P$z4c35toWOg?%VOMfI5Kr68U#lJ>QOB(=638yr%#`LC)YeB5z~-^0h*hA41>7# zASWw?z3hJb@bK^u_Sam)FtRf;GV&AhP;iH2-%7AF>1=h|>8NwzpJ)u|tW(FE*%={( zA?A97#asyd#D%TEJvy~u;8^9yw2H*@!HR)_3Gp-8dF#wbuC!5gUxicLTe+T$;RN_%zWop@~e23|tVsWE^tb zA?X$1C-XS5UFZtIY;S0V$BItz1FJrUN$^ubhV&ijEMhaOp2EW*Cld59v9go23A_pX7)vjrNh8xN zbl_|Rdx?5~hG90(eW?xFHuTh?CYcu?`HFT@i$56o$7G<0F~=&VTmkfyne5S;Q6Qh7 z>L?5O3iz%&YP3Yf4v86g9PRdx+|)(*-UsVc=mkz=kfXr#?kRxoRyC39m8+6XpX*AK zXl!FM!6&d_h5Gu|$ZRS_E`xcc1s#h#Q-Y;&n!6nys-){Rnog?Lev}kqz#TAC=a|}H z50COs4nLoa{`r9RZXeVAXKW#W;ooP&%Si-PgqZ&*Crf9L6t2PPI{%0fy$V?KKQO&Hj(0K zXnN7fW$gCat`$CzjjfEWk%@Wi))`kzq+EetP)*sNVo*({{{R30|Nqj{J$C>A00v1! bK~w_(?8XaeXmfun00000NkvXXu0mjfv>X$~ diff --git a/public/icons/icon-48.png b/public/icons/icon-48.png index 862d1631f1292748162fe2eef0a160a320c90de4..6ff6d61def289c2bf3868057c9c5425601adc5df 100644 GIT binary patch literal 3089 zcmbtWcUV(d7Jms+r3iur7&agnMMFX$l#m1>H8iOb3z3)PL82iE$%_OQK^Yv&>aGpc z8NrSM1C{|rMLX#fC#5hKVi3YiJ2p{IlV#-Ts%M5c0x$w*`{905H<(E-d*S^x@>RKpUbt)AB) zvelH#LgZyr7&Ic!M}Nihcg|7c6A`=WlYk5r&mS|g>waBe4q_n?oe8J|8X1K!04JyH z&IAJLq=s>F5&+OAHH@mLwPwhO3Y^F=P#r^G9JE#n0LFJz1C_hN*a`r&L-{dGm>CvI z=7^G=K(2@lImwg7DldTIEf<5F%@B-XLrHug6+3*h28-cysn|GD7$HpT3-S0t=@KY9 zJv@e!zM136#d^~WC~`7FkPN{fMxHDXO3896R?SOBWR)3@ML-g6B00)0UD(FMY}l3bl} zpevh*^KgUQArdDMba!^1X3@CJrU_uHScoK|8_Cnz)!E&XKo56z2efHSh@zt1EtXF7|-GKoN)pIubGYBaSvH%n&QL%Cy7fJ+W0vJn+6{_917%n8`CkZh)404hG zcCaZf3Vy~m|HrY!Gipy&9qJaKstaTRIg`uOnGlUkm%%)dgccD+j}D@T$6)AwL5vW3 z6oaC|{;`S_raI>T>5h@kF@4Fif{}yb8AXb~$m-$}Nn%7I0nHZ_ZUHe!#ZYHd2}V^J z)EU))QC9$UR@PJSf9Cp+@oSERG%Tcb;inrMqD;3)NQm@B3DN+^2P(RezFNZYqsQ>J z4>Gq3I;D$-D{hs(Ff3^aWwZxeGK>y!TiSLoEGYEI?zLBJ@7^V}d9Sp0BCqYk*;{-L zshXhwemTu5BxU)N9j-a>Dt$&WtK>#ud;IXw+mX&;%Ak?Fu$ zIi=b~t;7~$^8RCiEN1JQSLK3u;<3)$;B#h9KScG2P)SUO0otKpGh&^sa``+( z>=J?b9zlq`*;9^SamQHgf&_eH7P_n9nT=9U+;?^bxH8LBsii&Np~Z-~#d6*gx18$7 z@+FKelTKHD@S<0(-}=;SYs^O9VPAGo)@AzSgtEJ6%IKmy&p(tcXf-l2Dn(5e>=E?X zK=YSZEq$3%yO1KUpf36%SKKS#eW)qvHVf6EtE1DWJ09V?^6;f>yIYE%`$BWjj$Hfv zV@2)1KN=bwwAdj8wk)o+?(YeFzrxes45>d~%(3E6 zGD)-PGIX0;xeZ;-d-7x#d++-a)6Zi^Cvny5Ju{8pcY4{N>VHbt?LD*YJNkT+_08k% z-TQo79=J+RbXzy=aNicrukCpo6Vg`Z{pQt{<}kLkV9DDooxbl$ z$LzA-wes$7a$%8+?OIk>F1+BAZoBe5@x_WkN@mFpQQz^M&sdE59p80VDl4DT48GCn zdM*F<@{Q8$nn#?hg!n^;p))~O16^|n?}Wk)jhWZ0?29;N?-i|{7mE-7P||Jm{U^!hi;7Rw&dq;2%fIh6A8}gtZp8|LUhI`k84lMj8v5BCcy!P- zbC~QM9)~hPd*vVLk<^lii3JO~^M5&>I^P|5D{d;)Iq`GK^|6YMFGC+NFw5B5bxoYf zuG3>~Rk7Q(w^6i=L+rZuSGeUgM6CMwG2_RkUv55X$o%^lbScrRcXQjjf&!bgx-Yy{ z=PWAQjV8C}#s*xJWhUoYxd}|H=(}K+M@{pa_)EtILs|169ov5Fd*iYf3F8HWRrbkR zPN|s_xsqR%S-yMyi+N4s3Z~7I<8P}Lz0B{waevrY4&Kb;oEjP$THJEagv5?eMp|6R zqnz}2D>~O`cBiayJ&#&9yeGq)U{1_9Dn7XL{&@v+d@^$ByHUHF4jZ*6jEeRb^3K}E z5}qA#D0h&Wk|MSz#h{M79xOZSoH-}G=ZbhJ3kVB!Dny3~UbwFwN=iO;SFT_?UOF@g zqWjZN%s^R{^KIWw!w>-0&k zZ_b$ek7(cBx)*Ztn)OQ~0z=0O+ug2L#rIr)cz3)##)omswAN70PCo^CrSqHf+UzWf gg8UsHMTSfQCqUiP`dYoFsz1F9|8T#uRSDVu0&--oCjbBd delta 2665 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 literal 0 HcmV?d00001 From cfdc61a88c63d4b4781b96d8689260617501c17d Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 20:32:03 +0545 Subject: [PATCH 08/18] fix: current provider naming --- backend/reel_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/reel_generator.py b/backend/reel_generator.py index 9ec3eb1..d191ca8 100644 --- a/backend/reel_generator.py +++ b/backend/reel_generator.py @@ -1355,7 +1355,8 @@ def generate_reel( try: # Phase 1: Script generation self._print_step(1, 5, "Generating personalized script") - print(f"Provider : Gemini ({self.script_model})") + provider_name = "Groq" if self.script_provider == "groq" else "Gemini" + print(f"Provider : {provider_name} ({self.script_model})") with self._spinner("Generating script..."): script_data = self.generate_script(emotion, context) From 9107abe02dbd246f2ced998c1b77628ac0e65369 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 20:32:38 +0545 Subject: [PATCH 09/18] feat: implement job polling mechanism --- src/background/index.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/background/index.js b/src/background/index.js index f98d9ca..a9de4ef 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -195,6 +195,27 @@ function createReadyNotification() { } /** Polls the local backend for job completion (project summary §7, Phase 3). */ +let activePollTimeout = null; + +async function pollActiveJob() { + if (activePollTimeout) { + clearTimeout(activePollTimeout); + activePollTimeout = null; + } + + const cycle = await getCycle(); + if (cycle.cycle_status !== CYCLE_STATUS.PENDING || !cycle.job_id) { + return; + } + + await handleJobPoll(); + + const nextCycle = await getCycle(); + if (nextCycle.cycle_status === CYCLE_STATUS.PENDING && nextCycle.job_id) { + activePollTimeout = setTimeout(pollActiveJob, 3000); + } +} + async function handleJobPoll() { const cycle = await getCycle(); if (cycle.cycle_status !== CYCLE_STATUS.PENDING || !cycle.job_id) { @@ -214,6 +235,10 @@ async function handleJobPoll() { if (data.status === "ready") { chrome.alarms.clear(ALARM_NAMES.JOB_POLL); + if (activePollTimeout) { + clearTimeout(activePollTimeout); + activePollTimeout = null; + } await setCycle({ cycle_status: CYCLE_STATUS.READY, reel_url: data.reel_url ?? null, @@ -223,6 +248,10 @@ async function handleJobPoll() { createReadyNotification(); } else if (data.status === "failed") { chrome.alarms.clear(ALARM_NAMES.JOB_POLL); + if (activePollTimeout) { + clearTimeout(activePollTimeout); + activePollTimeout = null; + } await setCycle({ cycle_status: CYCLE_STATUS.FAILED, error_message: data.error ?? "Something went wrong while generating your reel.", @@ -244,6 +273,13 @@ async function handleJobPoll() { } } +// Check on worker startup if a job was already in progress +getCycle().then((cycle) => { + if (cycle.cycle_status === CYCLE_STATUS.PENDING && cycle.job_id) { + pollActiveJob(); + } +}); + // --- Notification clicks -------------------------------------------------- chrome.notifications.onClicked.addListener(async (notificationId) => { chrome.notifications.clear(notificationId); @@ -358,6 +394,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { // 4. Update the cycle status with job_id and clip_path await setCycle({ clip_path: message.clipPath, job_id: data.job_id }); + pollActiveJob(); } catch (err) { console.error("[background] Failed to check-in with Express server:", err); await setCycle({ From 41c7fdcf788b4e9d90fc29ecf5b89e2c5ee4d992 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 20:33:11 +0545 Subject: [PATCH 10/18] docs: update compositor documentation to movielite --- MINDSTREAM_PROJECT_SUMMARY.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MINDSTREAM_PROJECT_SUMMARY.md b/MINDSTREAM_PROJECT_SUMMARY.md index 0929b46..fb562f4 100644 --- a/MINDSTREAM_PROJECT_SUMMARY.md +++ b/MINDSTREAM_PROJECT_SUMMARY.md @@ -107,7 +107,7 @@ graph TD LLM[Gemini API: Script Generator] TTS[TTS Engine: edge-tts or KittenTTS] Assets[(Asset Library: bg videos + ambient audio)] - Compositor[MoviePy: Video Compositor] + Compositor[MovieLite / FFmpeg: Video Compositor] JobQueue -- reads emotion result --> MLWorker JobQueue -- sends emotion + context --> LLM @@ -139,9 +139,9 @@ graph TD - **Script Generation (Phase 3):** Google Gemini (`gemini-3.5-flash`) — returns a structured JSON payload with `script` (full spoken text) and `subtitles` (array of short display phrases) in a single API call, eliminating the need for a separate transcription step. - **TTS (Phase 3):** Xiaomi MiMo API (`mimo-v2.5-tts`, voice `Dean`) via `/v1/chat/completions`. Audio returned as base64-encoded MP3 in the response body — no streaming required. - **Subtitle Timing (Phase 3):** Purely local, proportional word-count distribution. Total TTS audio duration (read via `AudioFileClip.duration`) is divided across subtitle phrases proportionally by word count. Subtitles start at 0s with no delay, keeping them synced with the near-zero-latency MiMo TTS. Pause-weighting adds ~15% extra time to phrases ending with sentence punctuation (`.`, `!`, `?`, `…`). No upload to Gemini, no Whisper, no AssemblyAI — free-tier safe. -- **Subtitle Rendering:** MoviePy `SubtitlesClip` overlaid at vertical position `1700` (bottom ~11% of 1920px frame), font size `80`, uppercase, yellow text (`#FFFF00`) with black 3px stroke. +- **Subtitle Rendering:** Subtitles overlaid using MovieLite/FFmpeg compositor at bottom of frame, uppercase, yellow text (`#FFFF00`) with black stroke. - **Video Search:** Pexels API with cinematic/moody query terms extracted by Gemini from the script. Fallback terms (`moody nature`, `dusk calm`, `foggy forest`) used if primary queries return no results. -- **Media Rendering (Phase 3):** Python + `MoviePy`/`FFmpeg`, compositing Pexels-sourced video clips + Xiaomi MiMo TTS audio + ambient audio (15% volume) + proportional subtitles into a 9:16 (1080×1920) MP4. +- **Media Rendering (Phase 3):** Python + `MovieLite`/`FFmpeg` (4x faster than MoviePy with support for `normal` background preset and `fast` multi-worker preset), compositing Pexels-sourced video clips + Edge-TTS/MiMo audio + ambient audio (15% volume) + proportional subtitles into a 9:16 (720×1280 or 1080×1920) MP4. - **Asset Library:** A local folder (`assets/audio/`) of ambient audio tracks organised by emotion. Background videos are fetched dynamically from Pexels per generation (no pre-built video library needed). ## 7. Detailed Implementation Workflow @@ -306,7 +306,7 @@ const assets = ASSET_MAP[emotion.label] || ASSET_MAP["neutral"]; **Sourcing:** Pexels, Pixabay (free stock footage), or AI-generated (RunwayML, etc.) -#### 3.4: Video Composition (MoviePy) +#### 3.4: Video Composition (MovieLite) **File:** `backend/workers/reel_compositor.py` ```python @@ -352,7 +352,7 @@ generate_reel( - Duration distributed across phrases proportionally by word count (not character count) - Negative lead offset (-0.15s) ensures subtitles appear slightly before audio - Sentence-ending punctuation gets ~15% extra duration for natural pauses -- SRT written to `output/audio/.srt`, loaded by MoviePy `SubtitlesClip` +- SRT written to `output/audio/.srt`, loaded by MovieLite subtitle overlays - Rendered as uppercase yellow text (`#FFFF00`), font size 80, black 3px stroke - Positioned at y=1700 (bottom ~11% of 1920px frame) - **No upload to Gemini files API, no Whisper, no AssemblyAI — free-tier safe** @@ -524,7 +524,7 @@ Extension polls `GET /jobs/:id`, sees `status: "ready"`, fires notification. - [ ] Install dependencies: `moviepy`, `edge-tts` (or `kittentts`) - [ ] Generate TTS from script → `test_audio.wav` - [ ] Create a test asset: `assets/backgrounds/frustrated.mp4` (download from Pexels) -- [ ] Composite with MoviePy → output `test_reel.mp4` +- [ ] Composite with MovieLite → output `test_reel.mp4` - [ ] Verify: 9:16 video, ~30-40s duration, audio plays correctly **Success criteria:** Can generate a watchable reel from hardcoded inputs. @@ -580,7 +580,7 @@ Extension polls `GET /jobs/:id`, sees `status: "ready"`, fires notification. - ✅ Xiaomi MiMo TTS (`Dean` voice) generates MP3 audio - ✅ Pexels API fetches dynamic cinematic video clips based on Gemini-extracted keywords - ✅ Proportional local subtitle timing with negative lead offset — no STT/transcription/upload -- ✅ MoviePy composites clips + TTS + ambient audio + subtitles into 9:16 MP4 +- ✅ MovieLite composites clips + TTS + ambient audio + subtitles into 9:16 MP4 - ✅ Subtitles positioned at y=1700 (bottom of frame), font size 80 - ✅ Job status updated to `ready` or `failed` accordingly - ✅ DNF-style progress display with: From 52845d58ea434dee92e71a495782aa29b75cffc7 Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Mon, 27 Jul 2026 20:33:43 +0545 Subject: [PATCH 11/18] feat: enhance reel player interface and functionality --- src/panel/states/PlayerState.jsx | 50 +++++++++++++++++++++++++------- src/panel/states/ReadyState.jsx | 14 ++++++--- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/panel/states/PlayerState.jsx b/src/panel/states/PlayerState.jsx index 70f1d04..095a8a3 100644 --- a/src/panel/states/PlayerState.jsx +++ b/src/panel/states/PlayerState.jsx @@ -3,20 +3,33 @@ import ProgressBar from "../../components/ui/ProgressBar.jsx"; export default function PlayerState({ reelUrl, caption, onDone }) { return ( -
-
-
- 0:04 / 0:11 -
{reelUrl ? ( -
); } diff --git a/src/panel/states/ReadyState.jsx b/src/panel/states/ReadyState.jsx index 154ab89..2a3ff75 100644 --- a/src/panel/states/ReadyState.jsx +++ b/src/panel/states/ReadyState.jsx @@ -3,14 +3,20 @@ import TagPill from "../../components/ui/TagPill.jsx"; export default function ReadyState({ emotionLabel = "neutral", onPlay, onLater }) { return ( -
+
● {emotionLabel} → reset -
-
+
+
-

Ready when you are.

+

Ready when you are.

+

+ Your personalized focus reset reel has been generated. +

+
+ ); +} + +function StepPersonalise({ name, setName, onNext, onBack }) { + return ( +
+

+ What should we call you? +

+

+ Used in your reel narration. Entirely optional. +

+ + setName(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && onNext()} + placeholder="e.g. Alex" + autoFocus + className="w-full bg-surface-raised border border-border rounded-[8px] px-4 py-2.5 text-[13px] text-fg placeholder:text-fg-subtle focus:outline-none focus:border-fg-subtle focus:ring-1 focus:ring-fg/10 transition-all mb-6" + /> + +
+ + +
+
+ ); +} + +function StepPreferences({ preset, setPreset, onNext, onBack, skipBackend }) { + return ( +
+

+ Generation speed. +

+

+ How the reel pipeline uses your machine. Changeable later. +

+ +
+ setPreset(GENERATION_PRESETS.NORMAL)} + title="Normal" + Icon={Gauge} + description="Balanced CPU usage. Runs quietly while you work." + /> + setPreset(GENERATION_PRESETS.FAST)} + title="Fast" + Icon={Zap} + description="Multi-core rendering. Quicker output, brief CPU spike." + /> +
+ +
+ + +
+
+ ); +} + +function PresetCard({ selected, onClick, title, Icon, description }) { + return ( +
(e.key === " " || e.key === "Enter") && onClick()} + className={`p-4 rounded-[10px] border cursor-pointer transition-all select-none outline-none focus-visible:ring-1 focus-visible:ring-fg/20 ${ + selected + ? "border-fg-subtle bg-surface-raised" + : "border-border bg-surface hover:border-fg-subtle/40" + }`} + > +
+
+ + + {title} + +
+
+ {selected &&
} +
+
+

+ {description} +

+
+ ); +} + +function StepBackend({ + backendReachable, + keyStatus, + missingRequired, + onFinish, + onBack, +}) { + return ( +
+

+ API keys needed. +

+ + {backendReachable === false ? ( +
+

+ Backend not running +

+

+ Run{" "} + + cd backend && npm start + {" "} + first. +

+
+ ) : ( +

+ Add missing keys to{" "} + + backend/.env + + . They never leave your machine. +

+ )} + +
+

+ Required +

+ {REQUIRED_KEYS.map((k) => ( + + ))} +
+ +
+

+ Optional +

+ {OPTIONAL_KEYS.map((k) => ( + + ))} +
+ +
+ + +
+
+ ); +} + +function KeyRow({ provider, configured }) { + const isConfigured = configured === true; + const isMissing = configured === false; + + return ( +
+
+
+ {provider.name} +
+
+ {provider.purpose} +
+
+
+ + {isConfigured ? "✓ set" : isMissing ? "missing" : "—"} + + {isMissing && ( + + Get key → + + )} +
+
+ ); +} From 8404aa893f2177f14283aab1665a2cf9e0e6f9eb Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Wed, 29 Jul 2026 23:17:40 +0545 Subject: [PATCH 16/18] feat: install lucide-react and motion --- package-lock.json | 82 +++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 ++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd3e479..215d301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.3.2", + "lucide-react": "^1.27.0", + "motion": "^12.43.0", "react": "^19.2.7", "react-dom": "^19.2.7", "tailwindcss": "^4.3.2" @@ -1691,6 +1693,33 @@ "dev": true, "license": "ISC" }, + "node_modules/framer-motion": { + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz", + "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.43.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2176,6 +2205,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.27.0.tgz", + "integrity": "sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2201,6 +2239,47 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/motion": { + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.43.0.tgz", + "integrity": "sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.43.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz", + "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2520,8 +2599,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", diff --git a/package.json b/package.json index 7ab3432..cfb0716 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ }, "dependencies": { "@tailwindcss/vite": "^4.3.2", + "lucide-react": "^1.27.0", + "motion": "^12.43.0", "react": "^19.2.7", "react-dom": "^19.2.7", "tailwindcss": "^4.3.2" From acec558179dcbf4d036779272674d58c559868cf Mon Sep 17 00:00:00 2001 From: Prashant Adhikari Date: Wed, 29 Jul 2026 23:20:29 +0545 Subject: [PATCH 17/18] feat (ui): overhaul design system and panel states --- public/assets/logo.png | Bin 0 -> 229117 bytes src/components/layout/PanelHeader.jsx | 49 ++++---- src/components/layout/PanelShell.jsx | 22 ++-- src/components/layout/SprocketRail.jsx | 14 --- src/components/ui/BreathingDot.jsx | 20 ++-- src/components/ui/Button.jsx | 13 ++- src/components/ui/TagPill.jsx | 7 -- src/panel/SidePanel.jsx | 29 ++++- src/panel/states/CaptureState.jsx | 16 +-- src/panel/states/ConfirmState.jsx | 8 +- src/panel/states/CountdownState.jsx | 24 +++- src/panel/states/ErrorState.jsx | 42 ++++--- src/panel/states/IdleState.jsx | 39 +++---- src/panel/states/OnboardingState.jsx | 127 +++++++++++++------- src/panel/states/PendingState.jsx | 155 ++++++++++++------------- src/panel/states/PlayerState.jsx | 108 +++++++++-------- src/panel/states/ReadyState.jsx | 51 +++++--- src/panel/states/SkeletonState.jsx | 11 +- src/style.css | 81 +++++-------- 19 files changed, 436 insertions(+), 380 deletions(-) create mode 100644 public/assets/logo.png delete mode 100644 src/components/layout/SprocketRail.jsx delete mode 100644 src/components/ui/TagPill.jsx diff --git a/public/assets/logo.png b/public/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..302efc6a9201ebc0f65ea06b77e77293bd86220c GIT binary patch literal 229117 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Ξ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�+A(kMIt55##p!PanH9PBeVnW+Y8rNVpA8NByWB}BKp%_ zD!XzO8W$S{ScF$era{=OT&-e>SUHyDo;qC`t4ZTYok`EEod84&7Uw8Gih_qCZN# z>`Z@O;w>Wt0RHDVk8v-g8KeL+LaPuOB>Z~K$W54<^{PS!rzJLxBeMsB$DvunQRTy= z6boCh1)KW$z-jE)_yrY}YT>Z$|bgGJlRzb|i2&tHMe;|#|0bOFLE5cP ztY^J)Zkrk%XO36_P5k4`ICSTA(w@=^c}4y*MJ%N? z`C$egftg&R7&RtSYd=cXqHJGR!N!8wkEa@?3;>F{p_8A8#bE}%wMDgp_ozG)s`O}3 z;3`6R`_(V7+ipNNBG5Nn^p=2?EV{69{MLB<>*~!(7or1%#rC}aJ;o_=ex1)vZP=}z zuIu}gfPR$qym<2M@eDs&wD3j!r*FKoBT|+AtN*-0sbQVznceg{h$}c|{ejKJQ?RL) zk+tUL2tTTBf|7#^UM_Htm_n0D1D1Zy&F6#B9bAG+;9>F1Uyc2VnRQKttX)HYI;x!G zU8&B@gC^Iy&nN`c>DOrF0qt8VR-!s@11Ahz+vhc#{k>hdG>^>U5y?u4?#F zCTqqiS#xTCL6a$(8k=|~t1Dp6*ln9qQC$0IC*+%K>IT(no`h1%(NRdVbZKZ* z`Jut!ARiwZL#Kg}^X`#N)`FBvLnGI{A7bk|s?x$}Rp|~Sz(xU~EeYK#KctRq;eym>*gapQeICj@&%a|d!&n0+gPT{bhn`5*ks z9oFa@CZo{i(9+xx=BF#Z8p@`A$C0f0yg;gZ0!f1XwHOmf6lqQXaizJm49#JiS8KY? zLP^_GZEs;<%gHifY#W6`M*gVL0<0L+q%zj_-=dRfodYN3i*1cYlgP zj#Jx4o#U)Oc{hf<7q@s9JE{Pk00 zC1xYGlTuU8BdvK$wVC%K*@I>iO{|T5|8=xSY9py?UTbWu$IC-RaO;GxwW(VAi=_x% zbfgIpPkg3zhbh&sHdHv8Y9jhHs2|boJx~&RPbp6D^>xaukDRlFgInZCi|SVJGjD#) zdWAAcdFS^;Xo4j+2-_Yt5oGajj-C8=?9WFAa_n>|Av5aM7l0n}6|+c2u5j!BA6;tJ znQQQJ;;&Q`tQ^5~RIeUMYUyVdO=DMA%5j)zRU4Y<;gC)QWvX*PHA`QMmF>oIP;1qt z?&v%>AuM=LqnJOA#tbz?A4WkD8bIvwVpNMdR!t#pW(kLaRuEf6M*Hw_!UfGJb`ZFQ z6Ch2;Ki<*5G^71%)!U~fpr&XL-yVo2Z;u1?K43e61J}wDYM$=#L6s#dTtf*`Z3Qi z!lc434m)}m;W zUWly?+RLZw(HS93vaJuVp4(vHGEef<4gZKlIVZl*vB7dvInB$M6QE4>OVY=_4XE%W zr_oB}jwgTqnNHJ+RBv-}k6iZc*>)vK=fZ3bz<-70TR4Gt%;O20l;5LKp}YtMu$0E) zCOHnUq98E#WqkH@v^FE3YB~vRdc0z@cZTn~j!=aDO#JSv=tHvX;<^K#(-#z~@4~ZV zh)Q{BLPiy(jCnX&<1)fDf(EtILVtT}V<&qggt8}vkQo8xOt5JU7}4T0!94I6#rI0z zh^E{D`|f9%rVBts0M-+>{NVRM>Ga^ViMzu8evo z!aNzPI-3ezvYI=$bJdv2sSS)vFM}?T44Bxg`GsT%c~D@81@(qNSsmB=v(km493fgL z2k*p(9Yn)kvnG= z>o&29u)h1Bwm{ArL?evCaW5F2x00)JS4$e2<9(C^JP}F89p`Oi4}6oB=1$8PD#Io* z^J2m%xpmH-)egDrI2+;d>Fz$}F}8~fD;vf-5i9{Yl5dRis@dHV^CvEJ9i>dAWd-_Y&8RrKiQ;E&Q#!lHk-R`&HXW^4Jw zJ1qq>$_%}6@SOBkd?+)0IAJ(0W)~zJ5a7q=X}Z~rm|AXpi?YXd%H9#>*+fH4&F8K& zemdWL!VD~Pf{Dyty~g^SmZXQ93ERV6f0fa1GRJUrX+!)o-1g6M!pHVvACQ!|DTHyB z_hyXzzjFcy8cUF-Oo<$P-PA@GbRjWGr=$b!R< zJCnyXC`YzRhMm#PItODVZKX8;MK!Rz35iG-mWKBw*?EnOllS&N;`1cS+aHuFrF2T4 zw0Zq+EJ!su&q0(#lIg_}Sxvlc-*gH{k1@h@C>O;W_!Hw6(Z6R&@Pk3wAl2`zhZ=fn%lo>~Tuy<n>gAL8k4_rQ}(0iAC9+TY9{|4>Z%LuAZBZVojod!2!9KS95l{lo@ z4w#+XHZbW$c1@-2ZECk<=;7n3;{B@B6Kji@yWPH7>swCERtA*bTy81~?cGWNwusa#bH^Uvg~J zp0UM3fYaq>Mk8vLnA>vHEisCI#uxUyKD-bnV6AeWPhEegF|jtFHsp{I*#*f_|G707;DwK`Je8PAf7LsGg zbX9AbSd~*)wQl1)LnfZ6!RJX{T)s!4DpnPnxa;V6EBgGO`9f0+3rzgvM~YPpM<#G5 zo+LtE*f(OGA1w7={gw64!+TnZfwq49rqxI4C~g_K+UYCZPB1Q4k9a-j$^vGEqCC-; zvTq{MQpN*6{df)@CYzxh9KnU2iHshwzJ_cmIX_l*aq}I1Y<;@$9!o?@^1$!s>!_>P zDFzbZKa>D3{bbY8@^{)dnc6a}XC!AyH4oc1nf*D}o_woZN>K>C2igS)er64O#i8ye zOPExcvSu~!#Cl-g8+S_A`PR&7GCcX^w-4mYiQh|J>UcLmXUvu()p19%LUGtA&+p-J zFXIOIRWjU-b$nAr?y2HHoM71S@pP~{W{jfYGpoU<9qEb3mI)_P=i;TzH?xs5F<;g0 z*bQbFS9;7U(T)<1S^p0HJ10cnV&`6}F|lTu>Mwgv(+R$Yk%B!>BDxa`NAq-skMOyg zfgm8ZH>D666@tWb0VV}J?;ad7a6jQ66#IvwP#%bjea3SGvu7xbFIIkKv%uW*UZssqe;j*xEt}K zSNY#^7u&$KG<@Xcll6B_6xD%AbLI8z^_t?2$Fl3@W6FQmlA+u?Kn28=vl_ zab@aeo491sp*@fDWzQfW-8(`OTp$YDEN*nlJa0ojNYs!OxQPHeS1m`tSC>)|iLxKN z9u0f*HuY782fE>97^vkqE}j&bNPV8Z;2-@w$gqomho|AhFw*V@cQ)vl&x z6&|8~0R$iXLfy}{^_=yLE$(l=htrHQ;aD;BL{YwuA7Za4cg~b{JHFfN@XxKn?>e+{ zM7kC6rD%BXul2`@ygoXBJmRCQcGW#nIsagIy1!D-BoZDsRk;=okT9O7oV-v%RPrRv zaJGoQbnq(N9me?r!Oy5gO_rg{z9v*jAL7>aJmGd#vLlH-ihg#HkD*1%;2({QC~sqi z4DQ%YH*?G8otbQ3T%1dD!t>=S+pD`{S{Wg7;nqnz!H#`R9mnCZ0WN;%TO~DUO$trf zEAh>m;oxUGgEiysQUk~RA#xS_z>~D1ro9Nq7r!K+ZTwpij=;Y*vn;CYJ&IfMDlgY_ z9qNLZommq%qyNDcxt}~C#(ZA^3W5#wY!QC2Ti%AZX#(JV#Ve zh1$qzHp|AQ2`8tP-E7vf3fJe)T{iqtugtKtCRLaXEJ;j|j$o78;^|+fn8nC(9_yx6 zThlAd)_xM62SB|qE~omF|J)AKhXN}UZu9u0j`FVa!Y_RRO%T)}WPy8A;=0#8G*0@Q zh6HpS`2vc7DJL_tGai{VUUy`irWbC6g zf=lXr56vZ?@OON8LHMeILV%N5V8S^(z1|Y%4G!>DFtO5B9uWX%i<970Q{F4k+_A)> zXL=56rbau#cA>tNXT^)to_FQ*uVSHAlSs6pkN?wkI|CRQzaL8_l|rsL^6kCIt|IVF zjZW>AG{OS_Ob&__;n8)Da0~887pajudQRrgnbI>HLg%kBQcuLr9(fQxt~4>*>mFQD zm?o(#L%Bos5guo=bm@NXaBUZ?Mhj40Z(wIBZ4cpT*l7-S%RjZJ$*RQ+60~P0HnR&| zCsKl*!6MbIz};loORf<22Fl43?H?XYBL5*-ylrp6kR^%08TUk{lb2g@JrzDd=c3RP zCC%TCDQY6cp%T`tMcy+&=?O_21>*zJiKMQS){n7{V{-0QTDF^azSA+eswOqwSXsZ1 z{hVEsLZ~dJR$f|xTtP2?adW3TAr6a1%-GhkLuq!TkNrh$!C2Jek!3iC_{c!b@qmAf zXDoh3HblGC)_$z z;?5-&1GPpS_k&aaBYL*vdIgxls-R!P&h+;F=y+`vNU8mD{vtngbBu?1`)}3Ei|O3| zpFiuB^60oJaViSO2dE>fL|2mm5=N#c;lZWADq7tCY+YtSs#cs!#?}CGwwm2q54Kza zow1!hfIw2LZN%bJ;gy;52)$QyHKdHLcEEi7YzXZG{^ z>@$Vi3LTt7M(^?C!5sXI7dV;{B|V<)7tG%{TkrqvNtB#TO}D&K2;~)r=A=k!O7U%; z!b8u~vuCnhgtmghQ(J=w0C9R5)*~3z+8w%iP~FYjYGJPzQxr!>LvUMGzkyzjQ&5~> zH&${G{YzM}{yj0f(>^`9EA}|o&{_9%TdF-@vu@&Y%PVl&^2m$;yL@co>1Fr(2y2to zGNr3$J)Cu&V2G<#83alMG_%Q82?s1Rzr0x2<~$hxi|1=(pVDghwgXuQdCGlWoyfNz zMMsrd{h2|9=rEVERdKIN+$2>V{Q~m@Lu4c0_6m}T;Od6{)s0wvnSDCAZe{jDCgdL9 zUc)GUH^e8v(2W(f2pP)mELJdBp1qZVJ%0b{N~ilhD5m*6h~Im_!M26`j#TY(RUQ0A zTlBb z)KbDwrl;rcTC1#-{_N!BhaPcbu#L59y3G<018PS-J{}VzW6ed?gq!d(41JJH~eq+Aj85|cvICGXVQFD~4;@MVg?n6;|y`A7gVma>#;YKybf zkRG`<23MuD)`;YtVI3Ulgiw=!DsjHqSptYUM+KV#>^J^8;z*Fn@3);~zt`m6t@C{&i3Ey@*v#xh;QN7*V;1r1mQo{U*;dnVcm=LY8eaDsB25e z<5qdGCREx zQl(N3D=u%I{^_^`%brB&}xqi5{)?w?FZ|X{YC{LVn-uTJ;@kej1nFvu`5zb*bsH-4f@S zm5z+odll|y^gkksaMjX(0hjPHb{Kg0UT~E9aO_tpscg75ZUj&On7ZIjexMW`cHJhachRXQ+E<&H&H?mB} z*sx)24>wNY06^3ua|RrHkHR)uq2;8j#NY9LB#5|zBM6rHSXD91HD+z$OS%SP*1fNm zBVqgFlth0cQzgX_Lx87-0XfM2EcC)>x8l&d$ni~PP$iqDFx6h*lm9MSaJ&q7l!VfT zA>r_2QOreavw2#mMq9#d!nU(+i#ZcS|@%|#`=a1n91^!j5vlN zN&76)g;~~sXUCy0p}JQSce1KqLPuy6t2)?*w&kfe^B$wOfb$4H`nZi9BW5W>%$KaK z?%lH*IKdG$oU_zRM!CcI*bb^?6VSCOV?}pO>JQq-b2g8|$nMSHLf_y`7+6T#nY>g- zmQ0(xsXmnUjOc!ux$7V^}yL@nq!*m zz}Wvr&yDO%*m+aq0-FUH+n8QiJ86oeS!^WzQshZ2311&xk~hct{P<<=6KqwFI!N!> z4R@~s9FLKWA^Y>3jWswpT>c@V5HrA-I))>h`1qs#zMK8@X=*c)ouz6z{Kc&4+X=*X zmlbC9pj=mP2Ub;@j$He)4qwH9`}>XI%nMJ>+@u-h>5#hXvV~dM%BOEyY=%$pac*+5 zWB&lDy(ud)j9FH?EuY*)P-R@m#I>X=ECMk5sw-t`BtKb}zwjth!9NC=;L1!f<`4kTa3X&3*2%;T`s z@VDkPb5v|GvX2o001dS(9Vx!OzewvrU&45f`)LZ=QZf&Ar@!&7Ie4k6umqL^I&)eknn_+D$ zHO00`A)Wxd!1L4kHGQGq_FWB{v0C56Q!5(x(gvbg{15i`7CsOvc75hN!0^{il0n7b zWDr;SK5>ykuH|s}-UM9FV>53(|%d;FROmaPQ6t(d3Xw#U^zr*QhwY>3~2;kPmDD zn!V1Yvo&-mtLT$Q9QvFu_go1)8klV@Vg^{^MfkwU_K1L|?%z}0A4Roq-$aRjJ=5Yq zZCVs;0b)g7p$w=$*WzeC-s0#9fh|E1e|n*&s(#MbPdW{S*D82pnCc2}V-#=7A#dF- z@W|WV#M3D5Os;MAj2$aul;qxH`a;n}gP|B6Uz`7P>n`GW3BTy?Nns`x*;_?F%*gI; zj|<&tqfUJUH&-|O`#6aP6BUxKBAb%Be?Z^#PhK2{XcBRcy`*@t0jG$j=88$)JM}Bc z_s3lUZ69_#LLZWV+9+aMS+JcP1;{6JFTCX|B)@*ZC)g2*jNYYO;G4J>%EDSL+Y=hZ z*=5IM!XQN$wuL+iKjy6Dii#>kpa%-zfht=N!d)Bi=QSMfmWw@t%KO9fL=$|=Np|+4 zvi3oT^ozh?Qpnwcm)D&}0nV|T7M%&lUZOQJ{k8N6@c_QlJMg6w?BD$CV=K-=-6Qo6 zhTGARYY;vjg?Cx)>Q~$iDfzIp%UJ^L)}I(>)8T0Tx@c)>l|Pc>a{IiP&d9TGB>!&j zYR;;D*k>cL@9n5$O~An9X+kQQ)}p1xRpJ{jq8{^agG8WrtZEqv#RBKSXTHho+?gLa zPx=9mj1RUo_#n7t<;Pip!eSKpo4S@9Y_Lj+!{TKkPG71d>U)yxy2 zj7TpBDw6}@E%kQXvNZM_SWQh#Oi!~Trq62KgQwvKwcz?df#=OwQ)hII65Ug}f9X`Q zcxE3In&$}2%!RYrH0nUJMT5r~CK)6uf%T7he{K_T@Ms}uLK$Q)^K{$13unj5y(#>I z5|(49m-Bu9oPV-6J3N`3m%Cj_ZZhKk)PEC&u(-5?H9-T+I>ko&63$Efq(sR6+|!y+*{_RFa(Eua z!IV@R`xoOBG>Qaa@~6cH2Pb<-+SINDLp*!VFI;R9@Ai%@U78nE@K pJw|uaZ7ib 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