Initial commit

pull/1/head v0.1.0
Dominik Nakamura 3 years ago
commit a96db636f6
No known key found for this signature in database
GPG Key ID: E4C6A749B2491910

4
.gitignore vendored

@ -0,0 +1,4 @@
/target
.env
Cargo.lock

@ -0,0 +1,32 @@
[package]
name = "obws"
version = "0.1.0"
authors = ["Dominik Nakamura <dnaka91@gmail.com>"]
edition = "2018"
license = "MIT"
readme = "README.md"
description = "The obws (obvious) remote control library for OBS."
homepage = "https://github.com/dnaka91/obws"
repository = "https://github.com/dnaka91/obws"
categories = ["api-bindings", "web-programming"]
keywords = ["async", "obs", "obs-websocket", "remote-control", "tokio"]
[dependencies]
anyhow = "1.0.36"
async-stream = "0.3.0"
base64 = "0.13.0"
either = { version = "1.6.1", features = ["serde"] }
futures-util = { version = "0.3.8", features = ["sink"] }
log = "0.4.11"
serde = { version = "1.0.118", features = ["derive"] }
serde_json = "1.0.60"
serde_with = "1.6.0"
sha2 = "0.9.2"
tokio = { version = "0.3.6", features = ["net", "sync"] }
tokio-tungstenite = "0.12.0"
tungstenite = { version = "0.11.1", default-features = false }
[dev-dependencies]
dotenv = "0.15.0"
pretty_env_logger = "0.4.0"
tokio = { version = "0.3.6", features = ["fs", "macros", "rt-multi-thread", "time"] }

@ -0,0 +1,19 @@
Copyright (c) 2020 Dominik Nakamura
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,9 @@
# OBWS - The obws (obvious) remote control library for OBS
Library to remote control OBS with the [obs-websocket] plugin.
[obs-websocket]: https://github.com/Palakis/obs-websocket
## License
This project is licensed under [MIT License](LICENSE) (or <http://opensource.org/licenses/MIT>).

@ -0,0 +1,12 @@
# Examples
These are several examples that show how to use `obws`. If you require authentication for your OBS
instance, create a `.env` file in the project root and add a `OBS_PASSWORD` entry in it. The
examples will pick up the password and authenticate automatically.
- `simple` A very basic example showing how to connect, login and print out current version
information and the list of scenes.
- `iter_scenes` Get a list of all scenes and endlessly iterate through them with a small pause
between each change.
- `screenshot` Take a screenshot of the currently visible scene and save it as `screenshot.png`.
- `events` Shows how to get a stream of user events and simply print them out to the terminal.

@ -0,0 +1,27 @@
use std::env;
use anyhow::Result;
use futures_util::{pin_mut, StreamExt};
use obws::client::Client;
#[tokio::main]
async fn main() -> Result<()> {
dotenv::dotenv().ok();
env::set_var("RUST_LOG", "obws=debug");
pretty_env_logger::init();
let client = Client::connect("localhost", 4444).await?;
client.login(env::var("OBS_PASSWORD").ok()).await?;
let events = client.events();
pin_mut!(events);
while let Some(event) = events.next().await {
println!("{:#?}", event);
}
Ok(())
}

@ -0,0 +1,29 @@
use std::{env, time::Duration};
use anyhow::Result;
use obws::client::Client;
#[tokio::main]
async fn main() -> Result<()> {
dotenv::dotenv().ok();
env::set_var("RUST_LOG", "obws=debug");
pretty_env_logger::init();
let client = Client::connect("localhost", 4444).await?;
client.login(env::var("OBS_PASSWORD").ok()).await?;
let scene_list = client.scenes().get_scene_list().await?;
for scene in scene_list.scenes.iter().cycle() {
client
.scenes()
.set_current_scene(scene.name.clone())
.await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
Ok(())
}

@ -0,0 +1,39 @@
use std::env;
use anyhow::Result;
use tokio::fs;
use obws::{client::Client, requests::SourceScreenshot};
#[tokio::main]
async fn main() -> Result<()> {
dotenv::dotenv().ok();
env::set_var("RUST_LOG", "obws=debug");
pretty_env_logger::init();
let client = Client::connect("localhost", 4444).await?;
client.login(env::var("OBS_PASSWORD").ok()).await?;
let screenshot = client
.sources()
.take_source_screenshot(SourceScreenshot {
source_name: Some("Start".to_owned()),
embed_picture_format: Some("png".to_owned()),
save_to_file_path: None,
file_format: None,
compress_quality: None,
width: None,
height: None,
})
.await?;
let image = screenshot.img.unwrap();
let pos = image.find("base64,").unwrap();
let image = base64::decode(&image[pos + 7..])?;
fs::write("screenshot.png", &image).await?;
Ok(())
}

@ -0,0 +1,25 @@
use std::env;
use anyhow::Result;
use obws::client::Client;
#[tokio::main]
async fn main() -> Result<()> {
dotenv::dotenv().ok();
env::set_var("RUST_LOG", "obws=debug");
pretty_env_logger::init();
let client = Client::connect("localhost", 4444).await?;
let version = client.general().get_version().await?;
println!("{:#?}", version);
client.login(env::var("OBS_PASSWORD").ok()).await?;
let scene_list = client.scenes().get_scene_list().await?;
println!("{:#?}", scene_list);
Ok(())
}

@ -0,0 +1,88 @@
use anyhow::Result;
use serde::Serialize;
use super::Client;
use crate::requests::{Projector, RequestType};
use crate::responses;
/// General functions of the API.
pub struct General<'a> {
pub(super) client: &'a Client,
}
impl<'a> General<'a> {
/// Returns the latest version of the plugin and the API.
pub async fn get_version(&self) -> Result<responses::Version> {
self.client.send_message(RequestType::GetVersion).await
}
/// Tells the client if authentication is required. If so, returns authentication parameters
/// `challenge` and `salt`.
pub async fn get_auth_required(&self) -> Result<responses::AuthRequired> {
self.client.send_message(RequestType::GetAuthRequired).await
}
/// Attempt to authenticate the client to the server.
///
/// - `auth`: Response to the auth challenge.
pub async fn authenticate(&self, auth: String) -> Result<()> {
self.client
.send_message(RequestType::Authenticate { auth })
.await
}
/// Set the filename formatting string.
///
/// - `filename_formatting`: Filename formatting string to set.
pub async fn set_filename_formatting(&self, filename_formatting: String) -> Result<()> {
self.client
.send_message(RequestType::SetFilenameFormatting {
filename_formatting,
})
.await
}
/// Get the filename formatting string.
pub async fn get_filename_formatting(&self) -> Result<String> {
self.client
.send_message::<responses::FilenameFormatting>(RequestType::GetFilenameFormatting)
.await
.map(|ff| ff.filename_formatting)
}
/// Get OBS stats (almost the same info as provided in OBS' stats window).
pub async fn get_stats(&self) -> Result<responses::ObsStats> {
self.client
.send_message::<responses::Stats>(RequestType::GetStats)
.await
.map(|s| s.stats)
}
/// Broadcast custom message to all connected WebSocket clients.
///
/// - `realm`: Identifier to be choosen by the client.
/// - `data`: User-defined data.
pub async fn broadcast_custom_message<T>(&self, realm: String, data: T) -> Result<()>
where
T: Serialize,
{
self.client
.send_message(RequestType::BroadcastCustomMessage {
realm,
data: serde_json::to_value(&data)?,
})
.await
}
/// Get basic OBS video information.
pub async fn get_video_info(&self) -> Result<responses::VideoInfo> {
self.client.send_message(RequestType::GetVideoInfo).await
}
/// Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
pub async fn open_projector(&self, projector: Projector) -> Result<()> {
self.client
.send_message(RequestType::OpenProjector(projector))
.await
}
}

@ -0,0 +1,265 @@
//! The client to the obs-websocket API and main entry point.
use std::{
collections::HashMap,
sync::{atomic::AtomicU64, Arc},
};
use anyhow::{bail, Result};
use futures_util::{
sink::SinkExt,
stream::{SplitSink, Stream, StreamExt},
};
use log::{debug, error, trace};
use serde::de::DeserializeOwned;
use tokio::{
net::TcpStream,
sync::{broadcast, oneshot, Mutex},
};
use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
use crate::{
events::Event,
requests::{Request, RequestType},
responses::{AuthRequired, Response},
};
pub use self::{
general::General, outputs::Outputs, profiles::Profiles, recording::Recording,
replay_buffer::ReplayBuffer, scene_collections::SceneCollections, scene_items::SceneItems,
scenes::Scenes, sources::Sources, streaming::Streaming, studio_mode::StudioMode,
transitions::Transitions,
};
mod general;
mod outputs;
mod profiles;
mod recording;
mod replay_buffer;
mod scene_collections;
mod scene_items;
mod scenes;
mod sources;
mod streaming;
mod studio_mode;
mod transitions;
/// The client is the main entry point to access the obs-websocket API. It allows to call various
/// functions to remote control an OBS instance as well as to listen to events caused by the user
/// by interacting with OBS.
pub struct Client {
write: Mutex<MessageWriter>,
id_counter: AtomicU64,
receivers: Arc<Mutex<HashMap<String, oneshot::Sender<serde_json::Value>>>>,
event_sender: broadcast::Sender<Event>,
}
type MessageWriter = SplitSink<WebSocketStream<TcpStream>, Message>;
impl Client {
/// Connect to a obs-websocket instance on the given host and port.
pub async fn connect(host: impl AsRef<str>, port: u16) -> Result<Self> {
let (socket, _) =
tokio_tungstenite::connect_async(format!("ws://{}:{}", host.as_ref(), port)).await?;
let (write, mut read) = socket.split();
let receivers = Arc::new(Mutex::new(HashMap::<
String,
oneshot::Sender<serde_json::Value>,
>::new()));
let receivers2 = Arc::clone(&receivers);
let (event_sender, _) = broadcast::channel(100);
let events_tx = event_sender.clone();
tokio::spawn(async move {
while let Some(Ok(msg)) = read.next().await {
trace!("{}", msg);
let temp: Result<()> = async {
let json = serde_json::from_str::<serde_json::Value>(&msg.into_text()?)?;
if let Some(message_id) = json
.as_object()
.and_then(|obj| obj.get("message-id"))
.and_then(|id| id.as_str())
{
debug!("got message with id {}", message_id);
if let Some(tx) = receivers2.lock().await.remove(message_id) {
tx.send(json).ok();
}
} else {
let event = serde_json::from_value(json)?;
events_tx.send(event).ok();
}
Ok(())
}
.await;
if let Err(e) = temp {
error!("{:?}", e);
}
}
});
let write = Mutex::new(write);
let id_counter = AtomicU64::new(1);
Ok(Self {
write,
id_counter,
receivers,
event_sender,
})
}
async fn send_message<T>(&self, req: RequestType) -> Result<T>
where
T: DeserializeOwned,
{
let id = self
.id_counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
.to_string();
let req = Request {
message_id: id.clone(),
ty: req,
};
let json = serde_json::to_string(&req)?;
let (tx, rx) = oneshot::channel();
self.receivers.lock().await.insert(id, tx);
debug!("sending message: {}", json);
self.write.lock().await.send(Message::Text(json)).await?;
let resp = rx.await?;
if let Some(error) = resp
.as_object()
.and_then(|o| o.get("error"))
.and_then(|e| e.as_str())
{
bail!("{}", error);
}
serde_json::from_value::<Response<T>>(resp)
.map(|r| r.details)
.map_err(Into::into)
}
/// Login to the OBS websocket if an authentication is required.
pub async fn login(&self, password: Option<impl AsRef<str>>) -> Result<()> {
let auth_required = self.general().get_auth_required().await?;
if let AuthRequired {
auth_required: true,
challenge: Some(challenge),
salt: Some(salt),
} = auth_required
{
match password {
Some(password) => {
let auth = Self::create_auth_response(&challenge, &salt, password.as_ref());
self.general().authenticate(auth).await?;
}
None => bail!("authentication required but no password provided"),
}
}
Ok(())
}
fn create_auth_response(challenge: &str, salt: &str, password: &str) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(password.as_bytes());
hasher.update(salt.as_bytes());
let mut auth = String::with_capacity(Sha256::output_size() * 4 / 3 + 4);
base64::encode_config_buf(hasher.finalize_reset(), base64::STANDARD, &mut auth);
hasher.update(auth.as_bytes());
hasher.update(challenge.as_bytes());
auth.clear();
base64::encode_config_buf(hasher.finalize(), base64::STANDARD, &mut auth);
auth
}
/// Get a stream of events. Each call to this function creates a new listener, therefore it's
/// recommended to keep the stream around and iterate over it.
///
/// **Note**: To be able to iterate over the stream you have to pin it with
/// [`futures_util::pin_mut`] for example.
pub fn events(&self) -> impl Stream<Item = Event> {
let mut receiver = self.event_sender.subscribe();
async_stream::stream! {
while let Ok(event) = receiver.recv().await {
yield event;
}
}
}
/// Access general API functions.
pub fn general(&self) -> General<'_> {
General { client: self }
}
/// Access API functions related to sources.
pub fn sources(&self) -> Sources<'_> {
Sources { client: self }
}
/// Access API functions related to outputs.
pub fn outputs(&self) -> Outputs<'_> {
Outputs { client: self }
}
/// Access API functions related to profiles.
pub fn profiles(&self) -> Profiles<'_> {
Profiles { client: self }
}
/// Access API functions related to recording.
pub fn recording(&self) -> Recording<'_> {
Recording { client: self }
}
/// Access API functions related to the replay buffer.
pub fn replay_buffer(&self) -> ReplayBuffer<'_> {
ReplayBuffer { client: self }
}
/// Access API functions related to scene collections.
pub fn scene_collections(&self) -> SceneCollections<'_> {
SceneCollections { client: self }
}
/// Access API functions related to scene items.
pub fn scene_items(&self) -> SceneItems<'_> {
SceneItems { client: self }
}
/// Access API functions related to scenes.
pub fn scenes(&self) -> Scenes<'_> {
Scenes { client: self }
}
/// Access API functions related to streaming.
pub fn streaming(&self) -> Streaming<'_> {
Streaming { client: self }
}
/// Access API functions related to the studio mode.
pub fn studio_mode(&self) -> StudioMode<'_> {
StudioMode { client: self }
}
/// Access API functions related to transitions.
pub fn transitions(&self) -> Transitions<'_> {
Transitions { client: self }
}
}

@ -0,0 +1,51 @@
use anyhow::Result;
use super::Client;
use crate::requests::RequestType;
use crate::responses;
/// 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: String) -> Result<responses::Output> {
self.client
.send_message::<responses::OutputInfo>(RequestType::GetOutputInfo { output_name })
.await
.map(|o| o.output_info)
}
/// 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: String) -> Result<()> {
self.client
.send_message(RequestType::StartOutput { output_name })
.await
}
/// 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: String, force: Option<bool>) -> Result<()> {
self.client
.send_message(RequestType::StopOutput { output_name, force })
.await
}
}

@ -0,0 +1,37 @@
use anyhow::Result;
use super::Client;
use crate::requests::RequestType;
use crate::responses;
/// 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: String) -> 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)
}
}

@ -0,0 +1,59 @@
use anyhow::Result;
use super::Client;
use crate::requests::RequestType;
use crate::responses;
/// API functions related to recording.
pub struct Recording<'a> {
pub(super) client: &'a Client,
}
impl<'a> Recording<'a> {
/// 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: String) -> 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<String> {
self.client
.send_message::<responses::RecordingFolder>(RequestType::GetRecordingFolder)
.await
.map(|rf| rf.rec_folder)
}
}

@ -0,0 +1,44 @@
use anyhow::Result;
use super::Client;
use crate::requests::RequestType;
/// API functions related to the replay buffer.
pub struct ReplayBuffer<'a> {
pub(super) client: &'a Client,
}
impl<'a> ReplayBuffer<'a> {
/// 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
}
}

@ -0,0 +1,39 @@
use anyhow::Result;
use super::Client;
use crate::requests::RequestType;
use crate::responses;
/// 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: String) -> 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)
}
}

@ -0,0 +1,92 @@
use anyhow::Result;
use either::Either;
use super::Client;
use crate::requests::{
AddSceneItem, DuplicateSceneItem, RequestType, SceneItemProperties, SceneItemRender,
SceneItemSpecification,
};
use crate::responses;
/// API functions related to scene items.
pub struct SceneItems<'a> {
pub(super) client: &'a Client,
}
impl<'a> SceneItems<'a> {
/// 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<String>,
item: Either<String, SceneItemSpecification>,
) -> Result<Vec<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<String>,
item: Either<String, 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<String>,
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::AddSceneItem>(RequestType::AddSceneItem(scene_item))
.await
.map(|asi| asi.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
}
}

@ -0,0 +1,86 @@
use anyhow::Result;
use super::Client;
use crate::requests::{RequestType, Scene, SceneTransitionOverride};
use crate::responses;
/// API functions related to scenes.
pub struct Scenes<'a> {
pub(super) client: &'a Client,
}
impl<'a> Scenes<'a> {
/// Switch to the specified scene.
///
/// - `scene_name`: Name of the scene to switch to.
pub async fn set_current_scene(&self, scene_name: String) -> Result<()> {
self.client
.send_message(RequestType::SetCurrentScene { scene_name })
.await
}
/// Get the current scene's name and source items.
pub async fn get_current_scene(&self) -> Result<responses::CurrentScene> {
self.client.send_message(RequestType::GetCurrentScene).await
}
/// Get a list of scenes in the currently active profile.
pub async fn get_scene_list(&self) -> Result<responses::SceneList> {
self.client.send_message(RequestType::GetSceneList).await
}
/// Create a new scene scene.
///
/// - `scene_name`: Name of the scene to create.
pub async fn create_scene(&self, scene_name: String) -> Result<()> {
self.client
.send_message(RequestType::CreateScene { scene_name })
.await
}
/// Changes the order of scene items in the requested scene.
///
/// - `scene`: Name of the scene to reorder (defaults to current).
/// - `items`: Ordered list of objects with name and/or id specified. Id preferred due to
/// uniqueness per scene
pub async fn reorder_scene_items(
&self,
scene: Option<String>,
items: Vec<Scene>,
) -> Result<()> {
self.client
.send_message(RequestType::ReorderSceneItems { scene, items })
.await
}
/// Set a scene to use a specific transition override.
pub async fn set_scene_transition_override(
&self,
scene_transition: SceneTransitionOverride,
) -> Result<()> {
self.client
.send_message(RequestType::SetSceneTransitionOverride(scene_transition))
.await
}
/// Remove any transition override on a scene.
///
/// - `scene_name`: Name of the scene to remove the override from.
pub async fn remove_scene_transition_override(&self, scene_name: String) -> Result<()> {
self.client
.send_message(RequestType::RemoveSceneTransitionOverride { scene_name })
.await
}
/// Get the current scene transition override.
///
/// - `scene_name`: Name of the scene to get the override for.
pub async fn get_scene_transition_override(
&self,
scene_name: String,
) -> Result<responses::SceneTransitionOverride> {
self.client
.send_message(RequestType::GetSceneTransitionOverride { scene_name })
.await
}
}

@ -0,0 +1,348 @@
use anyhow::Result;
use serde::de::DeserializeOwned;
use super::Client;
use crate::requests::{
AddFilter, MoveFilter, ReorderFilter, RequestType, SourceFilterSettings,
SourceFilterVisibility, SourceScreenshot, SourceSettings, TextFreetype2Properties,
TextGdiPlusProperties, Volume,
};
use crate::responses;
/// API functions related to sources.
pub struct Sources<'a> {
pub(super) client: &'a Client,
}
impl<'a> Sources<'a> {
/// List all sources available in the running OBS instance.
pub async fn get_sources_list(&self) -> Result<Vec<responses::SourceListItem>> {
self.client
.send_message::<responses::SourcesList>(RequestType::GetSourcesList)
.await
.map(|sl| sl.sources)
}
/// Get a list of all available sources types.
pub async fn get_sources_types_list(&self) -> Result<Vec<responses::SourceTypeItem>> {
self.client
.send_message::<responses::SourceTypesList>(RequestType::GetSourceTypesList)
.await
.map(|stl| stl.types)
}
/// Get the volume of the specified source. Default response uses mul format, NOT SLIDER
/// PERCENTAGE.
///
/// - `source`: Source name.
/// - `use_decibel`: Output volume in decibels of attenuation instead of amplitude/mul.
pub async fn get_volume(
&self,
source: String,
use_decibel: Option<bool>,
) -> Result<responses::Volume> {
self.client
.send_message(RequestType::GetVolume {
source,
use_decibel,
})
.await
}
/// Set the volume of the specified source. Default request format uses mul, NOT SLIDER
/// PERCENTAGE.
pub async fn set_volume(&self, volume: Volume) -> Result<()> {
self.client
.send_message(RequestType::SetVolume(volume))
.await
}
/// Get the mute status of a specified source.
///
/// - `source`: Source name.
pub async fn get_mute(&self, source: String) -> Result<responses::Mute> {
self.client
.send_message(RequestType::GetMute { source })
.await
}
/// Sets the mute status of a specified source.
///
/// - `source`: Source name.
/// - `mute`: Desired mute status.
pub async fn set_mute(&self, source: String, mute: bool) -> Result<()> {
self.client
.send_message(RequestType::SetMute { source, mute })
.await
}
/// Inverts the mute status of a specified source.
///
/// - `source`: Source name.
pub async fn toggle_mute(&self, source: String) -> Result<()> {
self.client
.send_message(RequestType::ToggleMute { source })
.await
}
/// Get the audio's active status of a specified source.
///
/// - `source_name`: Source name.
pub async fn get_audio_active(&self, source_name: String) -> Result<bool> {
self.client
.send_message::<responses::AudioActive>(RequestType::GetAudioActive { source_name })
.await
.map(|aa| aa.audio_active)
}
/// Note: If the new name already exists as a source, obs-websocket will return an error.
///
/// - `source_name`: Source name.
/// - `new_name`: New source name.
pub async fn set_source_name(&self, source_name: String, new_name: String) -> Result<()> {
self.client
.send_message(RequestType::SetSourceName {
source_name,
new_name,
})
.await
}
/// Set the audio sync offset of a specified source.
///
/// - `source`: Source name.
/// - `offset`: The desired audio sync offset (in nanoseconds).
pub async fn set_sync_offset(&self, source: String, offset: i64) -> Result<()> {
self.client
.send_message(RequestType::SetSyncOffset { source, offset })
.await
}
/// Get the audio sync offset of a specified source.
///
/// - `source`: Source name.
pub async fn get_sync_offset(&self, source: String) -> Result<responses::SyncOffset> {
self.client
.send_message(RequestType::GetSyncOffset { source })
.await
}
/// Get settings of the specified source.
///
/// - `source_name`: Source name.
/// - `source_type`: Type of the specified source. Useful for type-checking if you expect a
/// specific settings schema.
pub async fn get_source_settings<T>(
&self,
source_name: String,
source_type: Option<String>,
) -> Result<responses::SourceSettings<T>>
where
T: DeserializeOwned,
{
self.client
.send_message(RequestType::GetSourceSettings {
source_name,
source_type,
})
.await
}
/// Set settings of the specified source.
pub async fn set_source_settings<T>(
&self,
source_settings: SourceSettings,
) -> Result<responses::SourceSettings<T>>
where
T: DeserializeOwned,
{
self.client
.send_message(RequestType::SetSourceSettings(source_settings))
.await
}
/// Get the current properties of a Text GDI Plus source.
///
/// - `source`: Source name.
pub async fn get_text_gdi_plus_properties(
&self,
source: String,
) -> Result<responses::TextGdiPlusProperties> {
self.client
.send_message(RequestType::GetTextGDIPlusProperties { source })
.await
}
/// Set the current properties of a Text GDI Plus source.
pub async fn set_text_gdi_plus_properties(
&self,
properties: TextGdiPlusProperties,
) -> Result<()> {
self.client
.send_message(RequestType::SetTextGDIPlusProperties(Box::new(properties)))
.await
}
/// Get the current properties of a Text Freetype 2 source.
///
/// - `source`: Source name.
pub async fn get_text_freetype2_properties(
&self,
source: String,
) -> Result<responses::TextFreetype2Properties> {
self.client
.send_message(RequestType::GetTextFreetype2Properties { source })
.await
}
/// Set the current properties of a Text Freetype 2 source.
pub async fn set_text_freetype2_properties(
&self,
properties: TextFreetype2Properties,
) -> Result<()> {
self.client
.send_message(RequestType::SetTextFreetype2Properties(properties))
.await
}
/// Get configured special sources like Desktop Audio and Mic/Aux sources.
pub async fn get_special_sources(&self) -> Result<responses::SpecialSources> {
self.client
.send_message(RequestType::GetSpecialSources)
.await
}
/// List filters applied to a source
///
/// - `source_name`: Source name.
pub async fn get_source_filters(
&self,
source_name: String,
) -> Result<Vec<responses::SourceFilter>> {
self.client
.send_message::<responses::SourceFilters>(RequestType::GetSourceFilters { source_name })
.await
.map(|sf| sf.filters)
}
/// List filters applied to a source.
///
/// - `source_name`: Source name.
/// - `filter_name`: Source filter name.
pub async fn get_source_filter_info<T>(
&self,
source_name: String,
filter_name: String,
) -> Result<responses::SourceFilterInfo<T>>
where
T: DeserializeOwned,
{
self.client
.send_message(RequestType::GetSourceFilterInfo {
source_name,
filter_name,
})
.await
}
/// Add a new filter to a source. Available source types along with their settings properties
/// are available from [`get_sources_types_list`](Self::get_sources_types_list).
pub async fn add_filter_to_source(&self, add_filter: AddFilter) -> Result<()> {
self.client
.send_message(RequestType::AddFilterToSource(add_filter))
.await
}
/// Remove a filter from a source.
///
/// - `source_name`: Name of the source from which the specified filter is removed.
/// - `filter_name`: Name of the filter to remove.
pub async fn remove_filter_from_source(
&self,
source_name: String,
filter_name: String,
) -> Result<()> {
self.client
.send_message(RequestType::RemoveFilterFromSource {
source_name,
filter_name,
})
.await
}
/// Move a filter in the chain (absolute index positioning).
pub async fn reorder_source_filter(&self, reorder_filter: ReorderFilter) -> Result<()> {
self.client
.send_message(RequestType::ReorderSourceFilter(reorder_filter))
.await
}
/// Move a filter in the chain (relative positioning).
pub async fn move_source_filter(&self, move_filter: MoveFilter) -> Result<()> {
self.client
.send_message(RequestType::MoveSourceFilter(move_filter))
.await
}
/// Update settings of a filter.
pub async fn set_source_filter_settings(&self, settings: SourceFilterSettings) -> Result<()> {
self.client
.send_message(RequestType::SetSourceFilterSettings(settings))
.await
}
/// Change the visibility/enabled state of a filter.
pub async fn set_source_filter_visibility(
&self,
visibility: SourceFilterVisibility,
) -> Result<()> {
self.client
.send_message(RequestType::SetSourceFilterVisibility(visibility))
.await
}
/// Get the audio monitoring type of the specified source.
///
/// - `source_name`: Source name.
pub async fn get_audio_monitor_type(&self, source_name: String) -> Result<String> {
self.client
.send_message::<responses::AudioMonitorType>(RequestType::GetAudioMonitorType {
source_name,
})
.await
.map(|amt| amt.monitor_type)
}
/// Set the audio monitoring type of the specified source.
///
/// - `source_name`: Source name.
/// - `monitor_type`: The monitor type to use. Options: `none`, `monitorOnly`,
/// `monitorAndOutput`.
pub async fn set_audio_monitor_type(
&self,
source_name: String,
monitor_type: String,
) -> Result<()> {
self.client
.send_message(RequestType::SetAudioMonitorType {
source_name,
monitor_type,
})
.await
}
/// At least [`embed_picture_format`](SourceScreenshot::embed_picture_format) or
/// [`save_to_file_path`](SourceScreenshot::save_to_file_path) must be specified.
///
/// Clients can specify [`width`](SourceScreenshot::width) and
/// [`height`](SourceScreenshot::height) parameters to receive scaled pictures. Aspect ratio is
/// preserved if only one of these two parameters is specified.
pub async fn take_source_screenshot(
&self,
source_screenshot: SourceScreenshot,
) -> Result<responses::SourceScreenshot> {
self.client
.send_message(RequestType::TakeSourceScreenshot(source_screenshot))
.await
}
}

@ -0,0 +1,74 @@
use anyhow::Result;
use super::Client;
use crate::requests::{RequestType, SetStreamSettings, Stream};
use crate::responses;
/// 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. Please 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: String) -> Result<()> {
self.client
.send_message(RequestType::SendCaptions { text })
.await
}
}

@ -0,0 +1,67 @@
use anyhow::Result;
use super::Client;
use crate::requests::{RequestType, Transition};
use crate::responses;
/// 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: String) -> 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
}
}

@ -0,0 +1,52 @@
use anyhow::Result;
use super::Client;
use crate::requests::RequestType;
use crate::responses;
/// 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: String) -> 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: u64) -> 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<u64> {
self.client
.send_message::<responses::TransitionDuration>(RequestType::GetTransitionDuration)
.await
.map(|td| td.transition_duration)
}
}

@ -0,0 +1,143 @@
//! Common data structures shared between [`requests`](crate::requests),
//! [`responses`](crate::responses) and [`events`](crate::events).
use serde::Deserialize;
/// 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.
pub alignment: u8,
/// 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. Value is one of the following: "input", "filter", "transition", "scene" or
/// "unknown".
#[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.
pub alignment: u8,
}
/// 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,
}
/// 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: i64,
/// The number of pixels cropped off the right of the source before scaling.
pub right: i64,
/// The number of pixels cropped off the bottom of the source before scaling.
pub bottom: i64,
/// The number of pixels cropped off the left of the source before scaling.
pub left: i64,
}
/// 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: String,
/// Alignment of the bounding box.
pub alignment: u8,
/// Width of the bounding box.
pub x: f64,
/// Height of the bounding box.
pub y: f64,
}

@ -0,0 +1,511 @@
//! All events that can be received from the API.
use serde::Deserialize;
use crate::common::{SceneItem, SceneItemTransform};
/// Events are sent when a recognized action occurs within OBS.
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Event {
/// Time elapsed between now and stream start (only present if OBS Studio is streaming).
pub stream_timecode: Option<String>,
/// Time elapsed between now and recording start (only present if OBS Studio is recording).
pub rec_timecode: Option<String>,
/// The type of event.
#[serde(flatten)]
pub ty: EventType,
}
/// All possible event types that can occur while the user interacts with OBS.
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "update-type")]
pub enum EventType {
// --------------------------------
// Scenes
// --------------------------------
/// Indicates a scene change.
#[serde(rename_all = "kebab-case")]
SwitchScenes {
/// The new scene.
scene_name: String,
/// List of scene items in the new scene.
sources: Vec<SceneItem>,
},
/// Note: This event is not fired when the scenes are reordered.
ScenesChanged,
/// Triggered when switching to another scene collection or when renaming the current scene
/// collection.
#[serde(rename_all = "camelCase")]
SceneCollectionChanged {
/// Name of the new current scene collection.
scene_collection: String,
},
/// Triggered when a scene collection is created, added, renamed, or removed.
#[serde(rename_all = "camelCase")]
SceneCollectionListChanged {
/// Scene collections list.
scene_collections: Vec<SceneCollection>,
},
// --------------------------------
// Transitions
// --------------------------------
/// The active transition has been changed.
#[serde(rename_all = "kebab-case")]
SwitchTransition {
/// The name of the new active transition.
transition_name: String,
},
/// The list of available transitions has been modified. Transitions have been added, removed,
/// or renamed.
TransitionListChanged,
/// The active transition duration has been changed.
#[serde(rename_all = "kebab-case")]
TransitionDurationChanged {
/// New transition duration.
new_duration: u64,
},
/// A transition (other than "cut") has begun.
#[serde(rename_all = "kebab-case")]
TransitionBegin {
/// Transition name.
name: String,
/// Transition type.
#[serde(rename = "type")]
ty: String,
/// Transition duration (in milliseconds). Will be -1 for any transition with a fixed
/// duration, such as a Stinger, due to limitations of the OBS API.
duration: u64,
/// Source scene of the transition.
from_scene: String,
/// Destination scene of the transition.
to_scene: String,
},
/// A transition (other than "cut") has ended. Please note that the `from_scene` field is not
/// available in TransitionEnd.
#[serde(rename_all = "kebab-case")]
TransitionEnd {
/// Transition name.
name: String,
/// Transition type.
#[serde(rename = "type")]
ty: String,
/// Transition duration (in milliseconds).
duration: u64,
/// Destination scene of the transition.
to_scene: String,
},
/// A stinger transition has finished playing its video.
#[serde(rename_all = "kebab-case")]
TransitionVideoEnd {
/// Transition name.
name: String,
/// Transition type.
#[serde(rename = "type")]
ty: String,
/// Transition duration (in milliseconds).
duration: u64,
/// Source scene of the transition.
from_scene: String,
/// Destination scene of the transition.
to_scene: String,
},
// --------------------------------
// Profiles
// --------------------------------
/// Triggered when switching to another profile or when renaming the current profile.
ProfileChanged,
/// Triggered when a profile is created, added, renamed, or removed.
ProfileListChanged,
// --------------------------------
// Streaming
// --------------------------------
/// A request to start streaming has been issued.
#[serde(rename_all = "kebab-case")]
StreamStarting {
/// Always false (retrocompatibility).
#[serde(default)]
preview_only: bool,
},
/// Streaming started successfully.
StreamStarted,
/// A request to stop streaming has been issued.
#[serde(rename_all = "kebab-case")]
StreamStopping {
/// Always false (retrocompatibility).
#[serde(default)]
preview_only: bool,
},
/// Streaming stopped successfully.
StreamStopped,
/// Emitted every 2 seconds when stream is active.
#[serde(rename_all = "kebab-case")]
StreamStatus {
/// Current streaming state.
streaming: bool,
/// Current recording state.
recording: bool,
/// Replay Buffer status.
replay_buffer_active: bool,
/// Amount of data per second (in bytes) transmitted by the stream encoder.
bytes_per_sec: u64,
/// Amount of data per second (in kilobits) transmitted by the stream encoder.
kbits_per_sec: u64,
/// Percentage of dropped frames.
strain: f64,
/// Total time (in seconds) since the stream started.
total_stream_time: u64,
/// Total number of frames transmitted since the stream started.
num_total_frames: u64,
/// Number of frames dropped by the encoder since the stream started.
num_dropped_frames: u64,
/// Current framerate.
fps: f64,
/// Number of frames rendered.
render_total_frames: u64,
/// Number of frames missed due to rendering lag.
render_missed_frames: u64,
/// Number of frames outputted.
output_total_frames: u64,
/// Number of frames skipped due to encoding lag.
output_skipped_frames: u64,
/// Average frame time (in milliseconds).
average_frame_time: f64,
/// Current CPU usage (percentage).
cpu_usage: f64,
/// Current RAM usage (in megabytes).
memory_usage: f64,
/// Free recording disk space (in megabytes).
free_disk_space: f64,
/// Always false (retrocompatibility).
#[serde(default)]
preview_only: bool,
},
// --------------------------------
// Recording
// --------------------------------
/// Note: `recording_filename` is not provided in this event because this information is not
/// available at the time this event is emitted.
RecordingStarting,
/// Recording started successfully.
#[serde(rename_all = "camelCase")]
RecordingStarted {
/// Absolute path to the file of the current recording.
recording_filename: String,
},
/// A request to stop recording has been issued.
#[serde(rename_all = "camelCase")]
RecordingStopping {
/// Absolute path to the file of the current recording.
recording_filename: String,
},
/// Recording stopped successfully.
#[serde(rename_all = "camelCase")]
RecordingStopped {
/// Absolute path to the file of the current recording.
recording_filename: String,
},
/// Current recording paused.
RecordingPaused,
/// Current recording resumed.
RecordingResumed,
// --------------------------------
// Replay Buffer
// --------------------------------
/// A request to start the replay buffer has been issued.
ReplayStarting,
/// Replay Buffer started successfully.
ReplayStarted,
/// A request to stop the replay buffer has been issued.
ReplayStopping,
/// Replay Buffer stopped successfully.
ReplayStopped,
// --------------------------------
// Other
// --------------------------------
/// OBS is exiting.
Exiting,
// --------------------------------
// General
// --------------------------------
/// A custom broadcast message, sent by the server, requested by one of the websocket clients.
BroadcastCustomMessage {
/// Identifier provided by the sender.
realm: String,
/// User-defined data.
data: serde_json::Value,
},
// --------------------------------
// Sources
// --------------------------------
/// A source has been created. A source can be an input, a scene or a transition.
#[serde(rename_all = "camelCase")]
SourceCreated {
/// Source name.
source_name: String,
/// Source type. Can be "input", "scene", "transition" or "filter".
source_type: String,
/// Source kind.
source_kind: String,
/// Source settings.
source_settings: serde_json::Value,
},
/// A source has been destroyed/removed. A source can be an input, a scene or a transition.
#[serde(rename_all = "camelCase")]
SourceDestroyed {
/// Source name.
source_name: String,
/// Source type. Can be "input", "scene", "transition" or "filter".
source_type: String,
/// Source kind.
source_kind: String,
},
/// The volume of a source has changed.
#[serde(rename_all = "camelCase")]
SourceVolumeChanged {
/// Source name.
source_name: String,
/// Source volume.
volume: f32,
},
/// A source has been muted or unmuted.
#[serde(rename_all = "camelCase")]
SourceMuteStateChanged {
/// Source name.
source_name: String,
/// Mute status of the source.
muted: bool,
},
/// A source has removed audio.
#[serde(rename_all = "camelCase")]
SourceAudioDeactivated {
/// Source name.
source_name: String,
},
/// A source has added audio.
#[serde(rename_all = "camelCase")]
SourceAudioActivated {
/// Source name.
source_name: String,
},
/// The audio sync offset of a source has changed.
#[serde(rename_all = "camelCase")]
SourceAudioSyncOffsetChanged {
/// Source name.
source_name: String,
/// Audio sync offset of the source (in nanoseconds).
sync_offset: i64,
},
/// Audio mixer routing changed on a source.
#[serde(rename_all = "camelCase")]
SourceAudioMixersChanged {
/// Source name.
source_name: String,
/// Routing status of the source for each audio mixer (array of 6 values).
mixers: [AudioMixer; 6],
/// Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value.
hex_mixers_value: String,
},
/// A source has been renamed.
#[serde(rename_all = "camelCase")]
SourceRenamed {
/// Previous source name.
previous_name: String,
/// New source name.
new_name: String,
/// Type of source (input, scene, filter, transition).
source_type: String,
},
/// A filter was added to a source.
#[serde(rename_all = "camelCase")]
SourceFilterAdded {
/// Source name.
source_name: String,
/// Filter name.
filter_name: String,
/// Filter type.
filter_type: String,
/// Filter settings.
filter_settings: serde_json::Value,
},
/// A filter was removed from a source.
#[serde(rename_all = "camelCase")]
SourceFilterRemoved {
/// Source name.
source_name: String,
/// Filter name.
filter_name: String,
/// Filter type.
filter_type: String,
},
/// The visibility/enabled state of a filter changed.
#[serde(rename_all = "camelCase")]
SourceFilterVisibilityChanged {
/// Source name.
source_name: String,
/// Filter name.
filter_name: String,
/// New filter state.
filter_enabled: bool,
},
/// Filters in a source have been reordered.
#[serde(rename_all = "camelCase")]
SourceFiltersReordered {
/// Source name.
source_name: String,
/// Ordered Filters list.
filters: Vec<SourceFilter>,
},
// --------------------------------
// Scene Items
// --------------------------------
/// Scene items within a scene have been reordered.
#[serde(rename_all = "kebab-case")]
SourceOrderChanged {
/// Name of the scene where items have been reordered.
scene_name: String,
/// Ordered list of scene items.
scene_items: Vec<SourceOrderSceneItem>,
},
/// A scene item has been added to a scene.
#[serde(rename_all = "kebab-case")]
SceneItemAdded {
/// Name of the scene.
scene_name: String,
/// Name of the item added to the scene.
item_name: String,
/// Scene item ID.
item_id: i64,
},
/// A scene item has been removed from a scene.
#[serde(rename_all = "kebab-case")]
SceneItemRemoved {
/// Name of the scene.
scene_name: String,
/// Name of the item removed from the scene.
item_name: String,
/// Scene item ID.
item_id: i64,
},
/// A scene item's visibility has been toggled.
#[serde(rename_all = "kebab-case")]
SceneItemVisibilityChanged {
/// Name of the scene.
scene_name: String,
/// Name of the item in the scene.
item_name: String,
/// Scene item ID.
item_id: i64,
/// New visibility state of the item.
item_visible: bool,
},
/// A scene item's locked status has been toggled.
#[serde(rename_all = "kebab-case")]
SceneItemLockChanged {
/// Name of the scene.
scene_name: String,
/// Name of the item in the scene.
item_name: String,
/// Scene item ID.
item_id: i64,
/// New locked state of the item.
item_locked: bool,
},
/// A scene item's transform has been changed.
#[serde(rename_all = "kebab-case")]
SceneItemTransformChanged {
/// Name of the scene.
scene_name: String,
/// Name of the item in the scene.
item_name: String,
/// Scene item ID.
item_id: i64,
/// Scene item transform properties.
transform: SceneItemTransform,
},
/// A scene item is selected.
#[serde(rename_all = "kebab-case")]
SceneItemSelected {
/// Name of the scene.
scene_name: String,
/// Name of the item in the scene.
item_name: String,
/// ID of the item in the scene.
item_id: i64,
},
/// A scene item is deselected.
#[serde(rename_all = "kebab-case")]
SceneItemDeselected {
/// Name of the scene.
scene_name: String,
/// Name of the item in the scene.
item_name: String,
/// ID of the item in the scene.
item_id: i64,
},
// --------------------------------
// Studio Mode
// --------------------------------
/// The selected preview scene has changed (only available in Studio Mode).
#[serde(rename_all = "kebab-case")]
PreviewSceneChanged {
/// Name of the scene being previewed.
scene_name: String,
/// List of sources composing the scene.
soruces: Vec<SceneItem>,
},
/// Studio Mode has been enabled or disabled.
#[serde(rename_all = "kebab-case")]
StudioModeSwitched {
/// The new enabled state of Studio Mode.
new_state: bool,
},
/// Fallback value for any unknown event type.
#[serde(other)]
Unknown,
}
/// Part of [`EventType::SceneCollectionListChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct SceneCollection {
/// Scene collection name.
pub name: String,
}
/// Part of [`EventType::ProfileListChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct Profile {
/// Profile name.
pub name: String,
}
/// Part of [`EventType::SourceAudioMixersChanged`].
#[derive(Clone, Debug, Deserialize)]
pub struct AudioMixer {
/// Mixer number.
pub id: i64,
/// Routing status.
pub enabled: bool,
}
/// Part of [`EventType::SourceFiltersReordered`].
#[derive(Clone, Debug, Deserialize)]
pub struct SourceFilter {
/// Filter name.
pub name: String,
/// Filter type.
#[serde(rename = "type")]
pub ty: String,
/// Filter visibility status.
pub enabled: bool,
}
/// Part of [`EventType::SourceOrderChanged`].
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SourceOrderSceneItem {
/// Item source name.
pub source_name: String,
/// Scene item unique ID.
pub item_id: i64,
}

@ -0,0 +1,9 @@
//! # OBSWS - The obws (obvious) remote control library for OBS
#![deny(missing_docs, rust_2018_idioms, clippy::all)]
pub mod client;
pub mod common;
pub mod events;
pub mod requests;
pub mod responses;

@ -0,0 +1,810 @@
//! All requests that can be send to the API.
use either::Either;
use serde::Serialize;
use serde_with::skip_serializing_none;
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct Request {
pub message_id: String,
#[serde(flatten)]
pub ty: RequestType,
}
#[derive(Serialize)]
#[serde(tag = "request-type")]
pub(crate) enum RequestType {
// --------------------------------
// General
// --------------------------------
GetVersion,
GetAuthRequired,
Authenticate {
/// Response to the auth challenge.
auth: String,
},
#[serde(rename_all = "kebab-case")]
SetFilenameFormatting {
/// Filename formatting string to set.
filename_formatting: String,
},
GetFilenameFormatting,
GetStats,
BroadcastCustomMessage {
/// Identifier to be choosen by the client.
realm: String,
/// User-defined data.
data: serde_json::Value,
},
GetVideoInfo,
OpenProjector(Projector),
// --------------------------------
// Sources
// --------------------------------
GetSourcesList,
GetSourceTypesList,
#[serde(rename_all = "camelCase")]
GetVolume {
/// Source name.
source: String,
/// Output volume in decibels of attenuation instead of amplitude/mul.
use_decibel: Option<bool>,
},
SetVolume(Volume),
GetMute {
/// Source name.
source: String,
},
SetMute {
/// Source name.
source: String,
/// Desired mute status.
mute: bool,
},
ToggleMute {
/// Source name.
source: String,
},
#[serde(rename_all = "camelCase")]
GetAudioActive {
/// Source name.
source_name: String,
},
#[serde(rename_all = "camelCase")]
SetSourceName {
/// Source name.
source_name: String,
/// New source name.
new_name: String,
},
SetSyncOffset {
/// Source name.
source: String,
/// The desired audio sync offset (in nanoseconds).
offset: i64,
},
GetSyncOffset {
/// Source name.
source: String,
},
#[serde(rename_all = "camelCase")]
GetSourceSettings {
/// Source name.
source_name: String,
/// Type of the specified source. Useful for type-checking if you expect a specific settings
/// schema.
source_type: Option<String>,
},
SetSourceSettings(SourceSettings),
GetTextGDIPlusProperties {
/// Source name.
source: String,
},
SetTextGDIPlusProperties(Box<TextGdiPlusProperties>),
GetTextFreetype2Properties {
/// Source name.
source: String,
},
SetTextFreetype2Properties(TextFreetype2Properties),
GetSpecialSources,
#[serde(rename_all = "camelCase")]
GetSourceFilters {
/// Source name.
source_name: String,
},
#[serde(rename_all = "camelCase")]
GetSourceFilterInfo {
/// Source name.
source_name: String,
/// Source filter name.
filter_name: String,
},
AddFilterToSource(AddFilter),
#[serde(rename_all = "camelCase")]
RemoveFilterFromSource {
/// Name of the source from which the specified filter is removed.
source_name: String,
/// Name of the filter to remove.
filter_name: String,
},
ReorderSourceFilter(ReorderFilter),
MoveSourceFilter(MoveFilter),
SetSourceFilterSettings(SourceFilterSettings),
SetSourceFilterVisibility(SourceFilterVisibility),
#[serde(rename_all = "camelCase")]
GetAudioMonitorType {
/// Source name.
source_name: String,
},
#[serde(rename_all = "camelCase")]
SetAudioMonitorType {
/// Source name.
source_name: String,
/// The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`.
monitor_type: String,
},
TakeSourceScreenshot(SourceScreenshot),
// --------------------------------
// Outputs
// --------------------------------
ListOutputs,
#[serde(rename_all = "camelCase")]
GetOutputInfo {
/// Output name.
output_name: String,
},
#[serde(rename_all = "camelCase")]
StartOutput {
/// Output name.
output_name: String,
},
#[serde(rename_all = "camelCase")]
StopOutput {
/// Output name.
output_name: String,
/// Force stop (default: false).
force: Option<bool>,
},
// --------------------------------
// Profiles
// --------------------------------
#[serde(rename_all = "kebab-case")]
SetCurrentProfile {
/// Name of the desired profile.
profile_name: String,
},
GetCurrentProfile,
ListProfiles,
// --------------------------------
// Recording
// --------------------------------
StartStopRecording,
StartRecording,
StopRecording,
PauseRecording,
ResumeRecording,
#[serde(rename_all = "kebab-case")]
SetRecordingFolder {
/// Path of the recording folder.
rec_folder: String,
},
GetRecordingFolder,
// --------------------------------
// Replay Buffer
// --------------------------------
StartStopReplayBuffer,
StartReplayBuffer,
StopReplayBuffer,
SaveReplayBuffer,
// --------------------------------
// Scene Collections
// --------------------------------
#[serde(rename_all = "kebab-case")]
SetCurrentSceneCollection {
/// Name of the desired scene collection.
sc_name: String,
},
GetCurrentSceneCollection,
ListSceneCollections,
// --------------------------------
// Scene Items
// --------------------------------
#[serde(rename_all = "kebab-case")]
GetSceneItemProperties {
/// Name of the scene the scene item belongs to. Defaults to the current scene.
scene_name: Option<String>,
/// Scene Item name (if this field is a string) or specification (if it is an object).
#[serde(with = "either::serde_untagged")]
item: Either<String, SceneItemSpecification>,
},
SetSceneItemProperties(SceneItemProperties),
#[serde(rename_all = "kebab-case")]
ResetSceneItem {
/// Name of the scene the scene item belongs to. Defaults to the current scene.
scene_name: Option<String>,
/// Scene Item name (if this field is a string) or specification (if it is an object).
#[serde(with = "either::serde_untagged")]
item: Either<String, SceneItemSpecification>,
},
SetSceneItemRender(SceneItemRender),
DeleteSceneItem {
/// Name of the scene the scene item belongs to. Defaults to the current scene.
scene: Option<String>,
/// Scene item to delete.
item: SceneItemSpecification, // TODO: fields are actually not optional
},
AddSceneItem(AddSceneItem),
DuplicateSceneItem(DuplicateSceneItem),
// --------------------------------
// Scenes
// --------------------------------
#[serde(rename_all = "kebab-case")]
SetCurrentScene {
/// Name of the scene to switch to.
scene_name: String,
},
GetCurrentScene,
GetSceneList,
#[serde(rename_all = "camelCase")]
CreateScene {
/// Name of the scene to create.
scene_name: String,
},
ReorderSceneItems {
/// Name of the scene to reorder (defaults to current).
scene: Option<String>,
/// Ordered list of objects with name and/or id specified. Id preferred due to uniqueness
/// per scene.
items: Vec<Scene>,
},
SetSceneTransitionOverride(SceneTransitionOverride),
#[serde(rename_all = "camelCase")]
RemoveSceneTransitionOverride {
/// Name of the scene to remove the override from.
scene_name: String,
},
#[serde(rename_all = "camelCase")]
GetSceneTransitionOverride {
/// Name of the scene to get the override for.
scene_name: String,
},
// --------------------------------
// Streaming
// --------------------------------
GetStreamingStatus,
StartStopStreaming,
StartStreaming {
/// Special stream configuration. Please note: these won't be saved to OBS' configuration.
stream: Option<Stream>,
},
StopStreaming,
SetStreamSettings(SetStreamSettings),
GetStreamSettings,
SaveStreamSettings,
SendCaptions {
/// Captions text.
text: String,
},
// --------------------------------
// Studio Mode
// --------------------------------
GetStudioModeStatus,
GetPreviewScene,
#[serde(rename_all = "kebab-case")]
SetPreviewScene {
/// The name of the scene to preview.
scene_name: String,
},
TransitionToProgram {
/// Change the active transition before switching scenes. Defaults to the active transition.
with_transition: Option<Transition>,
},
EnableStudioMode,
DisableStudioMode,
ToggleStudioMode,
// --------------------------------
// Transitions
// --------------------------------
GetTransitionList,
GetCurrentTransition,
#[serde(rename_all = "kebab-case")]
SetCurrentTransition {
/// The name of the transition.
transition_name: String,
},
SetTransitionDuration {
/// Desired duration of the transition (in milliseconds).
duration: u64,
},
GetTransitionDuration,
}
/// Request information for [`open_projector`](crate::client::General::open_projector).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Projector {
/// Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview`
/// (case insensitive).
#[serde(rename = "type")]
pub ty: Option<String>,
/// Monitor to open the projector on. If -1 or omitted, opens a window.
pub monitor: Option<i64>,
/// Size and position of the projector window (only if monitor is -1). Encoded in Base64 using
/// [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to
/// OBS's saved projectors.
pub geometry: Option<String>,
/// Name of the source or scene to be displayed (ignored for other projector types).
pub name: Option<String>,
}
/// Request information for [`set_volume`](crate::client::Sources::set_volume).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Volume {
/// Source name.
pub source: String,
/// Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will
/// interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum
/// of 1.0mul/0.0dB, however OBS actually supports larger values.
pub volume: f64,
/// Interperet `volume` data as decibels instead of amplitude/mul.
pub use_decibel: Option<bool>,
}
/// Request information for [`set_source_settings`](crate::client::Sources::set_source_settings).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceSettings {
/// Source name.
pub source_name: String,
/// Type of the specified source. Useful for type-checking to avoid settings a set of settings
/// incompatible with the actual source's type.
pub source_type: Option<String>,
/// Source settings (varies between source types, may require some probing around).
pub source_settings: serde_json::Value,
}
/// Request information for
/// [`set_text_gdi_plus_properties`](crate::client::Sources::set_text_gdi_plus_properties).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct TextGdiPlusProperties {
/// Name of the source.
pub source: String,
/// Text Alignment ("left", "center", "right").
pub align: Option<String>,
/// Background color.
pub bk_color: Option<u32>,
/// Background opacity (0-100).
pub bk_opacity: Option<u8>,
/// Chat log.
pub chatlog: Option<bool>,
/// Chat log lines.
pub chatlog_lines: Option<u64>,
/// Text color.
pub color: Option<u32>,
/// Extents wrap.
pub extents: Option<bool>,
/// Extents cx.
pub extents_cx: Option<i64>,
/// Extents cy.
pub extents_cy: Option<i64>,
/// File path name.
pub file: Option<String>,
/// Read text from the specified file.
pub read_from_file: Option<bool>,
/// Holds data for the font. Ex:
/// `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }`.
pub font: Option<Font>,
/// Gradient enabled.
pub gradient: Option<bool>,
/// Gradient color.
pub gradient_color: Option<u32>,
/// Gradient direction.
pub gradient_dir: Option<f32>,
/// Gradient opacity (0-100).
pub gradient_opacity: Option<u8>,
/// Outline.
pub outline: Option<bool>,
/// Outline color.
pub outline_color: Option<u32>,
/// Outline size.
pub outline_size: Option<u64>,
/// Outline opacity (0-100).
pub outline_opacity: Option<u8>,
/// Text content to be displayed.
pub text: Option<String>,
/// Text vertical alignment ("top", "center", "bottom").
pub valign: Option<String>,
/// Vertical text enabled.
pub vertical: Option<bool>,
/// Visibility of the scene item.
pub render: Option<bool>,
}
/// Request information for
/// [`set_text_freetype2_properties`](crate::client::Sources::set_text_freetype2_properties).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct TextFreetype2Properties {
/// Source name.
pub source: String,
/// Gradient top color.
pub color1: Option<u32>,
/// Gradient bottom color.
pub color2: Option<u32>,
/// Custom width (0 to disable).
pub custom_width: Option<u32>,
/// Drop shadow.
pub drop_shadow: Option<bool>,
/// Holds data for the font. Ex:
/// `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }`.
pub font: Option<Font>,
/// Read text from the specified file.
pub from_file: Option<bool>,
/// Chat log.
pub log_mode: Option<bool>,
/// Outline.
pub outline: Option<bool>,
/// Text content to be displayed.
pub text: Option<String>,
/// File path.
pub text_file: Option<String>,
/// Word wrap.
pub word_wrap: Option<bool>,
}
/// Request information for [`add_filter_to_source`](crate::client::Sources::add_filter_to_source).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddFilter {
/// Name of the source on which the filter is added.
pub source_name: String,
/// Name of the new filter.
pub filter_name: String,
/// Filter type.
pub filter_type: String,
/// Filter settings.
pub filter_settings: serde_json::Value,
}
/// Request information for
/// [`reorder_source_filter`](crate::client::Sources::reorder_source_filter).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReorderFilter {
/// Name of the source to which the filter belongs.
pub source_name: String,
/// Name of the filter to reorder.
pub filter_name: String,
/// Desired position of the filter in the chain.
pub new_index: u32,
}
/// Request information for [`move_source_filter`](crate::client::Sources::move_source_filter).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MoveFilter {
/// Name of the source to which the filter belongs.
pub source_name: String,
/// Name of the filter to reorder.
pub filter_name: String,
/// How to move the filter around in the source's filter chain. Either "up", "down", "top" or
/// "bottom".
pub movement_type: String,
}
/// Request information for
/// [`set_source_filter_settings`](crate::client::Sources::set_source_filter_settings).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceFilterSettings {
/// Name of the source to which the filter belongs.
pub source_name: String,
/// Name of the filter to reconfigure.
pub filter_name: String,
/// New settings. These will be merged to the current filter settings.
pub filter_settings: serde_json::Value,
}
/// Request information for
/// [`set_source_filter_visibility`](crate::client::Sources::set_source_filter_visibility).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceFilterVisibility {
/// Source name.
pub source_name: String,
/// Source filter name.
pub filter_name: String,
/// New filter state.
pub filter_enabled: bool,
}
/// Request information for
/// [`take_source_screenshot`](crate::client::Sources::take_source_screenshot).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceScreenshot {
/// Source name. Note that, since scenes are also sources, you can also provide a scene name. If
/// not provided, the currently active scene is used.
pub source_name: Option<String>,
/// Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other
/// value supported by Qt's Image module).
pub embed_picture_format: Option<String>,
/// Full file path (file extension included) where the captured image is to be saved. Can be in
/// a format different from [`embed_picture_format`](SourceScreenshot::embed_picture_format).
/// Can be a relative path.
pub save_to_file_path: Option<String>,
/// Format to save the image file as (one of the values provided in the
/// [`supported_image_export_formats`](crate::responses::Version::supported_image_export_formats)
/// response field of [`get_version`](crate::client::General::get_version)). If not specified,
/// tries to guess based on file extension.
pub file_format: Option<String>,
/// Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest
/// file/most compression, 100 is largest file/least compression. Varies with image type.
pub compress_quality: Option<i8>,
/// Screenshot width. Defaults to the source's base width.
pub width: Option<u32>,
/// Screenshot height. Defaults to the source's base height.
pub height: Option<u32>,
}
/// Request information for
/// [`set_scene_item_properties`](crate::client::SceneItems::set_scene_item_properties).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SceneItemProperties {
/// Name of the scene the source item belongs to. Defaults to the current scene.
pub scene_name: Option<String>,
/// Scene Item name (if this field is a string) or specification (if it is an object).
#[serde(with = "either::serde_untagged")]
pub item: Either<String, SceneItemSpecification>,
/// Position of the scene item.
pub position: Option<Position>,
/// The new clockwise rotation of the item in degrees.
pub rotation: Option<f64>,
/// Scaling factor of the scene item.
pub scale: Option<Scale>,
/// Pixel cropping of the scene item before scaling.
pub crop: Option<Crop>,
/// The new visibility of the source. 'true' shows source, 'false' hides source.
pub visible: Option<bool>,
/// The new locked status of the source. 'true' keeps it in its current position, 'false' allows
/// movement.
pub locked: Option<bool>,
/// Bounding box of the scene item.
pub bounds: Option<Bounds>,
}
/// Request information for
/// [`set_scene_item_render`](crate::client::SceneItems::set_scene_item_render).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SceneItemRender {
/// Name of the scene the scene item belongs to. Defaults to the currently active scene.
pub scene_name: Option<String>,
/// Scene Item name.
pub source: String,
/// true = shown ; false = hidden.
pub render: bool,
}
/// Request information for [`add_scene_item`](crate::client::SceneItems::add_scene_item).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddSceneItem {
/// Name of the scene to create the scene item in.
pub scene_name: String,
/// Name of the source to be added.
pub source_name: String,
/// Whether to make the sceneitem visible on creation or not. Default `true`.
pub set_visible: bool,
}
/// Request information for
/// [`duplicate_scene_item`](crate::client::SceneItems::duplicate_scene_item).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DuplicateSceneItem {
/// Name of the scene to copy the item from. Defaults to the current scene.
pub from_scene: Option<String>,
/// Name of the scene to create the item in. Defaults to the current scene.
pub to_scene: Option<String>,
/// Scene Item to duplicate from the source scene.
pub item: SceneItemSpecification, // TODO: fields are actually not optional
}
/// Request information for
/// [`set_scene_transition_override`](crate::client::Scenes::set_scene_transition_override).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SceneTransitionOverride {
/// Name of the scene to switch to.
pub scene_name: String,
/// Name of the transition to use.
pub transition_name: String,
/// Duration in milliseconds of the transition if transition is not fixed. Defaults to the
/// current duration specified in the UI if there is no current override and this value is not
/// given.
pub transition_duration: Option<i64>,
}
/// Request information for [`set_stream_settings`](crate::client::Streaming::set_stream_settings).
#[derive(Debug, Serialize)]
pub struct SetStreamSettings {
/// The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`.
#[serde(rename = "type")]
pub ty: String,
/// The actual settings of the stream.
pub settings: StreamSettings,
/// Persist the settings to disk.
pub save: bool,
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
/// Request information for
/// [`set_text_gdi_plus_properties`](crate::client::Sources::set_text_gdi_plus_properties) as part
/// of [`TextGdiPlusProperties`] and
/// [`set_text_freetype2_properties`](crate::client::Sources::set_text_freetype2_properties) as part
/// of [`TextFreetype2Properties`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Font {
/// Font face.
pub face: Option<String>,
/// Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`.
pub flags: Option<u8>,
/// Font text size.
pub size: Option<u32>,
/// Font Style (unknown function).
pub style: Option<String>,
}
/// Request information for
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties),
/// [`set_scene_item_properties`](crate::client::SceneItems::set_scene_item_properties) as part of
/// [`SceneItemProperties`], [`reset_scene_item`](crate::client::SceneItems::reset_scene_item),
/// [`delete_scene_item`](crate::client::SceneItems::delete_scene_item) and
/// [`duplicate_scene_item`](crate::client::SceneItems::duplicate_scene_item) as part of
/// [`DuplicateSceneItem`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct SceneItemSpecification {
/// Scene Item name.
pub name: Option<String>,
/// Scene Item ID.
pub id: Option<i64>,
}
/// Request information for
/// [`set_scene_item_properties`](crate::client::SceneItems::set_scene_item_properties) as part of
/// [`SceneItemProperties`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Position {
/// The new x position of the source.
pub x: Option<f64>,
/// The new y position of the source.
pub y: Option<f64>,
/// The new alignment of the source.
pub alignment: Option<u8>,
}
/// Request information for
/// [`set_scene_item_properties`](crate::client::SceneItems::set_scene_item_properties) as part of
/// [`SceneItemProperties`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Scale {
/// The new x scale of the item.
pub x: Option<f64>,
/// The new y scale of the item.
pub y: Option<f64>,
}
/// Request information for
/// [`set_scene_item_properties`](crate::client::SceneItems::set_scene_item_properties) as part of
/// [`SceneItemProperties`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Crop {
/// The new amount of pixels cropped off the top of the source before scaling.
pub top: Option<i64>,
/// The new amount of pixels cropped off the bottom of the source before scaling.
pub bottom: Option<i64>,
/// The new amount of pixels cropped off the left of the source before scaling.
pub left: Option<i64>,
/// The new amount of pixels cropped off the right of the source before scaling.
pub right: Option<i64>,
}
/// Request information for
/// [`set_scene_item_properties`](crate::client::SceneItems::set_scene_item_properties) as part of
/// [`SceneItemProperties`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Bounds {
/// The new bounds type of the source. 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: Option<String>,
/// The new alignment of the bounding box. (0-2, 4-6, 8-10).
pub alignment: Option<u8>,
/// The new width of the bounding box.
pub x: Option<f64>,
/// The new height of the bounding box.
pub y: Option<f64>,
}
/// Request information for
/// [`reorder_scene_items`](crate::client::Scenes::reorder_scene_items) as part of
/// [`ReorderLineItems`](RequestType::ReorderSceneItems).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Scene {
/// Id of a specific scene item. Unique on a scene by scene basis.
id: Option<i64>,
/// Name of a scene item. Sufficiently unique if no scene items share sources within the scene.
name: Option<String>,
}
/// Request information for [`start_streaming`](crate::client::Streaming::start_streaming).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Stream {
/// If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or
/// 'rtmp_common'). If the currently configured stream type does not match the given stream
/// type, all settings must be specified in the `settings` object or an error will occur when
/// starting the stream.
#[serde(rename = "type")]
ty: Option<String>,
/// Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP
/// stream. Used to pass data to the RTMP service about the streaming. May be any String,
/// Numeric, or Boolean field.
metadata: Option<serde_json::Value>,
/// Settings for the stream.
settings: Option<StreamSettings>,
}
/// Request information for [`start_streaming`](crate::client::Streaming::start_streaming) as part
/// of [`Stream`] and [`set_stream_settings`](crate::client::Streaming::set_stream_settings) as part
/// of [`SetStreamSettings`].
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct StreamSettings {
/// The publish URL.
server: Option<String>,
/// The publish key of the stream.
key: Option<String>,
/// Indicates whether authentication should be used when connecting to the streaming server.
use_auth: Option<bool>,
/// If authentication is enabled, the username for the streaming server. Ignored if
/// [`use_auth`](Self::use_auth) is not set to `true`.
username: Option<String>,
/// If authentication is enabled, the password for the streaming server. Ignored if
/// [`use_auth`](Self::use_auth) is not set to `true`.
password: Option<String>,
}
/// Request information for
/// [`transition_to_program`](crate::client::StudioMode::transition_to_program).
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub struct Transition {
/// Name of the transition.
name: String,
/// Transition duration (in milliseconds).
duration: Option<u64>,
}

@ -0,0 +1,14 @@
use std::iter::FromIterator;
use serde::de::{Deserialize, Deserializer};
#[allow(dead_code)]
pub fn string_comma_list<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromIterator<String>,
{
let s = <&str>::deserialize(deserializer)?;
Ok(s.split(',').map(|s| s.to_owned()).collect())
}

@ -0,0 +1,751 @@
//! All responses that can be received from the API.
use serde::Deserialize;
use crate::common::{Bounds, Crop, Position, Scale, SceneItem, SceneItemTransform};
mod de;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct Response<T> {
pub message_id: String,
pub status: Status,
pub error: Option<String>,
#[serde(flatten)]
pub details: T,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum Status {
Ok,
Error,
}
/// Response value for [`get_version`](crate::client::General::get_version).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Version {
/// OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.
pub version: f64,
/// obs-websocket plugin version.
pub obs_websocket_version: String,
/// OBS Studio program version.
pub obs_studio_version: String,
/// List of available request types, formatted as a comma-separated list string (e.g. :
/// "Method1,Method2,Method3").
pub available_requests: String,
/// List of supported formats for features that use image export (like the
/// [`TakeSourceScreenshot`](crate::requests::RequestType::TakeSourceScreenshot) request type)
/// formatted as a comma-separated list string.
pub supported_image_export_formats: String,
}
/// Response value for [`get_auth_required`](crate::client::General::get_auth_required).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthRequired {
/// Indicates whether authentication is required.
pub auth_required: bool,
/// A random string that will be used to generate the auth response.
pub challenge: Option<String>,
/// Applied to the password when generating the auth response.
pub salt: Option<String>,
}
/// Response value for [`get_filename_formatting`](crate::client::General::get_filename_formatting).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct FilenameFormatting {
/// Current filename formatting string.
pub filename_formatting: String,
}
/// Response value for [`get_stats`](crate::client::General::get_stats).
#[derive(Debug, Deserialize)]
pub(crate) struct Stats {
/// See [`ObsStats`].
pub stats: ObsStats,
}
/// Response value for [`get_video_info`](crate::client::General::get_video_info).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoInfo {
/// Base (canvas) width.
pub base_width: u64,
/// Base (canvas) height.
pub base_height: u64,
/// Output width.
pub output_width: u64,
/// Output height.
pub output_height: u64,
/// Scaling method used if output size differs from base size.
pub scale_type: String,
/// Frames rendered per second.
pub fps: f64,
/// Video color format.
pub video_format: String,
/// Color space for YUV.
pub color_space: String,
/// Color range (full or partial).
pub color_range: String,
}
/// Response value for [`get_sources_list`](crate::client::Sources::get_sources_list).
#[derive(Debug, Deserialize)]
pub(crate) struct SourcesList {
/// Array of sources.
pub sources: Vec<SourceListItem>,
}
/// Response value for [`get_sources_types_list`](crate::client::Sources::get_sources_types_list).
#[derive(Debug, Deserialize)]
pub(crate) struct SourceTypesList {
/// Array of source types.
pub types: Vec<SourceTypeItem>,
}
/// Response value for [`get_volume`](crate::client::Sources::get_volume).
#[derive(Debug, Deserialize)]
pub struct Volume {
/// Source name.
pub name: String,
/// Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB.
pub volume: f64,
/// Indicates whether the source is muted.
pub muted: bool,
}
/// Response value for [`get_mute`](crate::client::Sources::get_mute).
#[derive(Debug, Deserialize)]
pub struct Mute {
/// Source name.
pub name: String,
/// Mute status of the source.
pub muted: bool,
}
/// Response value for [`get_audio_active`](crate::client::Sources::get_audio_active).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AudioActive {
/// Audio active status of the source.
pub audio_active: bool,
}
/// Response value for [`get_sync_offset`](crate::client::Sources::get_sync_offset).
#[derive(Debug, Deserialize)]
pub struct SyncOffset {
/// Source name.
pub name: String,
/// The audio sync offset (in nanoseconds).
pub offset: i64,
}
/// Response value for [`get_source_settings`](crate::client::Sources::get_source_settings) and
/// [`set_source_settings`](crate::client::Sources::set_source_settings).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceSettings<T> {
/// Source name.
pub source_name: String,
/// Type of the specified source.
pub source_type: String,
/// Source settings (varies between source types, may require some probing around).
pub source_settings: T,
}
/// Response value for
/// [`get_text_gdi_plus_properties`](crate::client::Sources::get_text_gdi_plus_properties).
#[derive(Debug, Deserialize)]
pub struct TextGdiPlusProperties {
/// Source name.
pub source: String,
/// Text Alignment ("left", "center", "right").
pub align: String,
/// Background color.
pub bk_color: u32,
/// Background opacity (0-100).
pub bk_opacity: u8,
/// Chat log.
pub chatlog: bool,
/// Chat log lines.
pub chatlog_lines: u64,
/// Text color.
pub color: u32,
/// Extents wrap.
pub extents: bool,
/// Extents cx.
pub extents_cx: i64,
/// Extents cy.
pub extents_cy: i64,
/// File path name.
pub file: String,
/// Read text from the specified file.
pub read_from_file: bool,
/// Holds data for the font. Ex:
/// `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }`.
pub font: Font,
/// Gradient enabled.
pub gradient: bool,
/// Gradient color.
pub gradient_color: u32,
/// Gradient direction.
pub gradient_dir: f32,
/// Gradient opacity (0-100).
pub gradient_opacity: u8,
/// Outline.
pub outline: bool,
/// Outline color.
pub outline_color: u32,
/// Outline size.
pub outline_size: u64,
/// Outline opacity (0-100).
pub outline_opacity: u8,
/// Text content to be displayed.
pub text: String,
/// Text vertical alignment ("top", "center", "bottom").
pub valign: String,
/// Vertical text enabled.
pub vertical: bool,
}
/// Response value for
/// [`get_text_freetype2_properties`](crate::client::Sources::get_text_freetype2_properties).
#[derive(Debug, Deserialize)]
pub struct TextFreetype2Properties {
/// Source name.
pub source: String,
/// Gradient top color.
pub color1: u32,
/// Gradient bottom color.
pub color2: u32,
/// Custom width (0 to disable).
pub custom_width: u32,
/// Drop shadow.
pub drop_shadow: bool,
/// Holds data for the font. Ex:
/// `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }`.
pub font: Font,
/// Read text from the specified file.
pub from_file: bool,
/// Chat log.
pub log_mode: bool,
/// Outline.
pub outline: bool,
/// Text content to be displayed.
pub text: String,
/// File path.
pub text_file: String,
/// Word wrap.
pub word_wrap: bool,
}
/// Response value for [`get_special_sources`](crate::client::Sources::get_special_sources).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SpecialSources {
/// Name of the first Desktop Audio capture source.
pub desktop_1: Option<String>,
/// Name of the second Desktop Audio capture source.
pub desktop_2: Option<String>,
/// Name of the first Mic/Aux input source.
pub mic_1: Option<String>,
/// Name of the second Mic/Aux input source.
pub mic_2: Option<String>,
/// Name of the third Mic/Aux input source.
pub mic_3: Option<String>,
}
/// Response value for [`get_source_filters`](crate::client::Sources::get_source_filters).
#[derive(Debug, Deserialize)]
pub(crate) struct SourceFilters {
/// List of filters for the specified source.
pub filters: Vec<SourceFilter>,
}
/// Response value for [`get_source_filter_info`](crate::client::Sources::get_source_filter_info).
#[derive(Debug, Deserialize)]
pub struct SourceFilterInfo<T> {
/// Filter status (enabled or not).
pub enabled: bool,
/// Filter type.
#[serde(rename = "type")]
pub ty: String,
/// Filter name.
pub name: String,
/// Filter settings.
pub settings: T,
}
/// Response value for [`get_audio_monitor_type`](crate::client::Sources::get_audio_monitor_type).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AudioMonitorType {
/// The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`.
pub monitor_type: String,
}
/// Response value for [`take_source_screenshot`](crate::client::Sources::take_source_screenshot).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceScreenshot {
/// Source name.
pub source_name: String,
/// Image Data URI (if
/// [`embed_picture_format`](crate::requests::SourceScreenshot::embed_picture_format) was
/// specified in the request).
pub img: Option<String>,
/// Absolute path to the saved image file (if
/// [`save_to_file_path`](crate::requests::SourceScreenshot::save_to_file_path) was specified in
/// the request).
pub image_file: Option<String>,
}
/// Response value for [`list_outputs`](crate::client::Outputs::list_outputs).
#[derive(Debug, Deserialize)]
pub(crate) struct Outputs {
/// Outputs list.
pub outputs: Vec<Output>,
}
/// Response value for [`get_output_info`](crate::client::Outputs::get_output_info).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OutputInfo {
/// Output info.
pub output_info: Output,
}
/// Response value for [`get_current_profile`](crate::client::Profiles::get_current_profile).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct CurrentProfile {
/// Name of the currently active profile.
pub profile_name: String,
}
/// Response value for [`list_profiles`](crate::client::Profiles::list_profiles).
#[derive(Debug, Deserialize)]
pub(crate) struct Profiles {
/// List of available profiles.
pub profiles: Vec<Profile>,
}
/// Response value for [`get_recording_folder`](crate::client::Recording::get_recording_folder).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct RecordingFolder {
/// Path of the recording folder.
pub rec_folder: String,
}
/// Response value for
/// [`get_current_scene_collection`](crate::client::SceneCollections::get_current_scene_collection).
#[serde(rename_all = "kebab-case")]
#[derive(Debug, Deserialize)]
pub(crate) struct CurrentSceneCollection {
/// Name of the currently active scene collection.
pub sc_name: String,
}
/// Response value for
/// [`list_scene_collections`](crate::client::SceneCollections::list_scene_collections).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct SceneCollections {
/// Scene collections list.
pub scene_collections: Vec<SceneCollection>,
}
/// Response value for
/// [`get_scene_item_properties`](crate::client::SceneItems::get_scene_item_properties).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SceneItemProperties {
/// Scene Item name.
pub name: String,
/// Scene Item ID.
pub item_id: i64,
/// Position of the source.
pub position: Position,
/// The clockwise rotation of the item in degrees around the point of alignment.
pub rotation: f64,
/// Scaling factor of the source.
pub scale: Scale,
/// Pixel cropping of the source before scaling.
pub crop: Crop,
/// If the source is visible.
pub visible: bool,
/// If the source is muted.
pub muted: bool,
/// If the source's transform is locked.
pub locked: bool,
/// Bounding box of the source.
pub bounds: Bounds,
/// Base width (without scaling) of the source.
pub source_width: u32,
/// Base source (without scaling) of the source.
pub source_height: u32,
/// 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,
// pub alignment: u8,
/// 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 [`add_scene_item`](crate::client::SceneItems::add_scene_item).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AddSceneItem {
/// Numerical ID of the created scene item.
pub item_id: i64,
}
/// Response value for [`duplicate_scene_item`](crate::client::SceneItems::duplicate_scene_item).
#[derive(Debug, Deserialize)]
pub struct DuplicateSceneItem {
/// Name of the scene where the new item was created.
pub scene: String,
/// New item info.
pub item: SceneItemSpecification,
}
/// Response value for [`get_current_scene`](crate::client::Scenes::get_current_scene).
#[derive(Debug, Deserialize)]
pub struct CurrentScene {
/// Name of the currently active scene.
pub name: String,
/// Ordered list of the current scene's source items.
pub sources: Vec<SceneItem>,
}
/// Response value for [`get_scene_list`](crate::client::Scenes::get_scene_list).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SceneList {
/// Name of the currently active scene.
pub current_scene: String,
/// Ordered list of the current profile's scenes.
pub scenes: Vec<Scene>,
}
/// Response value for
/// [`get_scene_transition_override`](crate::client::Scenes::get_scene_transition_override).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SceneTransitionOverride {
/// Name of the current overriding transition. Empty string if no override is set.
pub transition_name: String,
/// Transition duration. `-1` if no override is set.
pub transition_duration: i64,
}
/// Response value for [`get_streaming_status`](crate::client::Streaming::get_streaming_status).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct StreamingStatus {
/// Current streaming status.
pub streaming: bool,
/// Current recording status.
pub recording: bool,
/// Time elapsed since streaming started (only present if currently streaming).
pub stream_timecode: Option<String>,
/// Time elapsed since recording started (only present if currently recording).
pub rec_timecode: Option<String>,
/// Always false. Retrocompatibility with OBSRemote.
#[serde(default)]
pub preview_only: bool,
}
/// Response value for [`get_stream_settings`](crate::client::Streaming::get_stream_settings).
#[derive(Debug, Deserialize)]
pub struct GetStreamSettings {
/// The type of streaming service configuration. Possible values: `rtmp_custom` or
/// `rtmp_common`.
#[serde(rename = "type")]
pub ty: String,
/// Stream settings object.
pub settings: StreamSettings,
}
/// Response value for
/// [`get_studio_mode_status`](crate::client::StudioMode::get_studio_mode_status).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct StudioModeStatus {
/// Indicates if Studio Mode is enabled.
pub studio_mode: bool,
}
/// Response value for [`get_preview_scene`](crate::client::StudioMode::get_preview_scene).
#[derive(Debug, Deserialize)]
pub struct PreviewScene {
/// The name of the active preview scene.
pub name: String,
/// Array of scene items of the active preview scene.
pub sources: Vec<SceneItem>,
}
/// Response value for [`get_transition_list`](crate::client::Transitions::get_transition_list).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TransitionList {
/// Name of the currently active transition.
pub current_transition: String,
/// List of transitions.
pub transitions: Vec<Transition>,
}
/// Response value for
/// [`get_current_transition`](crate::client::Transitions::get_current_transition).
#[derive(Debug, Deserialize)]
pub struct CurrentTransition {
/// Name of the selected transition.
pub name: String,
/// Transition duration (in milliseconds) if supported by the transition.
pub duration: Option<u64>,
}
/// Response value for
/// [`get_transition_duration`](crate::client::Transitions::get_transition_duration).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct TransitionDuration {
/// Duration of the current transition (in milliseconds).
pub transition_duration: u64,
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
/// Response value for [`get_stats`](crate::client::General::get_stats).
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ObsStats {
/// Current framerate.
pub fps: f64,
/// Number of frames rendered.
pub render_total_frames: u64,
/// Number of frames missed due to rendering lag.
pub render_missed_frames: u64,
/// Number of frames outputted.
pub output_total_frames: u64,
/// Number of frames skipped due to encoding lag.
pub output_skipped_frames: u64,
/// Average frame render time (in milliseconds).
pub average_frame_time: f64,
/// Current CPU usage (percentage).
pub cpu_usage: f64,
/// Current RAM usage (in megabytes).
pub memory_usage: f64,
/// Free recording disk space (in megabytes)
pub free_disk_space: f64,
}
/// Response value for [`get_sources_list`](crate::client::Sources::get_sources_list).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceListItem {
/// Unique source name.
pub name: String,
/// Non-unique source internal type (a.k.a kind).
pub type_id: String,
/// Source type. Value is one of the following: "input", "filter", "transition", "scene" or
/// "unknown".
#[serde(rename = "type")]
pub ty: String,
}
/// Response value for [`get_sources_types_list`](crate::client::Sources::get_sources_types_list).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceTypeItem {
/// Non-unique internal source type ID.
pub type_id: String,
/// Display name of the source type.
pub display_name: String,
/// Type. Value is one of the following: "input", "filter", "transition" or "other".
#[serde(rename = "type")]
pub ty: String,
/// Default settings of this source type.
pub default_settings: serde_json::Value,
/// Source type capabilities.
pub caps: Caps,
}
/// Response value for [`get_sources_types_list`](crate::client::Sources::get_sources_types_list) as
/// part of [`SourceTypeItem`].
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Caps {
/// True if source of this type provide frames asynchronously.
pub is_async: bool,
/// True if sources of this type provide video.
pub has_video: bool,
/// True if sources of this type provide audio.
pub has_audio: bool,
/// True if interaction with this sources of this type is possible.
pub can_interact: bool,
/// True if sources of this type composite one or more sub-sources.
pub is_composite: bool,
/// True if sources of this type should not be fully duplicated.
pub do_not_duplicate: bool,
/// True if sources of this type may cause a feedback loop if it's audio is monitored and
/// shouldn't be.
pub do_not_self_monitor: bool,
}
/// Response value for
/// [`get_text_gdi_plus_properties`](crate::client::Sources::get_text_gdi_plus_properties) as part
/// of [`TextGdiPlusProperties`] and
/// [`get_text_freetype2_properties`](crate::client::Sources::get_text_freetype2_properties) as part
/// of [`TextFreetype2Properties`].
#[derive(Debug, Deserialize)]
pub struct Font {
/// Font face.
pub face: String,
/// Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8`.
pub flags: u8,
/// Font text size.
pub size: u32,
/// Font Style (unknown function).
pub style: String,
}
/// Response value for [`get_source_filters`](crate::client::Sources::get_source_filters).
#[derive(Debug, Deserialize)]
pub struct SourceFilter {
/// Filter status (enabled or not).
pub enabled: bool,
/// Filter type.
#[serde(rename = "type")]
pub ty: String,
/// Filter name.
pub name: String,
/// Filter settings.
pub settings: serde_json::Value,
}
/// Response value for [`list_outputs`](crate::client::Outputs::list_outputs) and
/// [`get_output_info`](crate::client::Outputs::get_output_info).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Output {
/// Output name.
pub name: String,
/// Output type/kind.
#[serde(rename = "type")]
pub ty: String,
/// Video output width.
pub width: u32,
/// Video output height.
pub height: u32,
/// Output flags.
pub flags: OutputFlags,
/// Output settings.
pub settings: serde_json::Value,
/// Output status (active or not).
pub active: bool,
/// Output reconnection status (reconnecting or not).
pub reconnecting: bool,
/// Output congestion.
pub congestion: f64,
/// Number of frames sent.
pub total_frames: u64,
/// Number of frames dropped.
pub dropped_frames: u64,
/// Total bytes sent.
pub total_bytes: u64,
}
/// Response value for [`list_outputs`](crate::client::Outputs::list_outputs) and
/// [`get_output_info`](crate::client::Outputs::get_output_info) as part of [`Output`].
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OutputFlags {
/// Raw flags value.
pub raw_value: u64,
/// Output uses audio.
pub audio: bool,
/// Output uses video.
pub video: bool,
/// Output is encoded.
pub encoded: bool,
/// Output uses several audio tracks.
pub multi_track: bool,
/// Output uses a service.
pub service: bool,
}
/// Response value for [`list_profiles`](crate::client::Profiles::list_profiles).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Profile {
/// Profile name.
pub profile_name: String,
}
/// Response value for
/// [`list_scene_collections`](crate::client::SceneCollections::list_scene_collections).
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SceneCollection {
/// Scene collection name.
pub sc_name: String,
}
/// Response value for [`duplicate_scene_item`](crate::client::SceneItems::duplicate_scene_item) as
/// part of [`DuplicateSceneItem`].
#[derive(Debug, Deserialize)]
pub struct SceneItemSpecification {
/// New item ID.
pub id: i64,
/// New item name.
pub name: String,
}
/// Response value for [`get_scene_list`](crate::client::Scenes::get_scene_list) as part of
/// [`SceneList`].
// TODO: actually the same as `CurrentScene`.
#[derive(Clone, Debug, Deserialize)]
pub struct Scene {
/// Name of the scene.
pub name: String,
/// Ordered list of the scene's source items.
pub sources: Vec<SceneItem>,
}
/// Response value for [`get_stream_settings`](crate::client::Streaming::get_stream_settings) as
/// part of [`GetStreamSettings`].
#[derive(Debug, Deserialize)]
pub struct StreamSettings {
/// The publish URL.
pub server: String,
/// The publish key of the stream.
pub key: String,
/// Indicates whether authentication should be used when connecting to the streaming server.
pub use_auth: bool,
/// The username to use when accessing the streaming server. Only present if
/// [`use_auth`](Self::use_auth) is `true`.
pub username: Option<String>,
/// The password to use when accessing the streaming server. Only present if
/// [`use_auth`](Self::use_auth) is `true`.
pub password: Option<String>,
}
/// Response value for [`get_transition_list`](crate::client::Transitions::get_transition_list) as
/// part of [`TransitionList`].
#[derive(Debug, Deserialize)]
pub struct Transition {
/// Name of the transition.
pub name: String,
}
Loading…
Cancel
Save