Skip to main content

viser_contextaware/
lib.rs

1//! Device-aware encoding ladder generation for the `viser` video-encoding-optimizer workspace.
2//!
3//! Runs per-title analysis tuned for each device class (Mobile, Desktop, TV, TV 4K), applying
4//! per-class resolution caps, codec preferences, and VMAF model selection. `analyze` produces a
5//! convex hull and bitrate ladder for every device `Profile`.
6
7mod profile;
8
9pub use profile::*;
10
11use serde::{Deserialize, Serialize};
12use std::time::{Duration, Instant};
13use viser_encoding::{Config as EncodingConfig, ProgressSender};
14use viser_hull::{Hull, Point};
15use viser_ladder::{self, Ladder};
16
17/// Config for context-aware analysis.
18#[derive(Debug, Clone)]
19pub struct Config {
20    /// Device profiles to analyze, each with its own resolution and codec constraints.
21    pub profiles: Vec<Profile>,
22    /// CRF quality values to sweep across all profiles.
23    pub crf_values: Vec<i32>,
24    /// Generic encoder preset name shared by all profiles.
25    pub preset: String,
26    /// Frame subsample interval for VMAF scoring; 0 evaluates every frame.
27    pub subsample: i32,
28    /// Number of concurrent encodes per profile analysis.
29    pub parallel: i32,
30}
31
32impl Default for Config {
33    fn default() -> Self {
34        Self {
35            profiles: all_profiles(),
36            crf_values: vec![18, 22, 26, 30, 34, 38, 42],
37            preset: "veryfast".into(),
38            subsample: 5,
39            parallel: 2,
40        }
41    }
42}
43
44/// Analysis output for a single device profile.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct DeviceResult {
47    /// The device profile this result was produced for.
48    pub profile: Profile,
49    /// Convex hull of quality-vs-bitrate points for the profile.
50    pub hull: Hull,
51    /// Selected bitrate ladder derived from the hull.
52    pub ladder: Ladder,
53    /// All evaluated encoding points.
54    pub points: Vec<Point>,
55    /// Number of encode trials run for this profile.
56    pub trial_count: usize,
57}
58
59/// Combined result of a context-aware analysis across all device profiles.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Result {
62    /// Source video path that was analyzed.
63    pub source: String,
64    /// Per-device analysis results.
65    pub devices: Vec<DeviceResult>,
66    /// Total wall-clock time of the analysis.
67    pub duration: Duration,
68}
69
70/// Progress update emitted as each device profile finishes.
71#[derive(Debug, Clone)]
72pub struct Progress {
73    /// Number of profiles completed so far.
74    pub device_done: usize,
75    /// Total number of profiles to analyze.
76    pub device_total: usize,
77    /// Name of the most recently completed profile.
78    pub device_name: String,
79}
80
81/// Runs per-title analysis for each device profile.
82pub async fn analyze(
83    source: &str,
84    cfg: Config,
85    progress_tx: Option<tokio::sync::mpsc::Sender<Progress>>,
86) -> anyhow::Result<Result> {
87    let start = Instant::now();
88    let mut devices = Vec::new();
89    let sender = ProgressSender::new(progress_tx);
90
91    for (i, profile) in cfg.profiles.iter().enumerate() {
92        let pt_cfg = viser_pertitle::Config {
93            encoding: EncodingConfig {
94                resolutions: profile.resolutions.clone(),
95                crf_values: cfg.crf_values.clone(),
96                codecs: profile.codecs.clone(),
97                preset: cfg.preset.clone(),
98                subsample: cfg.subsample,
99                parallel: cfg.parallel,
100                ..Default::default()
101            },
102            ladder_opts: profile.ladder_opts.clone(),
103            vmaf_model: profile.vmaf_model.clone(),
104            checkpoint_path: String::new(),
105            opt_metric: Default::default(),
106            allow_hdr: false,
107        };
108
109        let pt_result = viser_pertitle::analyze(source, pt_cfg, None)
110            .await
111            .map_err(|e| anyhow::anyhow!("analysis for {} failed: {e}", profile.name))?;
112
113        let device_ladder = viser_ladder::select(&pt_result.hull, &profile.ladder_opts);
114
115        devices.push(DeviceResult {
116            profile: profile.clone(),
117            hull: pt_result.hull,
118            ladder: device_ladder,
119            points: pt_result.points,
120            trial_count: pt_result.trial_count,
121        });
122
123        sender.send(Progress {
124            device_done: i + 1,
125            device_total: cfg.profiles.len(),
126            device_name: profile.name.clone(),
127        });
128    }
129
130    Ok(Result { source: source.to_string(), devices, duration: start.elapsed() })
131}