#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum StreamType {
UncompressedVideo,
CompressedVideo,
UncompressedAudio,
CompressedAudio,
Ancillary,
}
impl StreamType {
#[must_use]
pub fn is_compressed(&self) -> bool {
matches!(
self,
StreamType::CompressedVideo | StreamType::CompressedAudio
)
}
#[must_use]
pub fn is_video(&self) -> bool {
matches!(
self,
StreamType::UncompressedVideo | StreamType::CompressedVideo
)
}
#[must_use]
pub fn is_audio(&self) -> bool {
matches!(
self,
StreamType::UncompressedAudio | StreamType::CompressedAudio
)
}
}
#[derive(Debug, Clone)]
pub struct StreamDescriptor {
id: String,
stream_type: StreamType,
width: u32,
height: u32,
rate: f64,
codec: String,
metadata: HashMap<String, String>,
}
impl StreamDescriptor {
pub fn new(
id: impl Into<String>,
stream_type: StreamType,
width: u32,
height: u32,
rate: f64,
codec: impl Into<String>,
) -> Self {
Self {
id: id.into(),
stream_type,
width,
height,
rate,
codec: codec.into(),
metadata: HashMap::new(),
}
}
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn stream_type(&self) -> &StreamType {
&self.stream_type
}
#[must_use]
pub fn is_hd(&self) -> bool {
self.stream_type.is_video() && self.width >= 1280
}
#[must_use]
pub fn is_uhd(&self) -> bool {
self.stream_type.is_video() && self.width >= 3840
}
#[must_use]
pub fn matches_resolution(&self, width: u32, height: u32) -> bool {
self.width == width && self.height == height
}
#[must_use]
pub fn codec(&self) -> &str {
&self.codec
}
#[must_use]
pub fn rate(&self) -> f64 {
self.rate
}
pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
}
pub fn get_metadata(&self, key: &str) -> Option<&str> {
self.metadata.get(key).map(String::as_str)
}
}
#[derive(Debug, Clone, Default)]
pub struct StreamDescriptorRegistry {
descriptors: HashMap<String, StreamDescriptor>,
}
impl StreamDescriptorRegistry {
#[must_use]
pub fn new() -> Self {
Self {
descriptors: HashMap::new(),
}
}
pub fn register(&mut self, desc: StreamDescriptor) -> bool {
let id = desc.id().to_owned();
if self.descriptors.contains_key(&id) {
return false;
}
self.descriptors.insert(id, desc);
true
}
#[must_use]
pub fn get(&self, id: &str) -> Option<&StreamDescriptor> {
self.descriptors.get(id)
}
#[must_use]
pub fn find_compatible(
&self,
stream_type: &StreamType,
width: u32,
height: u32,
) -> Vec<&StreamDescriptor> {
self.descriptors
.values()
.filter(|d| d.stream_type() == stream_type && d.matches_resolution(width, height))
.collect()
}
#[must_use]
pub fn count(&self) -> usize {
self.descriptors.len()
}
pub fn remove(&mut self, id: &str) -> bool {
self.descriptors.remove(id).is_some()
}
#[must_use]
pub fn by_type(&self, stream_type: &StreamType) -> Vec<&StreamDescriptor> {
self.descriptors
.values()
.filter(|d| d.stream_type() == stream_type)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hd_video() -> StreamDescriptor {
StreamDescriptor::new("hd-1", StreamType::CompressedVideo, 1920, 1080, 60.0, "av1")
}
fn sd_video() -> StreamDescriptor {
StreamDescriptor::new(
"sd-1",
StreamType::UncompressedVideo,
720,
576,
25.0,
"v210",
)
}
fn uhd_video() -> StreamDescriptor {
StreamDescriptor::new(
"uhd-1",
StreamType::CompressedVideo,
3840,
2160,
30.0,
"av1",
)
}
#[test]
fn test_stream_type_is_compressed_video() {
assert!(StreamType::CompressedVideo.is_compressed());
}
#[test]
fn test_stream_type_uncompressed_not_compressed() {
assert!(!StreamType::UncompressedVideo.is_compressed());
assert!(!StreamType::UncompressedAudio.is_compressed());
}
#[test]
fn test_stream_type_is_video() {
assert!(StreamType::CompressedVideo.is_video());
assert!(StreamType::UncompressedVideo.is_video());
assert!(!StreamType::CompressedAudio.is_video());
}
#[test]
fn test_stream_type_is_audio() {
assert!(StreamType::CompressedAudio.is_audio());
assert!(StreamType::UncompressedAudio.is_audio());
assert!(!StreamType::CompressedVideo.is_audio());
}
#[test]
fn test_is_hd_true() {
assert!(hd_video().is_hd());
}
#[test]
fn test_is_hd_false_for_sd() {
assert!(!sd_video().is_hd());
}
#[test]
fn test_is_uhd() {
assert!(uhd_video().is_uhd());
assert!(!hd_video().is_uhd());
}
#[test]
fn test_matches_resolution_true() {
assert!(hd_video().matches_resolution(1920, 1080));
}
#[test]
fn test_matches_resolution_false() {
assert!(!hd_video().matches_resolution(1280, 720));
}
#[test]
fn test_metadata_set_get() {
let mut d = hd_video();
d.set_metadata("bitrate", "50000000");
assert_eq!(d.get_metadata("bitrate"), Some("50000000"));
}
#[test]
fn test_registry_register_and_get() {
let mut reg = StreamDescriptorRegistry::new();
assert!(reg.register(hd_video()));
assert!(reg.get("hd-1").is_some());
}
#[test]
fn test_registry_duplicate_returns_false() {
let mut reg = StreamDescriptorRegistry::new();
reg.register(hd_video());
assert!(!reg.register(hd_video()));
assert_eq!(reg.count(), 1);
}
#[test]
fn test_registry_find_compatible() {
let mut reg = StreamDescriptorRegistry::new();
reg.register(hd_video());
reg.register(sd_video());
let found = reg.find_compatible(&StreamType::CompressedVideo, 1920, 1080);
assert_eq!(found.len(), 1);
assert_eq!(found[0].id(), "hd-1");
}
#[test]
fn test_registry_by_type() {
let mut reg = StreamDescriptorRegistry::new();
reg.register(hd_video());
reg.register(uhd_video());
reg.register(sd_video());
let compressed = reg.by_type(&StreamType::CompressedVideo);
assert_eq!(compressed.len(), 2);
}
#[test]
fn test_registry_remove() {
let mut reg = StreamDescriptorRegistry::new();
reg.register(sd_video());
assert!(reg.remove("sd-1"));
assert_eq!(reg.count(), 0);
}
#[test]
fn test_ancillary_not_compressed() {
assert!(!StreamType::Ancillary.is_compressed());
}
}