1use libobs_wrapper::{
39 context::ObsContext,
40 data::{output::ObsOutputRef, ObsData},
41 encoders::{ObsAudioEncoderType, ObsContextEncoders, ObsVideoEncoderType},
42 utils::{AudioEncoderInfo, ObsError, ObsPath, ObsString, OutputInfo, VideoEncoderInfo},
43};
44use std::io::Write;
45
46#[derive(Debug, Clone, Copy)]
48pub enum X264Preset {
49 UltraFast,
51 SuperFast,
53 VeryFast,
55 Faster,
57 Fast,
59 Medium,
61 Slow,
63 Slower,
65}
66
67impl X264Preset {
68 fn as_str(&self) -> &'static str {
69 match self {
70 X264Preset::UltraFast => "ultrafast",
71 X264Preset::SuperFast => "superfast",
72 X264Preset::VeryFast => "veryfast",
73 X264Preset::Faster => "faster",
74 X264Preset::Fast => "fast",
75 X264Preset::Medium => "medium",
76 X264Preset::Slow => "slow",
77 X264Preset::Slower => "slower",
78 }
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
84pub enum HardwarePreset {
85 Speed,
87 Balanced,
89 Quality,
91}
92
93impl HardwarePreset {
94 fn as_str(&self) -> &'static str {
95 match self {
96 HardwarePreset::Speed => "speed",
97 HardwarePreset::Balanced => "balanced",
98 HardwarePreset::Quality => "quality",
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
105pub enum VideoEncoder {
106 X264(X264Preset),
108 Hardware {
110 codec: HardwareCodec,
111 preset: HardwarePreset,
112 },
113 Custom(ObsVideoEncoderType),
115}
116
117#[derive(Debug, Clone, Copy)]
119pub enum HardwareCodec {
120 H264,
121 HEVC,
122 AV1,
123}
124
125#[derive(Debug, Clone)]
127pub enum AudioEncoder {
128 AAC,
130 Opus,
132 Custom(ObsAudioEncoderType),
134}
135
136#[derive(Debug, Clone, Copy, Default)]
138pub enum OutputFormat {
139 FlashVideo,
141 MatroskaVideo,
143 Mpeg4,
145 QuickTime,
147 #[default]
149 HybridMP4,
150 HybridMov,
152 FragmentedMP4,
154 FragmentedMOV,
156 MpegTs,
158}
159
160#[derive(Debug)]
162pub struct OutputSettings {
163 name: ObsString,
164 video_bitrate: u32,
165 audio_bitrate: u32,
166 video_encoder: VideoEncoder,
167 audio_encoder: AudioEncoder,
168 custom_encoder_settings: Option<String>,
169 path: ObsPath,
170 format: OutputFormat,
171 custom_muxer_settings: Option<String>,
172}
173
174impl OutputSettings {
175 pub fn with_video_bitrate(mut self, bitrate: u32) -> Self {
177 self.video_bitrate = bitrate;
178 self
179 }
180
181 pub fn with_audio_bitrate(mut self, bitrate: u32) -> Self {
183 self.audio_bitrate = bitrate;
184 self
185 }
186
187 pub fn with_x264_encoder(mut self, preset: X264Preset) -> Self {
189 self.video_encoder = VideoEncoder::X264(preset);
190 self
191 }
192
193 pub fn with_hardware_encoder(mut self, codec: HardwareCodec, preset: HardwarePreset) -> Self {
196 self.video_encoder = VideoEncoder::Hardware { codec, preset };
197 self
198 }
199
200 pub fn with_custom_video_encoder(mut self, encoder: ObsVideoEncoderType) -> Self {
202 self.video_encoder = VideoEncoder::Custom(encoder);
203 self
204 }
205
206 pub fn with_custom_settings<S: Into<String>>(mut self, settings: S) -> Self {
208 self.custom_encoder_settings = Some(settings.into());
209 self
210 }
211
212 pub fn with_path<P: Into<ObsPath>>(mut self, path: P) -> Self {
214 self.path = path.into();
215 self
216 }
217
218 pub fn with_format(mut self, format: OutputFormat) -> Self {
220 self.format = format;
221 self
222 }
223
224 pub fn with_custom_muxer_settings<S: Into<String>>(mut self, settings: S) -> Self {
226 self.custom_muxer_settings = Some(settings.into());
227 self
228 }
229
230 pub fn with_audio_encoder(mut self, encoder: AudioEncoder) -> Self {
232 self.audio_encoder = encoder;
233 self
234 }
235}
236
237#[derive(Debug)]
238pub struct SimpleOutputBuilder {
239 settings: OutputSettings,
240 context: ObsContext,
241}
242
243pub trait ObsContextSimpleExt {
244 fn simple_output_builder<K: Into<ObsPath>, T: Into<ObsString>>(
245 &self,
246 name: T,
247 path: K,
248 ) -> SimpleOutputBuilder;
249}
250
251impl ObsContextSimpleExt for ObsContext {
252 fn simple_output_builder<K: Into<ObsPath>, T: Into<ObsString>>(
253 &self,
254 name: T,
255 path: K,
256 ) -> SimpleOutputBuilder {
257 SimpleOutputBuilder::new(self.clone(), name, path)
258 }
259}
260
261impl SimpleOutputBuilder {
262 pub fn new<K: Into<ObsPath>, T: Into<ObsString>>(
264 context: ObsContext,
265 name: T,
266 path: K,
267 ) -> Self {
268 SimpleOutputBuilder {
269 settings: OutputSettings {
270 video_bitrate: 6000,
271 audio_bitrate: 160,
272 video_encoder: VideoEncoder::X264(X264Preset::VeryFast),
273 audio_encoder: AudioEncoder::AAC,
274 custom_encoder_settings: None,
275 path: path.into(),
276 format: OutputFormat::default(),
277 custom_muxer_settings: None,
278 name: name.into(),
279 },
280 context,
281 }
282 }
283
284 pub fn settings(mut self, settings: OutputSettings) -> Self {
286 self.settings = settings;
287 self
288 }
289
290 pub fn video_bitrate(mut self, bitrate: u32) -> Self {
292 self.settings.video_bitrate = bitrate;
293 self
294 }
295
296 pub fn audio_bitrate(mut self, bitrate: u32) -> Self {
298 self.settings.audio_bitrate = bitrate;
299 self
300 }
301
302 pub fn path<P: Into<ObsPath>>(mut self, path: P) -> Self {
304 self.settings.path = path.into();
305 self
306 }
307
308 pub fn format(mut self, format: OutputFormat) -> Self {
310 self.settings.format = format;
311 self
312 }
313
314 pub fn x264_encoder(mut self, preset: X264Preset) -> Self {
316 self.settings.video_encoder = VideoEncoder::X264(preset);
317 self
318 }
319
320 pub fn hardware_encoder(mut self, codec: HardwareCodec, preset: HardwarePreset) -> Self {
322 self.settings.video_encoder = VideoEncoder::Hardware { codec, preset };
323 self
324 }
325
326 pub fn build(mut self) -> Result<ObsOutputRef, ObsError> {
328 let output_id = match self.settings.format {
330 OutputFormat::HybridMP4 => "mp4_output",
331 OutputFormat::HybridMov => "mov_output",
332 _ => "ffmpeg_muxer",
333 };
334
335 let mut output_settings = self.context.data()?;
337 output_settings.set_string("path", self.settings.path.clone().build())?;
338
339 if let Some(ref muxer_settings) = self.settings.custom_muxer_settings {
340 output_settings.set_string("muxer_settings", muxer_settings.as_str())?;
341 }
342
343 let output_info = OutputInfo::new(
345 output_id,
346 self.settings.name.clone(),
347 Some(output_settings),
348 None,
349 );
350
351 log::trace!("Creating output with settings: {:?}", self.settings);
352 std::io::stdout().flush().unwrap();
353 let mut output = self.context.output(output_info)?;
354
355 let video_encoder_type = self.select_video_encoder_type(&self.settings.video_encoder)?;
357 let mut video_settings = self.context.data()?;
358
359 log::trace!("Selected video encoder: {:?}", video_encoder_type);
360 std::io::stdout().flush().unwrap();
361 self.configure_video_encoder(&mut video_settings)?;
362
363 let video_encoder_info = VideoEncoderInfo::new(
364 video_encoder_type,
365 "simple_video",
366 Some(video_settings),
367 None,
368 );
369
370 log::trace!("Creating video encoder with info: {:?}", video_encoder_info);
371 std::io::stdout().flush().unwrap();
372 output.create_and_set_video_encoder(video_encoder_info)?;
373
374 let audio_encoder_type = match &self.settings.audio_encoder {
376 AudioEncoder::AAC => ObsAudioEncoderType::FFMPEG_AAC,
377 AudioEncoder::Opus => ObsAudioEncoderType::FFMPEG_OPUS,
378 AudioEncoder::Custom(encoder_type) => encoder_type.clone(),
379 };
380
381 log::trace!("Selected audio encoder: {:?}", audio_encoder_type);
382 let mut audio_settings = self.context.data()?;
383 audio_settings.set_string("rate_control", "CBR")?;
384 audio_settings.set_int("bitrate", self.settings.audio_bitrate as i64)?;
385
386 let audio_encoder_info = AudioEncoderInfo::new(
387 audio_encoder_type,
388 "simple_audio",
389 Some(audio_settings),
390 None,
391 );
392
393 log::trace!("Creating audio encoder with info: {:?}", audio_encoder_info);
394 output.create_and_set_audio_encoder(audio_encoder_info, 0)?;
395
396 Ok(output)
397 }
398
399 fn select_video_encoder_type(
400 &self,
401 encoder: &VideoEncoder,
402 ) -> Result<ObsVideoEncoderType, ObsError> {
403 match encoder {
404 VideoEncoder::X264(_) => Ok(ObsVideoEncoderType::OBS_X264),
405 VideoEncoder::Custom(t) => Ok(t.clone()),
406 VideoEncoder::Hardware { codec, .. } => {
407 let candidates = self.hardware_candidates(*codec);
409 let available = self
411 .context
412 .available_video_encoders()?
413 .into_iter()
414 .map(|b| b.get_encoder_id().clone())
415 .collect::<Vec<_>>();
416 for cand in candidates {
418 if available.iter().any(|a| a == &cand) {
419 return Ok(cand);
420 }
421 }
422 Ok(ObsVideoEncoderType::OBS_X264)
424 }
425 }
426 }
427
428 fn hardware_candidates(&self, codec: HardwareCodec) -> Vec<ObsVideoEncoderType> {
429 match codec {
430 HardwareCodec::H264 => vec![
431 ObsVideoEncoderType::OBS_NVENC_H264_TEX,
432 ObsVideoEncoderType::H264_TEXTURE_AMF,
433 ObsVideoEncoderType::OBS_QSV11_V2,
434 ObsVideoEncoderType::OBS_NVENC_H264_SOFT,
436 ObsVideoEncoderType::OBS_QSV11_SOFT_V2,
437 ],
438 HardwareCodec::HEVC => vec![
439 ObsVideoEncoderType::OBS_NVENC_HEVC_TEX,
440 ObsVideoEncoderType::H265_TEXTURE_AMF,
441 ObsVideoEncoderType::OBS_QSV11_HEVC,
442 ObsVideoEncoderType::OBS_NVENC_HEVC_SOFT,
443 ObsVideoEncoderType::OBS_QSV11_HEVC_SOFT,
444 ],
445 HardwareCodec::AV1 => vec![
446 ObsVideoEncoderType::OBS_NVENC_AV1_TEX,
447 ObsVideoEncoderType::AV1_TEXTURE_AMF,
448 ObsVideoEncoderType::OBS_QSV11_AV1,
449 ObsVideoEncoderType::OBS_NVENC_AV1_SOFT,
450 ObsVideoEncoderType::OBS_QSV11_AV1_SOFT,
451 ],
452 }
453 }
454
455 fn get_encoder_preset(&self, encoder: &VideoEncoder) -> Option<&str> {
456 match encoder {
457 VideoEncoder::X264(preset) => Some(preset.as_str()),
458 VideoEncoder::Hardware { preset, .. } => Some(preset.as_str()),
459 VideoEncoder::Custom(_) => None,
460 }
461 }
462
463 fn configure_video_encoder(&self, settings: &mut ObsData) -> Result<(), ObsError> {
464 settings.set_string("rate_control", "CBR")?;
466 settings.set_int("bitrate", self.settings.video_bitrate as i64)?;
467
468 if let Some(preset) = self.get_encoder_preset(&self.settings.video_encoder) {
470 settings.set_string("preset", preset)?;
471 }
472
473 if let Some(ref custom) = self.settings.custom_encoder_settings {
475 settings.set_string("x264opts", custom.as_str())?;
476 }
477
478 Ok(())
479 }
480}