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:
Scott Lamb
2020-03-13 21:20:51 -07:00
parent 317a620e6e
commit 3968bfe912
8 changed files with 141 additions and 136 deletions

View File

@@ -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,
}
}
}

View File

@@ -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)
}