mirror of https://github.com/dnaka91/obws
Initial v5 support
parent
7f0e6e7fe9
commit
e9d0afda5e
@ -0,0 +1,56 @@
|
||||
use super::Client;
|
||||
use crate::{
|
||||
requests::{RequestType, SetProfileParameter},
|
||||
responses::{ProfileParameter, Profiles, SceneCollections},
|
||||
Result,
|
||||
};
|
||||
|
||||
/// API functions related to OBS configuration.
|
||||
pub struct Config<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Config<'a> {
|
||||
pub async fn get_scene_collection_list(&self) -> Result<SceneCollections> {
|
||||
self.client
|
||||
.send_message(RequestType::GetSceneCollectionList)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_current_scene_collection(&self, scene_collection_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetCurrentSceneCollection {
|
||||
scene_collection_name,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_profile_list(&self) -> Result<Profiles> {
|
||||
self.client.send_message(RequestType::GetProfileList).await
|
||||
}
|
||||
|
||||
pub async fn set_current_profile(&self, profile_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetCurrentProfile { profile_name })
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_profile_parameter(
|
||||
&self,
|
||||
parameter_category: &str,
|
||||
parameter_name: &str,
|
||||
) -> Result<ProfileParameter> {
|
||||
self.client
|
||||
.send_message(RequestType::GetProfileParameter {
|
||||
parameter_category,
|
||||
parameter_name,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_profile_parameter(&self, parameter: SetProfileParameter<'_>) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetProfileParameter(parameter))
|
||||
.await
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
use super::Client;
|
||||
use crate::{
|
||||
requests::{CreateInput, RequestType, SetInputSettings, Volume},
|
||||
responses, Result,
|
||||
};
|
||||
|
||||
/// API functions related to inputs.
|
||||
pub struct Inputs<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Inputs<'a> {
|
||||
pub async fn get_input_list(&self, input_kind: Option<&str>) -> Result<Vec<responses::Input>> {
|
||||
self.client
|
||||
.send_message::<responses::Inputs>(RequestType::GetInputList { input_kind })
|
||||
.await
|
||||
.map(|i| i.inputs)
|
||||
}
|
||||
|
||||
pub async fn get_input_kind_list(&self, unversioned: bool) -> Result<Vec<String>> {
|
||||
self.client
|
||||
.send_message::<responses::InputKinds>(RequestType::GetInputKindList { unversioned })
|
||||
.await
|
||||
.map(|ik| ik.input_kinds)
|
||||
}
|
||||
|
||||
pub async fn get_input_default_settings(&self, input_kind: &str) -> Result<serde_json::Value> {
|
||||
self.client
|
||||
.send_message::<responses::DefaultInputSettings>(RequestType::GetInputDefaultSettings {
|
||||
input_kind,
|
||||
})
|
||||
.await
|
||||
.map(|dis| dis.default_input_settings)
|
||||
}
|
||||
|
||||
pub async fn get_input_settings(&self, input_name: &str) -> Result<responses::InputSettings> {
|
||||
self.client
|
||||
.send_message(RequestType::GetInputSettings { input_name })
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_input_settings(&self, settings: SetInputSettings<'_>) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetInputSettings(settings))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_input_mute(&self, input_name: &str) -> Result<bool> {
|
||||
self.client
|
||||
.send_message::<responses::InputMuted>(RequestType::GetInputMute { input_name })
|
||||
.await
|
||||
.map(|im| im.input_muted)
|
||||
}
|
||||
|
||||
pub async fn set_input_mute(&self, input_name: &str, input_muted: bool) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetInputMute {
|
||||
input_name,
|
||||
input_muted,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn toggle_input_mute(&self, input_name: &str) -> Result<bool> {
|
||||
self.client
|
||||
.send_message::<responses::InputMuted>(RequestType::ToggleInputMute { input_name })
|
||||
.await
|
||||
.map(|im| im.input_muted)
|
||||
}
|
||||
|
||||
pub async fn get_input_volume(&self, input_name: &str) -> Result<responses::InputVolume> {
|
||||
self.client
|
||||
.send_message(RequestType::GetInputVolume { input_name })
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_input_volume(&self, input_name: &str, input_volume: Volume) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetInputVolume {
|
||||
input_name,
|
||||
input_volume,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_input_name(&self, input_name: &str, new_input_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetInputName {
|
||||
input_name,
|
||||
new_input_name,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_input(&self, input: CreateInput<'_>) -> Result<String> {
|
||||
self.client
|
||||
.send_message::<responses::SceneItemId>(RequestType::CreateInput(input))
|
||||
.await
|
||||
.map(|sii| sii.scene_item_id)
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
use chrono::Duration;
|
||||
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to media control.
|
||||
pub struct MediaControl<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> MediaControl<'a> {
|
||||
/// Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
/// - `play_pause`: Whether to pause or play the source. `false` for play, `true` for pause.
|
||||
pub async fn play_pause_media(
|
||||
&self,
|
||||
source_name: &str,
|
||||
play_pause: Option<bool>,
|
||||
) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::PlayPauseMedia {
|
||||
source_name,
|
||||
play_pause,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn restart_media(&self, source_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::RestartMedia { source_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn stop_media(&self, source_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StopMedia { source_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Skip to the next media item in the playlist. Supports only vlc media source (as of OBS
|
||||
/// v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn next_media(&self, source_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::NextMedia { source_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Go to the previous media item in the playlist. Supports only vlc media source (as of OBS
|
||||
/// v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn previous_media(&self, source_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::PreviousMedia { source_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS
|
||||
/// v25.0.8).
|
||||
///
|
||||
/// Note: For some reason, for the first 5 or so seconds that the media is playing, the total
|
||||
/// duration can be off by upwards of 50ms.
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn get_media_duration(&self, source_name: &str) -> Result<Duration> {
|
||||
self.client
|
||||
.send_message::<responses::MediaDuration>(RequestType::GetMediaDuration { source_name })
|
||||
.await
|
||||
.map(|md| md.media_duration)
|
||||
}
|
||||
|
||||
/// Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources
|
||||
/// (as of OBS v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn get_media_time(&self, source_name: &str) -> Result<Duration> {
|
||||
self.client
|
||||
.send_message::<responses::MediaTime>(RequestType::GetMediaTime { source_name })
|
||||
.await
|
||||
.map(|mt| mt.timestamp)
|
||||
}
|
||||
|
||||
/// Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS
|
||||
/// v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
/// - `timestamp`: Milliseconds to set the timestamp to.
|
||||
pub async fn set_media_time(&self, source_name: &str, timestamp: Duration) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetMediaTime {
|
||||
source_name,
|
||||
timestamp,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS
|
||||
/// v25.0.8).
|
||||
///
|
||||
/// Note: Due to processing/network delays, this request is not perfect. The processing rate of
|
||||
/// this request has also not been tested.
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
/// - `time_offset`: Millisecond offset (positive or negative) to offset the current media
|
||||
/// position.
|
||||
pub async fn scrub_media(&self, source_name: &str, time_offset: Duration) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::ScrubMedia {
|
||||
source_name,
|
||||
time_offset,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as
|
||||
/// of OBS v25.0.8).
|
||||
///
|
||||
/// - `source_name`: Source name.
|
||||
pub async fn get_media_state(&self, source_name: &str) -> Result<responses::MediaState> {
|
||||
self.client
|
||||
.send_message::<responses::GetMediaState>(RequestType::GetMediaState { source_name })
|
||||
.await
|
||||
.map(|msr| msr.media_state)
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to outputs.
|
||||
pub struct Outputs<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Outputs<'a> {
|
||||
/// List existing outputs.
|
||||
pub async fn list_outputs(&self) -> Result<Vec<responses::Output>> {
|
||||
self.client
|
||||
.send_message::<responses::Outputs>(RequestType::ListOutputs)
|
||||
.await
|
||||
.map(|o| o.outputs)
|
||||
}
|
||||
|
||||
/// Get information about a single output.
|
||||
///
|
||||
/// - `output_name`: Output name.
|
||||
pub async fn get_output_info(&self, output_name: &str) -> Result<responses::Output> {
|
||||
self.client
|
||||
.send_message::<responses::OutputInfo>(RequestType::GetOutputInfo { output_name })
|
||||
.await
|
||||
.map(|o| o.output_info)
|
||||
}
|
||||
|
||||
/// Start an output.
|
||||
///
|
||||
/// Note: Controlling outputs is an experimental feature of obs-websocket. Some plugins which
|
||||
/// add outputs to OBS may not function properly when they are controlled in this way.
|
||||
///
|
||||
/// - `output_name`: Output name.
|
||||
pub async fn start_output(&self, output_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartOutput { output_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stop an output.
|
||||
///
|
||||
/// Note: Controlling outputs is an experimental feature of obs-websocket. Some plugins which
|
||||
/// add outputs to OBS may not function properly when they are controlled in this way.
|
||||
///
|
||||
/// - `output_name`: Output name.
|
||||
/// - `force`: Force stop (default: false).
|
||||
pub async fn stop_output(&self, output_name: &str, force: Option<bool>) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StopOutput { output_name, force })
|
||||
.await
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to profiles.
|
||||
pub struct Profiles<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Profiles<'a> {
|
||||
/// Set the currently active profile.
|
||||
///
|
||||
/// - `profile_name`: Name of the desired profile.
|
||||
pub async fn set_current_profile(&self, profile_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetCurrentProfile { profile_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the name of the current profile.
|
||||
pub async fn get_current_profile(&self) -> Result<String> {
|
||||
self.client
|
||||
.send_message::<responses::CurrentProfile>(RequestType::GetCurrentProfile)
|
||||
.await
|
||||
.map(|cp| cp.profile_name)
|
||||
}
|
||||
|
||||
/// Get a list of available profiles.
|
||||
pub async fn list_profiles(&self) -> Result<Vec<responses::Profile>> {
|
||||
self.client
|
||||
.send_message::<responses::Profiles>(RequestType::ListProfiles)
|
||||
.await
|
||||
.map(|cp| cp.profiles)
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to recording.
|
||||
pub struct Recording<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Recording<'a> {
|
||||
/// Get current recording status.
|
||||
pub async fn get_recording_status(&self) -> Result<responses::RecordingStatus> {
|
||||
self.client
|
||||
.send_message(RequestType::GetRecordingStatus)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Toggle recording on or off (depending on the current recording state).
|
||||
pub async fn start_stop_recording(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartStopRecording)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Start recording. Will return an `error` if recording is already active.
|
||||
pub async fn start_recording(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::StartRecording).await
|
||||
}
|
||||
|
||||
/// Stop recording. Will return an `error` if recording is not active.
|
||||
pub async fn stop_recording(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::StopRecording).await
|
||||
}
|
||||
|
||||
/// Pause the current recording. Returns an `error` if recording is not active or already
|
||||
/// paused.
|
||||
pub async fn pause_recording(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::PauseRecording).await
|
||||
}
|
||||
|
||||
/// Resume/unpause the current recording (if paused). Returns an error if recording is not
|
||||
/// active or not paused.
|
||||
pub async fn resume_recording(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::ResumeRecording).await
|
||||
}
|
||||
|
||||
/// Please note: if this is called while a recording is in progress, the change won't be applied
|
||||
/// immediately and will be effective on the next recording.
|
||||
///
|
||||
/// - `rec_folder`: Path of the recording folder.
|
||||
pub async fn set_recording_folder(&self, rec_folder: &Path) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetRecordingFolder { rec_folder })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the path of the current recording folder.
|
||||
pub async fn get_recording_folder(&self) -> Result<PathBuf> {
|
||||
self.client
|
||||
.send_message::<responses::RecordingFolder>(RequestType::GetRecordingFolder)
|
||||
.await
|
||||
.map(|rf| rf.rec_folder)
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to the replay buffer.
|
||||
pub struct ReplayBuffer<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> ReplayBuffer<'a> {
|
||||
/// Get the status of the OBS replay buffer.
|
||||
pub async fn get_replay_buffer_status(&self) -> Result<bool> {
|
||||
self.client
|
||||
.send_message::<responses::ReplayBufferStatus>(RequestType::GetReplayBufferStatus)
|
||||
.await
|
||||
.map(|rbs| rbs.is_replay_buffer_active)
|
||||
}
|
||||
|
||||
/// Toggle the Replay Buffer on/off (depending on the current state of the replay buffer).
|
||||
pub async fn start_stop_replay_buffer(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartStopReplayBuffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Start recording into the Replay Buffer. Will return an `error` if the Replay Buffer is
|
||||
/// already active or if the "Save Replay Buffer" hotkey is not set in OBS' settings. Setting
|
||||
/// this hotkey is mandatory, even when triggering saves only through obs-websocket.
|
||||
pub async fn start_replay_buffer(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartReplayBuffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stop recording into the Replay Buffer. Will return an `error` if the Replay Buffer is not
|
||||
/// active.
|
||||
pub async fn stop_replay_buffer(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StopReplayBuffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Flush and save the contents of the Replay Buffer to disk. This is basically the same as
|
||||
/// triggering the "Save Replay Buffer" hotkey. Will return an `error` if the Replay Buffer is
|
||||
/// not active.
|
||||
pub async fn save_replay_buffer(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SaveReplayBuffer)
|
||||
.await
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to scene collections.
|
||||
pub struct SceneCollections<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> SceneCollections<'a> {
|
||||
/// Change the active scene collection.
|
||||
///
|
||||
/// - `sc_name`: Name of the desired scene collection.
|
||||
pub async fn set_current_scene_collection(&self, sc_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetCurrentSceneCollection { sc_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the name of the current scene collection.
|
||||
pub async fn get_current_scene_collection(&self) -> Result<String> {
|
||||
self.client
|
||||
.send_message::<responses::CurrentSceneCollection>(
|
||||
RequestType::GetCurrentSceneCollection,
|
||||
)
|
||||
.await
|
||||
.map(|csc| csc.sc_name)
|
||||
}
|
||||
|
||||
/// List available scene collections.
|
||||
pub async fn list_scene_collections(&self) -> Result<Vec<responses::SceneCollection>> {
|
||||
self.client
|
||||
.send_message::<responses::SceneCollections>(RequestType::ListSceneCollections)
|
||||
.await
|
||||
.map(|sc| sc.scene_collections)
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
use either::Either;
|
||||
|
||||
use super::Client;
|
||||
use crate::{
|
||||
requests::{
|
||||
AddSceneItem, DuplicateSceneItem, RequestType, SceneItemProperties, SceneItemRender,
|
||||
SceneItemSpecification,
|
||||
},
|
||||
responses, Result,
|
||||
};
|
||||
|
||||
/// API functions related to scene items.
|
||||
pub struct SceneItems<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> SceneItems<'a> {
|
||||
/// Get a list of all scene items in a scene.
|
||||
///
|
||||
/// - `scene_name`: Name of the scene to get the list of scene items from. Defaults to the
|
||||
/// current scene if not specified.
|
||||
pub async fn get_scene_item_list(
|
||||
&self,
|
||||
scene_name: Option<&str>,
|
||||
) -> Result<responses::SceneItemList> {
|
||||
self.client
|
||||
.send_message(RequestType::GetSceneItemList { scene_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets the scene specific properties of the specified source item. Coordinates are relative to
|
||||
/// the item's parent (the scene or group it belongs to).
|
||||
///
|
||||
/// - `scene_name`: Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
/// - `item`: Scene Item name (if this field is a string) or specification (if it is an object).
|
||||
pub async fn get_scene_item_properties(
|
||||
&self,
|
||||
scene_name: Option<&str>,
|
||||
item: Either<&str, SceneItemSpecification<'_>>,
|
||||
) -> Result<responses::SceneItemProperties> {
|
||||
self.client
|
||||
.send_message(RequestType::GetSceneItemProperties { scene_name, item })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Sets the scene specific properties of a source. Unspecified properties will remain
|
||||
/// unchanged. Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||
pub async fn set_scene_item_properties(
|
||||
&self,
|
||||
properties: SceneItemProperties<'_>,
|
||||
) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetSceneItemProperties(properties))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Reset a scene item.
|
||||
///
|
||||
/// - `scene_name`: Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
/// - `item`: Scene Item name (if this field is a string) or specification (if it is an object).
|
||||
pub async fn reset_scene_item(
|
||||
&self,
|
||||
scene_name: Option<&str>,
|
||||
item: Either<&str, SceneItemSpecification<'_>>,
|
||||
) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::ResetSceneItem { scene_name, item })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Show or hide a specified source item in a specified scene.
|
||||
pub async fn set_scene_item_render(
|
||||
&self,
|
||||
scene_item_render: SceneItemRender<'_>,
|
||||
) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetSceneItemRender(scene_item_render))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Deletes a scene item.
|
||||
///
|
||||
/// - `scene`: Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||
/// - `item`: Scene item to delete.
|
||||
pub async fn delete_scene_item(
|
||||
&self,
|
||||
scene: Option<&str>,
|
||||
item: SceneItemSpecification<'_>,
|
||||
) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::DeleteSceneItem { scene, item })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a scene item in a scene. In other words, this is how you add a source into a scene.
|
||||
pub async fn add_scene_item(&self, scene_item: AddSceneItem<'_>) -> Result<i64> {
|
||||
self.client
|
||||
.send_message::<responses::SceneItemId>(RequestType::AddSceneItem(scene_item))
|
||||
.await
|
||||
.map(|sii| sii.item_id)
|
||||
}
|
||||
|
||||
/// Duplicates a scene item.
|
||||
pub async fn duplicate_scene_item(
|
||||
&self,
|
||||
scene_item: DuplicateSceneItem<'_>,
|
||||
) -> Result<responses::DuplicateSceneItem> {
|
||||
self.client
|
||||
.send_message(RequestType::DuplicateSceneItem(scene_item))
|
||||
.await
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{
|
||||
requests::{RequestType, SetStreamSettings, Stream},
|
||||
responses, Result,
|
||||
};
|
||||
|
||||
/// API functions related to streaming.
|
||||
pub struct Streaming<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Streaming<'a> {
|
||||
/// Get current streaming and recording status.
|
||||
pub async fn get_streaming_status(&self) -> Result<responses::StreamingStatus> {
|
||||
self.client
|
||||
.send_message(RequestType::GetStreamingStatus)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Toggle streaming on or off (depending on the current stream state).
|
||||
pub async fn start_stop_streaming(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartStopStreaming)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Start streaming. Will return an `error` if streaming is already active.
|
||||
///
|
||||
/// - `stream`: Special stream configuration. Note: these won't be saved to OBS' configuration.
|
||||
pub async fn start_streaming(&self, stream: Option<Stream<'_>>) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartStreaming { stream })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stop streaming. Will return an `error` if streaming is not active.
|
||||
pub async fn stop_streaming(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::StopStreaming).await
|
||||
}
|
||||
|
||||
/// Sets one or more attributes of the current streaming server settings. Any options not passed
|
||||
/// will remain unchanged. Returns the updated settings in response. If 'type' is different than
|
||||
/// the current streaming service type, all settings are required. Returns the full settings of
|
||||
/// the stream (the same as GetStreamSettings).
|
||||
pub async fn set_stream_settings(&self, settings: SetStreamSettings<'_>) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetStreamSettings(settings))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the current streaming server settings.
|
||||
pub async fn get_stream_settings(&self) -> Result<responses::GetStreamSettings> {
|
||||
self.client
|
||||
.send_message(RequestType::GetStreamSettings)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Save the current streaming server settings to disk.
|
||||
pub async fn save_stream_settings(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SaveStreamSettings)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send the provided text as embedded CEA-608 caption data.
|
||||
///
|
||||
/// - `text`: Captions text.
|
||||
pub async fn send_captions(&self, text: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SendCaptions { text })
|
||||
.await
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{
|
||||
requests::{RequestType, Transition},
|
||||
responses, Result,
|
||||
};
|
||||
|
||||
/// API functions related to the studio mode.
|
||||
pub struct StudioMode<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> StudioMode<'a> {
|
||||
/// Indicates if Studio Mode is currently enabled.
|
||||
pub async fn get_studio_mode_status(&self) -> Result<bool> {
|
||||
self.client
|
||||
.send_message::<responses::StudioModeStatus>(RequestType::GetStudioModeStatus)
|
||||
.await
|
||||
.map(|sms| sms.studio_mode)
|
||||
}
|
||||
|
||||
/// Get the name of the currently previewed scene and its list of sources. Will return an
|
||||
/// `error` if Studio Mode is not enabled.
|
||||
pub async fn get_preview_scene(&self) -> Result<responses::PreviewScene> {
|
||||
self.client.send_message(RequestType::GetPreviewScene).await
|
||||
}
|
||||
|
||||
/// Set the active preview scene. Will return an `error` if Studio Mode is not enabled.
|
||||
///
|
||||
/// - `scene_name`: The name of the scene to preview.
|
||||
pub async fn set_preview_scene(&self, scene_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetPreviewScene { scene_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Transitions the currently previewed scene to the main output. Will return an `error` if
|
||||
/// Studio Mode is not enabled.
|
||||
///
|
||||
/// - `with_transition`: Change the active transition before switching scenes. Defaults to the
|
||||
/// active transition.
|
||||
pub async fn transition_to_program(
|
||||
&self,
|
||||
with_transition: Option<Transition<'_>>,
|
||||
) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::TransitionToProgram { with_transition })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Enables Studio Mode.
|
||||
pub async fn enable_studio_mode(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::EnableStudioMode)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Disables Studio Mode.
|
||||
pub async fn disable_studio_mode(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::DisableStudioMode)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Toggles Studio Mode (depending on the current state of studio mode).
|
||||
pub async fn toggle_studio_mode(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::ToggleStudioMode)
|
||||
.await
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
use chrono::Duration;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Error, Result};
|
||||
|
||||
/// API functions related to transitions.
|
||||
pub struct Transitions<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> Transitions<'a> {
|
||||
/// List of all transitions available in the frontend's dropdown menu.
|
||||
pub async fn get_transition_list(&self) -> Result<responses::TransitionList> {
|
||||
self.client
|
||||
.send_message(RequestType::GetTransitionList)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the name of the currently selected transition in the frontend's dropdown menu.
|
||||
pub async fn get_current_transition(&self) -> Result<responses::CurrentTransition> {
|
||||
self.client
|
||||
.send_message(RequestType::GetCurrentTransition)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Set the active transition.
|
||||
///
|
||||
/// - `transition_name`: The name of the transition.
|
||||
pub async fn set_current_transition(&self, transition_name: &str) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetCurrentTransition { transition_name })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Set the duration of the currently selected transition if supported.
|
||||
///
|
||||
/// - `duration`: Desired duration of the transition (in milliseconds).
|
||||
pub async fn set_transition_duration(&self, duration: Duration) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetTransitionDuration { duration })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the duration of the currently selected transition if supported.
|
||||
pub async fn get_transition_duration(&self) -> Result<Duration> {
|
||||
self.client
|
||||
.send_message::<responses::TransitionDuration>(RequestType::GetTransitionDuration)
|
||||
.await
|
||||
.map(|td| td.transition_duration)
|
||||
}
|
||||
|
||||
/// Get the position of the current transition.
|
||||
pub async fn get_transition_position(&self) -> Result<f64> {
|
||||
self.client
|
||||
.send_message::<responses::TransitionPosition>(RequestType::GetTransitionPosition)
|
||||
.await
|
||||
.map(|tp| tp.position)
|
||||
}
|
||||
|
||||
/// Get the current settings of a transition.
|
||||
///
|
||||
/// - `transition_name`: Transition name.
|
||||
pub async fn get_transition_settings(
|
||||
&self,
|
||||
transition_name: &str,
|
||||
) -> Result<serde_json::Value> {
|
||||
self.client
|
||||
.send_message::<responses::TransitionSettings>(RequestType::GetTransitionSettings {
|
||||
transition_name,
|
||||
})
|
||||
.await
|
||||
.map(|ts| ts.transition_settings)
|
||||
}
|
||||
|
||||
/// Change the current settings of a transition.
|
||||
///
|
||||
/// - `transition_name`: Transition name.
|
||||
/// - `transition_settings`: Transition settings (they can be partial)
|
||||
pub async fn set_transition_settings<T>(
|
||||
&self,
|
||||
transition_name: &str,
|
||||
transition_settings: &T,
|
||||
) -> Result<serde_json::Value>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.client
|
||||
.send_message::<responses::TransitionSettings>(RequestType::SetTransitionSettings {
|
||||
transition_name,
|
||||
transition_settings: &serde_json::to_value(transition_settings)
|
||||
.map_err(Error::SerializeCustomData)?,
|
||||
})
|
||||
.await
|
||||
.map(|ts| ts.transition_settings)
|
||||
}
|
||||
|
||||
/// Release the T-Bar (like a user releasing their mouse button after moving it). *YOU MUST CALL
|
||||
/// THIS if you called [`set_t_bar_position`](Self::set_t_bar_position) with the `release`
|
||||
/// parameter set to `false`.*
|
||||
pub async fn release_t_bar(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::ReleaseTBar).await
|
||||
}
|
||||
|
||||
/// If your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in
|
||||
/// response to a user moving a T-Bar control in your User Interface), set `release` to false
|
||||
/// and call [`release_t_bar`](Self::release_t_bar) later once the animation/interaction is
|
||||
/// over.
|
||||
///
|
||||
/// - `position`: T-Bar position. This value must be between 0.0 and 1.0.
|
||||
/// - `release`: Whether or not the T-Bar gets released automatically after setting its new
|
||||
/// position (like a user releasing their mouse button after moving the T-Bar). Call
|
||||
/// [`release_t_bar`](Self::release_t_bar) manually if you set `release` to false. Defaults to
|
||||
/// true.
|
||||
pub async fn set_t_bar_position(&self, position: f64, release: Option<bool>) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::SetTBarPosition { position, release })
|
||||
.await
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
use super::Client;
|
||||
use crate::{requests::RequestType, responses, Result};
|
||||
|
||||
/// API functions related to the virtual cam.
|
||||
pub struct VirtualCam<'a> {
|
||||
pub(super) client: &'a Client,
|
||||
}
|
||||
|
||||
impl<'a> VirtualCam<'a> {
|
||||
/// Get current virtual cam status.
|
||||
pub async fn get_virtual_cam_status(&self) -> Result<responses::VirtualCamStatus> {
|
||||
self.client
|
||||
.send_message(RequestType::GetVirtualCamStatus)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Toggle virtual cam on or off (depending on the current virtual cam state).
|
||||
pub async fn start_stop_virtual_cam(&self) -> Result<()> {
|
||||
self.client
|
||||
.send_message(RequestType::StartStopVirtualCam)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Start virtual cam. Will return an `error` if virtual cam is already active.
|
||||
pub async fn start_virtual_cam(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::StartVirtualCam).await
|
||||
}
|
||||
|
||||
/// Stop virtual cam. Will return an error if virtual cam is not active.
|
||||
pub async fn stop_virtual_cam(&self) -> Result<()> {
|
||||
self.client.send_message(RequestType::StopVirtualCam).await
|
||||
}
|
||||
}
|
@ -1,309 +0,0 @@
|
||||
//! Common data structures shared between [`requests`](crate::requests),
|
||||
//! [`responses`](crate::responses) and [`events`](crate::events).
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// Response value for [`get_current_scene`](crate::client::Scenes::get_current_scene) as part of
|
||||
/// [`CurrentScene`](crate::responses::CurrentScene),
|
||||
/// [`get_scene_list`](crate::client::Scenes::get_scene_list) as part of
|
||||
/// [`Scene`](crate::responses::Scene),
|
||||
/// [`get_preview_scene`](crate::client::StudioMode::get_preview_scene) as part of
|
||||
/// [`PreviewScene`](crate::responses::PreviewScene),
|
||||
/// [`EventType::SwitchScenes`](crate::events::EventType::SwitchScenes),
|
||||
/// [`EventType::PreviewSceneChanged`](crate::events::EventType::PreviewSceneChanged),
|
||||
/// and **itself**.
|
||||
#[allow(missing_docs)] // Docs missing in the obs-websocket spec.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct SceneItem {
|
||||
pub cy: f64,
|
||||
pub cx: f64,
|
||||
/// The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and
|
||||
/// 4=Top or 8=Bottom, or omit to center on that axis.
|
||||
#[serde(deserialize_with = "crate::de::bitflags_u8")]
|
||||
pub alignment: Alignment,
|
||||
/// The name of this Scene Item.
|
||||
pub name: String,
|
||||
/// Scene item ID.
|
||||
pub id: i64,
|
||||
/// Whether or not this Scene Item is set to "visible".
|
||||
pub render: bool,
|
||||
/// Whether or not this Scene Item is muted.
|
||||
pub muted: bool,
|
||||
/// Whether or not this Scene Item is locked and can't be moved around
|
||||
pub locked: bool,
|
||||
pub source_cx: f64,
|
||||
pub source_cy: f64,
|
||||
/// Source type.
|
||||
#[serde(rename = "type")]
|
||||
pub ty: String,
|
||||
pub volume: f64,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
/// Name of the item's parent (if this item belongs to a group).
|
||||
#[serde(rename = "parentGroupName")]
|
||||
pub parent_group_name: Option<String>,
|
||||
/// List of children (if this item is a group).
|
||||
#[serde(rename = "groupChildren", default)]
|
||||
pub group_children: Vec<SceneItem>,
|
||||
}
|
||||
|
||||
/// Response value for
|
||||
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties) as part of
|
||||
/// [`SceneItemProperties`](crate::responses::SceneItemProperties),
|
||||
/// [`EventType::SceneItemTransformChanged`](crate::events::EventType::SceneItemTransformChanged)
|
||||
/// and **itself**.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SceneItemTransform {
|
||||
/// Position of the scene item.
|
||||
pub position: Position,
|
||||
/// The clockwise rotation of the scene item in degrees around the point of alignment.
|
||||
pub rotation: f64,
|
||||
/// Scaling factor of the scene item.
|
||||
pub scale: Scale,
|
||||
/// Pixel cropping of the scene item before scaling.
|
||||
pub crop: Crop,
|
||||
/// If the scene item is visible.
|
||||
pub visible: bool,
|
||||
/// If the scene item is locked in position.
|
||||
pub locked: bool,
|
||||
/// Bounding box of the source item.
|
||||
pub bounds: Bounds,
|
||||
/// Base width (without scaling) of the source.
|
||||
pub source_width: u64,
|
||||
/// Base source (without scaling) of the source.
|
||||
pub source_height: u64,
|
||||
/// Scene item width (base source width multiplied by the horizontal scaling factor).
|
||||
pub width: f64,
|
||||
/// Scene item height (base source height multiplied by the vertical scaling factor).
|
||||
pub height: f64,
|
||||
/// Name of the item's parent (if this item belongs to a group).
|
||||
pub parent_group_name: Option<String>,
|
||||
/// List of children (if this item is a group).
|
||||
#[serde(default)]
|
||||
pub group_children: Vec<SceneItemTransform>,
|
||||
}
|
||||
|
||||
/// Response value for
|
||||
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties) as part of
|
||||
/// [`SceneItemProperties`](crate::responses::SceneItemProperties).
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Position {
|
||||
/// The x position of the source from the left.
|
||||
pub x: f64,
|
||||
/// The y position of the source from the top.
|
||||
pub y: f64,
|
||||
/// The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and
|
||||
/// 4=Top or 8=Bottom, or omit to center on that axis.
|
||||
#[serde(deserialize_with = "crate::de::bitflags_u8")]
|
||||
pub alignment: Alignment,
|
||||
}
|
||||
|
||||
/// Response value for
|
||||
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties) as part of
|
||||
/// [`SceneItemProperties`](crate::responses::SceneItemProperties) and [`SceneItemTransform`].
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Scale {
|
||||
/// The x-scale factor of the source.
|
||||
pub x: f64,
|
||||
/// The y-scale factor of the source.
|
||||
pub y: f64,
|
||||
/// The scale filter of the source.
|
||||
pub filter: ScaleFilter,
|
||||
}
|
||||
|
||||
/// Different scaling filters that can be applied to a scene item as part of [`Scale`].
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
pub enum ScaleFilter {
|
||||
/// Disable any scaling filters.
|
||||
#[serde(rename = "OBS_SCALE_DISABLE")]
|
||||
Disable,
|
||||
/// Nearest neighbor scaling.
|
||||
#[serde(rename = "OBS_SCALE_POINT")]
|
||||
Point,
|
||||
/// Sharpened scaling, 16 samples.
|
||||
#[serde(rename = "OBS_SCALE_BICUBIC")]
|
||||
Bicubic,
|
||||
/// Fast but blurry scaling.
|
||||
#[serde(rename = "OBS_SCALE_BILINEAR")]
|
||||
Bilinear,
|
||||
/// Sharpened scaling, 36 samples.
|
||||
#[serde(rename = "OBS_SCALE_LANCZOS")]
|
||||
Lanczos,
|
||||
/// Weighted sum, 4/6/9 samples.
|
||||
#[serde(rename = "OBS_SCALE_AREA")]
|
||||
Area,
|
||||
}
|
||||
|
||||
/// Response value for
|
||||
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties) as part of
|
||||
/// [`SceneItemProperties`](crate::responses::SceneItemProperties) and [`SceneItemTransform`].
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Crop {
|
||||
/// The number of pixels cropped off the top of the source before scaling.
|
||||
pub top: u32,
|
||||
/// The number of pixels cropped off the right of the source before scaling.
|
||||
pub right: u32,
|
||||
/// The number of pixels cropped off the bottom of the source before scaling.
|
||||
pub bottom: u32,
|
||||
/// The number of pixels cropped off the left of the source before scaling.
|
||||
pub left: u32,
|
||||
}
|
||||
|
||||
/// Response value for
|
||||
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties) as part of
|
||||
/// [`SceneItemProperties`](crate::responses::SceneItemProperties) and [`SceneItemTransform`].
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Bounds {
|
||||
/// Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER",
|
||||
/// "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT",
|
||||
/// "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||
#[serde(rename = "type")]
|
||||
pub ty: BoundsType,
|
||||
/// Alignment of the bounding box.
|
||||
#[serde(deserialize_with = "crate::de::bitflags_u8")]
|
||||
pub alignment: Alignment,
|
||||
/// Width of the bounding box.
|
||||
pub x: f64,
|
||||
/// Height of the bounding box.
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
/// Monitoring type for audio outputs.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum MonitorType {
|
||||
/// No monitoring.
|
||||
None,
|
||||
/// Only monitor but don't output any sounds.
|
||||
MonitorOnly,
|
||||
/// Mintor the audio and output it at the same time.
|
||||
MonitorAndOutput,
|
||||
}
|
||||
|
||||
/// Text alignment used for GDI+ text properties.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Align {
|
||||
/// Align to the left.
|
||||
Left,
|
||||
/// Center the text in the middle (horizontally).
|
||||
Center,
|
||||
/// Align to the right.
|
||||
Right,
|
||||
}
|
||||
|
||||
/// Vertical text alignment use for GDI+ text properties.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Valign {
|
||||
/// Align to the top.
|
||||
Top,
|
||||
/// Center the text in the middle (vertically).
|
||||
Center,
|
||||
/// Align to the bottom.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// The type of streaming for service configurations.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StreamType {
|
||||
/// Customized RTMP streaming.
|
||||
RtmpCustom,
|
||||
/// Common RTMP configuration.
|
||||
RtmpCommon,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Different flags for font display that can be combined together.
|
||||
pub struct FontFlags: u8 {
|
||||
/// Make the text appear thicker.
|
||||
const BOLD = 1;
|
||||
/// Make the text appear cursive.
|
||||
const ITALIC = 2;
|
||||
/// Underline the text with a straight line.
|
||||
const UNDERLINE = 5;
|
||||
/// Strikeout the text.
|
||||
const STRIKEOUT = 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for FontFlags {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Self::from_bits(value).ok_or(Error::UnknownFlags(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontFlags> for u8 {
|
||||
fn from(value: FontFlags) -> Self {
|
||||
value.bits
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Alignment for different items on the scene that is described in two axis. The default is
|
||||
/// center for both axis.
|
||||
///
|
||||
/// For example, only using `LEFT` would arrange the target to the left horzontally and centered
|
||||
/// vertically. To align to the top right, the alignments can be combined to `LEFT | TOP`.
|
||||
/// Combining both values for a single axis is invalid, like `LEFT | RIGHT`.
|
||||
pub struct Alignment: u8 {
|
||||
/// Align to the left side.
|
||||
const LEFT = 1;
|
||||
/// Align to the right side.
|
||||
const RIGHT = 2;
|
||||
/// Align to the top.
|
||||
const TOP = 4;
|
||||
/// Align to the bottom.
|
||||
const BOTTOM = 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Alignment {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Self::from_bits(value).ok_or(Error::UnknownFlags(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Alignment> for u8 {
|
||||
fn from(value: Alignment) -> Self {
|
||||
value.bits
|
||||
}
|
||||
}
|
||||
|
||||
/// Different kinds of bounds that can be applied to different items on the scene as part of the
|
||||
/// [`Bounds`] type.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum BoundsType {
|
||||
/// Stretch to bounds.
|
||||
#[serde(rename = "OBS_BOUNDS_STRETCH")]
|
||||
Stretch,
|
||||
/// Scale to inner bounds.
|
||||
#[serde(rename = "OBS_BOUNDS_SCALE_INNER")]
|
||||
ScaleInner,
|
||||
/// Scale to outer bounds.
|
||||
#[serde(rename = "OBS_BOUNDS_SCALE_OUTER")]
|
||||
ScaleOuter,
|
||||
/// Scale to width of bounds.
|
||||
#[serde(rename = "OBS_BOUNDS_SCALE_TO_WIDTH")]
|
||||
ScaleToWidth,
|
||||
/// Scale to height of bounds.
|
||||
#[serde(rename = "OBS_BOUNDS_SCALE_TO_HEIGHT")]
|
||||
ScaleToHeight,
|
||||
/// Maximum size only.
|
||||
#[serde(rename = "OBS_BOUNDS_MAX_ONLY")]
|
||||
MaxOnly,
|
||||
/// No bounds.
|
||||
#[serde(rename = "OBS_BOUNDS_NONE")]
|
||||
None,
|
||||
}
|
@ -1,637 +0,0 @@
|
||||
//! Custom deserializers that are used in both the [`events`](crate::events) and
|
||||
//! [`responses`](crate::responses) modules.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{self, Display},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use chrono::Duration;
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
#[error("hours missing")]
|
||||
HoursMissing,
|
||||
#[error("minutes missing")]
|
||||
MinutesMissing,
|
||||
#[error("seconds missing")]
|
||||
SecondsMissing,
|
||||
#[error("milliseconds missing")]
|
||||
MillisecondsMissing,
|
||||
#[error("invalid integer")]
|
||||
InvalidInteger(#[from] std::num::ParseIntError),
|
||||
#[error("value {1} is too large for an i64: {0}")]
|
||||
ValueTooLargeI64(#[source] std::num::TryFromIntError, u64),
|
||||
#[error("value doesn't fit into an u8 integer: {0}")]
|
||||
ValueDoesntFitU8(#[source] std::num::TryFromIntError),
|
||||
#[error("conversion from u8 failed: {0}")]
|
||||
ConversionFailed(String),
|
||||
}
|
||||
|
||||
pub fn duration_opt<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_option(DurationOptVisitor)
|
||||
}
|
||||
|
||||
struct DurationOptVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DurationOptVisitor {
|
||||
type Value = Option<Duration>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an optional duration formatted as 'HH:MM:SS.mmm'")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
let duration = || -> Result<Duration, Error> {
|
||||
let mut hms = v.splitn(3, ':');
|
||||
let hours = hms.next().ok_or(Error::HoursMissing)?.parse()?;
|
||||
let minutes = hms.next().ok_or(Error::MinutesMissing)?.parse()?;
|
||||
let seconds = hms.next().ok_or(Error::SecondsMissing)?;
|
||||
|
||||
let mut sm = seconds.splitn(2, '.');
|
||||
let seconds = sm.next().ok_or(Error::SecondsMissing)?.parse()?;
|
||||
let millis = sm.next().ok_or(Error::MillisecondsMissing)?.parse()?;
|
||||
|
||||
Ok(Duration::hours(hours)
|
||||
+ Duration::minutes(minutes)
|
||||
+ Duration::seconds(seconds)
|
||||
+ Duration::milliseconds(millis))
|
||||
};
|
||||
|
||||
duration().map(Some).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(Self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_millis_opt<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_option(DurationMillisOptVisitor)
|
||||
}
|
||||
|
||||
struct DurationMillisOptVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DurationMillisOptVisitor {
|
||||
type Value = Option<Duration>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a duration in milliseconds where -1 means a fixed duration")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(if v < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::milliseconds(v))
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match i64::try_from(v) {
|
||||
Ok(value) => self.visit_i64(value),
|
||||
Err(e) => Err(de::Error::custom(Error::ValueTooLargeI64(e, v))),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(Self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(DurationMillisVisitor)
|
||||
}
|
||||
|
||||
struct DurationMillisVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DurationMillisVisitor {
|
||||
type Value = Duration;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a duration in milliseconds")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(Duration::milliseconds(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match i64::try_from(v) {
|
||||
Ok(value) => self.visit_i64(value),
|
||||
Err(e) => Err(de::Error::custom(Error::ValueTooLargeI64(e, v))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_nanos<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_i64(DurationNanosVisitor)
|
||||
}
|
||||
|
||||
struct DurationNanosVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for DurationNanosVisitor {
|
||||
type Value = Duration;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a duration in nanoseconds")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(Duration::nanoseconds(v))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match i64::try_from(v) {
|
||||
Ok(value) => self.visit_i64(value),
|
||||
Err(e) => Err(de::Error::custom(Error::ValueTooLargeI64(e, v))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitflags_u8<'de, D, T, TE>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: TryFrom<u8, Error = TE>,
|
||||
TE: Display,
|
||||
{
|
||||
deserializer.deserialize_u8(BitflagsU8Visitor { flags: PhantomData })
|
||||
}
|
||||
|
||||
struct BitflagsU8Visitor<T, TE> {
|
||||
flags: PhantomData<(T, TE)>,
|
||||
}
|
||||
|
||||
impl<'de, T, TE> Visitor<'de> for BitflagsU8Visitor<T, TE>
|
||||
where
|
||||
T: TryFrom<u8, Error = TE>,
|
||||
TE: Display,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("bitflags encoded as u8 integer")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
u8::try_from(v)
|
||||
.map_err(|e| de::Error::custom(Error::ValueDoesntFitU8(e)))
|
||||
.and_then(|v| self.visit_u8(v))
|
||||
}
|
||||
|
||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
T::try_from(v).map_err(|e| de::Error::custom(Error::ConversionFailed(e.to_string())))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
u8::try_from(v)
|
||||
.map_err(|e| de::Error::custom(Error::ValueDoesntFitU8(e)))
|
||||
.and_then(|v| self.visit_u8(v))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Context;
|
||||
use bitflags::bitflags;
|
||||
use serde::Deserialize;
|
||||
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deser_duration_opt() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(deserialize_with = "duration_opt")]
|
||||
value: Option<Duration>,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(
|
||||
Duration::hours(2)
|
||||
+ Duration::minutes(15)
|
||||
+ Duration::seconds(4)
|
||||
+ Duration::milliseconds(310),
|
||||
),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Str("02:15:04.310"),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(
|
||||
Duration::hours(2)
|
||||
+ Duration::minutes(15)
|
||||
+ Duration::seconds(4)
|
||||
+ Duration::milliseconds(310),
|
||||
),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Some,
|
||||
Token::Str("02:15:04.310"),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deser_duration_millis_opt() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(deserialize_with = "duration_millis_opt")]
|
||||
value: Option<Duration>,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(Duration::milliseconds(150)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(-1),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(Duration::milliseconds(150)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleDuration>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(u64::MAX),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"value 18446744073709551615 is too large for an i64: \
|
||||
out of range integral type conversion attempted",
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(Duration::milliseconds(150)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Some,
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deser_duration_millis() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(deserialize_with = "duration_millis")]
|
||||
value: Duration,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Duration::milliseconds(150),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Duration::milliseconds(150),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleDuration>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(u64::MAX),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"value 18446744073709551615 is too large for an i64: \
|
||||
out of range integral type conversion attempted",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deser_duration_nanos() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(deserialize_with = "duration_nanos")]
|
||||
value: Duration,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Duration::nanoseconds(150),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleDuration {
|
||||
value: Duration::nanoseconds(150),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleDuration>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(u64::MAX),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"value 18446744073709551615 is too large for an i64: \
|
||||
out of range integral type conversion attempted",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deser_bitflags_u8() {
|
||||
bitflags! {
|
||||
struct Flags: u8 {
|
||||
const ONE = 1;
|
||||
const TWO = 2;
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Flags {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Self::from_bits(value).context("unknown flags found")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleFlags {
|
||||
#[serde(deserialize_with = "bitflags_u8")]
|
||||
value: Flags,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleFlags {
|
||||
value: Flags::ONE | Flags::TWO,
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(3),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleFlags>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(i64::MAX),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"value doesn't fit into an u8 integer: out of range integral type conversion attempted",
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleFlags {
|
||||
value: Flags::ONE | Flags::TWO,
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U8(3),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleFlags>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U8(100),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"conversion from u8 failed: unknown flags found",
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleFlags {
|
||||
value: Flags::ONE | Flags::TWO,
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(3),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleFlags>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(u64::MAX),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"value doesn't fit into an u8 integer: out of range integral type conversion attempted",
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,261 +0,0 @@
|
||||
use chrono::Duration;
|
||||
use rgb::RGBA8;
|
||||
use serde::ser::{self, Serializer};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
#[error("duration of {} days is too big to be serialized as nanoseconds", .0.num_days())]
|
||||
DurationTooBig(Duration),
|
||||
}
|
||||
|
||||
pub fn duration_millis_opt<S>(value: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match value {
|
||||
Some(duration) => serializer.serialize_some(&duration.num_milliseconds()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_millis<S>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i64(value.num_milliseconds())
|
||||
}
|
||||
|
||||
pub fn duration_nanos<S>(value: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match value.num_nanoseconds() {
|
||||
Some(nanos) => serializer.serialize_i64(nanos),
|
||||
None => Err(ser::Error::custom(Error::DurationTooBig(*value))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitflags_u8_opt<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
T: Into<u8> + Copy,
|
||||
{
|
||||
match value {
|
||||
Some(flags) => serializer.serialize_some(&(*flags).into()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgba8_inverse_opt<S>(value: &Option<RGBA8>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match value {
|
||||
Some(rgba) => {
|
||||
let abgr = (rgba.a as u32) << 24
|
||||
| (rgba.b as u32) << 16
|
||||
| (rgba.g as u32) << 8
|
||||
| (rgba.r as u32);
|
||||
serializer.serialize_some(&abgr)
|
||||
}
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bitflags::bitflags;
|
||||
use serde::Serialize;
|
||||
use serde_test::{assert_ser_tokens, assert_ser_tokens_error, Token};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ser_duration_millis_opt() {
|
||||
#[derive(Serialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(serialize_with = "duration_millis_opt")]
|
||||
value: Option<Duration>,
|
||||
}
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(Duration::milliseconds(150)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Some,
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleDuration { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_duration_millis() {
|
||||
#[derive(Serialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(serialize_with = "duration_millis")]
|
||||
value: Duration,
|
||||
}
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleDuration {
|
||||
value: Duration::milliseconds(150),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_duration_nanos() {
|
||||
#[derive(Serialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(serialize_with = "duration_nanos")]
|
||||
value: Duration,
|
||||
}
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleDuration {
|
||||
value: Duration::nanoseconds(150),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(150),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_ser_tokens_error(
|
||||
&SimpleDuration {
|
||||
value: Duration::days(365_000_000),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
],
|
||||
"duration of 365000000 days is too big to be serialized as nanoseconds",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_bitflags_u8_opt() {
|
||||
bitflags! {
|
||||
struct Flags: u8 {
|
||||
const ONE = 1;
|
||||
const TWO = 2;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Flags> for u8 {
|
||||
fn from(value: Flags) -> Self {
|
||||
value.bits
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SimpleFlags {
|
||||
#[serde(serialize_with = "bitflags_u8_opt")]
|
||||
value: Option<Flags>,
|
||||
}
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleFlags {
|
||||
value: Some(Flags::ONE | Flags::TWO),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Some,
|
||||
Token::U8(3),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleFlags { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleFlags",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ser_rgba8_inverse_opt() {
|
||||
#[derive(Serialize)]
|
||||
struct SimpleDuration {
|
||||
#[serde(serialize_with = "rgba8_inverse_opt")]
|
||||
value: Option<RGBA8>,
|
||||
}
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleDuration {
|
||||
value: Some(RGBA8::new(1, 2, 3, 4)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Some,
|
||||
Token::U32(0x04030201),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_ser_tokens(
|
||||
&SimpleDuration { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleDuration",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
use std::{convert::TryFrom, fmt, iter::FromIterator, marker::PhantomData};
|
||||
|
||||
use rgb::RGBA8;
|
||||
use serde::de::{Deserializer, Error, Visitor};
|
||||
|
||||
pub fn string_comma_list<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromIterator<String>,
|
||||
{
|
||||
deserializer.deserialize_str(StringListVisitor {
|
||||
sep: ',',
|
||||
container: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
struct StringListVisitor<T> {
|
||||
sep: char,
|
||||
container: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for StringListVisitor<T>
|
||||
where
|
||||
T: FromIterator<String>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
"a string containing values separated by '{}'",
|
||||
self.sep
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(v.split(self.sep).map(|s| s.to_owned()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgba8_inverse_opt<'de, D>(deserializer: D) -> Result<Option<RGBA8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_option(Rgba8InverseOptVisitor)
|
||||
}
|
||||
|
||||
struct Rgba8InverseOptVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for Rgba8InverseOptVisitor {
|
||||
type Value = Option<RGBA8>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a RGBA color value encoded as integer in inverse order (ABGR)")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match u32::try_from(v) {
|
||||
Ok(v) => self.visit_u32(v),
|
||||
Err(e) => Err(Error::custom(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(Some(RGBA8::new(
|
||||
(v & 0xff) as u8,
|
||||
(v >> 8 & 0xff) as u8,
|
||||
(v >> 16 & 0xff) as u8,
|
||||
(v >> 24 & 0xff) as u8,
|
||||
)))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match u32::try_from(v) {
|
||||
Ok(v) => self.visit_u32(v),
|
||||
Err(e) => Err(Error::custom(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_u32(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde::Deserialize;
|
||||
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn deser_string_comma_list() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleList {
|
||||
#[serde(deserialize_with = "string_comma_list")]
|
||||
value: Vec<String>,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleList {
|
||||
value: vec!["a".to_owned(), "b".to_owned(), "c".to_owned()],
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleList",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Str("a,b,c"),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deser_rgba8_inverse_opt() {
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
struct SimpleColor {
|
||||
#[serde(deserialize_with = "rgba8_inverse_opt")]
|
||||
value: Option<RGBA8>,
|
||||
}
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleColor {
|
||||
value: Some(RGBA8::new(1, 2, 3, 4)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(0x04030201),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleColor>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::I64(i64::MIN),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"out of range integral type conversion attempted",
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleColor {
|
||||
value: Some(RGBA8::new(1, 2, 3, 4)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U32(0x04030201),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleColor {
|
||||
value: Some(RGBA8::new(1, 2, 3, 4)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(0x04030201),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens_error::<SimpleColor>(
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::U64(u64::MAX),
|
||||
Token::StructEnd,
|
||||
],
|
||||
"out of range integral type conversion attempted",
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleColor { value: None },
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
|
||||
assert_de_tokens(
|
||||
&SimpleColor {
|
||||
value: Some(RGBA8::new(1, 2, 3, 4)),
|
||||
},
|
||||
&[
|
||||
Token::Struct {
|
||||
name: "SimpleColor",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("value"),
|
||||
Token::Some,
|
||||
Token::U32(0x04030201),
|
||||
Token::StructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,57 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
use obws::{
|
||||
requests::SetProfileParameter,
|
||||
responses::{Profiles, SceneCollections},
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.config();
|
||||
|
||||
let SceneCollections {
|
||||
current_scene_collection_name,
|
||||
scene_collections,
|
||||
} = client.get_scene_collection_list().await?;
|
||||
let other = scene_collections
|
||||
.iter()
|
||||
.find(|sc| *sc != ¤t_scene_collection_name)
|
||||
.unwrap();
|
||||
client.set_current_scene_collection(&other).await?;
|
||||
client
|
||||
.set_current_scene_collection(¤t_scene_collection_name)
|
||||
.await?;
|
||||
|
||||
let Profiles {
|
||||
current_profile_name,
|
||||
profiles,
|
||||
} = client.get_profile_list().await?;
|
||||
let other = profiles
|
||||
.iter()
|
||||
.find(|p| *p != ¤t_profile_name)
|
||||
.unwrap();
|
||||
client.set_current_profile(&other).await?;
|
||||
client.set_current_profile(¤t_profile_name).await?;
|
||||
|
||||
client.get_profile_parameter("General", "Name").await?;
|
||||
client
|
||||
.set_profile_parameter(SetProfileParameter {
|
||||
parameter_category: "OBWS",
|
||||
parameter_name: "Test",
|
||||
parameter_value: Some("Value"),
|
||||
})
|
||||
.await?;
|
||||
client
|
||||
.set_profile_parameter(SetProfileParameter {
|
||||
parameter_category: "OBWS",
|
||||
parameter_name: "Test",
|
||||
parameter_value: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
use obws::requests::{SetInputSettings, Volume};
|
||||
|
||||
use crate::common::{INPUT_KIND_BROWSER, TEST_BROWSER, TEST_BROWSER_RENAME, TEST_MEDIA};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.inputs();
|
||||
|
||||
client.get_input_list(None).await?;
|
||||
client.get_input_kind_list(false).await?;
|
||||
client
|
||||
.get_input_default_settings(INPUT_KIND_BROWSER)
|
||||
.await?;
|
||||
|
||||
let settings = client
|
||||
.get_input_settings(TEST_BROWSER)
|
||||
.await?
|
||||
.input_settings;
|
||||
client
|
||||
.set_input_settings(SetInputSettings {
|
||||
input_name: TEST_BROWSER,
|
||||
input_settings: settings,
|
||||
overlay: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let muted = client.get_input_mute(TEST_MEDIA).await?;
|
||||
client.set_input_mute(TEST_MEDIA, !muted).await?;
|
||||
client.set_input_mute(TEST_MEDIA, muted).await?;
|
||||
client.toggle_input_mute(TEST_MEDIA).await?;
|
||||
client.toggle_input_mute(TEST_MEDIA).await?;
|
||||
|
||||
let volume = client.get_input_volume(TEST_MEDIA).await?;
|
||||
client
|
||||
.set_input_volume(TEST_MEDIA, Volume::Mul(volume.input_volume_mul))
|
||||
.await?;
|
||||
|
||||
client
|
||||
.set_input_name(TEST_BROWSER, TEST_BROWSER_RENAME)
|
||||
.await?;
|
||||
client
|
||||
.set_input_name(TEST_BROWSER_RENAME, TEST_BROWSER)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use obws::events::{Event, EventType};
|
||||
|
||||
use crate::common::TEST_MEDIA;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let events = client.events()?;
|
||||
let client = client.media_control();
|
||||
|
||||
pin_mut!(events);
|
||||
|
||||
client.play_pause_media(TEST_MEDIA, Some(false)).await?;
|
||||
wait_for!(events, EventType::MediaPlaying { .. });
|
||||
client.next_media(TEST_MEDIA).await?;
|
||||
wait_for!(events, EventType::MediaNext { .. });
|
||||
client.previous_media(TEST_MEDIA).await?;
|
||||
wait_for!(events, EventType::MediaPrevious { .. });
|
||||
client.play_pause_media(TEST_MEDIA, Some(true)).await?;
|
||||
wait_for!(events, EventType::MediaPaused { .. });
|
||||
|
||||
let duration = client.get_media_duration(TEST_MEDIA).await?;
|
||||
client.set_media_time(TEST_MEDIA, duration / 2).await?;
|
||||
client.get_media_time(TEST_MEDIA).await?;
|
||||
client.scrub_media(TEST_MEDIA, duration / 4).await?;
|
||||
client.get_media_state(TEST_MEDIA).await?;
|
||||
|
||||
client.restart_media(TEST_MEDIA).await?;
|
||||
wait_for!(events, EventType::MediaRestarted { .. });
|
||||
client.stop_media(TEST_MEDIA).await?;
|
||||
wait_for!(events, EventType::MediaStopped { .. });
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::common::TEST_OUTPUT;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.outputs();
|
||||
|
||||
client.list_outputs().await?;
|
||||
client.get_output_info(TEST_OUTPUT).await?;
|
||||
client.start_output(TEST_OUTPUT).await?;
|
||||
client.stop_output(TEST_OUTPUT, Some(true)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::time;
|
||||
|
||||
use crate::common::TEST_PROFILE;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.profiles();
|
||||
|
||||
client.list_profiles().await?;
|
||||
|
||||
let original = client.get_current_profile().await?;
|
||||
client.set_current_profile(TEST_PROFILE).await?;
|
||||
|
||||
// Give OBS some time to switch profiles
|
||||
time::sleep(Duration::from_millis(200)).await;
|
||||
|
||||
client.set_current_profile(&original).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use std::{path::Path, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use obws::events::{Event, EventType};
|
||||
use tokio::time;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let events = client.events()?;
|
||||
let client = client.recording();
|
||||
|
||||
pin_mut!(events);
|
||||
|
||||
client.get_recording_status().await?;
|
||||
|
||||
client.start_stop_recording().await?;
|
||||
wait_for!(events, EventType::RecordingStarted { .. });
|
||||
client.start_stop_recording().await?;
|
||||
wait_for!(events, EventType::RecordingStopped { .. });
|
||||
|
||||
// Wait a little more as recording sometimes doesn't start when started/stopped frequently.
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
client.start_recording().await?;
|
||||
wait_for!(events, EventType::RecordingStarted { .. });
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
client.pause_recording().await?;
|
||||
wait_for!(events, EventType::RecordingPaused);
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
client.resume_recording().await?;
|
||||
wait_for!(events, EventType::RecordingResumed);
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
client.stop_recording().await?;
|
||||
wait_for!(events, EventType::RecordingStopped { .. });
|
||||
|
||||
let original = client.get_recording_folder().await?;
|
||||
client.set_recording_folder(Path::new("test")).await?;
|
||||
client.set_recording_folder(&original).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use obws::events::{Event, EventType};
|
||||
use tokio::time;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let events = client.events()?;
|
||||
let client = client.replay_buffer();
|
||||
|
||||
pin_mut!(events);
|
||||
|
||||
client.get_replay_buffer_status().await?;
|
||||
|
||||
client.start_stop_replay_buffer().await?;
|
||||
wait_for!(events, EventType::ReplayStarted { .. });
|
||||
client.start_stop_replay_buffer().await?;
|
||||
wait_for!(events, EventType::ReplayStopped { .. });
|
||||
|
||||
// Wait a little more as the replay buffer sometimes doesn't start when started/stopped
|
||||
// frequently.
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
client.start_replay_buffer().await?;
|
||||
wait_for!(events, EventType::ReplayStarted { .. });
|
||||
client.save_replay_buffer().await?;
|
||||
client.stop_replay_buffer().await?;
|
||||
wait_for!(events, EventType::ReplayStopped { .. });
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::time;
|
||||
|
||||
use crate::common::TEST_COLLECTION;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.scene_collections();
|
||||
|
||||
let other = client
|
||||
.list_scene_collections()
|
||||
.await?
|
||||
.into_iter()
|
||||
.find(|sc| sc.sc_name != TEST_COLLECTION)
|
||||
.context("only the test scene collection exists, but another is needed for tests")?
|
||||
.sc_name;
|
||||
|
||||
let original = client.get_current_scene_collection().await?;
|
||||
client.set_current_scene_collection(&other).await?;
|
||||
|
||||
// Give OBS some time to load the scene collection
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
client.set_current_scene_collection(&original).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
use either::Either;
|
||||
use obws::requests::{
|
||||
DuplicateSceneItem, SceneItemProperties, SceneItemRender, SceneItemSpecification,
|
||||
};
|
||||
|
||||
use crate::common::{TEST_SCENE, TEXT_SOURCE};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.scene_items();
|
||||
|
||||
client.get_scene_item_list(Some(TEST_SCENE)).await?;
|
||||
|
||||
let props = client
|
||||
.get_scene_item_properties(Some(TEST_SCENE), Either::Left(TEXT_SOURCE))
|
||||
.await?;
|
||||
client
|
||||
.reset_scene_item(Some(TEST_SCENE), Either::Left(TEXT_SOURCE))
|
||||
.await?;
|
||||
client
|
||||
.set_scene_item_properties(SceneItemProperties {
|
||||
scene_name: Some(TEST_SCENE),
|
||||
item: Either::Left(TEXT_SOURCE),
|
||||
position: Some((&props.position).into()),
|
||||
rotation: Some(props.rotation),
|
||||
scale: Some((&props.scale).into()),
|
||||
crop: Some((&props.crop).into()),
|
||||
visible: Some(props.visible),
|
||||
locked: Some(props.locked),
|
||||
bounds: Some((&props.bounds).into()),
|
||||
})
|
||||
.await?;
|
||||
|
||||
client
|
||||
.set_scene_item_render(SceneItemRender {
|
||||
scene_name: Some(TEST_SCENE),
|
||||
source: TEXT_SOURCE,
|
||||
item: None,
|
||||
render: !props.visible,
|
||||
})
|
||||
.await?;
|
||||
client
|
||||
.set_scene_item_render(SceneItemRender {
|
||||
scene_name: Some(TEST_SCENE),
|
||||
source: TEXT_SOURCE,
|
||||
item: None,
|
||||
render: props.visible,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let item = client
|
||||
.duplicate_scene_item(DuplicateSceneItem {
|
||||
from_scene: Some(TEST_SCENE),
|
||||
to_scene: Some(TEST_SCENE),
|
||||
item: SceneItemSpecification {
|
||||
id: None,
|
||||
name: Some(TEXT_SOURCE),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
client
|
||||
.delete_scene_item(
|
||||
Some(TEST_SCENE),
|
||||
SceneItemSpecification {
|
||||
id: Some(item.item.id),
|
||||
name: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO: Need to create a source first, but there is no way to delete it afterwards.
|
||||
// Therefore, we don't call this function until a method becomes available.
|
||||
//client.add_scene_item(AddSceneItem{});
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,86 +1,45 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::Duration;
|
||||
use obws::requests::{SceneItem, SceneTransitionOverride};
|
||||
|
||||
use crate::common::{
|
||||
TEST_BROWSER, TEST_MEDIA, TEST_SCENE, TEST_SCENE_2, TEST_TRANSITION, TEXT_SOURCE, TEXT_SOURCE_2,
|
||||
};
|
||||
use common::{TEST_SCENE, TEST_SCENE_CREATE, TEST_SCENE_RENAME};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let general = client.general();
|
||||
let client = client.scenes();
|
||||
|
||||
let original = client.get_current_scene().await?.name;
|
||||
client.set_current_scene(TEST_SCENE_2).await?;
|
||||
client.set_current_scene(&original).await?;
|
||||
general.set_studio_mode_enabled(true).await?;
|
||||
|
||||
let scenes = client.get_scene_list().await?.scenes;
|
||||
|
||||
let current = client.get_current_program_scene().await?;
|
||||
let other = &scenes
|
||||
.iter()
|
||||
.find(|s| s.scene_name != current)
|
||||
.unwrap()
|
||||
.scene_name;
|
||||
client.set_current_program_scene(other).await?;
|
||||
client.set_current_program_scene(¤t).await?;
|
||||
|
||||
client.get_scene_list().await?;
|
||||
let current = client.get_current_preview_scene().await?.unwrap();
|
||||
let other = &scenes
|
||||
.iter()
|
||||
.find(|s| s.scene_name != current)
|
||||
.unwrap()
|
||||
.scene_name;
|
||||
client.set_current_preview_scene(other).await?;
|
||||
client.set_current_preview_scene(¤t).await?;
|
||||
|
||||
// TODO: Currently no way of deleting scenes so we skip this to not
|
||||
// fill up OBS with random scenes on every run.
|
||||
// client.create_scene("__TEMP").await?;
|
||||
client.set_scene_name(TEST_SCENE, TEST_SCENE_RENAME).await?;
|
||||
client.set_scene_name(TEST_SCENE_RENAME, TEST_SCENE).await?;
|
||||
|
||||
client
|
||||
.reorder_scene_items(
|
||||
Some(TEST_SCENE),
|
||||
&[
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEXT_SOURCE_2),
|
||||
},
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEXT_SOURCE),
|
||||
},
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEST_BROWSER),
|
||||
},
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEST_MEDIA),
|
||||
},
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
client
|
||||
.reorder_scene_items(
|
||||
Some(TEST_SCENE),
|
||||
&[
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEXT_SOURCE),
|
||||
},
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEXT_SOURCE_2),
|
||||
},
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEST_BROWSER),
|
||||
},
|
||||
SceneItem {
|
||||
id: None,
|
||||
name: Some(TEST_MEDIA),
|
||||
},
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
client.create_scene(TEST_SCENE_CREATE).await?;
|
||||
client.remove_scene(TEST_SCENE_CREATE).await?;
|
||||
|
||||
client
|
||||
.set_scene_transition_override(SceneTransitionOverride {
|
||||
scene_name: TEST_SCENE,
|
||||
transition_name: TEST_TRANSITION,
|
||||
transition_duration: Some(Duration::milliseconds(10)),
|
||||
})
|
||||
.await?;
|
||||
client.get_scene_transition_override(TEST_SCENE).await?;
|
||||
client.remove_scene_transition_override(TEST_SCENE).await?;
|
||||
general.set_studio_mode_enabled(false).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::Duration;
|
||||
use obws::requests::Transition;
|
||||
|
||||
use crate::common::{TEST_SCENE_2, TEST_TRANSITION};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let client = client.studio_mode();
|
||||
|
||||
client.get_studio_mode_status().await?;
|
||||
client.enable_studio_mode().await?;
|
||||
|
||||
let original = client.get_preview_scene().await?.name;
|
||||
client.set_preview_scene(TEST_SCENE_2).await?;
|
||||
client.set_preview_scene(&original).await?;
|
||||
|
||||
client
|
||||
.transition_to_program(Some(Transition {
|
||||
name: TEST_TRANSITION,
|
||||
duration: Some(Duration::milliseconds(10)),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
client.disable_studio_mode().await?;
|
||||
client.toggle_studio_mode().await?;
|
||||
client.toggle_studio_mode().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::common::TEST_TRANSITION_2;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let studio_mode = client.studio_mode();
|
||||
let client = client.transitions();
|
||||
|
||||
client.get_transition_list().await?;
|
||||
let original = client.get_current_transition().await?.name;
|
||||
client.set_current_transition(TEST_TRANSITION_2).await?;
|
||||
client.set_current_transition(&original).await?;
|
||||
|
||||
let original = client.get_transition_duration().await?;
|
||||
client.set_transition_duration(original * 2).await?;
|
||||
client.set_transition_duration(original).await?;
|
||||
|
||||
client.get_transition_position().await?;
|
||||
let settings = client.get_transition_settings(TEST_TRANSITION_2).await?;
|
||||
client
|
||||
.set_transition_settings(TEST_TRANSITION_2, &settings)
|
||||
.await?;
|
||||
|
||||
studio_mode.enable_studio_mode().await?;
|
||||
client.set_t_bar_position(0.5, Some(false)).await?;
|
||||
client.set_t_bar_position(0.0, Some(false)).await?;
|
||||
client.release_t_bar().await?;
|
||||
studio_mode.disable_studio_mode().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
#![cfg(feature = "test-integration")]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use obws::events::{Event, EventType};
|
||||
use tokio::time;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
#[tokio::test]
|
||||
async fn main() -> Result<()> {
|
||||
let client = common::new_client().await?;
|
||||
let events = client.events()?;
|
||||
let client = client.virtual_cam();
|
||||
|
||||
pin_mut!(events);
|
||||
|
||||
client.get_virtual_cam_status().await?;
|
||||
|
||||
client.start_stop_virtual_cam().await?;
|
||||
wait_for!(events, EventType::VirtualCamStarted { .. });
|
||||
client.start_stop_virtual_cam().await?;
|
||||
wait_for!(events, EventType::VirtualCamStopped { .. });
|
||||
|
||||
// Wait a little more as the virtual cam sometimes doesn't start when started/stopped
|
||||
// frequently.
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
client.start_virtual_cam().await?;
|
||||
wait_for!(events, EventType::VirtualCamStarted { .. });
|
||||
client.stop_virtual_cam().await?;
|
||||
wait_for!(events, EventType::VirtualCamStopped { .. });
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue