use super::{xml_bool, xml_str};
use crate::error::OnvifError;
use crate::soap::{SoapError, XmlNode};
#[derive(Debug, Clone)]
pub struct MediaProfile {
pub token: String,
pub name: String,
pub fixed: bool,
pub video_source_config_token: Option<String>,
pub video_source_token: Option<String>,
pub video_encoder_token: Option<String>,
pub audio_source_token: Option<String>,
pub audio_encoder_token: Option<String>,
pub ptz_config_token: Option<String>,
}
impl MediaProfile {
pub(crate) fn from_xml(p: &XmlNode) -> Result<Self, OnvifError> {
let token = p
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("Profile/@token"))?
.to_string();
let vsc = p.child("VideoSourceConfiguration");
Ok(Self {
token,
fixed: p.attr("fixed") == Some("true"),
name: xml_str(p, "Name").unwrap_or_default(),
video_source_config_token: vsc.and_then(|n| n.attr("token")).map(str::to_string),
video_source_token: vsc.and_then(|n| xml_str(n, "SourceToken")),
video_encoder_token: p
.child("VideoEncoderConfiguration")
.and_then(|n| n.attr("token"))
.map(str::to_string),
audio_source_token: p
.child("AudioSourceConfiguration")
.and_then(|n| n.attr("token"))
.map(str::to_string),
audio_encoder_token: p
.child("AudioEncoderConfiguration")
.and_then(|n| n.attr("token"))
.map(str::to_string),
ptz_config_token: p
.child("PTZConfiguration")
.and_then(|n| n.attr("token"))
.map(str::to_string),
})
}
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("Profiles")
.map(Self::from_xml)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct StreamUri {
pub uri: String,
pub invalid_after_connect: bool,
pub invalid_after_reboot: bool,
pub timeout: String,
}
impl StreamUri {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let media_uri = resp
.child("MediaUri")
.ok_or_else(|| SoapError::missing("MediaUri"))?;
let uri = media_uri
.child("Uri")
.map(|n| n.text().to_string())
.filter(|s| !s.is_empty())
.ok_or_else(|| SoapError::missing("Uri"))?;
Ok(Self {
uri,
invalid_after_connect: xml_bool(media_uri, "InvalidAfterConnect"),
invalid_after_reboot: xml_bool(media_uri, "InvalidAfterReboot"),
timeout: xml_str(media_uri, "Timeout").unwrap_or_default(),
})
}
}
#[derive(Debug, Clone)]
pub struct SnapshotUri {
pub uri: String,
pub invalid_after_connect: bool,
pub invalid_after_reboot: bool,
pub timeout: String,
}
impl SnapshotUri {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let media_uri = resp
.child("MediaUri")
.ok_or_else(|| SoapError::missing("MediaUri"))?;
let uri = media_uri
.child("Uri")
.map(|n| n.text().to_string())
.filter(|s| !s.is_empty())
.ok_or_else(|| SoapError::missing("Uri"))?;
Ok(Self {
uri,
invalid_after_connect: xml_bool(media_uri, "InvalidAfterConnect"),
invalid_after_reboot: xml_bool(media_uri, "InvalidAfterReboot"),
timeout: xml_str(media_uri, "Timeout").unwrap_or_default(),
})
}
}
#[derive(Debug, Clone)]
pub struct MediaProfile2 {
pub token: String,
pub name: String,
pub fixed: bool,
pub video_source_config_token: Option<String>,
pub video_source_token: Option<String>,
pub video_encoder_token: Option<String>,
pub audio_source_token: Option<String>,
pub audio_encoder_token: Option<String>,
pub ptz_config_token: Option<String>,
}
impl MediaProfile2 {
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("Profiles")
.map(|p| {
let token = p
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("Profile/@token"))?
.to_string();
let vsc = p.path(&["Configurations", "VideoSource"]);
Ok(Self {
token,
name: xml_str(p, "Name").unwrap_or_default(),
fixed: p.attr("fixed") == Some("true"),
video_source_config_token: vsc
.and_then(|n| n.attr("token"))
.map(str::to_string),
video_source_token: vsc.and_then(|n| xml_str(n, "SourceToken")),
video_encoder_token: p
.path(&["Configurations", "VideoEncoder"])
.and_then(|n| n.attr("token"))
.map(str::to_string),
audio_source_token: p
.path(&["Configurations", "AudioSource"])
.and_then(|n| n.attr("token"))
.map(str::to_string),
audio_encoder_token: p
.path(&["Configurations", "Audio"])
.and_then(|n| n.attr("token"))
.map(str::to_string),
ptz_config_token: p
.path(&["Configurations", "PTZ"])
.and_then(|n| n.attr("token"))
.map(str::to_string),
})
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct MetadataConfiguration {
pub token: String,
pub name: String,
pub use_count: u32,
pub analytics: bool,
pub ptz_status: bool,
pub ptz_position: bool,
pub multicast_address: Option<String>,
pub multicast_port: Option<u32>,
}
impl MetadataConfiguration {
pub(crate) fn from_xml(n: &XmlNode) -> Result<Self, OnvifError> {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("MetadataConfiguration/@token"))?
.to_string();
let ptz = n.child("PTZStatus");
Ok(Self {
token,
name: xml_str(n, "Name").unwrap_or_default(),
use_count: n
.child("UseCount")
.and_then(|c| c.text().parse().ok())
.unwrap_or(0),
analytics: xml_bool(n, "Analytics"),
ptz_status: ptz.is_some_and(|p| xml_bool(p, "Status")),
ptz_position: ptz.is_some_and(|p| xml_bool(p, "Position")),
multicast_address: n
.path(&["Multicast", "Address", "IPv4Address"])
.map(|a| a.text().to_string()),
multicast_port: n
.path(&["Multicast", "Port"])
.and_then(|p| p.text().parse().ok()),
})
}
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("Configurations")
.map(Self::from_xml)
.collect()
}
pub(crate) fn to_xml_body(&self) -> String {
use super::xml_escape;
format!(
"<tr2:Configuration token=\"{token}\">\
<tt:Name>{name}</tt:Name>\
<tt:UseCount>{use_count}</tt:UseCount>\
<tt:Analytics>{analytics}</tt:Analytics>\
<tt:PTZStatus>\
<tt:Status>{status}</tt:Status>\
<tt:Position>{pos}</tt:Position>\
</tt:PTZStatus>\
</tr2:Configuration>",
token = xml_escape(&self.token),
name = xml_escape(&self.name),
use_count = self.use_count,
analytics = self.analytics,
status = self.ptz_status,
pos = self.ptz_position,
)
}
}
#[derive(Debug, Clone)]
pub struct MetadataConfigurationOptions {
pub ptz_status_filter_supported: bool,
pub analytics_supported: bool,
}
impl MetadataConfigurationOptions {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let opts = resp.child("Options").unwrap_or(resp);
Ok(Self {
ptz_status_filter_supported: opts.child("PTZStatusFilterOptions").is_some(),
analytics_supported: opts
.path(&["Extension", "AnalyticsSupported"])
.is_some_and(|n| n.text() == "true" || n.text() == "1"),
})
}
}
#[derive(Debug, Clone)]
pub struct AudioDecoderConfiguration {
pub token: String,
pub name: String,
pub use_count: u32,
}
impl AudioDecoderConfiguration {
pub(crate) fn from_xml(n: &XmlNode) -> Result<Self, OnvifError> {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("AudioDecoderConfiguration/@token"))?
.to_string();
Ok(Self {
token,
name: xml_str(n, "Name").unwrap_or_default(),
use_count: n
.child("UseCount")
.and_then(|c| c.text().parse().ok())
.unwrap_or(0),
})
}
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("Configurations")
.map(Self::from_xml)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct AudioOutputConfiguration {
pub token: String,
pub name: String,
pub use_count: u32,
pub output_token: String,
pub output_level: Option<u32>,
}
impl AudioOutputConfiguration {
pub(crate) fn from_xml(n: &XmlNode) -> Result<Self, OnvifError> {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("AudioOutputConfiguration/@token"))?
.to_string();
Ok(Self {
token,
name: xml_str(n, "Name").unwrap_or_default(),
use_count: n
.child("UseCount")
.and_then(|c| c.text().parse().ok())
.unwrap_or(0),
output_token: xml_str(n, "OutputToken").unwrap_or_default(),
output_level: n.child("OutputLevel").and_then(|c| c.text().parse().ok()),
})
}
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("Configurations")
.map(Self::from_xml)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct VideoSourceMode {
pub token: String,
pub max_framerate: f32,
pub max_resolution_width: u32,
pub max_resolution_height: u32,
pub encodings: Vec<String>,
pub reboot: bool,
}
impl VideoSourceMode {
pub(crate) fn from_xml(n: &XmlNode) -> Result<Self, OnvifError> {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("VideoSourceMode/@token"))?
.to_string();
Ok(Self {
token,
max_framerate: n
.child("MaxFramerate")
.and_then(|c| c.text().parse().ok())
.unwrap_or(0.0),
max_resolution_width: n
.path(&["MaxResolution", "Width"])
.and_then(|c| c.text().parse().ok())
.unwrap_or(0),
max_resolution_height: n
.path(&["MaxResolution", "Height"])
.and_then(|c| c.text().parse().ok())
.unwrap_or(0),
encodings: n
.child("Encodings")
.map(|e| e.text().split_whitespace().map(str::to_string).collect())
.unwrap_or_default(),
reboot: xml_bool(n, "Reboot"),
})
}
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("VideoSourceModes")
.map(Self::from_xml)
.collect()
}
}