I'm working on a rust program that takes a directory full of images as input, and outputs a video file which chaotically flashes, strobes, and displays images at random times, following intricate and chaotic patterns. The transitions are fast and chaotic, creating an almost epileptic effect. It has a modular way of implementing interesting effects. Here is the source code:

////// src/config.rs //////


#[derive(Debug, Clone)]
pub struct Config {
    pub input_dirs: Vec<String>,
    pub output_file: String,
    pub width: usize,
    pub height: usize,
    pub framerate: u32,
    pub duration_seconds: u32,
    pub total_frames: usize,
    pub timebase_num: u32,
    pub timebase_den: u32,
    pub quantizer: usize,
    pub speed: usize,
    pub bit_depth: usize,
    // Add other configuration parameters as needed
}

impl Default for Config
{
    fn default() -> Self {
        let framerate = 60;
        let duration_seconds = crate::DEFAULT_DURATION_S;
        let total_frames = framerate * duration_seconds;
        Self 
        { 
            input_dirs: crate::INPUT_DIRS.iter().map(|d| d.to_string()).collect(), 
            output_file: crate::DEFAULT_OUTPUT_FILE.to_string(), 
            width: crate::DEFAULT_WIDTH, 
            height: crate::DEFAULT_HEIGHT, 
            framerate, 
            duration_seconds, 
            total_frames: total_frames as usize,
            timebase_num: 1u32, 
            timebase_den: framerate,
            quantizer: 100,
            speed: 6,
            bit_depth: 8,
        }
    }
}


////// src/effects.rs //////

use image::{ImageBuffer, Pixel, Rgba, RgbaImage};
use rand::prelude::*;
use rusttype::Font;

use crate::types;

// Define the Effect enum with all possible effects
#[derive(Clone)]
pub enum Effect {
    InvertColors,
    ColorFlash(image::Rgba<u8>),
    Sparkle,
    Spiral,
    TransparencyStrobe(f32), // The parameter represents strobe speed.
    OverlayText(String),
    ZoomIn(f32), // This parameter represents the zoom speed.
    Kaleidoscope,
    BitFocus,
    Glow { max_intensity: f32 },
    GrowShrink { speed: f32 },     // New effect for growing and shrinking
    FadeOut,                       // New effect for fading out
    SplitApart,                    // New effect for splitting apart
    LowOpacityFlash {
        flash_duration: usize,
        flash_interval: usize,
    },
    // Add more effects as needed
}


// EffectFactory to generate random effects
pub struct EffectFactory {
    pub rng: ThreadRng,
    texts: Vec<String>,
    font: rusttype::Font<'static>,
}

impl EffectFactory {
    pub fn new() -> Self {
        // Random number generator
        let font_data = include_bytes!(r"C:\Project\Workspace\Javascript\gpt4o-site\media-cycler-rs\Atop-R99O3.ttf") as &[u8];
        Self
        {
            rng: rand::thread_rng(),
            texts: crate::TEXTS.iter().map(|t| t.to_string()).collect(),
            font: Font::try_from_bytes(font_data).expect("Error loading font"),
        }
    }

    pub fn generate_random_effects(&mut self) -> Vec<Effect> {
        let mut effects = Vec::new();

        if self.rng.gen_bool(0.3) {
            effects.push(Effect::InvertColors);
        }
        if self.rng.gen_bool(0.2) {
            let color = image::Rgba([
                self.rng.gen_range(0..=255),
                self.rng.gen_range(0..=255),
                self.rng.gen_range(0..=255),
                128,
            ]);
            effects.push(Effect::ColorFlash(color));
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Sparkle);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Spiral);
        }
        if self.rng.gen_bool(0.1) {
            effects.push(Effect::TransparencyStrobe(self.rng.gen_range(0.2..=10.0)));
        }
        if self.rng.gen_bool(0.2) {
            let text = self.texts.choose(&mut self.rng).unwrap().to_string();
            effects.push(Effect::OverlayText(text));
        }
        if self.rng.gen_bool(0.1) {
            let zoom_speed = self.rng.gen_range(0.001..0.01); // Adjust zoom speed as desired
            effects.push(Effect::ZoomIn(zoom_speed));
        }
        if self.rng.gen_bool(0.02) {
            effects.push(Effect::Kaleidoscope);
        }
        if self.rng.gen_bool(0.3) {
            effects.push(Effect::BitFocus);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Glow{max_intensity: self.rng.gen_range(1.5..3.0)});
        }
        effects
    }

    pub fn generate_overlay_effects(&mut self) -> Vec<Effect> {
        let mut effects = Vec::new();
        if self.rng.gen_bool(0.3) {
            let speed = self.rng.gen_range(0.01..0.05);
            effects.push(Effect::GrowShrink { speed });
        }
        if self.rng.gen_bool(0.3) {
            effects.push(Effect::FadeOut);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::SplitApart);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Sparkle);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Spiral);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Glow{max_intensity: self.rng.gen_range(1.5..3.0)});
        }
        if self.rng.gen_bool(0.3) {
            effects.push(Effect::BitFocus);
        }
        if self.rng.gen_bool(0.2) {
            let color = image::Rgba([
                self.rng.gen_range(0..=255),
                self.rng.gen_range(0..=255),
                self.rng.gen_range(0..=255),
                128,
            ]);
            effects.push(Effect::ColorFlash(color));
        }
        if self.rng.gen_bool(0.01) {
            effects.push(Effect::InvertColors);
        }

        effects
    }

    pub fn generate_random_global_effects(&mut self) -> Vec<Effect> {
        let mut effects = Vec::new();

        if self.rng.gen_bool(0.3) {
            effects.push(Effect::InvertColors);
        }
        if self.rng.gen_bool(0.2) {
            let color = image::Rgba([
                self.rng.gen_range(0..=255),
                self.rng.gen_range(0..=255),
                self.rng.gen_range(0..=255),
                128,
            ]);
            effects.push(Effect::ColorFlash(color));
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Sparkle);
        }
        if self.rng.gen_bool(0.2) {
            effects.push(Effect::Spiral);
        }
        if self.rng.gen_bool(0.2) {
            let text = self.texts.choose(&mut self.rng).unwrap().to_string();
            effects.push(Effect::OverlayText(text));
        }
        effects
    }

    // Implement effect functions
    /// Returns new image scale
    pub fn apply_effects(
        &mut self,
        image: &mut image::RgbaImage,
        frame_number: usize,
        state: &types::ImageState,
    ) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
        let mut current_scale = state.scale;
        let mut alpha = 1.0;
        for effect in &state.effects {
            match effect {
                Effect::InvertColors => Self::invert_colors(image),
                Effect::ColorFlash(color) => Self::add_color_flash(image, &color),
                Effect::Sparkle => self.add_sparkle(image),
                Effect::Spiral => self.add_spiral(image),
                Effect::TransparencyStrobe(speed) => {
                    Self::apply_transparency_strobe(image, frame_number, state.start_frame, *speed)
                },
                Effect::OverlayText(text) => {
                    self.overlay_text(image, &text);
                },
                Effect::ZoomIn(zoom_speed) => {
                    current_scale = Self::calculate_zoom_scale(
                        state.scale,
                        frame_number,
                        state.start_frame,
                        *zoom_speed,
                    );
                }
                Effect::Kaleidoscope => Self::apply_kaleidoscope(image),
                Effect::BitFocus => Self::apply_bit_focus(
                    image,
                    frame_number,
                    state.start_frame,
                    state.end_frame,
                ),
                Effect::Glow { max_intensity } => {
                    self.apply_glow(image, frame_number, state, *max_intensity);
                },
                Effect::GrowShrink { speed } => {
                    let scale_factor = self.calculate_grow_shrink_scale(
                        frame_number,
                        state.start_frame,
                        state.end_frame,
                        *speed,
                    );
                    current_scale *= scale_factor;
                },
                Effect::FadeOut => {
                    alpha = self.calculate_fade_out_alpha(
                        frame_number,
                        state.start_frame,
                        state.end_frame,
                    );
                },
                Effect::SplitApart => {
                    *image = self.apply_split_apart(image, frame_number, state);
                },
                Effect::LowOpacityFlash {
                    flash_duration,
                    flash_interval,
                } => {
                    alpha = self.calculate_low_opacity_flash_alpha(
                        frame_number,
                        state.start_frame,
                        *flash_duration,
                        *flash_interval,
                    );
                },
            }
        }
        // Apply scaling
        let scaled_image = image::imageops::resize(
            image,
            (image.width() as f32 * current_scale) as u32,
            (image.height() as f32 * current_scale) as u32,
            image::imageops::FilterType::Lanczos3,
        );

        // Apply alpha (transparency)
        let final_image = self.apply_alpha(&scaled_image, alpha);

        final_image
    }

    // Function to calculate alpha for LowOpacityFlash effect
    fn calculate_low_opacity_flash_alpha(
        &self,
        frame_number: usize,
        start_frame: usize,
        flash_duration: usize,
        flash_interval: usize,
    ) -> f32 {
        let elapsed_frames = frame_number - start_frame;
        let cycle = flash_duration + flash_interval;

        // Determine where we are in the flash cycle
        let position_in_cycle = elapsed_frames % cycle;

        if position_in_cycle < flash_duration {
            1.0 // Full opacity during the flash
        } else {
            0.2 // Low opacity otherwise
        }
    }

    // Function to calculate scale for GrowShrink effect
    fn calculate_grow_shrink_scale(
        &self,
        frame_number: usize,
        start_frame: usize,
        end_frame: usize,
        speed: f32,
    ) -> f32 {
        let total_frames = (end_frame - start_frame) as f32;
        let elapsed_frames = (frame_number - start_frame) as f32;

        let progress = elapsed_frames / total_frames;
        let scale = (progress * speed * std::f32::consts::PI * 2.0).sin() * 0.5 + 1.0; // Oscillate between 0.5 and 1.5

        scale
    }

    // Function to calculate alpha for FadeOut effect
    fn calculate_fade_out_alpha(
        &self,
        frame_number: usize,
        start_frame: usize,
        end_frame: usize,
    ) -> f32 {
        let total_frames = (end_frame - start_frame) as f32;
        let elapsed_frames = (frame_number - start_frame) as f32;

        let progress = (elapsed_frames / total_frames).clamp(0.0, 1.0);
        let alpha = 1.0 - progress; // Fade out from 1.0 to 0.0

        alpha
    }

    // Function to apply alpha to an image
    fn apply_alpha(
        &self,
        image: &RgbaImage,
        alpha: f32,
    ) -> RgbaImage {
        let mut final_image = image.clone();

        for pixel in final_image.pixels_mut() {
            pixel[3] = (pixel[3] as f32 * alpha).clamp(0.0, 255.0) as u8;
        }

        final_image
    }

    // Function to apply the SplitApart effect
    fn apply_split_apart(
        &self,
        image: &mut RgbaImage,
        frame_number: usize,
        state: &types::ImageState,
    ) -> RgbaImage {
        // For simplicity, we'll split the image into two halves moving apart

        let total_frames = (state.end_frame - state.start_frame) as f32;
        let elapsed_frames = (frame_number - state.start_frame) as f32;
        let progress = (elapsed_frames / total_frames).clamp(0.0, 1.0);

        let width = image.width();
        let height = image.height();

        let mut new_image = RgbaImage::new(width, height);

        // Left half moves left
        let offset = (progress * width as f32 / 2.0) as i64;
        let left_half = image::imageops::crop_imm(image, 0, 0, width / 2, height).to_image();
        image::imageops::overlay(&mut new_image, &left_half, -offset, 0);

        // Right half moves right
        let right_half = image::imageops::crop_imm(image, width / 2, 0, width / 2, height).to_image();
        image::imageops::overlay(&mut new_image, &right_half, offset, 0);

        new_image
    }

    // Implement the apply_glow function
    fn apply_glow(
        &self,
        image: &mut RgbaImage,
        frame_number: usize,
        state: &types::ImageState,
        max_intensity: f32
    ) {
        // Calculate the progress of the effect between 0.0 and 1.0
        let total_frames = (state.end_frame - state.start_frame) as f32;
        let elapsed_frames = (frame_number - state.start_frame) as f32;
        let progress = (elapsed_frames / total_frames).clamp(0.0, 1.0);

        let brightness_factor = 1.0 + progress * (max_intensity - 1.0);

        for pixel in image.pixels_mut() {
            for i in 0..3 {
                let value = pixel[i] as f32 * brightness_factor;
                pixel[i] = value.clamp(0.0, 255.0) as u8;
            }
        }
    }

    // Function to invert colors in an image
    fn invert_colors(image: &mut RgbaImage) {
        for pixel in image.pixels_mut() {
            pixel[0] = 255 - pixel[0];
            pixel[1] = 255 - pixel[1];
            pixel[2] = 255 - pixel[2];
        }
    }

    // Function to add a color flash effect
    fn add_color_flash(image: &mut RgbaImage, color: &image::Rgba<u8>) {
        for pixel in image.pixels_mut() {
            pixel.blend(&color);
        }
    }

    fn apply_transparency_strobe(
        image: &mut RgbaImage,
        frame_number: usize,
        start_frame: usize,
        strobe_speed: f32,
    ) {
        // Calculate the alpha value based on a sine wave for smooth strobing
        let t = (frame_number - start_frame) as f32;
        let alpha = ((strobe_speed * t).sin() * 0.5 + 0.5) * 255.0;
        let alpha = alpha as u8;

        for pixel in image.pixels_mut() {
            pixel[3] = alpha;
        }
    }

    // Function to add sparkle effects
    fn add_sparkle(&mut self, image: &mut RgbaImage) {
        if image.width() == 0 || image.height() == 0 
        {
            return;
        }
        let num_sparkles = self.rng.gen_range(100..500);
        for _ in 0..num_sparkles {
            let x = self.rng.gen_range(0..image.width());
            let y = self.rng.gen_range(0..image.height());
            let pixel = image.get_pixel_mut(x, y);
            pixel[0] = 255;
            pixel[1] = 255;
            pixel[2] = 255;
            pixel[3] = 255;
        }
    }

    // Function to add a spiral effect
    fn add_spiral(&mut self, image: &mut RgbaImage) {
        let center_x = image.width() as i32 as f32 / 2.0;
        let center_y = image.height() as i32 as f32 / 2.0;
        let max_radius = center_x.min(center_y);
        let num_turns = self.rng.gen_range(5.0..10.0);
        let color = image::Rgba([self.rng.gen(), self.rng.gen(), self.rng.gen(), 255]);

        for t in 0..1000 {
            let theta = num_turns * t as f32 * 0.01 * std::f32::consts::PI;
            let radius = max_radius * t as f32 * 0.001;
            let x = center_x + radius * theta.cos();
            let y = center_y + radius * theta.sin();

            if x >= 0.0 && x < image.width() as f32 && y >= 0.0 && y < image.height() as f32 {
                let pixel = image.get_pixel_mut(x as u32, y as u32);
                *pixel = color;
            }
        }
    }

    fn overlay_text(&mut self,
        image: &mut RgbaImage,
        text: &str
    ) {
        use imageproc::drawing::draw_text_mut;
        use rusttype::Scale;

        if image.width() == 0 || image.height() == 0 { 
            return;
        }

        let scale = Scale {
            x: self.rng.gen_range(20.0..100.0), // Random font size
            y: self.rng.gen_range(20.0..100.0),
        };

        // Random position
        let x = self.rng.gen_range(0..(image.width() as i32));
        let y = self.rng.gen_range(0..(image.height() as i32));

        // Random color
        let color = image::Rgba([
            self.rng.gen(),
            self.rng.gen(),
            self.rng.gen(),
            255,
        ]);

        draw_text_mut(
            image,
            color,
            x,
            y,
            scale,
            &self.font,
            text,
        );
    }

    fn calculate_zoom_scale(
        original_scale: f32,
        frame_number: usize,
        start_frame: usize,
        zoom_speed: f32,
    ) -> f32 {
        let t = (frame_number - start_frame) as f32;
        let scale = original_scale * (1.0 + zoom_speed * t);
        scale
    }

    fn apply_kaleidoscope(image: &mut RgbaImage) {
        let width = image.width();
        let height = image.height();
    
        // Create a new image buffer to store the kaleidoscope result
        let mut output = RgbaImage::new(width, height);
    
        // Number of segments in the kaleidoscope (adjust for different patterns)
        let num_segments = 8;
        let angle_per_segment = 360.0 / num_segments as f32;
        let center_x = width as f32 / 2.0;
        let center_y = height as f32 / 2.0;
    
        // Iterate over each pixel in the output image
        for y in 0..height {
            for x in 0..width {
                // Calculate coordinates relative to the center
                let dx = x as f32 - center_x;
                let dy = y as f32 - center_y;
    
                // Calculate the angle and radius from the center
                let mut angle = dy.atan2(dx).to_degrees();
                let radius = (dx * dx + dy * dy).sqrt();
    
                // Normalize the angle within [0, 360)
                if angle < 0.0 {
                    angle += 360.0;
                }
                // Map the angle into the first segment
                angle = angle % angle_per_segment;
    
                // Reflect the angle to create symmetry
                if angle > angle_per_segment / 2.0 {
                    angle = angle_per_segment - angle;
                }
    
                // Convert back to radians
                let angle_rad = angle.to_radians();
    
                // Calculate the source pixel coordinates
                let src_x = center_x + radius * angle_rad.cos();
                let src_y = center_y + radius * angle_rad.sin();
    
                // Check bounds and sample the pixel from the original image
                if src_x >= 0.0 && src_x < width as f32 && src_y >= 0.0 && src_y < height as f32 {
                    let src_pixel = image.get_pixel(src_x as u32, src_y as u32);
                    output.put_pixel(x, y, *src_pixel);
                } else {
                    // If out of bounds, set pixel to black or transparent
                    output.put_pixel(x, y, image::Rgba([0, 0, 0, 255]));
                }
            }
        }
    
        // Replace the original image data with the kaleidoscope result
        *image = output;
    }

    fn apply_bit_focus(
        image: &mut RgbaImage,
        frame_number: usize,
        start_frame: usize,
        end_frame: usize,
    ) {
        let total_duration = end_frame - start_frame;
        let elapsed = frame_number - start_frame;
    
        // Calculate the progression ratio [0.0, 1.0]
        let progress = elapsed as f32 / total_duration as f32;
    
        // Determine the pixelation level based on progress
        // Start with a small size (high pixelation) and increase to full size
        let min_pixels = 8; // Minimum number of pixels for pixelation (more pixelated)
        let max_pixels = 1; // Maximum number of pixels (full resolution)
    
        // Use an easing function for smoother animation (optional)
        let eased_progress = Self::ease_out_quad(progress);
    
        // Calculate current pixelation level
        let pixel_size = ((min_pixels as f32 * (1.0 - eased_progress)) + (max_pixels as f32 * eased_progress)).max(1.0);
    
        // Resize down to create pixelation, then upscale back to original size
        let (width, height) = image.dimensions();
        let new_width = (width as f32 / pixel_size).max(1.0) as u32;
        let new_height = (height as f32 / pixel_size).max(1.0) as u32;
    
        // Downscale the image to the new dimensions
        let downscaled = image::imageops::resize(
            image,
            new_width,
            new_height,
            image::imageops::FilterType::Nearest,
        );
    
        // Upscale back to original size
        let upscaled = image::imageops::resize(
            &downscaled,
            width,
            height,
            image::imageops::FilterType::Nearest,
        );
    
        // Replace the original image with the upscaled, pixelated image
        *image = upscaled;
    }
    
    // Optional easing function for smoother progression
    fn ease_out_quad(t: f32) -> f32 {
        1.0 - (1.0 - t).powi(2)
    }
}

////// src/frame_renderer.rs //////

use crate::transitions::{apply_reassembly_transition, TransitionEffect};
use crate::{config::Config, transitions::Transition};
use crate::effects::EffectFactory;
use crate::types::{ImageFrames, ImageState};
use image::{imageops, AnimationDecoder, DynamicImage, RgbaImage};
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use std::cell::RefCell;

pub fn render_frame(
    frame_number: usize,
    active_primary_images: &[ImageState],
    active_overlay_images: &[ImageState],
    active_global_effects: &[ImageState],
    config: &Config,
    effect_factory: &mut EffectFactory,
    transitions: &mut [&mut Transition],
) -> RgbaImage {
    // Create a new blank image (frame) with the specified dimensions
    let mut frame = DynamicImage::new_rgba8(config.width as u32, config.height as u32).to_rgba8();

    // Overlay images
    for state in active_primary_images  {
        // Obtain the current frame
        let current_frame_image = state.frames.get_current_frame(frame_number, state.start_frame, state.end_frame).unwrap_or_else(|| RgbaImage::new(1, 1));

        // Apply scaling
        let img = image::imageops::resize(
            &current_frame_image,
            (current_frame_image.width() as f32 * state.scale) as u32,
            (current_frame_image.height() as f32 * state.scale) as u32,
            image::imageops::FilterType::Lanczos3,
        );

        let mut img = img.clone();

        // Apply effects
        let img = effect_factory.apply_effects(
            &mut img,
            frame_number,
            &state,
        );

        // Overlay the image onto the frame
        imageops::overlay(&mut frame, &img, state.position.0, state.position.1);
    }

    // Render overlay images (smaller, fast images)
    for state in active_overlay_images {
        // Similar handling as above
        let current_frame_image = state.frames.get_current_frame(frame_number, state.start_frame, state.end_frame).unwrap_or_else(|| RgbaImage::new(1, 1));

        // Apply scaling
        let img = image::imageops::resize(
            &current_frame_image,
            (current_frame_image.width() as f32 * state.scale) as u32,
            (current_frame_image.height() as f32 * state.scale) as u32,
            image::imageops::FilterType::Lanczos3,
        );

        let mut img = img.clone();

        // Apply effects
        let img = effect_factory.apply_effects(&mut img, frame_number, state);

        // Overlay the image onto the frame
        imageops::overlay(&mut frame, &img, state.position.0, state.position.1);
    }

    // Apply global effects to the frame
    for global_state in active_global_effects {
        effect_factory.apply_effects(&mut frame, 0, global_state);
    }

    // Render transitions
    for transition in transitions {
        if frame_number >= transition.start_frame && frame_number < transition.end_frame {
            match transition.effect {
                TransitionEffect::Reassembly => {
                    let transition_frame = apply_reassembly_transition(
                        frame_number,
                        transition,
                        config,
                    );
                    // Overlay the transition_frame onto the main frame
                    image::imageops::overlay(&mut frame, &transition_frame, 0, 0);
                },
                // Handle other transition effects...
            }
        }
    }

    frame
}

// // Function to get the appropriate frame from an animated GIF
// fn get_gif_frame(
//     path: &PathBuf,
//     frame_number: usize,
//     start_frame: usize,
//     end_frame: usize,
//     _total_duration_ms: u64,
//     frame_count: usize,
//     frame_cache: &mut HashMap<(PathBuf, usize), RgbaImage>,
// ) -> Option<RgbaImage> {
//     let relative_frame = frame_number - start_frame;
//     let total_duration_frames = end_frame - start_frame;

//     // Calculate the corresponding frame index in the GIF
//     let progress = relative_frame as f64 / total_duration_frames as f64;
//     let gif_frame_index = (progress * frame_count as f64).floor() as usize % frame_count;

//     // Check if frame is in cache
//     let cache_key = (path.clone(), gif_frame_index);
//     if let Some(cached_frame) = frame_cache.get(&cache_key) {
//         return Some(cached_frame.clone());
//     }

//     // Load the frame
//     if let Ok(file) = File::open(path) {
//         if let Ok(decoder) = image::codecs::gif::GifDecoder::new(file) {
//             let frames = decoder.into_frames();
//             let mut frames_iter = frames.into_iter();

//             // Advance to the desired frame
//             for current_frame_index in 0..=gif_frame_index {
//                 if let Some(frame_result) = frames_iter.next() {
//                     if current_frame_index == gif_frame_index {
//                         if let Ok(frame) = frame_result {
//                             let image = frame.into_buffer();

//                             // Cache the frame
//                             frame_cache.insert(cache_key, image.clone());

//                             return Some(image);
//                         }
//                     }
//                 } else {
//                     break;
//                 }
//             }
//         }
//     }

//     None
// }
////// src/image_loader.rs //////

use anyhow::{Error, Result};
use image::{self, RgbaImage};
use image::AnimationDecoder;
use std::fs::{self, File};
use std::path::PathBuf;
use ffmpeg_next::{self as ffmpeg, codec, format, frame, media, Rational};
use std::rc::Rc;

pub enum Media {
    Static(RgbaImage),
    Animated {
        path: PathBuf,
        total_duration_ms: u64, // total duration in milliseconds
        frame_count: usize,
    },
    Video {
        path: PathBuf,
        total_duration_ms: u64,
        frame_rate: f64,
        width: u32,
        height: u32,
    },
}

struct VideoMetadata {
    duration_ms: u64,
    frame_rate: f64,
    width: u32,
    height: u32,
}

pub struct VideoClip {
    pub path: PathBuf,
    ictx: format::context::Input,
    decoder: codec::decoder::Video,
    stream_index: usize,
    scaler: ffmpeg_next::software::scaling::Context,
    next_packet: Option<ffmpeg_next::Packet>,
    pending_frames: Vec<frame::Video>,
    frame_finished: bool,
    pub total_duration_ms: u64,
    pub frame_rate: f64,
    pub width: u32,
    pub height: u32,
    pub current_pts: i64,
    pub time_base: Rational,
}

impl VideoClip {
    pub fn open(path: &PathBuf) -> Result<Self> {
        // Initialize FFmpeg and open the input file
        ffmpeg_next::init()?;
        let ictx = format::input(path)?;
        let input_stream = ictx
            .streams()
            .best(media::Type::Video)
            .ok_or_else(|| anyhow::anyhow!("Could not find video stream"))?;
        let stream_index = input_stream.index();

        // Get codec parameters and open the decoder
        let context_decoder = codec::Context::from_parameters(input_stream.parameters())?;
        let decoder = context_decoder.decoder().video()?;

        // Create a scaler to convert frames to RGBA
        let scaler = ffmpeg_next::software::scaling::Context::get(
            decoder.format(),
            decoder.width(),
            decoder.height(),
            ffmpeg_next::format::Pixel::RGBA,
            decoder.width(),
            decoder.height(),
            ffmpeg_next::software::scaling::flag::Flags::BILINEAR,
        )?;

        // Get video metadata
        let duration_ms = input_stream.duration() as u64;
        let frame_rate = input_stream.avg_frame_rate().0 as f64 / input_stream.avg_frame_rate().1 as f64;
        let width = decoder.width();
        let height = decoder.height();
        let time_base = input_stream.time_base();

        Ok(Self {
            path: path.clone(),
            ictx,
            decoder,
            stream_index,
            scaler,
            next_packet: None,
            pending_frames: Vec::new(),
            frame_finished: false,
            total_duration_ms: duration_ms,
            frame_rate,
            width: width as u32,
            height: height as u32,
            current_pts: 0,
            time_base,
        })
    }

    pub fn get_next_frame(&mut self) -> Result<Option<RgbaImage>> {
        // If we have frames pending from previous decode
        if let Some(frame) = self.pending_frames.pop() {
            self.current_pts += 1;
            return self.convert_frame_to_image(frame);
        }

        // Read packets and decode frames
        while let Some((stream, packet)) = self.ictx.packets().next() {
            if stream.index() == self.stream_index {
                self.decoder.send_packet(&packet)?;
                let mut frame = ffmpeg::util::frame::Video::empty();
                while let Ok(()) = self.decoder.receive_frame(&mut frame) {
                    self.current_pts += 1;
                    return self.convert_frame_to_image(frame);
                }
            }
        }

        // No more packets; flush the decoder
        self.decoder.send_eof()?;
        let mut frame = ffmpeg::util::frame::Video::empty();
        while let Ok(()) = self.decoder.receive_frame(&mut frame) {
            self.current_pts += 1;
            return self.convert_frame_to_image(frame);
        }

        Ok(None)
    }

    fn convert_frame_to_image(&mut self, frame: frame::Video) -> Result<Option<RgbaImage>> {
        let mut rgb_frame = frame::Video::empty();
        self.scaler.run(&frame, &mut rgb_frame)?;

        let data = rgb_frame.data(0);
        let width = rgb_frame.width() as u32;
        let height = rgb_frame.height() as u32;
        let stride = rgb_frame.stride(0);

        // Create RgbaImage
        let mut image = RgbaImage::new(width, height);
        let buf = image.as_mut();

        // Copy data from ffmpeg frame to image buffer
        for y in 0..height as usize {
            let src_start = y * stride as usize;
            let src_end = src_start + (width as usize * 4); // 4 bytes per pixel (RGBA)
            let src_row = &data[src_start..src_end];

            let dst_start = y * (width as usize * 4);
            let dst_end = dst_start + (width as usize * 4);

            buf[dst_start..dst_end].copy_from_slice(src_row);
        }

        Ok(Some(image))
    }
}

pub fn load_images(input_dirs: &[&str]) -> Result<Vec<Media>> {
    let mut media = Vec::new();

    // Initialize ffmpeg
    ffmpeg::init().unwrap();

    for input_dir in input_dirs {
        for entry in fs::read_dir(input_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_file() {
                let extension = path.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();

                match extension.as_str() {
                    "jpg" | "jpeg" | "png" | "bmp" | "tiff" => {
                        // Load static image
                        if let Ok(img) = image::open(&path) {
                            media.push(Media::Static(img.to_rgba8()));
                        }
                    },
                    "gif" => {
                        // Load animated GIF metadata only
                        if let Ok(file) = File::open(&path) {
                            if let Ok(decoder) = image::codecs::gif::GifDecoder::new(file) {
                                let frames = decoder.into_frames();
                                let mut total_duration_ms = 0u64;
                                let mut frame_count = 0usize;

                                // We need to get frame durations without loading frames into memory
                                for frame_result in frames.into_iter() {
                                    if let Ok(frame) = frame_result {
                                        let delay = frame.delay().numer_denom_ms().0;
                                        total_duration_ms += delay as u64;
                                        frame_count += 1;
                                    } else {
                                        break;
                                    }
                                }

                                // Store the path and minimal metadata
                                media.push(Media::Animated {
                                    path: path.clone(),
                                    total_duration_ms,
                                    frame_count,
                                });
                            }
                        }
                    },
                    "mp4" | "mov" | "avi" | "mkv" => {
                        // Load video files
                        let path_clone = path.clone();
                        if let Ok(metadata) = get_video_metadata(&path) {
                            media.push(Media::Video {
                                path: path_clone,
                                total_duration_ms: metadata.duration_ms,
                                frame_rate: metadata.frame_rate,
                                width: metadata.width,
                                height: metadata.height,
                            });
                        }
                    },
                    _ => {
                        println!("Unsupported file type: {}", path.display());
                    }
                }
            }
        }
    }

    if media.is_empty() {
        Err(anyhow::anyhow!("No media files found in the specified directory."))
    } else {
        Ok(media)
    }
}

fn get_video_metadata(path: &PathBuf) -> Result<VideoMetadata> {
    let ictx = ffmpeg::format::input(&path)?;
    let input: ffmpeg_next::Stream<'_> = ictx
        .streams()
        .best(ffmpeg::media::Type::Video)
        .ok_or_else(|| ffmpeg::Error::StreamNotFound)?;
    let codec_params = input.parameters();
    let (width, height) = if codec_params.medium() == ffmpeg::media::Type::Video {
        let width = unsafe {(*codec_params.as_ptr()).width} as u32;
        let height = unsafe {(*codec_params.as_ptr()).height} as u32;
        (width, height)
    } else { 
        return Err(Error::msg("Not a video"))
    };
    let time_base = input.time_base();
    let duration = input.duration();
    let frame_rate = input.avg_frame_rate();

    let duration_ms = ((duration as f64 * time_base.numerator() as f64 / time_base.denominator() as f64) * 1000.0) as u64;
    let frame_rate = frame_rate.numerator() as f64 / frame_rate.denominator() as f64;

    Ok(VideoMetadata {
        duration_ms,
        frame_rate,
        width,
        height,
    })
}

pub fn get_video_frame_at_timestamp(mp4_video_path: &PathBuf, timestamp_secs: u64) -> Result<RgbaImage> {
    // Initialize ffmpeg
    ffmpeg::init()?;

    // Open the input file
    let mut ictx = ffmpeg::format::input(mp4_video_path)?;

    // Find the best video stream
    let input_stream = ictx
        .streams()
        .best(ffmpeg::media::Type::Video)
        .ok_or_else(|| Error::msg("Could not find video stream"))?;

    let video_stream_index = input_stream.index();

    // Get codec parameters and open the decoder
    let codec_params = input_stream.parameters();
    let context_decoder = ffmpeg::codec::context::Context::from_parameters(codec_params)?;
    let mut decoder = context_decoder.decoder().video()?;

    // Get the stream's time base
    let tb = input_stream.time_base();

    // Convert the timestamp from seconds to the stream's time base units
    use ffmpeg_next::util::mathematics::rescale::Rescale;
    use ffmpeg::util::rational::Rational;

    // Check for timestamp overflow when casting to i64
    if timestamp_secs > i64::MAX as u64 {
        return Err(Error::msg("Timestamp is too large"));
    }
    let timestamp_secs_i64 = timestamp_secs as i64;

    let timestamp_in_stream_tb = timestamp_secs_i64.rescale(Rational(1, 1), tb);

    // Use av_seek_frame via FFI to seek to the desired timestamp
    unsafe {
        let ictx_ptr = ictx.as_mut_ptr();

        // Use AVSEEK_FLAG_BACKWARD to seek to the closest keyframe before the desired timestamp
        let ret = ffmpeg::ffi::av_seek_frame(
            ictx_ptr,
            video_stream_index as i32,
            timestamp_in_stream_tb,
            ffmpeg::ffi::AVSEEK_FLAG_BACKWARD,
        );
        if ret < 0 {
            return Err(Error::msg("Error seeking"));
        }
    }

    // Flush the decoder to clear any buffered frames
    decoder.flush();

    // Create a scaling context to convert frames to RGBA
    use ffmpeg::software::scaling::context::Context as ScalingContext;
    use ffmpeg::software::scaling::flag::Flags;

    let decoder_format = get_modern_pixel_format(decoder.format());
    let mut scaler = ScalingContext::get(
        decoder_format,
        decoder.width(),
        decoder.height(),
        ffmpeg::format::Pixel::RGBA,
        decoder.width(),
        decoder.height(),
        Flags::BILINEAR,
    )?;

    // Prepare frames
    let mut frame = ffmpeg::util::frame::Video::empty();
    let mut rgb_frame = ffmpeg::util::frame::Video::empty();

    // Read packets and decode frames
    for (stream, packet) in ictx.packets() {
        if stream.index() == video_stream_index {
            // Send the packet to the decoder
            decoder.send_packet(&packet)?;

            // Receive and process decoded frames
            while decoder.receive_frame(&mut frame).is_ok() {
                // Set color range for deprecated formats
                match decoder.format() {
                    ffmpeg::format::Pixel::YUVJ420P |
                    ffmpeg::format::Pixel::YUVJ422P |
                    ffmpeg::format::Pixel::YUVJ444P |
                    ffmpeg::format::Pixel::YUVJ440P => {
                        unsafe {
                            (*frame.as_mut_ptr()).color_range = ffmpeg::ffi::AVColorRange::AVCOL_RANGE_JPEG;
                        }
                    },
                    _ => {},
                }
                // Check if the frame PTS is greater than or equal to the desired timestamp
                if let Some(frame_pts) = frame.pts() {
                    if frame_pts >= timestamp_in_stream_tb {
                        // Convert the frame to RGBA format
                        scaler.run(&frame, &mut rgb_frame)?;

                        // Extract frame data
                        let data = rgb_frame.data(0);
                        let width = rgb_frame.width() as u32;
                        let height = rgb_frame.height() as u32;
                        let stride = rgb_frame.stride(0);

                        // Create RgbaImage
                        let mut image = RgbaImage::new(width, height);
                        let buf = image.as_mut();

                        // Copy data from ffmpeg frame to image buffer
                        for y in 0..height as usize {
                            let src_start = y * stride as usize;
                            let src_end = src_start + (width as usize * 4); // 4 bytes per pixel (RGBA)
                            let src_row = &data[src_start..src_end];

                            let dst_start = y * (width as usize * 4);
                            let dst_end = dst_start + (width as usize * 4);

                            buf[dst_start..dst_end].copy_from_slice(src_row);
                        }

                        return Ok(image);
                    }
                }
            }
        }
    }

    Err(Error::msg("Frame not found"))
}

fn get_modern_pixel_format(format: ffmpeg::format::Pixel) -> ffmpeg::format::Pixel {
    match format {
        ffmpeg::format::Pixel::YUVJ420P => ffmpeg::format::Pixel::YUV420P,
        ffmpeg::format::Pixel::YUVJ422P => ffmpeg::format::Pixel::YUV422P,
        ffmpeg::format::Pixel::YUVJ444P => ffmpeg::format::Pixel::YUV444P,
        ffmpeg::format::Pixel::YUVJ440P => ffmpeg::format::Pixel::YUV440P,
        _ => format,
    }
}
////// src/main.rs //////

use indicatif::{ProgressBar, ProgressStyle};
use std::cell::RefCell;
use std::collections::HashMap;

mod image_loader;
mod effects;
mod frame_renderer;
mod config;
mod scheduler;
mod types;
mod video_encoder;
mod transitions;

const INPUT_DIRS: &[&str] = &[
    r"G:\Backups\Images", 
    r"G:\Backups\Images\Set0", 
    r"G:\Backups\Images\Set1", 
    r"G:\Backups\Images\Set2", 
    r"G:\Backups\Images\Set3",
    r"G:\Backups\Images\Animated", 
    r"G:\Backups\Images\Animated2", 
    r"G:\Backups\Images\Animated3", 
    r"G:\Backups\Images\Animated4", 
];
pub(crate) const TEXTS: &[&str] = &[
    "SUBMIT",
    "OBEY",
    "HORNY",
    "CRAVE",
    "DEEP",
];

// const INPUT_DIRS: &[&str] = &[r"E:\Pictures\iPhone", r"E:\Pictures\Old Backgrounds"];
// pub(crate) const TEXTS: &[&str] = &[
//     "Wow",
//     "Cool",
//     "AI",
//     "Future",
//     "Overdrews",
// ];
pub const DEFAULT_DURATION_S: u32 = 60;
pub const DEFAULT_WIDTH: usize = 1920;
pub const DEFAULT_HEIGHT: usize = 1080;
pub const DEFAULT_OUTPUT_FILE: &str = "output2.ivf";

fn main() -> anyhow::Result<()> {
    let config = config::Config::default();

    // Initialize progress bar
    let pb = ProgressBar::new(config.total_frames as u64);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} frames ({eta} remaining)")?
            .progress_chars("#>-")
    );

    // Read all images from the input directory
    let images = image_loader::load_images(INPUT_DIRS).unwrap();

    // Initialize effect factory
    let mut effect_factory = effects::EffectFactory::new();

    // Initialize image scheduler
    let mut scheduler = scheduler::ImageScheduler::new(
        &images,
        &config,
        &mut effect_factory,
    );

    // Initialize video encoder
    let mut encoder = video_encoder::VideoEncoder::new(&config)?;

    // Main frame generation loop
    for frame_number in 0..config.total_frames {
        // Get the list of active image states for the current frame
        let active_primary_images = scheduler.get_active_primary_images(frame_number);
        let active_overlay_images = scheduler.get_active_overlay_images(frame_number);
        let active_globals = scheduler.get_active_global_effects(frame_number);
        let mut active_transitions = scheduler.get_active_transitions(frame_number);

        // Render the frame
        let frame = frame_renderer::render_frame(
            frame_number,
            &active_primary_images,
            &active_overlay_images,
            &active_globals,
            &config,
            &mut effect_factory,
            &mut active_transitions,
        );

        // Encode the frame
        encoder.encode_frame(&frame)?;

        // Update scheduler (if needed)
        scheduler.update(frame_number);

        // Update progress bar
        pb.inc(1);
    }

    // Finalize encoding
    encoder.finish()?;

    // Finish progress bar
    pb.finish_with_message(format!("Video has been successfully encoded to {}\n Convert the IVF file to MP4 using FFmpeg for playback.", config.output_file));

    Ok(())
}


////// src/scheduler.rs //////

use std::cell::RefCell;
use std::fs::File;
use std::path::PathBuf;
use std::rc::Rc;

use crate::config::Config;
use crate::effects::{self, Effect};
use crate::image_loader::{Media, VideoClip};
use crate::transitions::{Transition, TransitionEffect};
use crate::types::{ImageFrames, ImageState};
use image::{AnimationDecoder, RgbaImage};
use rand::prelude::*;
use rand::seq::SliceRandom;

pub struct ImageScheduler<'a> {
    primary_images: Vec<ImageState>,
    overlay_images: Vec<ImageState>,
    rng: ThreadRng,
    config: &'a Config,
    global_effects: Vec<ImageState>,
    transitions: Vec<Transition>,
    overlay_schedule: Vec<usize>,
}

impl<'a> ImageScheduler<'a> {
    pub fn new(
        images: &'a [Media],
        config: &'a crate::config::Config,
        effect_factory: &mut effects::EffectFactory,
    ) -> Self {
        let mut rng = rand::thread_rng();
        let mut primary_images = Vec::new();
        let mut overlay_images = Vec::new();
        let mut global_effects = Vec::new();

        // Schedule primary images
        Self::schedule_images(
            images,
            config,
            effect_factory,
            &mut rng,
            config.total_frames / 5,
            Self::generate_weighted_duration,
            (0.5, 1.5),
            |ef| ef.generate_random_effects(),
            &mut primary_images,
        );

        // Generate a schedule for overlay images
        let overlay_schedule = Self::generate_overlay_schedule(config.total_frames, &mut rng);

        // Schedule overlay images
        Self::schedule_overlay_images(
            images,
            config,
            effect_factory,
            &mut rng,
            overlay_schedule.clone(),
            &mut overlay_images,
        );

        // Schedule larger, slower overlay images
        Self::schedule_images(
            images,
            config,
            effect_factory,
            &mut rng.clone(),
            config.total_frames / 100,
            |rng, max| {
                let min_duration = config.framerate * 2;
                let max_duration = (config.framerate * 5).min(max as u32);
                if max_duration >= min_duration {
                    rng.gen_range(min_duration..=max_duration) as usize
                } else if max_duration > 0 {
                    rng.gen_range(1..=max_duration) as usize
                } else {
                    0
                }
            },
            (0.5, 1.5),
            |_| vec![Effect::LowOpacityFlash {
                flash_duration: rng.clone().gen_range(1..=3),
                flash_interval: rng.clone().gen_range(30..=100),
            }],
            &mut overlay_images,
        );

        // Schedule global effects
        Self::schedule_global_effects(
            config,
            effect_factory,
            &mut rng,
            &mut global_effects,
        );

        let mut scheduler = Self {
            primary_images,
            overlay_images,
            transitions: Vec::new(),
            rng,
            config,
            global_effects,
            overlay_schedule,
        };

        // Schedule transitions between images
        scheduler.schedule_transitions();

        scheduler
    }

    fn schedule_images(
        images: &'a [Media],
        config: &'a Config,
        effect_factory: &mut effects::EffectFactory,
        rng: &mut ThreadRng,
        num_images: usize,
        duration_generator: impl Fn(&mut ThreadRng, usize) -> usize,
        scale_range: (f32, f32),
        effect_generator: impl Fn(&mut effects::EffectFactory) -> Vec<Effect>,
        scheduled_images: &mut Vec<ImageState>,
    ) {
        for _ in 0..num_images {
            // Randomly select an image
            let media = images.choose(rng).unwrap();

            // Random start frame
            let start_frame = rng.gen_range(0..config.total_frames) as usize;

            // Maximum duration in frames
            let max_possible_duration = config.total_frames - start_frame;
            let duration = duration_generator(rng, max_possible_duration);
            if duration == 0 {
                continue;
            }

            // Create image state
            if let Some(image_state) = Self::create_image_state(
                media,
                config,
                effect_factory,
                rng,
                start_frame,
                duration,
                scale_range,
                &effect_generator,
            ) {
                scheduled_images.push(image_state);
            }
        }
    }

    fn schedule_overlay_images(
        images: &'a [Media],
        config: &'a Config,
        effect_factory: &mut effects::EffectFactory,
        rng: &mut ThreadRng,
        overlay_schedule: Vec<usize>,
        overlay_images: &mut Vec<ImageState>,
    ) {
        for &frame_number in &overlay_schedule {
            // Randomly select an image
            let media = images.choose(rng).unwrap();

            // Short durations for overlay images
            let max_possible_duration = config.total_frames - frame_number;
            let max_duration = 10.min(max_possible_duration);
            let duration = Self::generate_short_duration(rng, max_duration);
            if duration == 0 {
                continue;
            }

            // Create image state
            if let Some(image_state) = Self::create_image_state(
                media,
                config,
                effect_factory,
                rng,
                frame_number,
                duration,
                (0.05, 0.2),
                &|ef| ef.generate_overlay_effects(),
            ) {
                overlay_images.push(image_state);
            }
        }
    }

    fn schedule_global_effects(
        config: &'a Config,
        effect_factory: &mut effects::EffectFactory,
        rng: &mut ThreadRng,
        global_effects: &mut Vec<ImageState>,
    ) {
        for _ in 0..(config.total_frames / 50) {
            // Random start frame
            let start_frame = rng.gen_range(0..config.total_frames);

            // Maximum duration in frames
            let max_duration = ((config.framerate as f32 * 2.5)
                .min(config.total_frames as f32 - start_frame as f32)) as usize;
            let duration = Self::generate_weighted_duration(rng, max_duration);
            if duration == 0 {
                continue;
            }
            let end_frame = start_frame + duration;

            // Generate global effects
            let effects = effect_factory.generate_random_global_effects();

            // Create a dummy ImageState for global effects
            let image_state = ImageState {
                frames: ImageFrames::Static(image::RgbaImage::new(config.width as u32, config.height as u32)),
                position: (0, 0),
                scale: 1.0,
                start_frame,
                end_frame,
                effects,
            };

            global_effects.push(image_state);
        }
    }

    fn create_image_state(
        media: &Media,
        config: &Config,
        effect_factory: &mut effects::EffectFactory,
        rng: &mut ThreadRng,
        start_frame: usize,
        duration: usize,
        scale_range: (f32, f32),
        effect_generator: &impl Fn(&mut effects::EffectFactory) -> Vec<Effect>,
    ) -> Option<ImageState> {
        // Process media into frames and durations
        let (frames, any_frame, video_start_time) =
            Self::process_media_to_frames(media, duration, config, rng)?;

        // Calculate scale
        let scale = Self::calculate_scale(&any_frame, config, rng, scale_range);

        // Calculate position
        let position = Self::calculate_position(&any_frame, config, rng);

        // Generate effects
        let effects = effect_generator(effect_factory);

        // Create image state
        Some(ImageState {
            frames,
            position,
            scale,
            start_frame,
            end_frame: start_frame + duration,
            effects,
        })
    }

    fn process_media_to_frames(
        media: &Media,
        duration: usize,
        config: &Config,
        rng: &mut ThreadRng,
    ) -> Option<(ImageFrames, RgbaImage, Option<u64>)> {
        let mut video_start_time = None;

        let frames: ImageFrames = match media {
            Media::Static(img) => {
                ImageFrames::Static(img.clone())
            },
            Media::Animated { path, total_duration_ms, frame_count } => {
                ImageFrames::Animated {
                    path: path.clone(),
                    total_duration_ms: *total_duration_ms,
                    frame_count: *frame_count,
                }
            },
            Media::Video { path, total_duration_ms, frame_rate, width, height } => {
                let max_start_time_ms = *total_duration_ms as i64
                    - (duration as i64 * 1000 / config.framerate as i64);
                let video_start_time_ms = if max_start_time_ms > 0 {
                    rng.gen_range(0..=max_start_time_ms as u64)
                } else {
                    0
                };
                video_start_time = Some(video_start_time_ms);
                ImageFrames::Video {
                    clip: Rc::new(RefCell::new(VideoClip::open(&path).ok()?)),
                }
            }
        };

        let any_frame = frames.get_first_frame().unwrap_or_else(|| {
            // In case of error, use a placeholder image
            RgbaImage::new(1, 1)
        });

        Some((frames, any_frame, video_start_time))
    }

    fn calculate_scale(
        any_frame: &RgbaImage,
        config: &Config,
        rng: &mut ThreadRng,
        scale_range: (f32, f32),
    ) -> f32 {
        // You can customize this function further if needed
        let max_scale_x = config.width as f32 / any_frame.width() as f32;
        let max_scale_y = config.height as f32 / any_frame.height() as f32;
        let max_scale = max_scale_x.min(max_scale_y).min(scale_range.1); // Allow up to 1.5x scaling
        let min_scale = scale_range.0.min(max_scale);
        if max_scale > min_scale {
            rng.gen_range(min_scale..=max_scale)
        } else {
            min_scale
        }
    }
    

    fn calculate_position(
        any_frame: &RgbaImage,
        config: &Config,
        rng: &mut ThreadRng,
    ) -> (i64, i64) {
        let max_x = config.width.saturating_sub(any_frame.width() as usize) as i64;
        let max_y = config.height.saturating_sub(any_frame.height() as usize) as i64;
        let x = if max_x > 0 {
            rng.gen_range(0..=max_x)
        } else {
            0
        };
        let y = if max_y > 0 {
            rng.gen_range(0..=max_y)
        } else {
            0
        };
        (x, y)
    }

    // New function to generate overlay image schedule
    fn generate_overlay_schedule(total_frames: usize, rng: &mut ThreadRng) -> Vec<usize> {
        let mut overlay_schedule = Vec::new();
        let mut frame_number = 0;
    
        while frame_number < total_frames {
            // Randomly decide whether to schedule an overlay image
            if rng.gen_bool(0.5) { // 50% chance to schedule an image
                overlay_schedule.push(frame_number);
            }

            // Calculate the maximum possible gap without exceeding total_frames
            let max_gap = (total_frames - frame_number).min(30);
            if max_gap == 0 {
                break; // No more frames left
            }
    
            // Random gap before next potential overlay image
            let gap = rng.gen_range(1..=max_gap); // Adjust range as needed
            frame_number += gap;
        }
    
        overlay_schedule
    }

    // Helper function to generate short durations for overlay images
    fn generate_short_duration(rng: &mut ThreadRng, max_duration: usize) -> usize {
        if max_duration == 0 {
            1 // Default to a duration of 1 frame if max_duration is zero
        } else {
            let duration = rng.gen_range(1..=5).min(max_duration);
            duration
        }
    }

    pub fn get_active_primary_images(&self, frame_number: usize) -> Vec<ImageState> {
        self.primary_images
            .iter()
            .filter(|state| state.start_frame <= frame_number && state.end_frame > frame_number)
            .cloned()
            .collect()
    }

    pub fn get_active_overlay_images(&self, frame_number: usize) -> Vec<ImageState> {
        self.overlay_images
            .iter()
            .filter(|state| state.start_frame <= frame_number && state.end_frame > frame_number)
            .cloned()
            .collect()
    }

    pub fn get_active_global_effects(&self, frame_number: usize) -> Vec<ImageState> {
        self.global_effects
            .iter()
            .filter(|state| state.start_frame <= frame_number && state.end_frame > frame_number)
            .cloned()
            .collect()
    }

    pub fn get_active_transitions(&mut self, frame_number: usize) -> Vec<&mut Transition> {
        self.transitions
            .iter_mut()
            .filter(|transition| transition.start_frame <= frame_number && transition.end_frame > frame_number)
            .collect()
    }

    pub fn update(&mut self, _frame_number: usize) {
        // Optionally update the scheduler state if images need to be rescheduled
        // For example, remove images that have ended, add new ones, etc.
        // For now, we'll keep the initial set of images
    }

    fn generate_weighted_duration(rng: &mut ThreadRng, max_duration: usize) -> usize {
        let mut duration_options = Vec::new();

        // Always include duration of 1 frame if max_duration >= 1
        if max_duration >= 1 {
            duration_options.push((1..=1, 70)); // 70% chance of duration being 1 frame
        }

        // Include duration of 2 frames if max_duration >= 2
        if max_duration >= 2 {
            duration_options.push((2..=2, 15)); // 15% chance of duration being 2 frames
        }

        // Include durations between 3 and up to max_duration (min of 5)
        if max_duration >= 3 {
            let upper = max_duration.min(5);
            if upper >= 3 {
                duration_options.push((3..=upper, 10)); // 10% chance
            }
        }

        // Include longer durations if max_duration >= 6
        if max_duration >= 6 {
            duration_options.push((6..=max_duration, 5)); // 5% chance
        }

        // Recalculate total weight
        let total_weight: u32 = duration_options.iter().map(|(_, weight)| *weight).sum();

        // If no duration options are available (should not happen), default to 1 frame
        if total_weight == 0 {
            return 1;
        }
        
        // Generate a random number within the total weight
        let mut choice = rng.gen_range(0..total_weight);
        
        // Select a duration based on weighted probabilities
        for (duration_range, weight) in duration_options {
            if choice < weight {
                return rng.gen_range(duration_range);
            } else {
                choice -= weight;
            }
        }
        
        // Fallback in case of error
        1
    }

    fn generate_weighted_duration_continuous(rng: &mut ThreadRng, max_duration: usize) -> usize {
        // Use exponential distribution to favor shorter durations
        let lambda = 0.1; // Adjust lambda to control decay rate (higher lambda = more short durations)
        let duration = (-rng.gen::<f64>().ln() / lambda).ceil() as usize;
        
        // Ensure duration is at least 1 and does not exceed max_duration
        duration.clamp(1, max_duration)
    }

    pub fn schedule_transitions(&mut self) {
        let rng = &mut self.rng;

        // We need at least two images to create a transition
        if self.primary_images.len() < 2 {
            return;
        }

        // Iterate over pairs of images to create transitions
        for i in 0..(self.primary_images.len() - 1) {
            let (current_state, next_state)= self.primary_images.split_at_mut(i+1);
            let current_state = &mut current_state[current_state.len() - 1];
            let next_state = &mut next_state[0];

            // Decide whether to create a transition between these images
            if rng.gen_bool(0.3) { // 30% chance to create a transition
                // Determine the duration of the transition (e.g., between 1 and 3 seconds)
                let min_transition_duration = self.config.framerate; // 1 second
                let max_transition_duration = self.config.framerate * 3; // 3 seconds
                let transition_duration = rng.gen_range(min_transition_duration..=max_transition_duration);

                // Adjust start and end frames to accommodate the transition
                let transition_midpoint = (current_state.end_frame + next_state.start_frame) / 2;
                let transition_start_frame = transition_midpoint.saturating_sub(transition_duration as usize / 2);
                let transition_end_frame = transition_midpoint.saturating_add(transition_duration as usize / 2);

                // Ensure the frames are within bounds
                let transition_start_frame = transition_start_frame.max(current_state.start_frame);
                let transition_end_frame = transition_end_frame.min(next_state.end_frame);

                // Adjust current and next image durations if necessary
                if current_state.end_frame < transition_end_frame {
                    current_state.end_frame = transition_end_frame;
                }
                if next_state.start_frame > transition_start_frame {
                    next_state.start_frame = transition_start_frame;
                }

                // Resize images to frame dimensions
                let frame_width = self.config.width;
                let frame_height = self.config.height;

                // Resize current image
                // let from_image_resized: Vec<_> = current_state.frames.iter().map(|img| {
                //     image::imageops::resize(
                //         img,
                //         frame_width as u32,
                //         frame_height as u32,
                //         image::imageops::FilterType::Lanczos3,
                //     )
                // }).collect();

                // // Resize next image
                // let to_image_resized: Vec<_> = next_state.frames.iter().map(|img| {
                //     image::imageops::resize(
                //         img,
                //         frame_width as u32,
                //         frame_height as u32,
                //         image::imageops::FilterType::Lanczos3,
                //     )
                // }).collect();

                // Create the transition
                let transition = Transition {
                    from_frames: current_state.frames.clone(),//: from_image_resized,
                    to_frames: next_state.frames.clone(),//: to_image_resized,
                    start_frame: transition_start_frame,
                    end_frame: transition_end_frame,
                    duration: transition_end_frame - transition_start_frame,
                    effect: TransitionEffect::Reassembly,
                    particles: Vec::new(), // Particles will be initialized during rendering
                    initialized: false,
                };

                self.transitions.push(transition);
            }
        }
    }
}

////// src/transitions.rs //////

use image::{imageops, Rgba, RgbaImage};
use imageproc::geometric_transformations::{rotate_about_center, Interpolation};
use rand::prelude::*;
use crate::{config::Config, types::ImageFrames};

#[derive(Clone)]
pub struct Transition {
    pub from_frames: ImageFrames,
    pub to_frames: ImageFrames,
    pub start_frame: usize,
    pub end_frame: usize,
    pub duration: usize,
    pub effect: TransitionEffect,
    pub particles: Vec<Particle>,
    pub initialized: bool, 
}

#[derive(Clone)]
pub enum TransitionEffect {
    Reassembly,
    // You can add other transition effects in the future
}

// Define the Particle struct
#[derive(Clone)]
pub struct Particle {
    pub src_x: u32,
    pub src_y: u32,
    pub dst_x: u32,
    pub dst_y: u32,
    pub current_x: f32,
    pub current_y: f32,
    pub block_size: u32,
    // Transformations
    pub rotation: f32,
    pub rotation_speed: f32,
    pub scale: f32,
    pub alpha: f32,
}

impl Transition
{
    fn initialize_particles(&mut self, block_size: u32) {
        let first_frame = if let Some(first_frame) = self.from_frames.get_first_frame()
        {
            first_frame
        }
        else
        {
            return
        };
        let width = first_frame.width();
        let height = first_frame.height();

        let mut particles = Vec::new();
        let mut rng = rand::thread_rng();

        for y in (0..height).step_by(block_size as usize) {
            for x in (0..width).step_by(block_size as usize) {
                // Calculate block dimensions, ensuring they are at least 1
                let block_width = block_size.min(width - x).max(1);
                let block_height = block_size.min(height - y).max(1);

                // Skip blocks with zero dimensions
                if block_width == 0 || block_height == 0 {
                    continue;
                }

                // Randomize rotation speed
                let rotation_speed = rng.gen_range(-5.0..5.0);

                // Create the particle
                particles.push(Particle {
                    src_x: x,
                    src_y: y,
                    dst_x: x,
                    dst_y: y,
                    current_x: x as f32,
                    current_y: y as f32,
                    block_size,
                    rotation: 0.0,
                    rotation_speed,
                    scale: 1.0,
                    alpha: 1.0,
                });
            }
        }

        self.particles = particles;
        self.initialized = true;
    }
}

pub fn apply_reassembly_transition(
    frame_number: usize,
    transition: &mut Transition,
    config: &Config,
) -> RgbaImage {
    let progress = (frame_number - transition.start_frame) as f32 / transition.duration as f32;

    // Get the current frames
    let from_image = transition.from_frames.get_current_frame(frame_number, transition.start_frame, transition.end_frame)
        .unwrap_or_else(|| RgbaImage::new(config.width as u32, config.height as u32));
    let to_image = transition.to_frames.get_current_frame(frame_number, transition.start_frame, transition.end_frame)
        .unwrap_or_else(|| RgbaImage::new(config.width as u32, config.height as u32));

    let width = config.width;
    let height = config.height;

    // Initialize particles
    if !transition.initialized {
        let block_size = 16; // Adjust as needed
        transition.initialize_particles(block_size);
    }

    let mut frame = RgbaImage::new(width as u32, height as u32);

    for particle in &mut transition.particles {
        // Update transformations
        particle.rotation += particle.rotation_speed;
        particle.scale = (1.0 + 0.5 * (1.0 - progress)).max(0.1); // Ensure scale isn't too small

        // Interpolate position
        particle.current_x = particle.src_x as f32 + ((particle.dst_x as f32 - particle.src_x as f32) * progress);
        particle.current_y = particle.src_y as f32 + ((particle.dst_y as f32 - particle.src_y as f32) * progress);

        // Interpolate alpha for blending
        particle.alpha = 1.0 - ((progress * 2.0).min(1.0));

        // Extract blocks from current frames
        let src_block = extract_block(&from_image, particle.src_x, particle.src_y, particle.block_size);
        let dst_block = extract_block(&to_image, particle.dst_x, particle.dst_y, particle.block_size);

        // Blend and transform blocks
        let blended_block = blend_blocks(
            &src_block,
            &dst_block,
            progress,
            particle.rotation,
            particle.scale,
            particle.alpha,
        );

        // Overlay the transformed block onto the frame
        imageops::overlay(
            &mut frame,
            &blended_block,
            particle.current_x as i64,
            particle.current_y as i64,
        );
    }

    frame
}

fn extract_block(image: &RgbaImage, x: u32, y: u32, block_size: u32) -> RgbaImage {
    let width = image.width();
    let height = image.height();
    let block_width = block_size.min(width - x);
    let block_height = block_size.min(height - y);

    image::imageops::crop_imm(
        image,
        x,
        y,
        block_width,
        block_height,
    ).to_image()
}

fn blend_blocks(
    src_block: &RgbaImage,
    dst_block: &RgbaImage,
    progress: f32,
    rotation: f32,
    scale: f32,
    alpha: f32,
) -> RgbaImage {
    let (width, height) = src_block.dimensions();

    // Blend the pixels
    let mut blended_block = RgbaImage::new(width, height);
    for (x, y, pixel) in blended_block.enumerate_pixels_mut() {
        let src_pixel = get_pixel_safe(src_block, x, y);
        let dst_pixel = get_pixel_safe(dst_block, x, y);

        // Interpolate between src and dst pixels based on progress
        let blended_pixel = Rgba([
            (src_pixel[0] as f32 * (1.0 - progress) + dst_pixel[0] as f32 * progress) as u8,
            (src_pixel[1] as f32 * (1.0 - progress) + dst_pixel[1] as f32 * progress) as u8,
            (src_pixel[2] as f32 * (1.0 - progress) + dst_pixel[2] as f32 * progress) as u8,
            (src_pixel[3] as f32 * (1.0 - progress) + dst_pixel[3] as f32 * progress) as u8,
        ]);

        *pixel = blended_pixel;
    }

    // Apply transformations
    let transformed_block = transform_block(&blended_block, rotation, scale, alpha);

    transformed_block
}

fn transform_block(
    block: &RgbaImage,
    rotation: f32,
    scale: f32,
    alpha: f32,
) -> RgbaImage {
    let (width, height) = block.dimensions();

    // Early exit if width or height is zero
    if width == 0 || height == 0 {
        // Can't rotate or scale zero-dimension image
        return RgbaImage::new(1, 1); // Return a minimal image
    }

    // Apply rotation
    let rotated = rotate_about_center(
        block,
        rotation.to_radians(),
        Interpolation::Nearest,
        Rgba([0, 0, 0, 0]),
    );

    // Apply scaling
    let new_width = (width as f32 * scale).max(1.0) as u32;
    let new_height = (height as f32 * scale).max(1.0) as u32;

    // Skip if new dimensions are zero
    if new_width == 0 || new_height == 0 {
        return RgbaImage::new(1, 1); // Return a minimal image
    }

    let scaled = imageops::resize(
        &rotated,
        new_width,
        new_height,
        imageops::FilterType::Nearest, // TODO: Try nearest too
    );

    // Apply alpha
    let mut final_block = scaled.clone();
    for pixel in final_block.pixels_mut() {
        let new_alpha = (pixel[3] as f32 * alpha).clamp(0.0, 255.0) as u8;
        pixel[3] = new_alpha;
    }

    final_block
}

fn blend_pixels(
    base: &image::Rgba<u8>,
    overlay: &image::Rgba<u8>,
    alpha: f32,
) -> image::Rgba<u8> {
    let inv_alpha = 1.0 - alpha;
    image::Rgba([
        (base[0] as f32 * inv_alpha + overlay[0] as f32 * alpha) as u8,
        (base[1] as f32 * inv_alpha + overlay[1] as f32 * alpha) as u8,
        (base[2] as f32 * inv_alpha + overlay[2] as f32 * alpha) as u8,
        255,
    ])
}

// Helper function to get pixel safely, returns black if out of bounds
pub fn get_pixel_safe(image: &RgbaImage, x: u32, y: u32) -> image::Rgba<u8> {
    if x < image.width() && y < image.height() {
        *image.get_pixel(x, y)
    } else {
        image::Rgba([0, 0, 0, 255])
    }
}

////// src/types.rs //////

use image::{AnimationDecoder, RgbaImage};
use std::{cell::RefCell, fs::File, path::PathBuf, rc::Rc};

use crate::image_loader::VideoClip;

#[derive(Clone)]
pub struct ImageState {
    pub frames: ImageFrames,
    pub position: (i64, i64),
    pub scale: f32,
    pub start_frame: usize,
    pub end_frame: usize,
    pub effects: Vec<crate::effects::Effect>,
}

#[derive(Clone)]
pub enum ImageFrames {
    Static(RgbaImage),
    Animated {
        path: PathBuf,
        total_duration_ms: u64, // total duration in milliseconds
        frame_count: usize,
    },
    Video {
        clip: Rc<RefCell<VideoClip>>,
    },
}

impl ImageFrames
{
    pub fn get_first_frame(&self) -> Option<RgbaImage> {
        match self
        {
            ImageFrames::Static(img) => Some(img.clone()),
            ImageFrames::Animated { path, total_duration_ms: _, frame_count:_ } => 
            {
                // Load the first frame of the animated GIF
                if let Ok(file) = File::open(path) {
                    if let Ok(decoder) = image::codecs::gif::GifDecoder::new(file) {
                        let frames = decoder.into_frames();
                        let mut frames_iter = frames.into_iter();
                        if let Some(frame_result) = frames_iter.next() {
                            if let Ok(frame) = frame_result {
                                return Some(frame.into_buffer());
                            }
                        }
                    }
                }
                None
            },
            ImageFrames::Video { clip } => {
                if let Ok(frame) = crate::image_loader::get_video_frame_at_timestamp(&clip.borrow().path, 0 as u64) {
                    return Some(frame);
                }
                None
            },
        }
    }

    pub fn get_current_frame(&self, frame_number: usize, start_frame: usize, end_frame: usize) -> Option<RgbaImage> 
    {
        match self {
            ImageFrames::Static(img) => Some(img.clone()),
            ImageFrames::Animated { path, frame_count, .. } => {
                let relative_frame = frame_number - start_frame;
                let total_duration_frames = end_frame - start_frame;
                let progress = relative_frame as f64 / total_duration_frames as f64;
                let gif_frame_index = (progress * *frame_count as f64).floor() as usize % *frame_count;

                if let Ok(file) = File::open(path) {
                    if let Ok(decoder) = image::codecs::gif::GifDecoder::new(file) {
                        let frames = decoder.into_frames();
                        let mut frames_iter = frames.into_iter();
                        for current_frame_index in 0..=gif_frame_index {
                            if let Some(frame_result) = frames_iter.next() {
                                if current_frame_index == gif_frame_index {
                                    if let Ok(frame) = frame_result {
                                        return Some(frame.into_buffer());
                                    }
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
                None
            },
            ImageFrames::Video { clip } => {
                let mut clip = clip.borrow_mut();
                // Calculate the desired timestamp based on frame_number
                let relative_frame = frame_number - start_frame;
                let time_base: f64 = clip.time_base.into();
                let desired_pts: i64 = (relative_frame as f64 / clip.frame_rate as f64 / time_base).round() as i64;
                while clip.current_pts < desired_pts {
                    if clip.get_next_frame().ok().is_none() {
                        break;
                    }
                    clip.current_pts += 1;
                }
                clip.get_next_frame().ok().flatten()
            },
        }
    }
}

////// src/video_encoder.rs //////

use crate::config::Config;
use anyhow::Result;
use image::RgbaImage;
use rav1e::prelude::*;
use std::fs::File;
use std::io::Write;

pub struct VideoEncoder {
    ctx: Context<u8>,
    output: File,
}

impl VideoEncoder {
    pub fn new(config: &Config) -> Result<Self> {
        // Initialize rav1e encoder
        let mut encoder_cfg = EncoderConfig::default();
        encoder_cfg.width = config.width as usize;
        encoder_cfg.height = config.height as usize;
        encoder_cfg.time_base = Rational::new(config.timebase_num as u64, config.timebase_den as u64);
        encoder_cfg.quantizer = config.quantizer; // Adjust quality (0-255)
        encoder_cfg.speed_settings = SpeedSettings::from_preset(config.speed as u8);
        encoder_cfg.bit_depth = config.bit_depth;
        let cfg = rav1e::Config::default().with_encoder_config(encoder_cfg);
        let ctx: Context<u8> = cfg.new_context()?;

        // Open the output file and write IVF header
        let mut output = File::create(&config.output_file)?;
        write_ivf_header(
            &mut output,
            config.width as u32,
            config.height as u32,
            config.timebase_num,
            config.timebase_den,
        )?;

        Ok(Self { ctx, output })
    }

    pub fn encode_frame(&mut self, img: &RgbaImage) -> Result<()> {
        // Convert image to rav1e frame
        let rav1e_frame = {
            let mut frame = self.ctx.new_frame();
            image_to_frame(&img, &mut frame)?;
            frame
        };

        // Send the frame to the encoder
        self.ctx.send_frame(rav1e_frame)?;

        // Receive packets
        while let Some(packet) = self.ctx.receive_packet().ok() {
            write_ivf_frame(&mut self.output, &packet)?;
        }

        Ok(())
    }

    pub fn finish(&mut self) -> Result<()> {
        // Flush the encoder
        self.ctx.flush();

        // Receive and write remaining packets
        while let Some(packet) = self.ctx.receive_packet().ok() {
            write_ivf_frame(&mut self.output, &packet)?;
        }

        Ok(())
    }
}

// Helper function to convert image to rav1e frame
fn image_to_frame(img: &RgbaImage, frame: &mut Frame<u8>) -> anyhow::Result<()> {
    let (width, height) = img.dimensions();

    // Ensure the frame dimensions match the image dimensions
    // Y Plane
    let y_plane = &mut frame.planes[0];
    let y_width = y_plane.cfg.width;
    let y_stride = y_plane.cfg.stride;
    for y in 0..(height as usize) {
        let y_row = &mut y_plane.data_origin_mut()[y * y_stride..][..y_width];
        for x in 0..(width as usize) {
            let pixel = img.get_pixel(x as u32, y as u32);
            let r = pixel[0] as f32 / 255.0;
            let g = pixel[1] as f32 / 255.0;
            let b = pixel[2] as f32 / 255.0;
            let y_val = (0.299 * r + 0.587 * g + 0.114 * b) * 255.0;
            y_row[x] = y_val as u8;
        }
    }

    // U Plane (Subsampled)
    let u_plane = &mut frame.planes[1];
    let u_width = u_plane.cfg.width;
    let u_stride = u_plane.cfg.stride;
    for y in 0..(height as usize / 2) {
        let u_row = &mut u_plane.data_origin_mut()[y * u_stride..][..u_width];
        for x in 0..(width as usize / 2) {
            let mut r = 0.0;
            let mut g = 0.0;
            let mut b = 0.0;
            for dy in 0..2 {
                for dx in 0..2 {
                    let px = (2 * x + dx) as u32;
                    let py = (2 * y + dy) as u32;
                    if px < width && py < height {
                        let pixel = img.get_pixel(px, py);
                        r += pixel[0] as f32 / 255.0;
                        g += pixel[1] as f32 / 255.0;
                        b += pixel[2] as f32 / 255.0;
                    }
                }
            }
            r /= 4.0;
            g /= 4.0;
            b /= 4.0;
            let u_val = ((-0.169 * r - 0.331 * g + 0.5 * b) + 0.5) * 255.0;
            u_row[x] = u_val as u8;
        }
    }

    // V Plane (Subsampled)
    let v_plane = &mut frame.planes[2];
    let v_width = v_plane.cfg.width;
    let v_stride = v_plane.cfg.stride;
    for y in 0..(height as usize / 2) {
        let v_row = &mut v_plane.data_origin_mut()[y * v_stride..][..v_width];
        for x in 0..(width as usize / 2) {
            let mut r = 0.0;
            let mut g = 0.0;
            let mut b = 0.0;
            for dy in 0..2 {
                for dx in 0..2 {
                    let px = (2 * x + dx) as u32;
                    let py = (2 * y + dy) as u32;
                    if px < width && py < height {
                        let pixel = img.get_pixel(px, py);
                        r += pixel[0] as f32 / 255.0;
                        g += pixel[1] as f32 / 255.0;
                        b += pixel[2] as f32 / 255.0;
                    }
                }
            }
            r /= 4.0;
            g /= 4.0;
            b /= 4.0;
            let v_val = ((0.5 * r - 0.419 * g - 0.081 * b) + 0.5) * 255.0;
            v_row[x] = v_val as u8;
        }
    }

    Ok(())
}

// Function to write IVF header
fn write_ivf_header<W: Write>(
    w: &mut W,
    width: u32,
    height: u32,
    timebase_num: u32,
    timebase_den: u32,
) -> std::io::Result<()> {
    // IVF header is 32 bytes
    w.write_all(b"DKIF")?;                              // 0-3   file signature
    w.write_all(&0u16.to_le_bytes())?;                  // 4-5   version
    w.write_all(&32u16.to_le_bytes())?;                 // 6-7   header size
    w.write_all(b"AV01")?;                              // 8-11  fourcc
    w.write_all(&(width as u16).to_le_bytes())?;        // 12-13 width
    w.write_all(&(height as u16).to_le_bytes())?;       // 14-15 height
    w.write_all(&timebase_den.to_le_bytes())?;          // 16-19 timebase denominator (little endian)
    w.write_all(&timebase_num.to_le_bytes())?;          // 20-23 timebase numerator (little endian)
    w.write_all(&0u32.to_le_bytes())?;                  // 24-27 frame count (unknown)
    w.write_all(&0u32.to_le_bytes())?;                  // 28-31 unused
    Ok(())
}

// Function to write IVF frame
fn write_ivf_frame<W: Write>(w: &mut W, packet: &Packet<u8>) -> std::io::Result<()> {
    let size = packet.data.len() as u32;
    let timestamp = packet.input_frameno as u64;

    w.write_all(&size.to_le_bytes())?;
    w.write_all(&timestamp.to_le_bytes())?;
    w.write_all(&packet.data)?;

    Ok(())
}

////// end of source code //////

Please tell me how to update this so transitions and effects are combined into a single system (there shouldn't be a distinction, transisions are just different kinds of effects), that stil does everything it currently does.