mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-04-01 02:03:42 -04:00
support run splitting in json api
This commit is contained in:
parent
9041eeb907
commit
6eda26a9cc
@ -118,6 +118,8 @@ Valid request parameters:
|
|||||||
* `startTime90k` and and `endTime90k` limit the data returned to only
|
* `startTime90k` and and `endTime90k` limit the data returned to only
|
||||||
recordings which overlap with the given half-open interval. Either or both
|
recordings which overlap with the given half-open interval. Either or both
|
||||||
may be absent; they default to the beginning and end of time, respectively.
|
may be absent; they default to the beginning and end of time, respectively.
|
||||||
|
* `split90k` causes long runs of recordings to be split at the next
|
||||||
|
convenient boundary after the given duration.
|
||||||
* TODO(slamb): `continue` to support paging. (If data is too large, the
|
* TODO(slamb): `continue` to support paging. (If data is too large, the
|
||||||
server should return a `continue` key which is expected to be returned on
|
server should return a `continue` key which is expected to be returned on
|
||||||
following requests.)
|
following requests.)
|
||||||
|
51
src/web.rs
51
src/web.rs
@ -319,8 +319,9 @@ impl Service {
|
|||||||
|
|
||||||
fn camera_html(&self, db: MutexGuard<db::LockedDatabase>, query: Option<&str>,
|
fn camera_html(&self, db: MutexGuard<db::LockedDatabase>, query: Option<&str>,
|
||||||
uuid: Uuid) -> Result<Vec<u8>, Error> {
|
uuid: Uuid) -> Result<Vec<u8>, Error> {
|
||||||
let (r, trim) = {
|
let (r, split, trim) = {
|
||||||
let mut time = recording::Time(i64::min_value()) .. recording::Time(i64::max_value());
|
let mut time = recording::Time(i64::min_value()) .. recording::Time(i64::max_value());
|
||||||
|
let mut split = recording::Duration(60 * 60 * recording::TIME_UNITS_PER_SEC);
|
||||||
let mut trim = false;
|
let mut trim = false;
|
||||||
if let Some(q) = query {
|
if let Some(q) = query {
|
||||||
for (key, value) in form_urlencoded::parse(q.as_bytes()) {
|
for (key, value) in form_urlencoded::parse(q.as_bytes()) {
|
||||||
@ -328,12 +329,13 @@ impl Service {
|
|||||||
match key {
|
match key {
|
||||||
"startTime" => time.start = recording::Time::parse(value)?,
|
"startTime" => time.start = recording::Time::parse(value)?,
|
||||||
"endTime" => time.end = recording::Time::parse(value)?,
|
"endTime" => time.end = recording::Time::parse(value)?,
|
||||||
|
"split" => split = recording::Duration(i64::from_str(value)?),
|
||||||
"trim" if value == "true" => trim = true,
|
"trim" if value == "true" => trim = true,
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(time, trim)
|
(time, split, trim)
|
||||||
};
|
};
|
||||||
let camera = db.get_camera(uuid)
|
let camera = db.get_camera(uuid)
|
||||||
.ok_or_else(|| Error::new("no such camera".to_owned()))?;
|
.ok_or_else(|| Error::new("no such camera".to_owned()))?;
|
||||||
@ -358,13 +360,8 @@ impl Service {
|
|||||||
</tr>\n",
|
</tr>\n",
|
||||||
HtmlEscaped(&camera.short_name), HtmlEscaped(&camera.description))?;
|
HtmlEscaped(&camera.short_name), HtmlEscaped(&camera.description))?;
|
||||||
|
|
||||||
// Rather than listing each 60-second recording, generate a HTML row for aggregated .mp4
|
|
||||||
// files of up to FORCE_SPLIT_DURATION each, provided there is no gap or change in video
|
|
||||||
// parameters between recordings.
|
|
||||||
static FORCE_SPLIT_DURATION: recording::Duration =
|
|
||||||
recording::Duration(60 * 60 * recording::TIME_UNITS_PER_SEC);
|
|
||||||
let mut rows = Vec::new();
|
let mut rows = Vec::new();
|
||||||
db.list_aggregated_recordings(camera.id, r.clone(), FORCE_SPLIT_DURATION, |row| {
|
db.list_aggregated_recordings(camera.id, r.clone(), split, |row| {
|
||||||
rows.push(row.clone());
|
rows.push(row.clone());
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
@ -412,7 +409,22 @@ impl Service {
|
|||||||
|
|
||||||
fn camera_recordings(&self, uuid: Uuid, query: Option<&str>, req: &Request)
|
fn camera_recordings(&self, uuid: Uuid, query: Option<&str>, req: &Request)
|
||||||
-> Result<Response<slices::Body>, Error> {
|
-> Result<Response<slices::Body>, Error> {
|
||||||
let r = Service::get_optional_range(query)?;
|
let (r, split) = {
|
||||||
|
let mut time = recording::Time(i64::min_value()) .. recording::Time(i64::max_value());
|
||||||
|
let mut split = recording::Duration(i64::max_value());
|
||||||
|
if let Some(q) = query {
|
||||||
|
for (key, value) in form_urlencoded::parse(q.as_bytes()) {
|
||||||
|
let (key, value) = (key.borrow(), value.borrow());
|
||||||
|
match key {
|
||||||
|
"startTime90k" => time.start = recording::Time::parse(value)?,
|
||||||
|
"endTime90k" => time.end = recording::Time::parse(value)?,
|
||||||
|
"split90k" => split = recording::Duration(i64::from_str(value)?),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(time, split)
|
||||||
|
};
|
||||||
if !is_json(req) {
|
if !is_json(req) {
|
||||||
let body: slices::Body = Box::new(stream::once(
|
let body: slices::Body = Box::new(stream::once(
|
||||||
Ok(ARefs::new(&b"only available for JSON requests"[..]))));
|
Ok(ARefs::new(&b"only available for JSON requests"[..]))));
|
||||||
@ -425,8 +437,7 @@ impl Service {
|
|||||||
let db = self.db.lock();
|
let db = self.db.lock();
|
||||||
let camera = db.get_camera(uuid)
|
let camera = db.get_camera(uuid)
|
||||||
.ok_or_else(|| Error::new("no such camera".to_owned()))?;
|
.ok_or_else(|| Error::new("no such camera".to_owned()))?;
|
||||||
db.list_aggregated_recordings(camera.id, r, recording::Duration(i64::max_value()),
|
db.list_aggregated_recordings(camera.id, r, split, |row| {
|
||||||
|row| {
|
|
||||||
let end = row.ids.end - 1; // in api, ids are inclusive.
|
let end = row.ids.end - 1; // in api, ids are inclusive.
|
||||||
out.recordings.push(json::Recording {
|
out.recordings.push(json::Recording {
|
||||||
start_id: row.ids.start,
|
start_id: row.ids.start,
|
||||||
@ -555,24 +566,6 @@ impl Service {
|
|||||||
let mp4 = builder.build(self.db.clone(), self.dir.clone())?;
|
let mp4 = builder.build(self.db.clone(), self.dir.clone())?;
|
||||||
Ok(http_entity::serve(mp4, req))
|
Ok(http_entity::serve(mp4, req))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses optional `startTime90k` and `endTime90k` query parameters, defaulting to the
|
|
||||||
/// full range of possible values.
|
|
||||||
fn get_optional_range(query: Option<&str>) -> Result<Range<recording::Time>, Error> {
|
|
||||||
let mut start = i64::min_value();
|
|
||||||
let mut end = i64::max_value();
|
|
||||||
if let Some(q) = query {
|
|
||||||
for (key, value) in form_urlencoded::parse(q.as_bytes()) {
|
|
||||||
let (key, value) = (key.borrow(), value.borrow());
|
|
||||||
match key {
|
|
||||||
"startTime90k" => start = i64::from_str(value)?,
|
|
||||||
"endTime90k" => end = i64::from_str(value)?,
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(recording::Time(start) .. recording::Time(end))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl server::Service for Service {
|
impl server::Service for Service {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user