diff --git a/src/error.rs b/src/error.rs index 6b02648..ef2b659 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,6 +40,7 @@ use serde_json; use std::boxed::Box; use std::convert::From; use std::error; +use std::error::Error as E; use std::fmt; use std::io; use std::result; @@ -92,56 +93,49 @@ impl fmt::Display for Error { impl From for Error { fn from(err: rusqlite::Error) -> Self { - use std::error::{Error as E}; - Error{description: String::from(err.description()), - cause: Some(Box::new(err))} + Error{description: String::from(err.description()), cause: Some(Box::new(err))} + } +} + +impl From for Error { + fn from(err: fmt::Error) -> Self { + Error{description: String::from(err.description()), cause: Some(Box::new(err))} } } impl From for Error { fn from(err: io::Error) -> Self { - use std::error::{Error as E}; - Error{description: String::from(err.description()), - cause: Some(Box::new(err))} + Error{description: String::from(err.description()), cause: Some(Box::new(err))} } } impl From for Error { fn from(err: time::ParseError) -> Self { - use std::error::{Error as E}; - Error{description: String::from(err.description()), - cause: Some(Box::new(err))} + Error{description: String::from(err.description()), cause: Some(Box::new(err))} } } impl From for Error { fn from(err: num::ParseIntError) -> Self { - use std::error::{Error as E}; - Error{description: err.description().to_owned(), - cause: Some(Box::new(err))} + Error{description: err.description().to_owned(), cause: Some(Box::new(err))} } } impl From for Error { fn from(err: serde_json::Error) -> Self { - use std::error::{Error as E}; - Error{description: format!("{} ({})", err.description(), err), - cause: Some(Box::new(err))} + Error{description: format!("{} ({})", err.description(), err), cause: Some(Box::new(err))} } } impl From for Error { fn from(err: ffmpeg::Error) -> Self { - use std::error::{Error as E}; - Error{description: format!("{} ({})", err.description(), err), - cause: Some(Box::new(err))} + Error{description: format!("{} ({})", err.description(), err), cause: Some(Box::new(err))} } } impl From for Error { fn from(_: uuid::ParseError) -> Self { - Error{description: String::from("UUID parse error"), - cause: None} + Error{description: String::from("UUID parse error"), cause: None} } } diff --git a/src/web.rs b/src/web.rs index 0b67720..8326704 100644 --- a/src/web.rs +++ b/src/web.rs @@ -308,7 +308,21 @@ impl Handler { fn camera_html(&self, db: MutexGuard, query: &str, uuid: Uuid) -> Result, Error> { - let r = Handler::get_optional_range(query)?; + let (r, trim) = { + let mut start = i64::min_value(); + let mut end = i64::max_value(); + let mut trim = false; + for (key, value) in form_urlencoded::parse(query.as_bytes()) { + let (key, value) = (key.borrow(), value.borrow()); + match key { + "start_time_90k" => start = i64::from_str(value)?, + "end_time_90k" => end = i64::from_str(value)?, + "trim" if value == "true" => trim = true, + _ => {}, + } + }; + (recording::Time(start) .. recording::Time(end), trim) + }; let camera = db.get_camera(uuid) .ok_or_else(|| Error::new("no such camera".to_owned()))?; let mut buf = Vec::new(); @@ -338,17 +352,41 @@ impl Handler { static FORCE_SPLIT_DURATION: recording::Duration = recording::Duration(60 * 60 * recording::TIME_UNITS_PER_SEC); let mut rows = Vec::new(); - db.list_aggregated_recordings(camera.id, r, FORCE_SPLIT_DURATION, |row| { + db.list_aggregated_recordings(camera.id, r.clone(), FORCE_SPLIT_DURATION, |row| { rows.push(row.clone()); Ok(()) })?; - rows.sort_by(|r1, r2| r1.time.start.cmp(&r2.time.start)); + + // Display newest recording first. + rows.sort_by(|r1, r2| r2.ids.start.cmp(&r1.ids.start)); + for row in &rows { let seconds = (row.time.end.0 - row.time.start.0) / recording::TIME_UNITS_PER_SEC; + let url = { + let mut url = String::with_capacity(64); + use std::fmt::Write; + write!(&mut url, "view.mp4?s={}", row.ids.start)?; + if row.ids.end != row.ids.start + 1 { + write!(&mut url, "-{}", row.ids.end - 1)?; + } + if trim { + let rel_start = if row.time.start < r.start { Some(r.start - row.time.start) } + else { None }; + let rel_end = if row.time.end > r.end { Some(r.end - row.time.start) } + else { None }; + if rel_start.is_some() || rel_end.is_some() { + url.push('.'); + if let Some(s) = rel_start { write!(&mut url, "{}", s.0)?; } + url.push('-'); + if let Some(e) = rel_end { write!(&mut url, "{}", e.0)?; } + } + } + url + }; write!(&mut buf, "\ - {}\ + {}\ {}{}x{}{:.0}{:b}B{}bps\n", - row.ids.start, row.ids.end - 1, HumanizedTimestamp(Some(row.time.start)), + url, HumanizedTimestamp(Some(row.time.start)), HumanizedTimestamp(Some(row.time.end)), row.video_sample_entry.width, row.video_sample_entry.height, if seconds == 0 { 0. } else { row.video_samples as f32 / seconds as f32 },