this repo has no description
at main 144 lines 3.8 kB view raw
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}