use std::any::Any;
use crate::stream::{
camera::{CameraCapture, CameraCaptureConfig},
error::StreamCaptureError,
quote_pipeline_value,
};
pub struct RTSPCameraConfig {
pub url: String,
pub latency: u32,
}
impl CameraCaptureConfig for RTSPCameraConfig {
fn as_any(&self) -> &dyn Any {
self
}
}
impl RTSPCameraConfig {
pub fn new() -> Self {
Self {
url: String::new(),
latency: 0,
}
}
pub fn with_url(mut self, url: &str) -> Self {
self.url = url.to_string();
self
}
pub fn with_latency(mut self, latency: u32) -> Self {
self.latency = latency;
self
}
pub fn with_settings(
mut self,
username: &str,
password: &str,
ip: &str,
port: &u16,
stream: &str,
) -> Self {
let username = percent_encode_userinfo(username);
let password = percent_encode_userinfo(password);
self.url = format!("rtsp://{username}:{password}@{ip}:{port}/{stream}");
self
}
pub fn build(self) -> Result<CameraCapture, StreamCaptureError> {
CameraCapture::new(&self)
}
}
impl Default for RTSPCameraConfig {
fn default() -> Self {
Self::new()
}
}
pub fn rtsp_camera_pipeline_description(
url: &str,
latency: u32,
) -> Result<String, StreamCaptureError> {
let url = quote_pipeline_value(url)?;
Ok(format!(
"rtspsrc location={url} latency={latency} ! rtph264depay ! avdec_h264 ! videoconvert ! video/x-raw,format=RGB ! appsink name=sink"
))
}
fn percent_encode_userinfo(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for b in value.bytes() {
if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~') {
out.push(b as char);
} else {
out.push_str(&format!("%{b:02X}"));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn credentials_are_percent_encoded() {
let cfg = RTSPCameraConfig::new().with_settings("us er", "p@ss:!/", "10.0.0.1", &554, "s");
assert_eq!(cfg.url, "rtsp://us%20er:p%40ss%3A%21%2F@10.0.0.1:554/s");
}
#[test]
fn pipeline_url_is_quoted_and_validated() -> Result<(), StreamCaptureError> {
let desc = rtsp_camera_pipeline_description("rtsp://x ! filesink location=/tmp/x", 0)?;
assert!(desc.starts_with("rtspsrc location=\"rtsp://x ! filesink location=/tmp/x\" "));
assert!(rtsp_camera_pipeline_description("rtsp://x\" ! fakesink", 0).is_err());
Ok(())
}
}