mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-12-03 06:22:32 -05:00
reorganize /recordings JSON response
I want to start returning the pixel aspect ratio of each video sample entry. It's silly to duplicate it for each returned recording, so let's instead return a videoSampleEntryId and then put all the information about each VSE once. This change doesn't actually handle pixel aspect ratio server-side yet. Most likely I'll require a new schema version for that, to store it as a new column in the database. Codec-specific logic in the database layer is awkward and I'd like to avoid it. I did a similar schema change to add the rfc6381_codec. I also adjusted ui-src/lib/models/Recording.js in a few ways: * fixed a couple mismatches between its field name and the key defined in the API. Consistency aids understanding. * dropped all the getters in favor of just setting the fields (with type annotations) as described here: https://google.github.io/styleguide/jsguide.html#features-classes-fields * where the wire format used undefined (to save space), translate it to a more natural null or false.
This commit is contained in:
48
src/json.rs
48
src/json.rs
@@ -372,9 +372,31 @@ impl<'a> TopLevel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ListRecordings {
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all="camelCase")]
|
||||
pub struct ListRecordings<'a> {
|
||||
pub recordings: Vec<Recording>,
|
||||
|
||||
// There are likely very few video sample entries for a given stream in a given day, so
|
||||
// representing with an unordered Vec (and having O(n) insert-if-absent) is probably better
|
||||
// than dealing with a HashSet's code bloat.
|
||||
#[serde(serialize_with = "ListRecordings::serialize_video_sample_entries")]
|
||||
pub video_sample_entries: (&'a db::LockedDatabase, Vec<i32>),
|
||||
}
|
||||
|
||||
impl<'a> ListRecordings<'a> {
|
||||
fn serialize_video_sample_entries<S>(video_sample_entries: &(&db::LockedDatabase, Vec<i32>),
|
||||
serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
let (db, ref v) = *video_sample_entries;
|
||||
let mut map = serializer.serialize_map(Some(v.len()))?;
|
||||
for id in v {
|
||||
map.serialize_entry(
|
||||
id,
|
||||
&VideoSampleEntry::from(&db.video_sample_entries_by_id().get(id).unwrap()))?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -384,7 +406,7 @@ pub struct Recording {
|
||||
pub end_time_90k: i64,
|
||||
pub sample_file_bytes: i64,
|
||||
pub video_samples: i64,
|
||||
pub video_sample_entry_sha1: String,
|
||||
pub video_sample_entry_id: String,
|
||||
pub start_id: i32,
|
||||
pub open_id: u32,
|
||||
|
||||
@@ -393,9 +415,25 @@ pub struct Recording {
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub end_id: Option<i32>,
|
||||
pub video_sample_entry_width: u16,
|
||||
pub video_sample_entry_height: u16,
|
||||
|
||||
#[serde(skip_serializing_if = "Not::not")]
|
||||
pub growing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all="camelCase")]
|
||||
pub struct VideoSampleEntry {
|
||||
pub sha1: String,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl VideoSampleEntry {
|
||||
fn from(e: &db::VideoSampleEntry) -> Self {
|
||||
Self {
|
||||
sha1: base::strutil::hex(&e.sha1),
|
||||
width: e.width,
|
||||
height: e.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
src/web.rs
59
src/web.rs
@@ -344,35 +344,36 @@ impl ServiceInner {
|
||||
}
|
||||
(time, split)
|
||||
};
|
||||
let mut out = json::ListRecordings{recordings: Vec::new()};
|
||||
{
|
||||
let db = self.db.lock();
|
||||
let camera = db.get_camera(uuid)
|
||||
.ok_or_else(|| plain_response(StatusCode::NOT_FOUND,
|
||||
format!("no such camera {}", uuid)))?;
|
||||
let stream_id = camera.streams[type_.index()]
|
||||
.ok_or_else(|| plain_response(StatusCode::NOT_FOUND,
|
||||
format!("no such stream {}/{}", uuid, type_)))?;
|
||||
db.list_aggregated_recordings(stream_id, r, split, &mut |row| {
|
||||
let end = row.ids.end - 1; // in api, ids are inclusive.
|
||||
let vse = db.video_sample_entries_by_id().get(&row.video_sample_entry_id).unwrap();
|
||||
out.recordings.push(json::Recording {
|
||||
start_id: row.ids.start,
|
||||
end_id: if end == row.ids.start { None } else { Some(end) },
|
||||
start_time_90k: row.time.start.0,
|
||||
end_time_90k: row.time.end.0,
|
||||
sample_file_bytes: row.sample_file_bytes,
|
||||
open_id: row.open_id,
|
||||
first_uncommitted: row.first_uncommitted,
|
||||
video_samples: row.video_samples,
|
||||
video_sample_entry_width: vse.width,
|
||||
video_sample_entry_height: vse.height,
|
||||
video_sample_entry_sha1: strutil::hex(&vse.sha1),
|
||||
growing: row.growing,
|
||||
});
|
||||
Ok(())
|
||||
}).map_err(internal_server_err)?;
|
||||
}
|
||||
let db = self.db.lock();
|
||||
let mut out = json::ListRecordings {
|
||||
recordings: Vec::new(),
|
||||
video_sample_entries: (&db, Vec::new()),
|
||||
};
|
||||
let camera = db.get_camera(uuid)
|
||||
.ok_or_else(|| plain_response(StatusCode::NOT_FOUND,
|
||||
format!("no such camera {}", uuid)))?;
|
||||
let stream_id = camera.streams[type_.index()]
|
||||
.ok_or_else(|| plain_response(StatusCode::NOT_FOUND,
|
||||
format!("no such stream {}/{}", uuid, type_)))?;
|
||||
db.list_aggregated_recordings(stream_id, r, split, &mut |row| {
|
||||
let end = row.ids.end - 1; // in api, ids are inclusive.
|
||||
out.recordings.push(json::Recording {
|
||||
start_id: row.ids.start,
|
||||
end_id: if end == row.ids.start { None } else { Some(end) },
|
||||
start_time_90k: row.time.start.0,
|
||||
end_time_90k: row.time.end.0,
|
||||
sample_file_bytes: row.sample_file_bytes,
|
||||
open_id: row.open_id,
|
||||
first_uncommitted: row.first_uncommitted,
|
||||
video_samples: row.video_samples,
|
||||
video_sample_entry_id: row.video_sample_entry_id.to_string(),
|
||||
growing: row.growing,
|
||||
});
|
||||
if !out.video_sample_entries.1.contains(&row.video_sample_entry_id) {
|
||||
out.video_sample_entries.1.push(row.video_sample_entry_id);
|
||||
}
|
||||
Ok(())
|
||||
}).map_err(internal_server_err)?;
|
||||
serve_json(req, &out)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user