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