//! MoMentry Playground - Main entry point //! //! Unified media player with ASR/YOLO/Chunks overlay support use anyhow::Result; use log::{error, info}; use sdl2::pixels::PixelFormatEnum; use std::path::Path; mod config; mod overlay; mod player; mod web; use config::Config; use overlay::{AsrLoader, YoloLoader}; use player::ffmpeg::FFmpegDecoder; use player::state::{PlaybackState, PlayerState}; fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let config = Config::load()?; info!("MoMentry Playground starting..."); info!("Window: {}x{}", config.width, config.height); if let Err(e) = run(&config) { error!("Application error: {}", e); std::process::exit(1); } Ok(()) } fn run(config: &Config) -> Result<()> { let sdl_context = sdl2::init().map_err(|e| anyhow::anyhow!("SDL init failed: {}", e))?; let video_subsystem = sdl_context .video() .map_err(|e| anyhow::anyhow!("Video subsystem failed: {}", e))?; let window = video_subsystem .window("MoMentry Playground", config.width, config.height) .position_centered() .build() .map_err(|e| anyhow::anyhow!("Window creation failed: {}", e))?; let mut canvas = window .into_canvas() .build() .map_err(|e| anyhow::anyhow!("Canvas creation failed: {}", e))?; let texture_creator = canvas.texture_creator(); let mut decoder: Option = None; let mut texture: Option = None; let mut asr: Option = None; let mut yolo: Option = None; if let Some(ref video_path) = config.video { info!("Loading video: {:?}", video_path); let path = Path::new(video_path); let mut dec = FFmpegDecoder::new(path)?; let info = dec.get_info(); info!( "Video info: {}x{} @ {:.2}fps, {} frames", info.width, info.height, info.fps, info.frame_count ); let tex = texture_creator .create_texture_streaming(PixelFormatEnum::RGB24, info.width, info.height) .map_err(|e| anyhow::anyhow!("Texture creation failed: {}", e))?; texture = Some(tex); dec.start_decoding(0)?; decoder = Some(dec); } if let Some(ref asr_path) = config.asr { info!("Loading ASR: {:?}", asr_path); match AsrLoader::load(asr_path) { Ok(loader) => { info!("ASR loaded: {} segments", loader.segment_count()); asr = Some(loader); } Err(e) => { error!("Failed to load ASR: {}", e); } } } if let Some(ref yolo_path) = config.yolo { info!("Loading YOLO: {:?}", yolo_path); match YoloLoader::load(yolo_path) { Ok(loader) => { info!("YOLO loaded: {} frames", loader.total_frames()); yolo = Some(loader); } Err(e) => { error!("Failed to load YOLO: {}", e); } } } let mut player_state = PlayerState::default(); if let Some(ref dec) = decoder { let info = dec.get_info(); player_state.total_frames = info.frame_count; player_state.duration_ms = info.duration_ms; player_state.fps = info.fps; } let mut event_pump = sdl_context .event_pump() .map_err(|e| anyhow::anyhow!("Event pump failed: {}", e))?; info!("Main loop started - waiting for events..."); let mut running = true; while running { for event in event_pump.poll_iter() { match event { sdl2::event::Event::Quit { .. } => { running = false; } sdl2::event::Event::KeyDown { keycode, .. } => { if let Some(key) = keycode { match key { sdl2::keyboard::Keycode::Escape => running = false, sdl2::keyboard::Keycode::Space => { player_state.playback = if player_state.playback == PlaybackState::Playing { PlaybackState::Paused } else { PlaybackState::Playing }; } sdl2::keyboard::Keycode::S => { player_state.show_subtitle = !player_state.show_subtitle; } sdl2::keyboard::Keycode::Y => { player_state.show_yolo = !player_state.show_yolo; } sdl2::keyboard::Keycode::C => { player_state.show_chunks = !player_state.show_chunks; } sdl2::keyboard::Keycode::M => { player_state.muted = !player_state.muted; } sdl2::keyboard::Keycode::Left => { if let Some(ref mut dec) = decoder { let current = player_state.current_frame.saturating_sub(1); dec.seek( ((current as f64 / player_state.fps) * 1000.0) as u64, )?; player_state.current_frame = current; } } sdl2::keyboard::Keycode::Right => { if let Some(ref mut dec) = decoder { let current = player_state.current_frame + 1; dec.seek( ((current as f64 / player_state.fps) * 1000.0) as u64, )?; player_state.current_frame = current; } } _ => {} } } } _ => {} } } canvas.set_draw_color(sdl2::pixels::Color::BLACK); canvas.clear(); if player_state.playback == PlaybackState::Playing { if let Some(ref mut dec) = decoder { if let Some(ref mut tex) = texture { match dec.read_frame() { Ok(Some(data)) => { let info = dec.get_info(); player_state.current_frame += 1; player_state.current_time_ms = ((player_state.current_frame as f64 / info.fps) * 1000.0) as u64; tex.update(None, &data, (info.width * 3) as usize) .map_err(|e| anyhow::anyhow!("Texture update failed: {}", e))?; canvas .copy(tex, None, None) .map_err(|e| anyhow::anyhow!("Copy failed: {}", e))?; if player_state.show_yolo { if let Some(ref mut yolo_loader) = yolo { let detections = yolo_loader.get_detections(player_state.current_frame); for det in detections { let x1 = det.x1 as i32; let y1 = det.y1 as i32; let w = (det.x2 - det.x1) as u32; let h = (det.y2 - det.y1) as u32; canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 255, 0)); let _ = canvas.draw_rect(sdl2::rect::Rect::new(x1, y1, w, h)); } } } } Ok(None) => { info!("Playback ended"); break; } Err(e) => { error!("Frame read error: {}", e); break; } } } } } canvas.present(); std::thread::sleep(std::time::Duration::from_millis(16)); } info!("Application closed"); Ok(()) }