#![allow(dead_code)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StageType {
Decode,
Encode,
Filter,
Scale,
AudioProcess,
Demux,
Mux,
Passthrough,
Analyse,
}
impl StageType {
#[must_use]
pub fn is_transform(self) -> bool {
matches!(
self,
Self::Decode | Self::Encode | Self::Filter | Self::Scale | Self::AudioProcess
)
}
#[must_use]
pub fn is_transcode_class(self) -> bool {
matches!(self, Self::Decode | Self::Encode)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Decode => "decode",
Self::Encode => "encode",
Self::Filter => "filter",
Self::Scale => "scale",
Self::AudioProcess => "audio_process",
Self::Demux => "demux",
Self::Mux => "mux",
Self::Passthrough => "passthrough",
Self::Analyse => "analyse",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphStage {
pub id: u32,
pub name: String,
pub stage_type: StageType,
pub enabled: bool,
pub cost_estimate: u32,
}
impl GraphStage {
pub fn new(id: u32, name: impl Into<String>, stage_type: StageType) -> Self {
Self {
id,
name: name.into(),
stage_type,
enabled: true,
cost_estimate: 1,
}
}
#[must_use]
pub fn is_passthrough(&self) -> bool {
self.stage_type == StageType::Passthrough || !self.enabled
}
#[must_use]
pub fn is_transform(&self) -> bool {
self.enabled && self.stage_type.is_transform()
}
#[must_use]
pub fn with_cost(mut self, cost: u32) -> Self {
self.cost_estimate = cost;
self
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn enable(&mut self) {
self.enabled = true;
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TranscodeGraph {
stages: Vec<GraphStage>,
edges: Vec<(u32, u32)>,
}
impl TranscodeGraph {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_stage(&mut self, stage: GraphStage) -> u32 {
let id = stage.id;
self.stages.push(stage);
id
}
pub fn connect(&mut self, from_id: u32, to_id: u32) -> bool {
let has_from = self.stages.iter().any(|s| s.id == from_id);
let has_to = self.stages.iter().any(|s| s.id == to_id);
if has_from && has_to {
self.edges.push((from_id, to_id));
true
} else {
false
}
}
#[must_use]
pub fn stage_count(&self) -> usize {
self.stages.len()
}
#[must_use]
pub fn active_stage_count(&self) -> usize {
self.stages.iter().filter(|s| s.enabled).count()
}
#[must_use]
pub fn has_transcode_stage(&self) -> bool {
self.stages
.iter()
.any(|s| s.enabled && s.stage_type.is_transcode_class())
}
#[must_use]
pub fn is_fully_passthrough(&self) -> bool {
self.stages
.iter()
.all(|s| s.is_passthrough() || s.stage_type == StageType::Analyse)
}
#[must_use]
pub fn total_cost(&self) -> u32 {
self.stages
.iter()
.filter(|s| s.enabled)
.map(|s| s.cost_estimate)
.sum()
}
#[must_use]
pub fn stage_labels(&self) -> Vec<&str> {
self.stages.iter().map(|s| s.stage_type.label()).collect()
}
#[must_use]
pub fn find_stage(&self, id: u32) -> Option<&GraphStage> {
self.stages.iter().find(|s| s.id == id)
}
pub fn remove_stage(&mut self, id: u32) -> bool {
if let Some(pos) = self.stages.iter().position(|s| s.id == id) {
self.stages.remove(pos);
self.edges.retain(|(f, t)| *f != id && *t != id);
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_stage(id: u32, t: StageType) -> GraphStage {
GraphStage::new(id, format!("stage_{id}"), t)
}
#[test]
fn test_stage_type_is_transform_decode() {
assert!(StageType::Decode.is_transform());
}
#[test]
fn test_stage_type_is_transform_passthrough() {
assert!(!StageType::Passthrough.is_transform());
}
#[test]
fn test_stage_type_transcode_class() {
assert!(StageType::Encode.is_transcode_class());
assert!(!StageType::Filter.is_transcode_class());
}
#[test]
fn test_stage_type_labels_unique() {
let all = [
StageType::Decode,
StageType::Encode,
StageType::Filter,
StageType::Scale,
StageType::AudioProcess,
StageType::Demux,
StageType::Mux,
StageType::Passthrough,
StageType::Analyse,
];
let labels: Vec<_> = all.iter().map(|t| t.label()).collect();
let unique: std::collections::HashSet<_> = labels.iter().collect();
assert_eq!(labels.len(), unique.len());
}
#[test]
fn test_graph_stage_is_passthrough_when_disabled() {
let mut s = make_stage(1, StageType::Encode);
assert!(!s.is_passthrough());
s.disable();
assert!(s.is_passthrough());
s.enable();
assert!(!s.is_passthrough());
}
#[test]
fn test_graph_stage_passthrough_type() {
let s = make_stage(2, StageType::Passthrough);
assert!(s.is_passthrough());
}
#[test]
fn test_graph_add_stage_count() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Decode));
g.add_stage(make_stage(2, StageType::Encode));
assert_eq!(g.stage_count(), 2);
}
#[test]
fn test_has_transcode_stage_true() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Decode));
g.add_stage(make_stage(2, StageType::Encode));
assert!(g.has_transcode_stage());
}
#[test]
fn test_has_transcode_stage_false() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Passthrough));
g.add_stage(make_stage(2, StageType::Filter));
assert!(!g.has_transcode_stage());
}
#[test]
fn test_is_fully_passthrough() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Passthrough));
g.add_stage(make_stage(2, StageType::Analyse));
assert!(g.is_fully_passthrough());
}
#[test]
fn test_connect_valid() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Decode));
g.add_stage(make_stage(2, StageType::Encode));
assert!(g.connect(1, 2));
assert_eq!(g.edges.len(), 1);
}
#[test]
fn test_connect_invalid_id() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Decode));
assert!(!g.connect(1, 99));
}
#[test]
fn test_total_cost() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Decode).with_cost(10));
g.add_stage(make_stage(2, StageType::Encode).with_cost(20));
assert_eq!(g.total_cost(), 30);
}
#[test]
fn test_remove_stage_removes_edges() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(1, StageType::Decode));
g.add_stage(make_stage(2, StageType::Encode));
g.connect(1, 2);
g.remove_stage(1);
assert_eq!(g.stage_count(), 1);
assert!(g.edges.is_empty());
}
#[test]
fn test_find_stage() {
let mut g = TranscodeGraph::new();
g.add_stage(make_stage(42, StageType::Filter));
let s = g.find_stage(42).expect("should succeed in test");
assert_eq!(s.id, 42);
assert!(g.find_stage(0).is_none());
}
}