mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-26 13:31:00 +00:00
onvif: Switch from minidom to xmltree for parsing ONVIF timed metadata
minidom doesn't handle various valid but suboptimal XML documents.
This commit is contained in:
parent
97e0852156
commit
b2ddb34258
4 changed files with 287 additions and 243 deletions
|
@ -15,11 +15,11 @@ gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/g
|
||||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
|
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
xmlparser = "0.13"
|
xmlparser = "0.13"
|
||||||
minidom = "0.15"
|
|
||||||
chrono = { version = "0.4", default-features = false }
|
chrono = { version = "0.4", default-features = false }
|
||||||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"] }
|
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"] }
|
||||||
pango = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
pango = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
||||||
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
||||||
|
xmltree = "0.10"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstrsonvif"
|
name = "gstrsonvif"
|
||||||
|
|
|
@ -21,6 +21,9 @@ mod onvifmetadataoverlay;
|
||||||
mod onvifmetadataparse;
|
mod onvifmetadataparse;
|
||||||
mod onvifmetadatapay;
|
mod onvifmetadatapay;
|
||||||
|
|
||||||
|
// ONVIF Timed Metadata schema
|
||||||
|
pub(crate) const ONVIF_METADATA_SCHEMA: &str = "http://www.onvif.org/ver10/schema";
|
||||||
|
|
||||||
// Offset in nanoseconds from midnight 01-01-1900 (prime epoch) to
|
// Offset in nanoseconds from midnight 01-01-1900 (prime epoch) to
|
||||||
// midnight 01-01-1970 (UNIX epoch)
|
// midnight 01-01-1970 (UNIX epoch)
|
||||||
pub(crate) const PRIME_EPOCH_OFFSET: gst::ClockTime = gst::ClockTime::from_seconds(2_208_988_800);
|
pub(crate) const PRIME_EPOCH_OFFSET: gst::ClockTime = gst::ClockTime::from_seconds(2_208_988_800);
|
||||||
|
@ -43,7 +46,7 @@ pub(crate) fn lookup_reference_timestamp(buffer: &gst::Buffer) -> Option<gst::Cl
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn xml_from_buffer(buffer: &gst::Buffer) -> Result<minidom::Element, gst::ErrorMessage> {
|
pub(crate) fn xml_from_buffer(buffer: &gst::Buffer) -> Result<xmltree::Element, gst::ErrorMessage> {
|
||||||
let map = buffer.map_readable().map_err(|_| {
|
let map = buffer.map_readable().map_err(|_| {
|
||||||
gst::error_msg!(gst::ResourceError::Read, ["Failed to map buffer readable"])
|
gst::error_msg!(gst::ResourceError::Read, ["Failed to map buffer readable"])
|
||||||
})?;
|
})?;
|
||||||
|
@ -55,7 +58,7 @@ pub(crate) fn xml_from_buffer(buffer: &gst::Buffer) -> Result<minidom::Element,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let root = utf8.parse::<minidom::Element>().map_err(|err| {
|
let root = xmltree::Element::parse(std::io::Cursor::new(utf8)).map_err(|err| {
|
||||||
gst::error_msg!(
|
gst::error_msg!(
|
||||||
gst::ResourceError::Read,
|
gst::ResourceError::Read,
|
||||||
["Failed to parse buffer as XML: {}", err]
|
["Failed to parse buffer as XML: {}", err]
|
||||||
|
@ -66,40 +69,45 @@ pub(crate) fn xml_from_buffer(buffer: &gst::Buffer) -> Result<minidom::Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn iterate_video_analytics_frames(
|
pub(crate) fn iterate_video_analytics_frames(
|
||||||
root: &minidom::Element,
|
root: &xmltree::Element,
|
||||||
) -> impl Iterator<
|
) -> impl Iterator<
|
||||||
Item = Result<(chrono::DateTime<chrono::FixedOffset>, &minidom::Element), gst::ErrorMessage>,
|
Item = Result<(chrono::DateTime<chrono::FixedOffset>, &xmltree::Element), gst::ErrorMessage>,
|
||||||
> {
|
> {
|
||||||
root.get_child("VideoAnalytics", "http://www.onvif.org/ver10/schema")
|
root.get_child(("VideoAnalytics", ONVIF_METADATA_SCHEMA))
|
||||||
.map(|analytics| {
|
.map(|analytics| {
|
||||||
analytics.children().filter_map(|el| {
|
analytics
|
||||||
// We are only interested in associating Frame metadata with video frames
|
.children
|
||||||
if el.is("Frame", "http://www.onvif.org/ver10/schema") {
|
.iter()
|
||||||
let timestamp = match el.attr("UtcTime") {
|
.filter_map(|n| n.as_element())
|
||||||
Some(timestamp) => timestamp,
|
.filter_map(|el| {
|
||||||
None => {
|
// We are only interested in associating Frame metadata with video frames
|
||||||
return Some(Err(gst::error_msg!(
|
if el.name == "Frame" && el.namespace.as_deref() == Some(ONVIF_METADATA_SCHEMA)
|
||||||
gst::ResourceError::Read,
|
{
|
||||||
["Frame element has no UtcTime attribute"]
|
let timestamp = match el.attributes.get("UtcTime") {
|
||||||
)));
|
Some(timestamp) => timestamp,
|
||||||
}
|
None => {
|
||||||
};
|
return Some(Err(gst::error_msg!(
|
||||||
|
gst::ResourceError::Read,
|
||||||
|
["Frame element has no UtcTime attribute"]
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let dt = match chrono::DateTime::parse_from_rfc3339(timestamp) {
|
let dt = match chrono::DateTime::parse_from_rfc3339(timestamp) {
|
||||||
Ok(dt) => dt,
|
Ok(dt) => dt,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Some(Err(gst::error_msg!(
|
return Some(Err(gst::error_msg!(
|
||||||
gst::ResourceError::Read,
|
gst::ResourceError::Read,
|
||||||
["Failed to parse UtcTime {}: {}", timestamp, err]
|
["Failed to parse UtcTime {}: {}", timestamp, err]
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Ok((dt, el)))
|
Some(Ok((dt, el)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
|
@ -9,8 +9,6 @@ use once_cell::sync::Lazy;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use minidom::Element;
|
|
||||||
|
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
"onvifmetadataoverlay",
|
"onvifmetadataoverlay",
|
||||||
|
@ -445,212 +443,231 @@ impl OnvifMetadataOverlay {
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let root = utf8.parse::<Element>().map_err(|err| {
|
let root =
|
||||||
gst::element_imp_error!(
|
xmltree::Element::parse(std::io::Cursor::new(utf8)).map_err(|err| {
|
||||||
self,
|
gst::element_imp_error!(
|
||||||
gst::ResourceError::Read,
|
self,
|
||||||
["Failed to parse buffer as XML: {}", err]
|
gst::ResourceError::Read,
|
||||||
);
|
["Failed to parse buffer as XML: {}", err]
|
||||||
|
);
|
||||||
|
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for object in root
|
for object in root
|
||||||
.get_child("VideoAnalytics", "http://www.onvif.org/ver10/schema")
|
.get_child(("VideoAnalytics", crate::ONVIF_METADATA_SCHEMA))
|
||||||
.map(|el| el.children().into_iter().collect())
|
.map(|e| e.children.iter().filter_map(|n| n.as_element()))
|
||||||
.unwrap_or_else(Vec::new)
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
{
|
{
|
||||||
if object.is("Frame", "http://www.onvif.org/ver10/schema") {
|
if object.name == "Frame"
|
||||||
for object in object.children() {
|
&& object.namespace.as_deref() == Some(crate::ONVIF_METADATA_SCHEMA)
|
||||||
if object.is("Object", "http://www.onvif.org/ver10/schema") {
|
{
|
||||||
gst::trace!(CAT, imp: self, "Handling object {:?}", object);
|
for object in object
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|n| n.as_element())
|
||||||
|
.filter(|e| {
|
||||||
|
e.name == "Object"
|
||||||
|
&& e.namespace.as_deref()
|
||||||
|
== Some(crate::ONVIF_METADATA_SCHEMA)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
gst::trace!(CAT, imp: self, "Handling object {:?}", object);
|
||||||
|
|
||||||
let object_id = match object.attr("ObjectId") {
|
let object_id = match object.attributes.get("ObjectId") {
|
||||||
Some(id) => id.to_string(),
|
Some(id) => id.to_string(),
|
||||||
None => {
|
None => {
|
||||||
gst::warning!(
|
gst::warning!(
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"XML Object with no ObjectId"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !object_ids.insert(object_id.clone()) {
|
|
||||||
gst::debug!(
|
|
||||||
CAT,
|
CAT,
|
||||||
"Skipping older version of object {}",
|
imp: self,
|
||||||
object_id
|
"XML Object with no ObjectId"
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let appearance = match object.get_child(
|
if !object_ids.insert(object_id.clone()) {
|
||||||
"Appearance",
|
gst::debug!(
|
||||||
"http://www.onvif.org/ver10/schema",
|
CAT,
|
||||||
) {
|
"Skipping older version of object {}",
|
||||||
Some(appearance) => appearance,
|
object_id
|
||||||
None => continue,
|
);
|
||||||
};
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let shape = match appearance
|
let appearance = match object
|
||||||
.get_child("Shape", "http://www.onvif.org/ver10/schema")
|
.get_child(("Appearance", crate::ONVIF_METADATA_SCHEMA))
|
||||||
|
{
|
||||||
|
Some(appearance) => appearance,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let shape = match appearance
|
||||||
|
.get_child(("Shape", crate::ONVIF_METADATA_SCHEMA))
|
||||||
|
{
|
||||||
|
Some(shape) => shape,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let tag = appearance
|
||||||
|
.get_child(("Class", crate::ONVIF_METADATA_SCHEMA))
|
||||||
|
.and_then(|class| {
|
||||||
|
class.get_child(("Type", crate::ONVIF_METADATA_SCHEMA))
|
||||||
|
})
|
||||||
|
.and_then(|t| t.get_text())
|
||||||
|
.map(|t| t.into_owned());
|
||||||
|
|
||||||
|
let bbox = match shape
|
||||||
|
.get_child(("BoundingBox", crate::ONVIF_METADATA_SCHEMA))
|
||||||
|
{
|
||||||
|
Some(bbox) => bbox,
|
||||||
|
None => {
|
||||||
|
gst::warning!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"XML Shape with no BoundingBox"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let left: f64 = match bbox
|
||||||
|
.attributes
|
||||||
|
.get("left")
|
||||||
|
.and_then(|val| val.parse().ok())
|
||||||
|
{
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
gst::warning!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"BoundingBox with no left attribute"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let right: f64 = match bbox
|
||||||
|
.attributes
|
||||||
|
.get("right")
|
||||||
|
.and_then(|val| val.parse().ok())
|
||||||
|
{
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
gst::warning!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"BoundingBox with no right attribute"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let top: f64 = match bbox
|
||||||
|
.attributes
|
||||||
|
.get("top")
|
||||||
|
.and_then(|val| val.parse().ok())
|
||||||
|
{
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
gst::warning!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"BoundingBox with no top attribute"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bottom: f64 = match bbox
|
||||||
|
.attributes
|
||||||
|
.get("bottom")
|
||||||
|
.and_then(|val| val.parse().ok())
|
||||||
|
{
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
gst::warning!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"BoundingBox with no bottom attribute"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let x1 = width / 2 + ((left * (width / 2) as f64) as i32);
|
||||||
|
let y1 = height / 2 - ((top * (height / 2) as f64) as i32);
|
||||||
|
let x2 = width / 2 + ((right * (width / 2) as f64) as i32);
|
||||||
|
let y2 = height / 2 - ((bottom * (height / 2) as f64) as i32);
|
||||||
|
|
||||||
|
let w = (x2 - x1) as u32;
|
||||||
|
let h = (y2 - y1) as u32;
|
||||||
|
|
||||||
|
let mut points = vec![];
|
||||||
|
|
||||||
|
if let Some(polygon) =
|
||||||
|
shape.get_child(("Polygon", crate::ONVIF_METADATA_SCHEMA))
|
||||||
|
{
|
||||||
|
for point in
|
||||||
|
polygon.children.iter().filter_map(|n| n.as_element())
|
||||||
{
|
{
|
||||||
Some(shape) => shape,
|
if point.name == "Point"
|
||||||
None => continue,
|
&& point.namespace.as_deref()
|
||||||
};
|
== Some(crate::ONVIF_METADATA_SCHEMA)
|
||||||
|
{
|
||||||
let tag = appearance
|
let px: f64 = match point
|
||||||
.get_child("Class", "http://www.onvif.org/ver10/schema")
|
.attributes
|
||||||
.and_then(|class| {
|
.get("x")
|
||||||
class.get_child(
|
.and_then(|val| val.parse().ok())
|
||||||
"Type",
|
|
||||||
"http://www.onvif.org/ver10/schema",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(|t| t.text());
|
|
||||||
|
|
||||||
let bbox = match shape.get_child(
|
|
||||||
"BoundingBox",
|
|
||||||
"http://www.onvif.org/ver10/schema",
|
|
||||||
) {
|
|
||||||
Some(bbox) => bbox,
|
|
||||||
None => {
|
|
||||||
gst::warning!(
|
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"XML Shape with no BoundingBox"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let left: f64 =
|
|
||||||
match bbox.attr("left").and_then(|val| val.parse().ok()) {
|
|
||||||
Some(val) => val,
|
|
||||||
None => {
|
|
||||||
gst::warning!(
|
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"BoundingBox with no left attribute"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let right: f64 =
|
|
||||||
match bbox.attr("right").and_then(|val| val.parse().ok()) {
|
|
||||||
Some(val) => val,
|
|
||||||
None => {
|
|
||||||
gst::warning!(
|
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"BoundingBox with no right attribute"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let top: f64 =
|
|
||||||
match bbox.attr("top").and_then(|val| val.parse().ok()) {
|
|
||||||
Some(val) => val,
|
|
||||||
None => {
|
|
||||||
gst::warning!(
|
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"BoundingBox with no top attribute"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let bottom: f64 = match bbox
|
|
||||||
.attr("bottom")
|
|
||||||
.and_then(|val| val.parse().ok())
|
|
||||||
{
|
|
||||||
Some(val) => val,
|
|
||||||
None => {
|
|
||||||
gst::warning!(
|
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"BoundingBox with no bottom attribute"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let x1 = width / 2 + ((left * (width / 2) as f64) as i32);
|
|
||||||
let y1 = height / 2 - ((top * (height / 2) as f64) as i32);
|
|
||||||
let x2 = width / 2 + ((right * (width / 2) as f64) as i32);
|
|
||||||
let y2 = height / 2 - ((bottom * (height / 2) as f64) as i32);
|
|
||||||
|
|
||||||
let w = (x2 - x1) as u32;
|
|
||||||
let h = (y2 - y1) as u32;
|
|
||||||
|
|
||||||
let mut points = vec![];
|
|
||||||
|
|
||||||
if let Some(polygon) = shape
|
|
||||||
.get_child("Polygon", "http://www.onvif.org/ver10/schema")
|
|
||||||
{
|
|
||||||
for point in polygon.children() {
|
|
||||||
if point
|
|
||||||
.is("Point", "http://www.onvif.org/ver10/schema")
|
|
||||||
{
|
{
|
||||||
let px: f64 = match point
|
Some(val) => val,
|
||||||
.attr("x")
|
None => {
|
||||||
.and_then(|val| val.parse().ok())
|
gst::warning!(
|
||||||
{
|
CAT,
|
||||||
Some(val) => val,
|
imp: self,
|
||||||
None => {
|
"Point with no x attribute"
|
||||||
gst::warning!(
|
);
|
||||||
CAT,
|
continue;
|
||||||
imp: self,
|
}
|
||||||
"Point with no x attribute"
|
};
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let py: f64 = match point
|
let py: f64 = match point
|
||||||
.attr("y")
|
.attributes
|
||||||
.and_then(|val| val.parse().ok())
|
.get("y")
|
||||||
{
|
.and_then(|val| val.parse().ok())
|
||||||
Some(val) => val,
|
{
|
||||||
None => {
|
Some(val) => val,
|
||||||
gst::warning!(
|
None => {
|
||||||
CAT,
|
gst::warning!(
|
||||||
imp: self,
|
CAT,
|
||||||
"Point with no y attribute"
|
imp: self,
|
||||||
);
|
"Point with no y attribute"
|
||||||
continue;
|
);
|
||||||
}
|
continue;
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let px =
|
let px = width / 2 + ((px * (width / 2) as f64) as i32);
|
||||||
width / 2 + ((px * (width / 2) as f64) as i32);
|
let px = (px as u32).saturating_sub(x1 as u32).min(w);
|
||||||
let px =
|
|
||||||
(px as u32).saturating_sub(x1 as u32).min(w);
|
|
||||||
|
|
||||||
let py = height / 2
|
let py =
|
||||||
- ((py * (height / 2) as f64) as i32);
|
height / 2 - ((py * (height / 2) as f64) as i32);
|
||||||
let py =
|
let py = (py as u32).saturating_sub(y1 as u32).min(h);
|
||||||
(py as u32).saturating_sub(y1 as u32).min(h);
|
|
||||||
|
|
||||||
points.push(Point { x: px, y: py });
|
points.push(Point { x: px, y: py });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shapes.push(Shape {
|
|
||||||
x: x1 as u32,
|
|
||||||
y: y1 as u32,
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
points,
|
|
||||||
tag,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shapes.push(Shape {
|
||||||
|
x: x1 as u32,
|
||||||
|
y: y1 as u32,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
points,
|
||||||
|
tag,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,18 +76,19 @@ impl Default for Settings {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Frame {
|
struct Frame {
|
||||||
video_analytics: minidom::Element,
|
video_analytics: xmltree::Element,
|
||||||
other_elements: Vec<minidom::Element>,
|
other_elements: Vec<xmltree::Element>,
|
||||||
events: Vec<gst::Event>,
|
events: Vec<gst::Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Frame {
|
impl Default for Frame {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let mut video_analytics = xmltree::Element::new("VideoAnalytics");
|
||||||
|
video_analytics.namespace = Some(String::from(crate::ONVIF_METADATA_SCHEMA));
|
||||||
|
video_analytics.prefix = Some(String::from("tt"));
|
||||||
|
|
||||||
Frame {
|
Frame {
|
||||||
video_analytics: minidom::Element::bare(
|
video_analytics,
|
||||||
"VideoAnalytics",
|
|
||||||
"http://www.onvif.org/ver10/schema",
|
|
||||||
),
|
|
||||||
other_elements: Vec::new(),
|
other_elements: Vec::new(),
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -372,22 +373,32 @@ impl OnvifMetadataParse {
|
||||||
.entry(dt_unix_ns)
|
.entry(dt_unix_ns)
|
||||||
.or_insert_with(Frame::default);
|
.or_insert_with(Frame::default);
|
||||||
|
|
||||||
frame.video_analytics.append_child(el.clone());
|
frame
|
||||||
|
.video_analytics
|
||||||
|
.children
|
||||||
|
.push(xmltree::XMLNode::Element(el.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let utc_time = running_time_to_utc_time(utc_time_running_time_mapping, running_time)
|
let utc_time = running_time_to_utc_time(utc_time_running_time_mapping, running_time)
|
||||||
.unwrap_or(gst::ClockTime::ZERO);
|
.unwrap_or(gst::ClockTime::ZERO);
|
||||||
|
|
||||||
for child in root.children() {
|
for child in root.children.iter().filter_map(|n| n.as_element()) {
|
||||||
let frame = queued_frames.entry(utc_time).or_insert_with(Frame::default);
|
let frame = queued_frames.entry(utc_time).or_insert_with(Frame::default);
|
||||||
|
|
||||||
if child.is("VideoAnalytics", "http://www.onvif.org/ver10/schema") {
|
if child.name == "VideoAnalytics"
|
||||||
for subchild in child.children() {
|
&& child.namespace.as_deref() == Some(crate::ONVIF_METADATA_SCHEMA)
|
||||||
if subchild.is("Frame", "http://www.onvif.org/ver10/schema") {
|
{
|
||||||
|
for subchild in child.children.iter().filter_map(|n| n.as_element()) {
|
||||||
|
if subchild.name == "Frame"
|
||||||
|
&& subchild.namespace.as_deref() == Some(crate::ONVIF_METADATA_SCHEMA)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.video_analytics.append_child(subchild.clone());
|
frame
|
||||||
|
.video_analytics
|
||||||
|
.children
|
||||||
|
.push(xmltree::XMLNode::Element(subchild.clone()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
frame.other_elements.push(child.clone());
|
frame.other_elements.push(child.clone());
|
||||||
|
@ -678,8 +689,7 @@ impl OnvifMetadataParse {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if frame.video_analytics.children().next().is_none() && frame.other_elements.is_empty()
|
if frame.video_analytics.children.is_empty() && frame.other_elements.is_empty() {
|
||||||
{
|
|
||||||
// Generate a gap event if there's no actual data for this time
|
// Generate a gap event if there's no actual data for this time
|
||||||
if !had_events {
|
if !had_events {
|
||||||
data.push(BufferOrEvent::Event(
|
data.push(BufferOrEvent::Event(
|
||||||
|
@ -753,21 +763,30 @@ impl OnvifMetadataParse {
|
||||||
frame_pts
|
frame_pts
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut xml =
|
let mut xml = xmltree::Element::new("MetadataStream");
|
||||||
minidom::Element::builder("MetadataStream", "http://www.onvif.org/ver10/schema")
|
xml.namespaces
|
||||||
.prefix(Some("tt".into()), "http://www.onvif.org/ver10/schema")
|
.get_or_insert_with(|| xmltree::Namespace(Default::default()))
|
||||||
.unwrap()
|
.put("tt", crate::ONVIF_METADATA_SCHEMA);
|
||||||
.build();
|
xml.namespace = Some(String::from(crate::ONVIF_METADATA_SCHEMA));
|
||||||
|
xml.prefix = Some(String::from("tt"));
|
||||||
|
|
||||||
if video_analytics.children().next().is_some() {
|
if !video_analytics.children.is_empty() {
|
||||||
xml.append_child(video_analytics);
|
xml.children
|
||||||
|
.push(xmltree::XMLNode::Element(video_analytics));
|
||||||
}
|
}
|
||||||
for child in other_elements {
|
for child in other_elements {
|
||||||
xml.append_child(child);
|
xml.children.push(xmltree::XMLNode::Element(child));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
if let Err(err) = xml.write_to_decl(&mut vec) {
|
if let Err(err) = xml.write_with_config(
|
||||||
|
&mut vec,
|
||||||
|
xmltree::EmitterConfig {
|
||||||
|
write_document_declaration: false,
|
||||||
|
perform_indent: true,
|
||||||
|
..xmltree::EmitterConfig::default()
|
||||||
|
},
|
||||||
|
) {
|
||||||
gst::error!(CAT, imp: self, "Can't serialize XML element: {}", err);
|
gst::error!(CAT, imp: self, "Can't serialize XML element: {}", err);
|
||||||
for event in eos_events {
|
for event in eos_events {
|
||||||
data.push(BufferOrEvent::Event(event));
|
data.push(BufferOrEvent::Event(event));
|
||||||
|
|
Loading…
Reference in a new issue