videocall_daemon/
cli_args.rs

1use std::str::FromStr;
2
3use clap::{ArgGroup, Args, Parser, Subcommand};
4use thiserror::Error;
5use url::Url;
6use videocall_nokhwa::utils::FrameFormat;
7
8/// Video Call Daemon
9///
10/// This daemon connects to the videocall.rs and streams audio and video to the specified meeting.
11///
12/// You can watch the video at https://videocall.rs/meeting/{user_id}/{meeting_id}
13#[derive(Parser, Debug)]
14#[clap(name = "client")]
15pub struct Opt {
16    #[clap(subcommand)]
17    pub mode: Mode,
18}
19
20#[derive(Clone, Debug)]
21pub enum IndexKind {
22    String(String),
23    Index(u32),
24}
25
26#[derive(Error, Debug)]
27pub enum ParseIndexKindError {
28    #[error("Invalid index value: {0}")]
29    InvalidIndex(String),
30}
31
32impl FromStr for IndexKind {
33    type Err = ParseIndexKindError;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        if let Ok(index) = s.parse::<u32>() {
37            Ok(IndexKind::Index(index))
38        } else {
39            Ok(IndexKind::String(s.to_string()))
40        }
41    }
42}
43
44#[derive(Subcommand, Debug)]
45pub enum Mode {
46    /// Stream audio and video to the specified meeting.
47    Stream(Stream),
48
49    /// Information mode to list cameras, formats, and resolutions.
50    Info(Info),
51}
52
53#[derive(Args, Debug, Clone)]
54pub struct Stream {
55    /// URL to connect to.
56    #[clap(long = "url", default_value = "https://transport.rustlemania.com")]
57    pub url: Url,
58
59    #[clap(long = "user-id")]
60    pub user_id: String,
61
62    #[clap(long = "meeting-id")]
63    pub meeting_id: String,
64
65    /// Specify which camera to use, either by index number or name.
66    ///
67    /// Examples:
68    ///   --video-device-index 0    # Use first camera
69    ///   --video-device-index "HD WebCam"  # Use camera by name
70    ///
71    /// If not provided, the program will list all available cameras.
72    /// You can also see available cameras by running:
73    ///   videocall-daemon info --list-cameras
74    ///
75    /// Note for MacOS users: You must use the device UUID instead of the human-readable name.
76    /// The UUID can be found in the "Extras" field when listing cameras.
77    #[clap(long = "video-device-index", short = 'v')]
78    pub video_device_index: Option<IndexKind>,
79
80    #[clap(long = "audio-device", short = 'a')]
81    pub audio_device: Option<String>,
82
83    /// Resolution in WIDTHxHEIGHT format (e.g., 1920x1080)
84    #[clap(long = "resolution", short = 'r')]
85    #[clap(default_value = "1280x720")]
86    pub resolution: String,
87
88    /// Frame rate for the video stream.
89    #[clap(long = "fps")]
90    #[clap(default_value = "30")]
91    pub fps: u32,
92
93    #[clap(long = "bitrate-kbps")]
94    #[clap(default_value = "500")]
95    pub bitrate_kbps: u32,
96
97    /// Controls the speed vs. quality tradeoff for VP9 encoding.
98    ///
99    /// The value ranges from `0` (slowest, best quality) to `15` (fastest, lowest quality).
100    ///
101    /// The cli does not allow selecting values below 4 because they are useless for realtime streaming.
102    ///
103    /// ## Valid Values:
104    /// - `4` to `8`: **Fast encoding**, lower quality (good for real-time streaming, live video).
105    /// - `9` to `15`: **Very fast encoding**, lowest quality, largest files (for ultra-low-latency applications).
106    ///
107    /// videocall-daemon --vp9-cpu-used 5  # Fast encoding, good for live streaming
108    #[arg(long, default_value_t = 5, value_parser = clap::value_parser!(u8).range(4..=15))]
109    pub vp9_cpu_used: u8,
110
111    /// Frame format to use for the video stream.
112    /// Different cameras support different formats.
113    /// Please use the `info` subcommand to list supported formats for a specific camera.
114    #[arg(long, default_value_t = FrameFormat::NV12, value_parser = parse_frame_format)]
115    pub frame_format: FrameFormat,
116
117    /// Perform NSS-compatible TLS key logging to the file specified in `SSLKEYLOGFILE`.
118    #[clap(long = "debug-keylog")]
119    pub keylog: bool,
120
121    /// Send test pattern instead of camera video.
122    #[clap(long = "debug-send-test-pattern")]
123    pub send_test_pattern: bool,
124
125    /// This is for ensuring that we can open the camera and encode video
126    #[clap(long = "debug-offline-streaming-test")]
127    pub local_streaming_test: bool,
128}
129
130fn parse_frame_format(s: &str) -> Result<FrameFormat, String> {
131    match s {
132        "NV12" => Ok(FrameFormat::NV12),
133        // TODO: Merge MR with MacOS BGRA support
134        // "BGRA" => Ok(FrameFormat::BGRA),
135        "YUYV" => Ok(FrameFormat::YUYV),
136        _ => Err("Invalid frame format, please use one of [NV12, BGRA, YUYV]".to_string()),
137    }
138}
139
140#[derive(Args, Debug)]
141#[clap(group = ArgGroup::new("info").required(true))]
142pub struct Info {
143    /// List available cameras.
144    #[clap(long = "list-cameras", group = "info")]
145    pub list_cameras: bool,
146
147    /// List supported formats for a specific camera using the index from `list-cameras`
148    #[clap(long = "list-formats", group = "info")]
149    pub list_formats: Option<IndexKind>, // Camera index
150
151    /// List supported resolutions for a specific camera using the index from `list-cameras`
152    #[clap(long = "list-resolutions", group = "info")]
153    pub list_resolutions: Option<IndexKind>, // Camera index
154}
155
156#[derive(Args, Debug, Clone)]
157pub struct TestCamera {
158    #[clap(long = "video-device-index")]
159    pub video_device_index: IndexKind,
160}