60 Commits
Author SHA1 Message Date
onyx_online dc8ac155ec patch bump 2023-08-11 14:45:02 +01:00
onyx_online 23d64ef9d8 test files renamed with test_ prefix
error tests added

Rakefile updated with new test file names

event tasks moved into :e namespace
2023-08-11 14:44:51 +01:00
onyx_online 9be9dc80a2 adds connect_timeout kwarg for base class 2023-08-11 14:41:35 +01:00
onyx_online a40ab77be9 cleanup error messages 2023-08-11 14:41:09 +01:00
onyx_online b440ace20c fix req_name
patch bump
2023-08-11 13:18:28 +01:00
onyx_online f5a817ab4e fix req_name
patch bump
2023-08-11 13:16:27 +01:00
onyx_online 13f57f79f6 rename name to req_name in OBSWSRequestError
patch bump
2023-08-11 13:10:44 +01:00
onyx_online 976c8f19a8 new error classes
OBSWSConnectionError, OBSWSRequestError added

they subclass OBSWSError

readme updated with new error classes
2023-08-11 02:22:14 +01:00
onyx_online 515fa565d4 use conditional assignment
patch bump
2023-08-06 11:33:15 +01:00
onyx_online 46bfb53db8 add docstring 2023-08-03 14:39:44 +01:00
onyx_online f669498c69 remove_observers now accepts array of callbacks
callbacks aliases observers

patch bump
2023-08-03 14:15:46 +01:00
onyx_online aeec0635ca add and control flow operator 2023-07-30 00:06:39 +01:00
onyx_online 86b84aeef9 minor syntax changes 2023-07-28 19:05:28 +01:00
onyx_online e4f4961c56 upd Gemfile.lock 2023-07-27 15:08:33 +01:00
onyx_online 4fdebc8178 Callbacks module extended
Now supports callback methods as well observer classes

levels example now uses callback methods

register,deregister now alias add/remove observer methods

minor version bump
2023-07-27 14:55:00 +01:00
onyx_online 155cbe019a upd Gemfile.lock 2023-07-26 19:53:04 +01:00
onyx_online 6293ae7b8c patch bump 2023-07-26 18:55:57 +01:00
onyx_online 57fca646b5 remove the monkey patching 2023-07-26 18:55:35 +01:00
onyx_online d12a1a5954 refactor Callbacks 2023-07-26 18:55:19 +01:00
onyx_online 438f3b1659 upd Gemfile.lock 2023-07-26 17:27:41 +01:00
onyx_online d15418a660 mixin only methods for directing the driver
patch bump
2023-07-26 16:55:59 +01:00
onyx_online 2883fd42cc Socket class and driver methods
moved into Driver module

patch bump
2023-07-26 16:38:36 +01:00
onyx_online 88b2eabc0c typo fix 2023-07-26 16:15:43 +01:00
onyx_online e15e17cc9f update readme title 2023-07-26 16:12:38 +01:00
onyx_online 72e09d5278 minor version bump 2023-07-26 14:38:12 +01:00
onyx_online 11d991b039 examples updated 2023-07-26 14:38:01 +01:00
onyx_online 3d3d8f3020 log level may now be set with environment variable 2023-07-26 14:37:49 +01:00
onyx_online 82c6ced760 logger module added 2023-07-26 14:37:35 +01:00
onyx_online 72ee539b96 upd gemfile.lock 2023-07-26 10:54:38 +01:00
onyx_online bbfaf486c3 observer dependency removed
patchbump
2023-07-26 10:52:23 +01:00
onyx_online 8534c59fa2 close now aliases stop_driver 2023-07-26 10:52:07 +01:00
onyx_online 9940fbbf9f assign client updater methods as base lambdas 2023-07-26 10:51:38 +01:00
onyx_online 18d291c6eb rename info to infostring, now returns a string 2023-07-26 10:18:32 +01:00
onyx_online 6dc21314e8 break instead of exit 2023-07-21 07:55:50 +01:00
onyx_online 15585c90e9 fix error in rakefile 2023-07-21 06:39:53 +01:00
onyx_online 15c4baf5d7 raekfile updated
rework examples
2023-07-21 06:37:14 +01:00
onyx_online 15dcaeedda remove unnecessary assignment. 2023-07-21 06:04:09 +01:00
onyx_online 856b7b5a5b add attr_reader r_client for OBSWSTest 2023-07-19 15:24:15 +01:00
onyx_online af85d8b9ab minor version bump 2023-07-19 15:14:46 +01:00
onyx_online 5f08b97759 example in readme and main.rb refactored
code style badge changed to standard
2023-07-19 15:14:28 +01:00
onyx_online 73ae24eb4b examples refactored 2023-07-19 15:13:26 +01:00
onyx_online 153f35e742 now using symbols when making api calls
in run, yield now passes a reference ot the client as an argument
2023-07-19 15:12:25 +01:00
onyx_online 17dcaa18d8 now using standard 2023-07-19 15:11:15 +01:00
onyx_online 955b00571a whitespace fix 2023-07-19 15:11:05 +01:00
onyx_online d9b054b108 whitespace fix
load obs.yml instead
2023-07-19 15:10:38 +01:00
onyx_online 799ae52b02 add prettier settings 2023-05-26 21:17:17 +01:00
onyx_online bc93654297 move identify payload out of auth block
patch bump
2022-11-25 18:05:10 +00:00
onyx_online aafcd185d0 patch bump
add full request payload to logger.

raise OBSWSError if auth enabled but no password provided

add example tasks to rake file

add logging section to README
2022-11-25 17:57:56 +00:00
onyx_online 43ecfb37f5 upd gemfile.lock 2022-11-18 15:00:34 +00:00
onyx_online 2e70c63ae7 fix patch ver 2022-11-18 14:54:29 +00:00
onyx_online c67ce47026 pass payload for call_vendor_request
patch bump
2022-11-18 14:52:28 +00:00
norm 0bac7eaf3a added missing response classes
patch bump
2022-11-03 04:11:59 +00:00
norm 5b0ce79e46 fix bug in get_group_list
patch bump
2022-11-03 03:36:56 +00:00
norm a0f5d8e57b add levels example 2022-10-27 06:45:52 +01:00
norm f1a1c970e0 add low, high, all constants to SUBS 2022-10-27 06:45:21 +01:00
onyx-and-iris 449684c405 upd gemfile.lock files 2022-10-25 00:33:24 +01:00
onyx-and-iris da5ef76c81 minor bump 2022-10-25 00:06:10 +01:00
onyx-and-iris 8752132012 scene rotate example added 2022-10-25 00:05:12 +01:00
onyx-and-iris fb162ca195 logger level set to info in events example 2022-10-25 00:04:28 +01:00
onyx-and-iris b33fe94cee moved logger auth success to req,event classes
override to_s for req,event classes

rename authenticate to identify in base class
2022-10-25 00:03:43 +01:00
26 changed files with 717 additions and 545 deletions
+5 -2
View File
@@ -46,7 +46,8 @@ build-iPhoneSimulator/
# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
.ruby-version
.ruby_version
# .ruby-gemset
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
@@ -56,6 +57,8 @@ build-iPhoneSimulator/
# .rubocop-https?--*
# config
obs.toml
obs.yml
# quick test
quick.rb
.vscode/
+44 -6
View File
@@ -1,20 +1,58 @@
PATH
remote: .
specs:
obsws (0.0.3)
observer (~> 0.1.1)
obsws (0.5.3)
waitutil (~> 0.2.1)
websocket-driver (~> 0.7.5)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.6.3)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
minitest (5.16.3)
observer (0.1.1)
perfect_toml (0.9.0)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
racc
racc (1.7.1)
rainbow (3.1.1)
rake (11.3.0)
regexp_parser (2.8.1)
rexml (3.2.5)
rubocop (1.52.1)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-performance (1.18.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-progressbar (1.13.0)
standard (1.30.1)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.52.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.1.0)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.1.2)
lint_roller (~> 1.1)
rubocop-performance (~> 1.18.0)
unicode-display_width (2.4.2)
waitutil (0.2.1)
websocket-driver (0.7.5)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -24,8 +62,8 @@ PLATFORMS
DEPENDENCIES
minitest (~> 5.16, >= 5.16.3)
obsws!
perfect_toml (~> 0.9.0)
rake (~> 11.2, >= 11.2.2)
standard (~> 1.30)
BUNDLED WITH
2.3.22
+43 -29
View File
@@ -1,8 +1,8 @@
[![Gem Version](https://badge.fury.io/rb/obsws.svg)](https://badge.fury.io/rb/obsws)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/obsws-ruby/blob/dev/LICENSE)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/plugin-ruby)
[![Ruby Code Style](https://img.shields.io/badge/code_style-standard-violet.svg)](https://github.com/standardrb/standard)
# A Ruby wrapper around OBS Studio WebSocket v5.0
# Ruby Clients for OBS Studio WebSocket v5.0
## Requirements
@@ -16,77 +16,75 @@
### Bundler
```
bundle add 'obsws'
bundle add obsws
bundle install
```
### Gem
`gem install 'obsws'`
## `Use`
#### Example `main.rb`
pass `host`, `port` and `password` as keyword arguments.
Pass `host`, `port` and `password` as keyword arguments.
```ruby
require "obsws"
def main
r_client =
OBSWS::Requests::Client.new(
host: "localhost",
port: 4455,
password: "strongpassword"
)
r_client.run do
class Main
def run
OBSWS::Requests::Client
.new(host: "localhost", port: 4455, password: "strongpassword")
.run do |client|
# Toggle the mute state of your Mic input
r_client.toggle_input_mute("Mic/Aux")
client.toggle_input_mute("Mic/Aux")
end
end
end
main if $0 == __FILE__
Main.new.run if $PROGRAM_NAME == __FILE__
```
Passing OBSWS::Requests::Client.run a block closes the socket once the block returns.
### Requests
Method names for requests match the API calls but snake cased. `run` accepts a block that closes the socket once you are done.
Method names for requests match the API calls but snake cased.
example:
```ruby
r_client.run do
# GetVersion
resp = r_client.get_version
# SetCurrentProgramScene
r_client.set_current_program_scene("BRB")
end
```
For a full list of requests refer to [Requests](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requests)
### Events
Register an observer class and define `on_` methods for events. Method names should match the api event but snake cased.
Register `on_` callback methods. Method names should match the api event but snake cased.
example:
```ruby
class Observer
def initialize
@e_client = OBSWS::Events::Client.new(**kwargs)
# register class with the event client
@e_client.add_observer(self)
@e_client = OBSWS::Events::Client.new(host: "localhost", port: 4455, password: "strongpassword")
# register callback methods with the Event client
@e_client.register(
[
method(:on_current_program_scene_changed),
method(:on_input_mute_state_changed)
]
)
end
# define "on_" event methods.
def on_current_program_scene_changed
def on_current_program_scene_changed(data)
...
end
def on_input_mute_state_changed
def on_input_mute_state_changed(data)
...
end
...
@@ -111,10 +109,26 @@ def on_scene_created(data):
### Errors
If a request fails an `OBSWSError` will be raised with a status code.
If a general error occurs an `OBSWSError` will be raised.
If a connection attempt fails or times out an `OBSWSConnectionError` will be raised.
If a request fails an `OBSWSRequestError` will be raised with a status code.
- The request name and code are retrievable through attributes {OBSWSRequestError}.req_name and {OBSWSRequestError}.code
For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus)
### Logging
To enable logs set an environmental variable `OBSWS_LOG_LEVEL` to the appropriate level.
example in powershell:
```powershell
$env:OBSWS_LOG_LEVEL="DEBUG"
```
### Tests
To run all tests:
+21 -1
View File
@@ -1,9 +1,29 @@
require "minitest/test_task"
HERE = __dir__
Minitest::TestTask.create(:test) do |t|
t.libs << "test"
t.warning = false
t.test_globs = ["test/**/*_test.rb"]
t.test_globs = ["test/**/test_*.rb"]
end
task default: :test
namespace :e do
desc "Runs the events example"
task :events do
filepath = File.join(HERE, "examples", "events", "main.rb")
ruby filepath
end
desc "Runs the levels example"
task :levels do
filepath = File.join(HERE, "examples", "levels", "main.rb")
ruby filepath
end
desc "Runs the scene_rotate example"
task :scene_rotate do
filepath = File.join(HERE, "examples", "scene_rotate", "main.rb")
ruby filepath
end
end
-9
View File
@@ -1,9 +0,0 @@
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rails"
gem "perfect_toml", "~> 0.9.0"
gem "obsws", path: "../.."
-27
View File
@@ -1,27 +0,0 @@
PATH
remote: ../..
specs:
obsws (0.0.2)
observer (~> 0.1.1)
waitutil (~> 0.2.1)
websocket-driver (~> 0.7.5)
GEM
remote: https://rubygems.org/
specs:
observer (0.1.1)
perfect_toml (0.9.0)
waitutil (0.2.1)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
PLATFORMS
x64-mingw-ucrt
DEPENDENCIES
obsws!
perfect_toml (~> 0.9.0)
BUNDLED WITH
2.3.24
+15 -20
View File
@@ -1,9 +1,8 @@
require "obsws"
require "perfect_toml"
require_relative "../../lib/obsws"
require "yaml"
OBSWS::LOGGER.debug!
class Observer
class Main
attr_reader :running
def initialize(**kwargs)
@@ -11,18 +10,20 @@ class Observer
@e_client = OBSWS::Events::Client.new(**kwargs)
@e_client.add_observer(self)
puts info.join("\n")
puts infostring
@running = true
end
def info
def run
sleep(0.1) while running
end
def infostring
resp = @r_client.get_version
[
"Using obs version:",
resp.obs_version,
"With websocket version:",
resp.obs_web_socket_version
]
"Using obs version: #{resp.obs_version}.",
"With websocket version: #{resp.obs_web_socket_version}"
].join(" ")
end
def on_current_program_scene_changed(data)
@@ -45,14 +46,8 @@ class Observer
end
end
def conn_from_toml
PerfectTOML.load_file("obs.toml", symbolize_names: true)[:connection]
def conn_from_yaml
YAML.load_file("obs.yml", symbolize_names: true)[:connection]
end
def main
o = Observer.new(**conn_from_toml)
sleep(0.1) while o.running
end
main if $0 == __FILE__
Main.new(**conn_from_yaml).run if $PROGRAM_NAME == __FILE__
+52
View File
@@ -0,0 +1,52 @@
require_relative "../../lib/obsws"
require "yaml"
module LevelTypes
VU = 0
POSTFADER = 1
PREFADER = 2
end
class Main
DEVICE = "Desktop Audio"
def initialize(**kwargs)
subs = OBSWS::Events::SUBS::LOW_VOLUME | OBSWS::Events::SUBS::INPUTVOLUMEMETERS
@e_client = OBSWS::Events::Client.new(subs:, **kwargs)
@e_client.register(
[
method(:on_input_mute_state_changed),
method(:on_input_volume_meters)
]
)
end
def run
puts "press <Enter> to quit"
loop { break if gets.chomp.empty? }
end
def on_input_mute_state_changed(data)
if data.input_name == DEVICE
puts "#{DEVICE} mute toggled"
end
end
def on_input_volume_meters(data)
fget = ->(x) { (x > 0) ? (20 * Math.log(x, 10)).round(1) : -200.0 }
data.inputs.each do |d|
name = d[:inputName]
if name == DEVICE && !d[:inputLevelsMul].empty?
left, right = d[:inputLevelsMul]
puts "#{name} [L: #{fget.call(left[LevelTypes::POSTFADER])}, R: #{fget.call(right[LevelTypes::POSTFADER])}]"
end
end
end
end
def conn_from_yaml
YAML.load_file("obs.yml", symbolize_names: true)[:connection]
end
Main.new(**conn_from_yaml).run if $PROGRAM_NAME == __FILE__
+23
View File
@@ -0,0 +1,23 @@
require_relative "../../lib/obsws"
require "yaml"
class Main
def conn_from_yaml
YAML.load_file("obs.yml", symbolize_names: true)[:connection]
end
def run
OBSWS::Requests::Client.new(**conn_from_yaml).run do |client|
resp = client.get_scene_list
resp.scenes.reverse_each do |scene|
puts "Switching to scene #{scene[:sceneName]}"
client.set_current_program_scene(scene[:sceneName])
sleep(0.5)
end
end
end
end
Main.new.run if $PROGRAM_NAME == __FILE__
+35 -4
View File
@@ -1,11 +1,42 @@
require "digest/sha2"
require "json"
require "waitutil"
require "socket"
require "websocket/driver"
require "logger"
require_relative "obsws/logger"
require_relative "obsws/driver"
require_relative "obsws/util"
require_relative "obsws/mixin"
require_relative "obsws/base"
require_relative "obsws/req"
require_relative "obsws/event"
module OBSWS
include Logger::Severity
require_relative "obsws/version"
LOGGER = Logger.new(STDOUT)
LOGGER.level = WARN
module OBSWS
class OBSWSError < StandardError; end
class OBSWSConnectionError < OBSWSError; end
class OBSWSRequestError < OBSWSError
attr_reader :req_name, :code
def initialize(req_name, code, msg)
@req_name = req_name
@code = code
@msg = msg
super(message)
end
def message
msg = [
"Request #{@req_name} returned code #{@code}."
]
msg << "With message: #{@msg}" if @msg
msg.join(" ")
end
end
end
+22 -68
View File
@@ -1,72 +1,27 @@
require "socket"
require "websocket/driver"
require "digest/sha2"
require "json"
require "observer"
require "waitutil"
require_relative "mixin"
require_relative "error"
module OBSWS
class Socket
attr_reader :url
def initialize(url, socket)
@url = url
@socket = socket
end
def write(s)
@socket.write(s)
end
end
class Base
include Observable
include Logging
include Driver::Director
include Mixin::OPCodes
attr_reader :id, :driver, :closed
attr_reader :closed
attr_writer :updater
def initialize(**kwargs)
host = kwargs[:host] || "localhost"
port = kwargs[:port] || 4455
@password = kwargs[:password] || ""
@subs = kwargs[:subs] || 0
@socket = TCPSocket.new(host, port)
@driver =
WebSocket::Driver.client(Socket.new("ws://#{host}:#{port}", @socket))
@driver.on :open do |msg|
LOGGER.debug("driver socket open")
end
@driver.on :close do |msg|
LOGGER.debug("driver socket closed")
@closed = true
end
@driver.on :message do |msg|
LOGGER.debug("received [#{msg}] passing to handler")
msg_handler(JSON.parse(msg.data, symbolize_names: true))
end
start_driver
@connect_timeout = kwargs[:connect_timeout] || 3
setup_driver(host, port) and start_driver
WaitUtil.wait_for_condition(
"waiting successful identification",
"successful identification",
delay_sec: 0.01,
timeout_sec: 3
timeout_sec: @connect_timeout
) { @identified }
end
def start_driver
Thread.new do
@driver.start
loop do
@driver.parse(@socket.readpartial(4096))
rescue EOFError
break
end
end
end
private
def auth_token(salt:, challenge:)
Digest::SHA256.base64digest(
@@ -74,7 +29,7 @@ module OBSWS
)
end
def authenticate(auth = nil)
def identify(auth)
payload = {
op: Mixin::OPCodes::IDENTIFY,
d: {
@@ -82,29 +37,28 @@ module OBSWS
eventSubscriptions: @subs
}
}
payload[:d][:authentication] = auth_token(**auth) if auth
if auth
if @password.empty?
raise OBSWSError("auth enabled but no password provided")
end
logger.info("initiating authentication")
payload[:d][:authentication] = auth_token(**auth)
end
@driver.text(JSON.generate(payload))
end
def msg_handler(data)
case data[:op]
when Mixin::OPCodes::HELLO
if data[:d].key? :authentication
LOGGER.debug("initiating authentication")
else
LOGGER.debug("authentication disabled... skipping.")
end
authenticate(data[:d][:authentication])
identify(data[:d][:authentication])
when Mixin::OPCodes::IDENTIFIED
LOGGER.debug("client succesfully identified with server")
@identified = true
when Mixin::OPCodes::EVENT, Mixin::OPCodes::REQUESTRESPONSE
changed
notify_observers(data[:op], data[:d])
@updater.call(data[:op], data[:d])
end
end
def req(id, type_, data = nil)
public def req(id, type_, data = nil)
payload = {
op: Mixin::OPCodes::REQUEST,
d: {
@@ -113,8 +67,8 @@ module OBSWS
}
}
payload[:d][:requestData] = data if data
queued = @driver.text(JSON.generate(payload))
LOGGER.debug("request with id #{id} queued? #{queued}")
logger.debug("sending request: #{payload}")
@driver.text(JSON.generate(payload))
end
end
end
+50
View File
@@ -0,0 +1,50 @@
module OBSWS
module Driver
class Socket
attr_reader :url
def initialize(url, socket)
@url = url
@socket = socket
end
def write(s)
@socket.write(s)
end
end
module Director
def setup_driver(host, port)
@socket = TCPSocket.new(host, port)
@driver =
WebSocket::Driver.client(Socket.new("ws://#{host}:#{port}", @socket))
@driver.on :open do |msg|
logger.debug("driver socket open")
end
@driver.on :close do |msg|
logger.debug("driver socket closed")
@closed = true
end
@driver.on :message do |msg|
msg_handler(JSON.parse(msg.data, symbolize_names: true))
end
end
private def start_driver
Thread.new do
@driver.start
loop do
@driver.parse(@socket.readpartial(4096))
rescue EOFError
break
end
end
end
public def stop_driver
@driver.close
end
end
end
end
-6
View File
@@ -1,6 +0,0 @@
module OBSWS
module Error
class OBSWSError < StandardError
end
end
end
+54 -51
View File
@@ -1,91 +1,94 @@
require "json"
require_relative "util"
require_relative "mixin"
module OBSWS
module Events
module SUBS
NONE = 0
GENERAL = (1 << 0)
CONFIG = (1 << 1)
SCENES = (1 << 2)
INPUTS = (1 << 3)
TRANSITIONS = (1 << 4)
FILTERS = (1 << 5)
OUTPUTS = (1 << 6)
SCENEITEMS = (1 << 7)
MEDIAINPUTS = (1 << 8)
VENDORS = (1 << 9)
UI = (1 << 10)
GENERAL = 1 << 0
CONFIG = 1 << 1
SCENES = 1 << 2
INPUTS = 1 << 3
TRANSITIONS = 1 << 4
FILTERS = 1 << 5
OUTPUTS = 1 << 6
SCENEITEMS = 1 << 7
MEDIAINPUTS = 1 << 8
VENDORS = 1 << 9
UI = 1 << 10
def low_volume
GENERAL | CONFIG | SCENES | INPUTS | TRANSITIONS | FILTERS | OUTPUTS |
LOW_VOLUME = GENERAL | CONFIG | SCENES | INPUTS | TRANSITIONS | FILTERS | OUTPUTS |
SCENEITEMS | MEDIAINPUTS | VENDORS | UI
end
INPUTVOLUMEMETERS = (1 << 16)
INPUTACTIVESTATECHANGED = (1 << 17)
INPUTSHOWSTATECHANGED = (1 << 18)
SCENEITEMTRANSFORMCHANGED = (1 << 19)
INPUTVOLUMEMETERS = 1 << 16
INPUTACTIVESTATECHANGED = 1 << 17
INPUTSHOWSTATECHANGED = 1 << 18
SCENEITEMTRANSFORMCHANGED = 1 << 19
def high_volume
INPUTVOLUMEMETERS | INPUTACTIVESTATECHANGED | INPUTSHOWSTATECHANGED |
HIGH_VOLUME = INPUTVOLUMEMETERS | INPUTACTIVESTATECHANGED | INPUTSHOWSTATECHANGED |
SCENEITEMTRANSFORMCHANGED
end
def all
low_volume | high_volume
end
module_function :low_volume, :high_volume, :all
ALL = LOW_VOLUME | HIGH_VOLUME
end
module Callbacks
include Util
include Util::String
def observers
@observers ||= []
end
def add_observer(observer)
@observers = [] unless defined?(@observers)
observer = [observer] if !observer.respond_to? :each
observer.each { |o| @observers.append(o) }
observer = [observer] unless observer.respond_to? :each
observer.each { |o| observers << o unless observers.include? o }
end
def remove_observer(observer)
@observers.delete(observer)
observer = [observer] unless observer.respond_to? :each
observers.reject! { |o| observer.include? o }
end
def notify_observers(event, data)
if defined?(@observers)
@observers.each do |o|
if o.respond_to? "on_#{event.to_snake}"
if data.empty?
o.send("on_#{event.to_snake}")
else
o.send("on_#{event.to_snake}", data)
private def notify_observers(event, data)
observers.each do |o|
if o.is_a? Method
if o.name.to_s == "on_#{snakecase(event)}"
data.empty? ? o.call : o.call(data)
end
end
elsif o.respond_to? "on_#{snakecase(event)}"
data.empty? ? o.send("on_#{snakecase(event)}") : o.send("on_#{snakecase(event)}", data)
end
end
end
alias_method :callbacks, :observers
alias_method :register, :add_observer
alias_method :deregister, :remove_observer
end
class Client
include Logging
include Callbacks
include Mixin::TearDown
include Mixin::OPCodes
def initialize(**kwargs)
kwargs[:subs] = SUBS.low_volume
kwargs[:subs] ||= SUBS::LOW_VOLUME
@base_client = Base.new(**kwargs)
@base_client.add_observer(self)
end
def update(op_code, data)
logger.info("#{self} successfully identified with server")
rescue Errno::ECONNREFUSED, WaitUtil::TimeoutError => e
msg = "#{e.class.name}: #{e.message}"
logger.error(msg)
raise OBSWSConnectionError.new(msg)
else
@base_client.updater = ->(op_code, data) {
if op_code == Mixin::OPCodes::EVENT
logger.debug("received: #{data}")
event = data[:eventType]
data = data.key?(:eventData) ? data[:eventData] : {}
data = data.fetch(:eventData, {})
notify_observers(event, Mixin::Data.new(data, data.keys))
end
}
end
def to_s
self.class.name.split("::").last(2).join("::")
end
end
end
+9
View File
@@ -0,0 +1,9 @@
module OBSWS
module Logging
def logger
@logger ||= Logger.new($stdout, level: ENV.fetch("OBSWS_LOG_LEVEL", "WARN"))
@logger.progname = instance_of?(::Module) ? name : self.class.name
@logger
end
end
end
+12 -12
View File
@@ -1,13 +1,11 @@
require_relative "util"
module OBSWS
module Mixin
module Meta
include Util
include Util::String
def make_field_methods(*params)
params.each do |param|
define_singleton_method(param.to_s.to_snake) { @resp[param] }
define_singleton_method(snakecase(param.to_s)) { @resp[param] }
end
end
end
@@ -18,24 +16,26 @@ module OBSWS
def initialize(resp, fields)
@resp = resp
@fields = fields
self.make_field_methods *fields
make_field_methods(*fields)
end
def empty? = @fields.empty?
def attrs = @fields.map { |f| f.to_s.to_snake }
def attrs = @fields.map { |f| snakecase(f.to_s) }
end
class Response < MetaObject
end
class Response < MetaObject; end
# Represents a request response object
class Data < MetaObject
end
class Data < MetaObject; end
# Represents an event data object
module TearDown
def close
@base_client.driver.close
def stop_driver
@base_client.stop_driver
end
alias_method :close, :stop_driver
end
module OPCodes
+169 -166
View File
@@ -1,88 +1,88 @@
require "waitutil"
require_relative "base"
require_relative "error"
require_relative "util"
require_relative "mixin"
module OBSWS
module Requests
class Client
include Error
include Logging
include Mixin::TearDown
include Mixin::OPCodes
def initialize(**kwargs)
@base_client = Base.new(**kwargs)
@base_client.add_observer(self)
logger.info("#{self} successfully identified with server")
rescue Errno::ECONNREFUSED, WaitUtil::TimeoutError => e
logger.error("#{e.class.name}: #{e.message}")
raise OBSWSConnectionError.new(e.message)
else
@base_client.updater = ->(op_code, data) {
logger.debug("response received: #{data}")
@response = data if op_code == Mixin::OPCodes::REQUESTRESPONSE
}
@response = {requestId: 0}
end
def to_s
self.class.name.split("::").last(2).join("::")
end
def run
yield
yield(self)
ensure
close
stop_driver
WaitUtil.wait_for_condition(
"driver has closed",
"driver to close",
delay_sec: 0.01,
timeout_sec: 1
) { @base_client.closed }
end
def update(op_code, data)
@response = data if op_code == Mixin::OPCodes::REQUESTRESPONSE
end
def call(req, data = nil)
id = rand(1..1000)
@base_client.req(id, req, data)
WaitUtil.wait_for_condition(
"reponse id matches request id",
"reponse id to match request id",
delay_sec: 0.001,
timeout_sec: 3
) { @response[:requestId] == id }
if !@response[:requestStatus][:result]
error = [
"Request #{@response[:requestType]} returned code #{@response[:requestStatus][:code]}"
]
if @response[:requestStatus].key?(:comment)
error += ["With message: #{@response[:requestStatus][:comment]}"]
end
raise OBSWSError.new(error.join("\n"))
unless @response[:requestStatus][:result]
raise OBSWSRequestError.new(@response[:requestType], @response[:requestStatus][:code], @response[:requestStatus][:comment])
end
@response[:responseData]
rescue WaitUtil::TimeoutError
msg = "no response with matching id received"
LOGGER.error(msg)
raise OBSWSError.new(msg)
rescue OBSWSRequestError => e
logger.error(["#{e.class.name}: #{e.message}", *e.backtrace].join("\n"))
raise
rescue WaitUtil::TimeoutError => e
logger.error(["#{e.class.name}: #{e.message}", *e.backtrace].join("\n"))
raise OBSWSError.new([e.message, *e.backtrace].join("\n"))
end
def get_version
resp = call("GetVersion")
resp = call(:GetVersion)
Mixin::Response.new(resp, resp.keys)
end
def get_stats
resp = call("GetStats")
resp = call(:GetStats)
Mixin::Response.new(resp, resp.keys)
end
def broadcast_custom_event(data)
call("BroadcastCustomEvent", data)
call(:BroadcastCustomEvent, data)
end
def call_vendor_request(name, type_, data = nil)
call(requestType, requestData)
def call_vendor_request(vendor_name, request_type, data = nil)
payload = {vendorName: vendor_name, requestType: request_type}
payload[:requestData] = data if data
resp = call(:CallVendorRequest, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_hotkey_list
resp = call("GetHotkeyList")
resp = call(:GetHotkeyList)
Mixin::Response.new(resp, resp.keys)
end
def trigger_hotkey_by_name(name)
payload = {hotkeyName: name}
call("TriggerHotkeyByName", payload)
call(:TriggerHotkeyByName, payload)
end
def trigger_hotkey_by_key_sequence(
@@ -101,63 +101,63 @@ module OBSWS
cmd: press_cmd
}
}
call("TriggerHotkeyByKeySequence", payload)
call(:TriggerHotkeyByKeySequence, payload)
end
def sleep(sleep_millis = nil, sleep_frames = nil)
payload = {sleepMillis: sleep_millis, sleepFrames: sleep_frames}
call("Sleep", payload)
call(:Sleep, payload)
end
def get_persistent_data(realm, slot_name)
payload = {realm: realm, slotName: slot_name}
resp = call("GetPersistentData", payload)
resp = call(:GetPersistentData, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_persistent_data(realm, slot_name, slot_value)
payload = {realm: realm, slotName: slot_name, slotValue: slot_value}
call("SetPersistentData", payload)
call(:SetPersistentData, payload)
end
def get_scene_collection_list
resp = call("GetSceneCollectionList")
resp = call(:GetSceneCollectionList)
Mixin::Response.new(resp, resp.keys)
end
def set_current_scene_collection(name)
payload = {sceneCollectionName: name}
call("SetCurrentSceneCollection", payload)
call(:SetCurrentSceneCollection, payload)
end
def create_scene_collection(name)
payload = {sceneCollectionName: name}
call("CreateSceneCollection", payload)
call(:CreateSceneCollection, payload)
end
def get_profile_list
resp = call("GetProfileList")
resp = call(:GetProfileList)
Mixin::Response.new(resp, resp.keys)
end
def set_current_profile(name)
payload = {profileName: name}
call("SetCurrentProfile", payload)
call(:SetCurrentProfile, payload)
end
def create_profile(name)
payload = {profileName: name}
call("CreateProfile", payload)
call(:CreateProfile, payload)
end
def remove_profile(name)
payload = {profileName: name}
call("RemoveProfile", payload)
call(:RemoveProfile, payload)
end
def get_profile_parameter(category, name)
payload = {parameterCategory: category, parameterName: name}
resp = call("GetProfileParameter", payload)
resp = call(:GetProfileParameter, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -167,11 +167,11 @@ module OBSWS
parameterName: name,
parameterValue: value
}
call("SetProfileParameter", payload)
call(:SetProfileParameter, payload)
end
def get_video_settings
resp = call("GetVideoSettings")
resp = call(:GetVideoSettings)
Mixin::Response.new(resp, resp.keys)
end
@@ -191,11 +191,11 @@ module OBSWS
outputWidth: out_width,
outputHeight: out_height
}
call("SetVideoSettings", payload)
call(:SetVideoSettings, payload)
end
def get_stream_service_settings
resp = call("GetStreamServiceSettings")
resp = call(:GetStreamServiceSettings)
Mixin::Response.new(resp, resp.keys)
end
@@ -204,17 +204,17 @@ module OBSWS
streamServiceType: ss_type,
streamServiceSettings: ss_settings
}
call("SetStreamServiceSettings", payload)
call(:SetStreamServiceSettings, payload)
end
def get_record_directory
resp = call("GetRecordDirectory")
resp = call(:GetRecordDirectory)
Mixin::Response.new(resp, resp.keys)
end
def get_source_active(name)
payload = {sourceName: name}
resp = call("GetSourceActive", payload)
resp = call(:GetSourceActive, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -226,7 +226,7 @@ module OBSWS
imageHeight: height,
imageCompressionQuality: quality
}
resp = call("GetSourceScreenshot", payload)
resp = call(:GetSourceScreenshot, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -246,58 +246,58 @@ module OBSWS
imageHeight: height,
imageCompressionQuality: quality
}
resp = call("SaveSourceScreenshot", payload)
resp = call(:SaveSourceScreenshot, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_scene_list
resp = call("GetSceneList")
resp = call(:GetSceneList)
Mixin::Response.new(resp, resp.keys)
end
def get_group_list
resp = call("GetSceneList")
resp = call(:GetGroupList)
Mixin::Response.new(resp, resp.keys)
end
def get_current_program_scene
resp = call("GetCurrentProgramScene")
resp = call(:GetCurrentProgramScene)
Mixin::Response.new(resp, resp.keys)
end
def set_current_program_scene(name)
payload = {sceneName: name}
call("SetCurrentProgramScene", payload)
call(:SetCurrentProgramScene, payload)
end
def get_current_preview_scene
resp = call("GetCurrentPreviewScene")
resp = call(:GetCurrentPreviewScene)
Mixin::Response.new(resp, resp.keys)
end
def set_current_preview_scene(name)
payload = {sceneName: name}
call("SetCurrentPreviewScene", payload)
call(:SetCurrentPreviewScene, payload)
end
def create_scene(name)
payload = {sceneName: name}
call("CreateScene", payload)
call(:CreateScene, payload)
end
def remove_scene(name)
payload = {sceneName: name}
call("RemoveScene", payload)
call(:RemoveScene, payload)
end
def set_scene_name(old_name, new_name)
payload = {sceneName: old_name, newSceneName: new_name}
call("SetSceneName", payload)
call(:SetSceneName, payload)
end
def get_scene_scene_transition_override(name)
payload = {sceneName: name}
resp = call("GetSceneSceneTransitionOverride", payload)
resp = call(:GetSceneSceneTransitionOverride, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -307,23 +307,24 @@ module OBSWS
transitionName: tr_name,
transitionDuration: tr_duration
}
call("SetSceneSceneTransitionOverride", payload)
call(:SetSceneSceneTransitionOverride, payload)
end
def get_input_list(kind = nil)
payload = {inputKind: kind}
resp = call("GetInputList", payload)
resp = call(:GetInputList, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_input_kind_list(unversioned)
payload = {unversioned: unversioned}
resp = call("GetInputKindList", payload)
resp = call(:GetInputKindList, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_special_inputs
resp = call("GetSpecialInputs")
resp = call(:GetSpecialInputs)
Mixin::Response.new(resp, resp.keys)
end
def create_input(
@@ -340,56 +341,57 @@ module OBSWS
inputSettings: input_settings,
sceneItemEnabled: scene_item_enabled
}
resp = call("CreateInput", payload)
resp = call(:CreateInput, payload)
Mixin::Response.new(resp, resp.keys)
end
def remove_input(name)
payload = {inputName: name}
call("RemoveInput", payload)
call(:RemoveInput, payload)
end
def set_input_name(old_name, new_name)
payload = {inputName: old_name, newInputName: new_name}
call("SetInputName", payload)
call(:SetInputName, payload)
end
def get_input_default_settings(kind)
payload = {inputKind: kind}
resp = call("GetInputDefaultSettings", payload)
resp = call(:GetInputDefaultSettings, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_input_settings(name)
payload = {inputName: name}
resp = call("GetInputSettings", payload)
resp = call(:GetInputSettings, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_input_settings(name, settings, overlay)
payload = {inputName: name, inputSettings: settings, overlay: overlay}
call("SetInputSettings", payload)
call(:SetInputSettings, payload)
end
def get_input_mute(name)
payload = {inputName: name}
resp = call("GetInputMute", payload)
resp = call(:GetInputMute, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_input_mute(name, muted)
payload = {inputName: name, inputMuted: muted}
call("SetInputMute", payload)
call(:SetInputMute, payload)
end
def toggle_input_mute(name)
payload = {inputName: name}
resp = call("ToggleInputMute", payload)
resp = call(:ToggleInputMute, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_input_volume(name)
payload = {inputName: name}
resp = call("GetInputVolume", payload)
resp = call(:GetInputVolume, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -399,117 +401,117 @@ module OBSWS
inputVolumeMul: vol_mul,
inputVolumeDb: vol_db
}
call("SetInputVolume", payload)
call(:SetInputVolume, payload)
end
def get_input_audio_balance(name)
payload = {inputName: name}
resp = call("GetInputAudioBalance", payload)
resp = call(:GetInputAudioBalance, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_input_audio_balance(name, balance)
payload = {inputName: name, inputAudioBalance: balance}
call("SetInputAudioBalance", payload)
call(:SetInputAudioBalance, payload)
end
def get_input_audio_sync_offset(name)
payload = {inputName: name}
resp = call("GetInputAudioSyncOffset", payload)
resp = call(:GetInputAudioSyncOffset, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_input_audio_sync_offset(name, offset)
payload = {inputName: name, inputAudioSyncOffset: offset}
call("SetInputAudioSyncOffset", payload)
call(:SetInputAudioSyncOffset, payload)
end
def get_input_audio_monitor_type(name)
payload = {inputName: name}
resp = call("GetInputAudioMonitorType", payload)
resp = call(:GetInputAudioMonitorType, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_input_audio_monitor_type(name, mon_type)
payload = {inputName: name, monitorType: mon_type}
call("SetInputAudioMonitorType", payload)
call(:SetInputAudioMonitorType, payload)
end
def get_input_audio_tracks(name)
payload = {inputName: name}
resp = call("GetInputAudioTracks", payload)
resp = call(:GetInputAudioTracks, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_input_audio_tracks(name, track)
payload = {inputName: name, inputAudioTracks: track}
call("SetInputAudioTracks", payload)
call(:SetInputAudioTracks, payload)
end
def get_input_properties_list_property_items(input_name, prop_name)
payload = {inputName: input_name, propertyName: prop_name}
resp = call("GetInputPropertiesListPropertyItems", payload)
resp = call(:GetInputPropertiesListPropertyItems, payload)
Mixin::Response.new(resp, resp.keys)
end
def press_input_properties_button(input_name, prop_name)
payload = {inputName: input_name, propertyName: prop_name}
call("PressInputPropertiesButton", payload)
call(:PressInputPropertiesButton, payload)
end
def get_transition_kind_list
resp = call("GetTransitionKindList")
resp = call(:GetTransitionKindList)
Mixin::Response.new(resp, resp.keys)
end
def get_scene_transition_list
resp = call("GetSceneTransitionList")
resp = call(:GetSceneTransitionList)
Mixin::Response.new(resp, resp.keys)
end
def get_current_scene_transition
resp = call("GetCurrentSceneTransition")
resp = call(:GetCurrentSceneTransition)
Mixin::Response.new(resp, resp.keys)
end
def set_current_scene_transition(name)
payload = {transitionName: name}
call("SetCurrentSceneTransition", payload)
call(:SetCurrentSceneTransition, payload)
end
def set_current_scene_transition_duration(duration)
payload = {transitionDuration: duration}
call("SetCurrentSceneTransitionDuration", payload)
call(:SetCurrentSceneTransitionDuration, payload)
end
def set_current_scene_transition_settings(settings, overlay = nil)
payload = {transitionSettings: settings, overlay: overlay}
call("SetCurrentSceneTransitionSettings", payload)
call(:SetCurrentSceneTransitionSettings, payload)
end
def get_current_scene_transition_cursor
resp = call("GetCurrentSceneTransitionCursor")
resp = call(:GetCurrentSceneTransitionCursor)
Mixin::Response.new(resp, resp.keys)
end
def trigger_studio_mode_transition
call("TriggerStudioModeTransition")
call(:TriggerStudioModeTransition)
end
def set_t_bar_position(pos, release = nil)
payload = {position: pos, release: release}
call("SetTBarPosition", payload)
call(:SetTBarPosition, payload)
end
def get_source_filter_list(name)
payload = {sourceName: name}
resp = call("GetSourceFilterList", payload)
resp = call(:GetSourceFilterList, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_source_filter_default_settings(kind)
payload = {filterKind: kind}
resp = call("GetSourceFilterDefaultSettings", payload)
resp = call(:GetSourceFilterDefaultSettings, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -525,12 +527,12 @@ module OBSWS
filterKind: filter_kind,
filterSettings: filter_settings
}
call("CreateSourceFilter", payload)
call(:CreateSourceFilter, payload)
end
def remove_source_filter(source_name, filter_name)
payload = {sourceName: source_name, filterName: filter_name}
call("RemoveSourceFilter", payload)
call(:RemoveSourceFilter, payload)
end
def set_source_filter_name(source_name, old_filter_name, new_filter_name)
@@ -539,12 +541,12 @@ module OBSWS
filterName: old_filter_name,
newFilterName: new_filter_name
}
call("SetSourceFilterName", payload)
call(:SetSourceFilterName, payload)
end
def get_source_filter(source_name, filter_name)
payload = {sourceName: source_name, filterName: filter_name}
resp = call("GetSourceFilter", payload)
resp = call(:GetSourceFilter, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -554,7 +556,7 @@ module OBSWS
filterName: filter_name,
filterIndex: filter_index
}
call("SetSourceFilterIndex", payload)
call(:SetSourceFilterIndex, payload)
end
def set_source_filter_settings(
@@ -569,7 +571,7 @@ module OBSWS
filterSettings: settings,
overlay: overlay
}
call("SetSourceFilterSettings", payload)
call(:SetSourceFilterSettings, payload)
end
def set_source_filter_enabled(source_name, filter_name, enabled)
@@ -578,18 +580,18 @@ module OBSWS
filterName: filter_name,
filterEnabled: enabled
}
call("SetSourceFilterEnabled", payload)
call(:SetSourceFilterEnabled, payload)
end
def get_scene_item_list(name)
payload = {sceneName: name}
resp = call("GetSceneItemList", payload)
resp = call(:GetSceneItemList, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_group_scene_item_list(name)
payload = {sceneName: name}
resp = call("GetGroupSceneItemList", payload)
resp = call(:GetGroupSceneItemList, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -599,7 +601,7 @@ module OBSWS
sourceName: source_name,
searchOffset: offset
}
resp = call("GetSceneItemId", payload)
resp = call(:GetSceneItemId, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -609,13 +611,13 @@ module OBSWS
sourceName: source_name,
sceneItemEnabled: enabled
}
resp = call("CreateSceneItem", payload)
resp = call(:CreateSceneItem, payload)
Mixin::Response.new(resp, resp.keys)
end
def remove_scene_item(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id}
call("RemoveSceneItem", payload)
call(:RemoveSceneItem, payload)
end
def duplicate_scene_item(scene_name, item_id, dest_scene_name = nil)
@@ -624,13 +626,13 @@ module OBSWS
sceneItemId: item_id,
destinationSceneName: dest_scene_name
}
resp = call("DuplicateSceneItem", payload)
resp = call(:DuplicateSceneItem, payload)
Mixin::Response.new(resp, resp.keys)
end
def get_scene_item_transform(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemTransform", payload)
resp = call(:GetSceneItemTransform, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -640,12 +642,12 @@ module OBSWS
sceneItemId: item_id,
sceneItemTransform: transform
}
call("SetSceneItemTransform", payload)
call(:SetSceneItemTransform, payload)
end
def get_scene_item_enabled(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemEnabled", payload)
resp = call(:GetSceneItemEnabled, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -655,12 +657,12 @@ module OBSWS
sceneItemId: item_id,
sceneItemEnabled: enabled
}
call("SetSceneItemEnabled", payload)
call(:SetSceneItemEnabled, payload)
end
def get_scene_item_locked(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemLocked", payload)
resp = call(:GetSceneItemLocked, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -670,12 +672,12 @@ module OBSWS
sceneItemId: item_id,
sceneItemLocked: locked
}
call("SetSceneItemLocked", payload)
call(:SetSceneItemLocked, payload)
end
def get_scene_item_index(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemIndex", payload)
resp = call(:GetSceneItemIndex, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -683,14 +685,14 @@ module OBSWS
payload = {
sceneName: scene_name,
sceneItemId: item_id,
sceneItemLocked: item_index
sceneItemIndex: item_index
}
call("SetSceneItemIndex", payload)
call(:SetSceneItemIndex, payload)
end
def get_scene_item_blend_mode(scene_name, item_id)
payload = {sceneName: scene_name, sceneItemId: item_id}
resp = call("GetSceneItemBlendMode", payload)
resp = call(:GetSceneItemBlendMode, payload)
Mixin::Response.new(resp, resp.keys)
end
@@ -700,191 +702,192 @@ module OBSWS
sceneItemId: item_id,
sceneItemBlendMode: blend
}
call("SetSceneItemBlendMode", payload)
call(:SetSceneItemBlendMode, payload)
end
def get_virtual_cam_status
resp = call("GetVirtualCamStatus")
resp = call(:GetVirtualCamStatus)
Mixin::Response.new(resp, resp.keys)
end
def toggle_virtual_cam
resp = call("ToggleVirtualCam")
resp = call(:ToggleVirtualCam)
Mixin::Response.new(resp, resp.keys)
end
def start_virtual_cam
call("StartVirtualCam")
call(:StartVirtualCam)
end
def stop_virtual_cam
call("StopVirtualCam")
call(:StopVirtualCam)
end
def get_replay_buffer_status
resp = call("GetReplayBufferStatus")
resp = call(:GetReplayBufferStatus)
Mixin::Response.new(resp, resp.keys)
end
def toggle_replay_buffer
resp = call("ToggleReplayBuffer")
resp = call(:ToggleReplayBuffer)
Mixin::Response.new(resp, resp.keys)
end
def start_replay_buffer
call("StartReplayBuffer")
call(:StartReplayBuffer)
end
def stop_replay_buffer
call("StopReplayBuffer")
call(:StopReplayBuffer)
end
def save_replay_buffer
call("SaveReplayBuffer")
call(:SaveReplayBuffer)
end
def get_last_replay_buffer_replay
resp = call("GetLastReplayBufferReplay")
resp = call(:GetLastReplayBufferReplay)
Mixin::Response.new(resp, resp.keys)
end
def get_output_list
resp = call("GetOutputList")
resp = call(:GetOutputList)
Mixin::Response.new(resp, resp.keys)
end
def get_output_status(name)
payload = {outputName: name}
resp = call("GetOutputStatus", payload)
resp = call(:GetOutputStatus, payload)
Mixin::Response.new(resp, resp.keys)
end
def toggle_output(name)
payload = {outputName: name}
resp = call("ToggleOutput", payload)
resp = call(:ToggleOutput, payload)
Mixin::Response.new(resp, resp.keys)
end
def start_output(name)
payload = {outputName: name}
call("StartOutput", payload)
call(:StartOutput, payload)
end
def stop_output(name)
payload = {outputName: name}
call("StopOutput", payload)
call(:StopOutput, payload)
end
def get_output_settings(name)
payload = {outputName: name}
resp = call("GetOutputSettings", payload)
resp = call(:GetOutputSettings, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_output_settings(name, settings)
payload = {outputName: name, outputSettings: settings}
call("SetOutputSettings", payload)
call(:SetOutputSettings, payload)
end
def get_stream_status
resp = call("GetStreamStatus")
resp = call(:GetStreamStatus)
Mixin::Response.new(resp, resp.keys)
end
def toggle_stream
resp = call("ToggleStream")
resp = call(:ToggleStream)
Mixin::Response.new(resp, resp.keys)
end
def start_stream
call("StartStream")
call(:StartStream)
end
def stop_stream
call("StopStream")
call(:StopStream)
end
def send_stream_caption(caption)
call("SendStreamCaption")
call(:SendStreamCaption)
end
def get_record_status
resp = call("GetRecordStatus")
resp = call(:GetRecordStatus)
Mixin::Response.new(resp, resp.keys)
end
def toggle_record
call("ToggleRecord")
call(:ToggleRecord)
end
def start_record
call("StartRecord")
call(:StartRecord)
end
def stop_record
resp = call("StopRecord")
resp = call(:StopRecord)
Mixin::Response.new(resp, resp.keys)
end
def toggle_record_pause
call("ToggleRecordPause")
call(:ToggleRecordPause)
end
def pause_record
call("PauseRecord")
call(:PauseRecord)
end
def resume_record
call("ResumeRecord")
call(:ResumeRecord)
end
def get_media_input_status(name)
payload = {inputName: name}
resp = call("GetMediaInputStatus", payload)
resp = call(:GetMediaInputStatus, payload)
Mixin::Response.new(resp, resp.keys)
end
def set_media_input_cursor(name, cursor)
payload = {inputName: name, mediaCursor: cursor}
call("SetMediaInputCursor", payload)
call(:SetMediaInputCursor, payload)
end
def offset_media_input_cursor(name, offset)
payload = {inputName: name, mediaCursorOffset: offset}
call("OffsetMediaInputCursor", payload)
call(:OffsetMediaInputCursor, payload)
end
def trigger_media_input_action(name, action)
payload = {inputName: name, mediaAction: action}
call("TriggerMediaInputAction", payload)
call(:TriggerMediaInputAction, payload)
end
def get_studio_mode_enabled
resp = call("GetStudioModeEnabled")
resp = call(:GetStudioModeEnabled)
Mixin::Response.new(resp, resp.keys)
end
def set_studio_mode_enabled(enabled)
payload = {studioModeEnabled: enabled}
call("SetStudioModeEnabled", payload)
call(:SetStudioModeEnabled, payload)
end
def open_input_properties_dialog(name)
payload = {inputName: name}
call("OpenInputPropertiesDialog", payload)
call(:OpenInputPropertiesDialog, payload)
end
def open_input_filters_dialog(name)
payload = {inputName: name}
call("OpenInputFiltersDialog", payload)
call(:OpenInputFiltersDialog, payload)
end
def open_input_interact_dialog(name)
payload = {inputName: name}
call("OpenInputInteractDialog", payload)
call(:OpenInputInteractDialog, payload)
end
def get_monitor_list
resp = call("GetMonitorList")
resp = call(:GetMonitorList)
Mixin::Response.new(resp, resp.keys)
end
end
+5 -5
View File
@@ -1,12 +1,12 @@
module OBSWS
module Util
class ::String
def to_camel
self.split(/_/).map(&:capitalize).join
module String
def camelcase(s)
s.split("_").map(&:capitalize).join
end
def to_snake
self
def snakecase(s)
s
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.downcase
+3 -3
View File
@@ -1,5 +1,5 @@
module OBSWS
module Version
module VERSION
module_function
def major
@@ -7,11 +7,11 @@ module OBSWS
end
def minor
0
5
end
def patch
3
4
end
def to_a
+7 -8
View File
@@ -1,17 +1,16 @@
require_relative "lib/obsws"
require "obsws"
def main
r_client =
class Main
def run
OBSWS::Requests::Client.new(
host: "localhost",
port: 4455,
password: "strongpassword"
)
r_client.run do
).run do |client|
# Toggle the mute state of your Mic input
r_client.toggle_input_mute("Mic/Aux")
client.toggle_input_mute("Mic/Aux")
end
end
end
main if $0 == __FILE__
Main.new.run if $PROGRAM_NAME == __FILE__
+4 -5
View File
@@ -1,10 +1,10 @@
# frozen_string_literal: true
require File.expand_path("lib/obsws/version", __dir__)
lib = File.expand_path("./lib")
Gem::Specification.new do |spec|
spec.name = "obsws"
spec.version = OBSWS::Version
spec.version = OBSWS::VERSION
spec.summary = "OBS Websocket v5 wrapper"
spec.description = "A Ruby wrapper around OBS Websocket v5"
spec.authors = ["onyx_online"]
@@ -13,14 +13,13 @@ Gem::Specification.new do |spec|
spec.extra_rdoc_files = Dir["README.md", "CHANGELOG.md", "LICENSE"]
spec.homepage = "https://rubygems.org/gems/obsws"
spec.license = "MIT"
spec.add_runtime_dependency "observer", "~> 0.1.1"
spec.add_runtime_dependency "websocket-driver", "~> 0.7.5"
spec.add_runtime_dependency "waitutil", "~> 0.2.1"
spec.add_development_dependency "perfect_toml", "~> 0.9.0"
spec.add_development_dependency "standard", "~> 1.30"
spec.add_development_dependency "minitest", "~> 5.16", ">= 5.16.3"
spec.add_development_dependency "rake", "~> 11.2", ">= 11.2.2"
spec.required_ruby_version = ">= 3.0"
spec.metadata = {
"source_code_uri" => "https://github.com/onyx-and-iris/obsws.git"
"source_code_uri" => "https://github.com/onyx-and-iris/obsws-ruby"
}
end
+15 -11
View File
@@ -1,17 +1,21 @@
require "minitest"
require "minitest/autorun"
require "perfect_toml"
require "yaml"
require_relative "../lib/obsws"
class OBSWSTest < Minitest::Test
def self.before_run
conn = PerfectTOML.load_file("obs.toml", symbolize_names: true)[:connection]
@@r_client = OBSWS::Requests::Client.new(**conn)
class << self
attr_reader :r_client
end
@@r_client.create_scene("START_TEST")
@@r_client.create_scene("BRB_TEST")
@@r_client.create_scene("END_TEST")
def self.before_run
conn = YAML.load_file("obs.yml", symbolize_names: true)[:connection]
@r_client = OBSWS::Requests::Client.new(**conn)
r_client.create_scene("START_TEST")
r_client.create_scene("BRB_TEST")
r_client.create_scene("END_TEST")
end
before_run
@@ -23,9 +27,9 @@ class OBSWSTest < Minitest::Test
end
Minitest.after_run do
@@r_client.remove_scene("START_TEST")
@@r_client.remove_scene("BRB_TEST")
@@r_client.remove_scene("END_TEST")
@@r_client.close
r_client.remove_scene("START_TEST")
r_client.remove_scene("BRB_TEST")
r_client.remove_scene("END_TEST")
r_client.close
end
end
-17
View File
@@ -1,17 +0,0 @@
require_relative "../minitest_helper"
class AttrsTest < OBSWSTest
def test_get_version_attrs
resp = @@r_client.get_version
assert resp.attrs ==
%w[
available_requests
obs_version
obs_web_socket_version
platform
platform_description
rpc_version
supported_image_formats
]
end
end
+17
View File
@@ -0,0 +1,17 @@
require_relative "../minitest_helper"
class AttrsTest < OBSWSTest
def test_get_version_attrs
resp = OBSWSTest.r_client.get_version
assert resp.attrs ==
%w[
available_requests
obs_version
obs_web_socket_version
platform
platform_description
rpc_version
supported_image_formats
]
end
end
+17
View File
@@ -0,0 +1,17 @@
require_relative "../minitest_helper"
class OBSWSConnectionErrorTest < Minitest::Test
def test_it_raises_an_obsws_connection_error_on_wrong_password
e = assert_raises(OBSWS::OBSWSConnectionError) { OBSWS::Requests::Client.new(host: "localhost", port: 4455, password: "wrongpassword", connect_timeout: 1).new }
assert_equal(e.message, "Timed out waiting for successful identification (1 seconds elapsed)")
end
end
class OBSWSRequestErrorTest < Minitest::Test
def test_it_raises_an_obsws_request_error_on_invalid_request
e = assert_raises(OBSWS::OBSWSRequestError) { OBSWSTest.r_client.toggle_input_mute("unknown") }
assert_equal(e.req_name, "ToggleInputMute")
assert_equal(e.code, 600)
assert_equal(e.message, "Request ToggleInputMute returned code 600. With message: No source was found by the name of `unknown`.")
end
end
@@ -2,21 +2,21 @@ require_relative "../minitest_helper"
class RequestTest < OBSWSTest
def test_it_checks_obs_major_version
resp = @@r_client.get_version
resp = OBSWSTest.r_client.get_version
ver = resp.obs_version.split(".").map(&:to_i)
assert ver[0] >= 28
end
def test_it_checks_ws_major_version
resp = @@r_client.get_version
resp = OBSWSTest.r_client.get_version
ver = resp.obs_web_socket_version.split(".").map(&:to_i)
assert ver[0] >= 5
end
def test_it_sets_and_gets_current_program_scene
%w[START_TEST BRB_TEST END_TEST].each do |s|
@@r_client.set_current_program_scene(s)
resp = @@r_client.get_current_program_scene
OBSWSTest.r_client.set_current_program_scene(s)
resp = OBSWSTest.r_client.get_current_program_scene
assert resp.current_program_scene_name == s
end
end
@@ -26,8 +26,8 @@ class RequestTest < OBSWSTest
server: "rtmp://addressofrtmpserver",
key: "live_myvery_secretkey"
}
@@r_client.set_stream_service_settings("rtmp_common", settings)
resp = @@r_client.get_stream_service_settings
OBSWSTest.r_client.set_stream_service_settings("rtmp_common", settings)
resp = OBSWSTest.r_client.get_stream_service_settings
assert resp.stream_service_type == "rtmp_common"
assert resp.stream_service_settings ==
{