1use clap::Parser;
5use eyre::{self, Result, WrapErr};
6use tracing::debug;
7
8use braid::braid_start;
9use braid_config_data::parse_config_file;
10use braid_types::{BraidCameraConfig, RawCamName, StartCameraBackend, TriggerType};
11use strand_bui_backend_session_types::BuiServerAddrInfo;
12
13mod callback_handling;
14mod mainbrain;
15mod multicam_http_session_handler;
16
17#[derive(Debug, Parser)]
18#[command(author, version, about)]
19struct BraidRunCliArgs {
20 config_file: std::path::PathBuf,
22 #[arg(short, long)]
24 disable_console: bool,
25}
26
27fn compute_strand_cam_args(
28 camera: &BraidCameraConfig,
29 mainbrain_internal_addr: &BuiServerAddrInfo,
30) -> Result<Vec<String>> {
31 let urls = strand_bui_backend_session::build_urls(mainbrain_internal_addr)?;
32 let url = urls
33 .first()
34 .ok_or_else(|| eyre::eyre!("need at least one URL"))?;
35 let url_string = format!("{url}");
36 let mut args = vec![
37 "--camera-name".into(),
38 camera.name.clone(),
39 "--braid-url".into(),
40 url_string,
41 ];
42 if let Some(backend) = camera.start_backend.camera_backend_arg() {
45 args.push("--camera-backend".into());
46 args.push(backend.into());
47 }
48 Ok(args)
49}
50
51fn launch_strand_cam(
52 strand_cam_set: &mut tokio::task::JoinSet<()>,
53 camera: &BraidCameraConfig,
54 mainbrain_internal_addr: &BuiServerAddrInfo,
55) -> Result<()> {
56 let braid_run_exe = std::env::current_exe().unwrap();
61 let exe_dir = braid_run_exe
62 .parent()
63 .expect("Executable must be in some directory");
64 #[cfg(target_os = "windows")]
65 let ext = ".exe";
66 #[cfg(not(target_os = "windows"))]
67 let ext = "";
68 let exe = exe_dir.join(format!(
69 "{}{}",
70 camera.start_backend.strand_cam_exe_name().unwrap(),
71 ext
72 ));
73 debug!("strand cam executable name: \"{}\"", exe.display());
74
75 let cam_name = camera.name.clone();
76
77 let mut exec = std::process::Command::new(&exe);
78 let args = compute_strand_cam_args(camera, mainbrain_internal_addr)?;
79 exec.args(&args);
80 debug!("exec: {:?}", exec);
81 let mut obj = exec.spawn().context(format!(
82 "Starting Strand Cam executable \"{}\"",
83 exe.display()
84 ))?;
85
86 let _abort_handle = strand_cam_set.spawn_blocking(move || {
87 let exit_code = obj.wait().unwrap();
88 if !exit_code.success() {
89 tracing::error!(
90 "Strand Cam executable for {cam_name} exited with exit code {:?}",
91 exit_code.code()
92 );
93 } else {
94 debug!("Strand Cam executable done.");
95 }
96 });
97 Ok(())
98}
99
100#[tokio::main]
101async fn main() -> Result<()> {
102 std::panic::set_hook(Box::new(tracing_panic::panic_hook));
103 braid_start("run")?;
104
105 let args = BraidRunCliArgs::parse();
106 let cfg = parse_config_file(&args.config_file).with_context(|| {
107 format!(
108 "when parsing configuration file {}",
109 args.config_file.display()
110 )
111 })?;
112
113 let log_file_name = chrono::Local::now()
114 .format("~/.braid-%Y%m%d_%H%M%S.%f.log")
115 .to_string();
116
117 let log_file_name = std::path::PathBuf::from(shellexpand::full(&log_file_name)?.to_string());
118 let _guard = env_tracing_logger::initiate_logging(Some(&log_file_name), args.disable_console)
121 .map_err(|e| eyre::eyre!("error initiating logging: {e}"))?;
122
123 let version = format!("{} (git {})", env!("CARGO_PKG_VERSION"), env!("GIT_HASH"));
124 tracing::info!("{} {}", "run", version);
125 tracing::debug!("{:?}", cfg);
126
127 let camera_configs = cfg
128 .cameras
129 .iter()
130 .map(|cfg| {
131 let raw_cam_name = RawCamName::new(cfg.name.to_string());
132 (raw_cam_name, cfg.clone())
133 })
134 .collect();
135
136 let trig_cfg = cfg.trigger;
137
138 let (force_camera_sync_mode, software_limit_framerate) = match &trig_cfg {
139 TriggerType::TriggerboxV1(_) => (true, braid_types::StartSoftwareFrameRateLimit::NoChange),
140 TriggerType::FakeSync(cfg) => (
141 false,
142 braid_types::StartSoftwareFrameRateLimit::Enable(cfg.framerate),
143 ),
144 TriggerType::PtpSync(_) | TriggerType::DeviceTimestamp => {
145 (false, braid_types::StartSoftwareFrameRateLimit::NoChange)
146 }
147 };
148 let show_tracking_params = false;
149
150 let all_expected_cameras = cfg
152 .cameras
153 .iter()
154 .map(|x| RawCamName::new(x.name.clone()))
155 .collect();
156
157 let address_string: String = cfg.mainbrain.http_api_server_addr.clone();
158 let persistent_secret = mainbrain::load_persistent_secret(cfg.mainbrain.secret_base64.clone())?;
161 let (listener, mainbrain_server_info) =
162 braid_types::start_listener(&address_string, &persistent_secret).await?;
163 let mainbrain_internal_addr = mainbrain_server_info.clone();
164
165 let cfg_cameras = cfg.cameras;
166 let mut strand_cam_set = tokio::task::JoinSet::new();
167 for camera in cfg_cameras.into_iter() {
168 if camera.start_backend != StartCameraBackend::Remote {
169 launch_strand_cam(&mut strand_cam_set, &camera, &mainbrain_internal_addr)?;
170 } else {
171 tracing::info!(
172 "Not starting remote camera \"{}\". Use args: {}",
173 camera.name,
174 compute_strand_cam_args(&camera, &mainbrain_internal_addr)
175 .unwrap()
176 .join(" ")
177 );
178 strand_cam_set.spawn(std::future::pending());
181 }
182 }
183
184 debug!("done launching cameras");
185
186 mainbrain::do_run_forever(
188 show_tracking_params,
189 camera_configs,
192 trig_cfg,
193 cfg.mainbrain,
194 persistent_secret,
195 all_expected_cameras,
196 force_camera_sync_mode,
197 software_limit_framerate.clone(),
198 "braid",
199 listener,
200 mainbrain_server_info,
201 strand_cam_set,
202 )
203 .await?;
204
205 debug!("done {}:{}", file!(), line!());
206
207 Ok(())
208}