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