this repo has no description
1use std::path::PathBuf;
2
3use clap::Parser;
4use url::Url;
5
6use crate::error::ConfigError;
7
8#[derive(Debug, Parser, Clone)]
9#[command(
10 name = "browser-stream",
11 version,
12 about = "Stream a browser page to RTMP"
13)]
14pub struct CliArgs {
15 #[arg(long)]
16 pub url: String,
17
18 #[arg(long, default_value_t = 1920)]
19 pub width: u32,
20
21 #[arg(long, default_value_t = 1080)]
22 pub height: u32,
23
24 #[arg(long, default_value_t = 30)]
25 pub fps: u32,
26
27 #[arg(long, default_value_t = 4500)]
28 pub bitrate_kbps: u32,
29
30 #[arg(long, default_value_t = 1)]
31 pub keyint_sec: u32,
32
33 #[arg(long, default_value = "bframes=0")]
34 pub x264_opts: String,
35
36 #[arg(long)]
37 pub rtmp_url: Option<String>,
38
39 #[arg(long)]
40 pub stream_key: Option<String>,
41
42 #[arg(long)]
43 pub output: Option<String>,
44
45 #[arg(long, default_value_t = 5)]
46 pub retries: u32,
47
48 #[arg(long, default_value_t = 1000)]
49 pub retry_backoff_ms: u64,
50
51 #[arg(long, default_value_t = 2000)]
52 pub startup_delay_ms: u64,
53
54 #[arg(long, default_value_t = 30000)]
55 pub frame_timeout_ms: u64,
56
57 #[arg(long, default_value_t = false)]
58 pub no_audio: bool,
59
60 #[arg(long)]
61 pub ffmpeg_path: Option<PathBuf>,
62
63 #[arg(long)]
64 pub chromium_path: Option<PathBuf>,
65
66 #[arg(long, default_value_t = false)]
67 pub verbose: bool,
68}
69
70#[derive(Debug, Clone)]
71pub struct AppConfig {
72 pub website_url: Url,
73 pub width: u32,
74 pub height: u32,
75 pub fps: u32,
76 pub bitrate_kbps: u32,
77 pub keyint_sec: u32,
78 pub x264_opts: String,
79 pub output: String,
80 pub retries: u32,
81 pub retry_backoff_ms: u64,
82 pub startup_delay_ms: u64,
83 pub frame_timeout_ms: u64,
84 pub no_audio: bool,
85 pub ffmpeg_path: Option<PathBuf>,
86 pub chromium_path: Option<PathBuf>,
87 pub verbose: bool,
88}
89
90impl CliArgs {
91 pub fn into_config(self) -> Result<AppConfig, ConfigError> {
92 validate_range("width", self.width as u64, 16, u32::MAX as u64)?;
93 validate_range("height", self.height as u64, 16, u32::MAX as u64)?;
94 validate_range("fps", self.fps as u64, 1, 120)?;
95 validate_range(
96 "bitrate-kbps",
97 self.bitrate_kbps as u64,
98 100,
99 u32::MAX as u64,
100 )?;
101 validate_range("keyint-sec", self.keyint_sec as u64, 1, 60)?;
102 validate_range("frame-timeout-ms", self.frame_timeout_ms, 1000, u64::MAX)?;
103
104 let website_url =
105 Url::parse(&self.url).map_err(|_| ConfigError::InvalidWebsiteUrl(self.url.clone()))?;
106 match website_url.scheme() {
107 "http" | "https" => {}
108 other => return Err(ConfigError::UnsupportedWebsiteScheme(other.to_string())),
109 }
110
111 let output = crate::rtmp::build_output(self.output, self.rtmp_url, self.stream_key)?;
112
113 Ok(AppConfig {
114 website_url,
115 width: self.width,
116 height: self.height,
117 fps: self.fps,
118 bitrate_kbps: self.bitrate_kbps,
119 keyint_sec: self.keyint_sec,
120 x264_opts: self.x264_opts,
121 output,
122 retries: self.retries,
123 retry_backoff_ms: self.retry_backoff_ms,
124 startup_delay_ms: self.startup_delay_ms,
125 frame_timeout_ms: self.frame_timeout_ms,
126 no_audio: self.no_audio,
127 ffmpeg_path: self.ffmpeg_path,
128 chromium_path: self.chromium_path,
129 verbose: self.verbose,
130 })
131 }
132}
133
134fn validate_range(field: &'static str, actual: u64, min: u64, max: u64) -> Result<(), ConfigError> {
135 if actual < min || actual > max {
136 return Err(ConfigError::OutOfRange {
137 field,
138 min,
139 max,
140 actual,
141 });
142 }
143 Ok(())
144}