feat(player): improve video scaling, YOLO positioning, and controls

This commit is contained in:
2026-03-19 01:40:25 +08:00
parent 64f217fdc4
commit f3443867c5

View File

@@ -42,7 +42,7 @@ fn run(config: &Config) -> Result<()> {
let ttf_context = ttf::init().map_err(|e| anyhow::anyhow!("TTF init failed: {}", e))?; let ttf_context = ttf::init().map_err(|e| anyhow::anyhow!("TTF init failed: {}", e))?;
let font: Option<Font> = ttf_context let font: Option<Font> = ttf_context
.load_font("/System/Library/Fonts/Supplemental/Arial.ttf", 20) .load_font("/System/Library/Fonts/Supplemental/Arial.ttf", 18)
.ok(); .ok();
let window = video_subsystem let window = video_subsystem
@@ -121,6 +121,8 @@ fn run(config: &Config) -> Result<()> {
.event_pump() .event_pump()
.map_err(|e| anyhow::anyhow!("Event pump failed: {}", e))?; .map_err(|e| anyhow::anyhow!("Event pump failed: {}", e))?;
let info_height: i32 = 50;
info!("Main loop started - waiting for events..."); info!("Main loop started - waiting for events...");
let mut running = true; let mut running = true;
@@ -150,21 +152,11 @@ fn run(config: &Config) -> Result<()> {
} }
sdl2::keyboard::Keycode::S => { sdl2::keyboard::Keycode::S => {
player_state.show_subtitle = !player_state.show_subtitle; player_state.show_subtitle = !player_state.show_subtitle;
info!( info!("Subtitle: {}", if player_state.show_subtitle { "ON" } else { "OFF" });
"Subtitle: {}",
if player_state.show_subtitle {
"ON"
} else {
"OFF"
}
);
} }
sdl2::keyboard::Keycode::Y => { sdl2::keyboard::Keycode::Y => {
player_state.show_yolo = !player_state.show_yolo; player_state.show_yolo = !player_state.show_yolo;
info!( info!("YOLO: {}", if player_state.show_yolo { "ON" } else { "OFF" });
"YOLO: {}",
if player_state.show_yolo { "ON" } else { "OFF" }
);
} }
sdl2::keyboard::Keycode::C => { sdl2::keyboard::Keycode::C => {
player_state.show_chunks = !player_state.show_chunks; player_state.show_chunks = !player_state.show_chunks;
@@ -172,61 +164,43 @@ fn run(config: &Config) -> Result<()> {
sdl2::keyboard::Keycode::M => { sdl2::keyboard::Keycode::M => {
player_state.muted = !player_state.muted; player_state.muted = !player_state.muted;
} }
sdl2::keyboard::Keycode::F => {}
sdl2::keyboard::Keycode::Left => { sdl2::keyboard::Keycode::Left => {
if shift { let step = if shift { 60 } else { 1 };
if let Some(ref mut dec) = decoder { if let Some(ref mut dec) = decoder {
let current = let current = player_state.current_frame.saturating_sub(step);
player_state.current_frame.saturating_sub(60); dec.seek(((current as f64 / player_state.fps) * 1000.0) as u64)?;
dec.seek(
((current as f64 / player_state.fps) * 1000.0) as u64,
)?;
player_state.current_frame = current; player_state.current_frame = current;
} }
} else {
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 => { sdl2::keyboard::Keycode::Right => {
if shift { let step = if shift { 60 } else { 1 };
if let Some(ref mut dec) = decoder { if let Some(ref mut dec) = decoder {
let current = player_state.current_frame + 60; let current = player_state.current_frame + step;
dec.seek( dec.seek(((current as f64 / player_state.fps) * 1000.0) as u64)?;
((current as f64 / player_state.fps) * 1000.0) as u64,
)?;
player_state.current_frame = current;
}
} else {
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; player_state.current_frame = current;
} }
} }
sdl2::keyboard::Keycode::Up => {
if player_state.zoom > 1.0 {
player_state.pan_y = (player_state.pan_y - 50.0).max(-500.0);
} }
sdl2::keyboard::Keycode::Equals
| sdl2::keyboard::Keycode::KpPlus => {
player_state.zoom = (player_state.zoom * 1.2).min(5.0);
} }
sdl2::keyboard::Keycode::Minus sdl2::keyboard::Keycode::Down => {
| sdl2::keyboard::Keycode::KpMinus => { if player_state.zoom > 1.0 {
player_state.pan_y = (player_state.pan_y + 50.0).min(500.0);
}
}
sdl2::keyboard::Keycode::Equals | sdl2::keyboard::Keycode::KpPlus => {
player_state.zoom = (player_state.zoom * 1.2).min(10.0);
}
sdl2::keyboard::Keycode::Minus | sdl2::keyboard::Keycode::KpMinus => {
player_state.zoom = (player_state.zoom / 1.2).max(0.5); player_state.zoom = (player_state.zoom / 1.2).max(0.5);
} if player_state.zoom == 1.0 {
sdl2::keyboard::Keycode::Backquote => {
player_state.zoom = 1.0;
player_state.pan_x = 0.0; player_state.pan_x = 0.0;
player_state.pan_y = 0.0; player_state.pan_y = 0.0;
} }
sdl2::keyboard::Keycode::R => { }
sdl2::keyboard::Keycode::Backquote | sdl2::keyboard::Keycode::R => {
player_state.zoom = 1.0; player_state.zoom = 1.0;
player_state.pan_x = 0.0; player_state.pan_x = 0.0;
player_state.pan_y = 0.0; player_state.pan_y = 0.0;
@@ -235,6 +209,17 @@ fn run(config: &Config) -> Result<()> {
} }
} }
} }
sdl2::event::Event::MouseWheel { y, .. } => {
if y > 0 {
player_state.zoom = (player_state.zoom * 1.1).min(10.0);
} else if y < 0 {
player_state.zoom = (player_state.zoom / 1.1).max(0.5);
if player_state.zoom == 1.0 {
player_state.pan_x = 0.0;
player_state.pan_y = 0.0;
}
}
}
_ => {} _ => {}
} }
} }
@@ -269,54 +254,55 @@ fn run(config: &Config) -> Result<()> {
} }
} }
if let Some(ref mut tex) = texture { let (vid_width, vid_height) = video_info
let dst = if player_state.zoom != 1.0 { .as_ref()
let info = video_info.as_ref().unwrap(); .map(|i| (i.width, i.height))
let w = (info.width as f32 * player_state.zoom) as u32; .unwrap_or((config.width, config.height));
let h = (info.height as f32 * player_state.zoom) as u32;
let x = ((config.width as i32 - w as i32) / 2) as i32 let scale: f64 = if player_state.zoom != 1.0 {
+ player_state.pan_x as i32; player_state.zoom as f64
let y = ((config.height as i32 - h as i32) / 2) as i32
+ player_state.pan_y as i32;
Rect::new(x, y, w, h)
} else { } else {
Rect::new(0, 0, 0, 0) let scale_x = config.width as f64 / vid_width as f64;
let scale_y = (config.height as i32 - info_height) as f64 / vid_height as f64;
scale_x.min(scale_y).min(1.0)
}; };
if player_state.zoom == 1.0 { let scaled_w = (vid_width as f64 * scale) as u32;
canvas.copy(tex, None, None).ok(); let scaled_h = (vid_height as f64 * scale) as u32;
} else { let offset_x =
((config.width as i32 - scaled_w as i32) / 2) as i32 + player_state.pan_x as i32;
let offset_y = ((config.height as i32 - info_height - scaled_h as i32) / 2) as i32
+ player_state.pan_y as i32;
if let Some(ref mut tex) = texture {
let dst = Rect::new(offset_x, offset_y, scaled_w, scaled_h);
canvas.copy(tex, None, Some(dst)).ok(); canvas.copy(tex, None, Some(dst)).ok();
} }
}
if player_state.show_yolo { if player_state.show_yolo {
if let Some(ref mut yolo_loader) = yolo { if let Some(ref mut yolo_loader) = yolo {
let detections = yolo_loader.get_detections(player_state.current_frame); let detections = yolo_loader.get_detections(player_state.current_frame);
for det in detections { for det in detections {
let x1 = (det.x1 as f32 * player_state.zoom) as i32 let x1 = (det.x1 * scale) as i32 + offset_x;
+ player_state.pan_x as i32 let y1 = (det.y1 * scale) as i32 + offset_y;
+ ((config.width as i32 - video_info.as_ref().map(|i| i.width as i32).unwrap_or(0)) / 2); let w = ((det.x2 - det.x1) * scale) as u32;
let y1 = (det.y1 as f32 * player_state.zoom) as i32 let h = ((det.y2 - det.y1) * scale) as u32;
+ player_state.pan_y as i32
+ ((config.height as i32 - video_info.as_ref().map(|i| i.height as i32).unwrap_or(0)) / 2);
let w = ((det.x2 - det.x1) as f32 * player_state.zoom) as u32;
let h = ((det.y2 - det.y1) as f32 * player_state.zoom) as u32;
canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 255, 0)); canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 255, 0));
let _ = canvas.draw_rect(Rect::new(x1, y1, w, h)); let _ = canvas.draw_rect(Rect::new(x1, y1, w, h));
if w > 30 && h > 10 {
if let Some(ref f) = font { if let Some(ref f) = font {
let label = format!("{} {:.0}%", det.class_name, det.confidence * 100.0); let label =
if let Ok(surface) = f format!("{} {:.0}%", det.class_name, det.confidence * 100.0);
.render(&label) if let Ok(surface) =
.solid(sdl2::pixels::Color::RGB(0, 255, 0)) f.render(&label).solid(sdl2::pixels::Color::RGB(0, 255, 0))
{ {
let tex_label = texture_creator if let Ok(tex_label) =
.create_texture_from_surface(&surface) texture_creator.create_texture_from_surface(&surface)
.ok(); {
if let Some(tex_label) = tex_label { let lw = surface.width().min(w as u32);
let label_rect = Rect::new(x1, y1 - 24, w.min(150), 24); let label_rect = Rect::new(x1, y1.saturating_sub(20), lw, 18);
canvas.copy(&tex_label, None, Some(label_rect)).ok(); canvas.copy(&tex_label, None, Some(label_rect)).ok();
} }
} }
@@ -324,6 +310,7 @@ fn run(config: &Config) -> Result<()> {
} }
} }
} }
}
if player_state.show_subtitle { if player_state.show_subtitle {
if let Some(ref asr_loader) = asr { if let Some(ref asr_loader) = asr {
@@ -333,21 +320,13 @@ fn run(config: &Config) -> Result<()> {
.render(&text) .render(&text)
.blended(sdl2::pixels::Color::RGBA(255, 255, 255, 255)) .blended(sdl2::pixels::Color::RGBA(255, 255, 255, 255))
{ {
let tex_label = texture_creator if let Ok(tex_label) = texture_creator.create_texture_from_surface(&surface) {
.create_texture_from_surface(&surface)
.ok();
if let Some(tex_label) = tex_label {
let query = tex_label.query(); let query = tex_label.query();
let x = (config.width - query.width) / 2; let x = (config.width - query.width) / 2;
let y = config.height - query.height - 40; let y = config.height - query.height - 20;
let rect = Rect::new(x as i32, y as i32, query.width, query.height); let rect = Rect::new(x as i32, y as i32, query.width, query.height);
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0, 0, 0, 180)); canvas.set_draw_color(sdl2::pixels::Color::RGBA(0, 0, 0, 200));
let _ = canvas.fill_rect(Rect::new( let _ = canvas.fill_rect(Rect::new(rect.x() - 8, rect.y() - 4, rect.width() + 16, rect.height() + 8));
rect.x() - 10,
rect.y() - 5,
rect.width() + 20,
rect.height() + 10,
));
canvas.copy(&tex_label, None, Some(rect)).ok(); canvas.copy(&tex_label, None, Some(rect)).ok();
} }
} }
@@ -358,51 +337,25 @@ fn run(config: &Config) -> Result<()> {
if let Some(ref f) = font { if let Some(ref f) = font {
let time_str = format_time(player_state.current_time_ms); let time_str = format_time(player_state.current_time_ms);
let frame_str = format!( let line1 = format!("Time: {} Frame: {}/{} FPS: {:.1}",
"Frame: {}/{} ({:.1}fps)", time_str, player_state.current_frame, player_state.total_frames, player_state.fps);
player_state.current_frame, player_state.total_frames, player_state.fps let line2 = format!("[S]ubtitle:{} [Y]OLO:{} [C]hunks [M]ute [+/-]Zoom{:.1}x [`]Reset",
if player_state.show_subtitle { " ON " } else { " OFF" },
if player_state.show_yolo { " ON " } else { " OFF" },
player_state.zoom
); );
let status_parts = vec![
format!("Time: {}", time_str),
frame_str,
if player_state.show_subtitle {
"Subtitle: ON".to_string()
} else {
String::new()
},
if player_state.show_yolo {
"YOLO: ON".to_string()
} else {
String::new()
},
if player_state.zoom != 1.0 {
format!("Zoom: {:.1}x", player_state.zoom)
} else {
String::new()
},
];
let y_offset = 10; for (i, line) in [line1, line2].iter().enumerate() {
for (i, part) in status_parts.iter().enumerate() { if let Ok(surface) = f.render(line).solid(sdl2::pixels::Color::RGB(180, 180, 180)) {
if !part.is_empty() { if let Ok(tex_label) = texture_creator.create_texture_from_surface(&surface) {
if let Ok(surface) = f let rect = Rect::new(10, 8 + (i as i32 * 20), surface.width(), surface.height());
.render(part)
.solid(sdl2::pixels::Color::RGB(200, 200, 200))
{
let tex_label = texture_creator
.create_texture_from_surface(&surface)
.ok();
if let Some(tex_label) = tex_label {
let rect = Rect::new(10, y_offset + (i as i32 * 22), surface.width(), surface.height());
canvas.copy(&tex_label, None, Some(rect)).ok(); canvas.copy(&tex_label, None, Some(rect)).ok();
} }
} }
} }
} }
}
canvas.present(); canvas.present();
std::thread::sleep(std::time::Duration::from_millis(16)); std::thread::sleep(std::time::Duration::from_millis(16));
} }