diff --git a/src/Cargo.toml b/src/Cargo.toml index 548128c..d154652 100644 --- a/src/Cargo.toml +++ b/src/Cargo.toml @@ -9,6 +9,6 @@ edition = "2021" dioxus = "0.4.3" lib = { path = "../src-lib" } base64 = "0.21.5" -#dioxus-web = "0.4.3" opener = "0.6.1" +#dioxus-web = "0.4.3" dioxus-desktop = "0.4.3" diff --git a/src/src/main.rs b/src/src/main.rs index 8b3eac2..4b0b881 100644 --- a/src/src/main.rs +++ b/src/src/main.rs @@ -3,6 +3,8 @@ use dioxus::prelude::*; use lib; use base64::{engine::general_purpose, Engine as _}; +use core::cmp::Ordering; +use std::str::FromStr; fn main() { // launch the dioxus app in a webview @@ -17,20 +19,30 @@ fn App(cx: Scope) -> Element { let status = use_state(cx, String::new); cx.render(rsx! { header { - h1 { "home-video-blue-extractinator" } + style { " + body {{ + max-width: 600pt; + margin: auto; + }} + label {{ + display: block; + }} + label:has(input[type=checkbox]:checked) {{ + background-color: lightgreen; + }} + " } } main { rsx! { + h1 { "home-video-blue-extractinator" } div { input { r#type: "file", onchange: |evt| { to_owned![file]; - async move { - if let Some(file_engine) = &evt.files { - for f in &file_engine.files() { - file.set(f.clone()); - } + if let Some(file_engine) = &evt.files { + for f in &file_engine.files() { + file.set(f.clone()); } } }, @@ -38,7 +50,7 @@ fn App(cx: Scope) -> Element { p { file.get().clone() } } div { - input { r#type: "button", value: "analyze", disabled: file.get().len() == 0, onclick: move |evt| { + input { r#type: "button", value: "analyze", disabled: file.get().len() == 0, onclick: |evt| { to_owned![file]; to_owned![analysis]; to_owned![status]; @@ -49,20 +61,13 @@ fn App(cx: Scope) -> Element { } else { status.set(format!( "found {} clips to keep and {} clips to drop", - analyzed.with_content.len(), - analyzed.without_content.len(), + analyzed.result.iter().filter(|x| x.has_content).count(), + analyzed.result.iter().filter(|x| !x.has_content).count(), )); } analysis.set(analyzed); } }} - input { r#type: "button", value: "clipify", disabled: file.get().len() == 0, onclick: move |evt| { - to_owned![file]; - to_owned![status]; - async move { - status.set(clipify(file.get().clone())); - } - }} } div { br {} @@ -70,28 +75,43 @@ fn App(cx: Scope) -> Element { br {} } div { - h3 { "Clips to Keep" } - div { - analysis.get().with_content.iter().map(|a| { - rsx! { - p { - "{a.start}..{a.stop}: " - br {} - img { src: "data:image/png;base64, {a.screenshot}" } - } + h3 { "Content Spans" } + form { + onsubmit: |evt| { + let content_spans: Vec<_> = evt.values.iter() + .filter(|kv| kv.1.len() > 0) + .map(|kv| { + let span_str = kv.0; + let idx = span_str.find("..").unwrap(); + lib::video::ContentSpan{ + start: f32::from_str(&span_str[0..idx]).unwrap(), + stop: f32::from_str(&span_str[idx+2..span_str.len()]).unwrap(), + } + }) + .collect(); + status.set(format!("clipifying {:?}", content_spans)); + let file = file.get().clone(); + to_owned![status]; + async move { + let f = clipify(file, content_spans).await; + status.set(f); } - }) - } - } - div { - h3 { "Clips to Remove" } - div { - analysis.get().without_content.iter().map(|a| { + }, + input { r#type: "submit", value: "clipify selected spans", disabled: file.get().len() == 0 } + hr {} + analysis.get().result.iter().map(|a| { rsx! { - p { - "{a.start}..{a.stop}: " - br {} - img { src: "data:image/png;base64, {a.screenshot}" } + div { + label { + input { + r#type: "checkbox", + checked: a.has_content, + name: "{a.start}..{a.stop}", + } + "{a.start}..{a.stop}: " + br {} + img { src: "data:image/png;base64, {a.screenshot}" } + } } } }) @@ -103,25 +123,25 @@ fn App(cx: Scope) -> Element { } struct Analysis { - with_content: Vec, - without_content: Vec, + result: Vec, err: String, } impl Analysis { fn new() -> Self { Self{ - with_content: vec![], - without_content: vec![], + result: vec![], err: "".to_string(), } } } +#[derive(Clone)] struct Analyzed { start: String, stop: String, screenshot: String, + has_content: bool, } fn analyze(file: String) -> Analysis { @@ -129,8 +149,7 @@ fn analyze(file: String) -> Analysis { //let content_spans: Result, String> = Ok(vec![lib::video::ContentSpan{start: 0.5, stop: 20.2}]); if content_spans.is_err() { return Analysis{ - with_content: vec![], - without_content: vec![], + result: vec![], err: format!("failed to analyze {}: {}", file, content_spans.err().unwrap()), }; } @@ -155,26 +174,51 @@ fn analyze(file: String) -> Analysis { screenshot: match lib::video::screenshot_png(&file, ts) { Ok(png) => general_purpose::STANDARD.encode(&png), Err(_) => a_png.to_string(), - } + }, + has_content: false, } }; + let mut result = vec![]; + content_spans.iter() + .map(screenshot) + .map(|x| { + let mut x = x.clone(); + x.has_content = true; + x + }) + .for_each(|x| result.push(x)); + non_content_spans.iter() + .map(screenshot) + .for_each(|x| result.push(x)); + result.sort_by(|x, y| x.start.partial_cmp(&y.start).unwrap()); + Analysis{ - with_content: content_spans.iter().map(screenshot).collect(), - without_content: non_content_spans.iter().map(screenshot).collect(), + result: result, err: "".to_string(), } } -fn clipify(file: String) -> String { - match lib::clipify(&file.to_string()) { - Ok(path) => { - match opener::open(path.clone()) { - Ok(_) => path, +async fn clipify(file: String, content_spans: Vec) -> String { + let d = format!("{}.d", &file); + match content_spans.iter() + .map(|content_span| { + lib::video::clip( + &format!("{}/{}.mp4", &d, content_span.start), + &file, + *content_span, + ) + }) + .filter(|x| x.is_err()) + .map(|x| x.err().unwrap()) + .nth(0) { + Some(err_msg) => err_msg, + None => { + match opener::open(d.clone()) { + Ok(_) => d, Err(msg) => format!("failed to open clipify result: {}", msg), } }, - Err(msg) => msg, } }