Merge pull request from obs-websocket/event/changing_events

Put normal commits on this branch by accident dammit
This commit is contained in:
tt2468 2021-11-17 03:16:15 -08:00 committed by GitHub
commit 061c228ad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 198 additions and 58 deletions

View File

@ -1,6 +1,6 @@
<!--- Please fill out the following template, which will help other contributors review your Pull Request. -->
<!--- Make sure youve read the contribution guidelines here: https://github.com/Palakis/obs-websocket/blob/master/CONTRIBUTING.md -->
<!--- Make sure youve read the contribution guidelines here: https://github.com/obs-websocket/obs-websocket/blob/master/CONTRIBUTING.md -->
### Description
<!--- Describe your changes. -->
@ -27,7 +27,7 @@ Tested OS(s):
### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] I have read the [**contributing** document](https://github.com/Palakis/obs-websocket/blob/4.x-current/CONTRIBUTING.md).
- [ ] I have read the [**contributing** document](https://github.com/obs-websocket/obs-websocket/blob/4.x-current/CONTRIBUTING.md).
- [ ] My code is not on the master branch.
- [ ] The code has been tested.
- [ ] All commit messages are properly formatted and commits squashed where appropriate.

View File

@ -21,7 +21,7 @@ On Debian/Ubuntu :
```shell
sudo apt-get install libboost-all-dev
git clone --recursive https://github.com/Palakis/obs-websocket.git
git clone --recursive https://github.com/obs-websocket/obs-websocket.git
cd obs-websocket
mkdir build && cd build
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
@ -51,7 +51,7 @@ Of course, you're encouraged to dig through the contents of these scripts to
look for issues or specificities.
```shell
git clone --recursive https://github.com/Palakis/obs-websocket.git
git clone --recursive https://github.com/obs-websocket/obs-websocket.git
cd obs-websocket
./CI/install-dependencies-macos.sh
./CI/install-build-obs-macos.sh
@ -63,4 +63,4 @@ This will result in a ready-to-use `obs-websocket.pkg` installer in the `release
## Automated Builds
![Github Actions](https://github.com/Palakis/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)
![Github Actions](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)

View File

@ -108,6 +108,7 @@ set(obs-websocket_SOURCES
src/requesthandler/RequestHandler_SceneItems.cpp
src/requesthandler/RequestHandler_Stream.cpp
src/requesthandler/RequestHandler_Record.cpp
src/requesthandler/RequestHandler_MediaInputs.cpp
src/requesthandler/rpc/Request.cpp
src/requesthandler/rpc/RequestResult.cpp
src/forms/SettingsDialog.cpp

View File

@ -29,21 +29,20 @@ These are required to automatically generate the [protocol specification documen
* Favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
```cpp
if (success) {
if (success)
return RequestResult::Success();
} else {
else
return RequestResult::Error(RequestStatus::GenericError);
}
```
is better like this:
```cpp
if (!success) {
if (!success)
return RequestResult::Error(RequestStatus::GenericError);
}
return RequestResult::Success();
```
* Try to use the [built-in](https://github.com/Palakis/obs-websocket/blob/master/src/requesthandler/rpc/Request.h) request checks when possible.
* Try to use the [built-in](https://github.com/obs-websocket/obs-websocket/blob/master/src/requesthandler/rpc/Request.h) request checks when possible.
* Refer to existing requests for usage examples.
* Some example common response/request property names are:
@ -54,12 +53,14 @@ return RequestResult::Success();
* Response parameters which have no attributed data due to an invalid state should be set to `null` (versus being left out)
* For example, when `GetSceneList` is called and OBS is not in studio mode, `currentPreviewSceneName` will be `null`
* If a request's core response data depends on a state, an error should be thrown unless `ignoreNonFatalRequestChecks` is set. See `GetCurrentPreviewScene` as an example.
* If a request's core response data depends on a state, an error should be thrown. See `GetCurrentPreviewScene` as an example.
* In general, try to match the style of existing code as best as possible. We try our best to keep a consistent code style, and may suggest nitpicks as necessary.
### Commit Guidelines
* Commits follow the 50/72 standard:
* 50 characters max for the commit title (excluding scope name)
* 50 characters suggested max for the commit title (absolute maximum 72 including scope)
* One empty line after the title
* Description wrapped to 72 columns max width per line.

View File

@ -12,13 +12,13 @@
WebSockets API for OBS Studio.
[![CI Multiplatform Build](https://github.com/Palakis/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/Palakis/obs-websocket/actions/workflows/main.yml)
[![CI Multiplatform Build](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml)
[![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A)
[![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket)
## Downloads
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/obs-websocket/obs-websocket/releases) section.
### Homebrew
@ -75,7 +75,7 @@ Please join the localization project on [Crowdin](https://crowdin.com/project/ob
### Code Contributors
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
<a href="https://github.com/Palakis/obs-websocket/graphs/contributors"><img src="https://opencollective.com/obs-websocket/contributors.svg?width=890&button=false" /></a>
<a href="https://github.com/obs-websocket/obs-websocket/graphs/contributors"><img src="https://opencollective.com/obs-websocket/contributors.svg?width=890&button=false" /></a>
### Financial Contributors

View File

@ -1,7 +1,7 @@
#define MyAppName "obs-websocket"
#define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
#define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket"
#define MyAppURL "http://github.com/obs-websocket/obs-websocket"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.

View File

@ -237,18 +237,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
break;
// Config
//case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
// eventHandler->HandleCurrentSceneCollectionChanging();
// break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
eventHandler->HandleCurrentSceneCollectionChanging();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
eventHandler->HandleCurrentSceneCollectionChanged();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
eventHandler->HandleSceneCollectionListChanged();
break;
//case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
// eventHandler->HandleCurrentProfileChanging();
// break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
eventHandler->HandleCurrentProfileChanging();
break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
eventHandler->HandleCurrentProfileChanged();
break;

View File

@ -97,6 +97,12 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"PauseRecord", &RequestHandler::PauseRecord},
{"ResumeRecord", &RequestHandler::ResumeRecord},
//{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
// Media Inputs
{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},
{"SetMediaInputCursor", &RequestHandler::SetMediaInputCursor},
{"OffsetMediaInputCursor", &RequestHandler::OffsetMediaInputCursor},
{"TriggerMediaInputAction", &RequestHandler::TriggerMediaInputAction},
};
RequestHandler::RequestHandler(SessionPtr session) :

View File

@ -119,6 +119,12 @@ class RequestHandler {
RequestResult ResumeRecord(const Request&);
RequestResult GetRecordDirectory(const Request&);
// Media Inputs
RequestResult GetMediaInputStatus(const Request&);
RequestResult SetMediaInputCursor(const Request&);
RequestResult OffsetMediaInputCursor(const Request&);
RequestResult TriggerMediaInputAction(const Request&);
SessionPtr _session;
static const std::map<std::string, RequestMethodHandler> _handlerMap;
};

View File

@ -23,10 +23,10 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
json responseData;
json persistentData;
if (!(Utils::Json::GetJsonFileContent(persistentDataPath, persistentData) && persistentData.contains(slotName)))
responseData["slotValue"] = nullptr;
else
if (Utils::Json::GetJsonFileContent(persistentDataPath, persistentData) && persistentData.contains(slotName))
responseData["slotValue"] = persistentData[slotName];
else
responseData["slotValue"] = nullptr;
return RequestResult::Success(responseData);
}
@ -347,7 +347,7 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
obs_service_update(currentStreamService, newStreamServiceSettings);
} else {
// TODO: This leaks memory. I have no idea why.
OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, NULL);
OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, nullptr);
// TODO: Check service type here, instead of relying on service creation to fail.
if (!newStreamService)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the stream service with the requested streamServiceType. It may be an invalid type.");

View File

@ -8,7 +8,7 @@
RequestResult RequestHandler::GetVersion(const Request& request)
{
json responseData;
responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersionString();
responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion();
responseData["obsWebSocketVersion"] = OBS_WEBSOCKET_VERSION;
responseData["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION;
responseData["availableRequests"] = GetRequestList();

View File

@ -319,7 +319,7 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorTypeString(input);
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input);
return RequestResult::Success(responseData);
}
@ -365,6 +365,8 @@ std::vector<json> GetListPropertyItems(obs_property_t *property)
itemData["itemValue"] = obs_property_list_item_float(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
itemData["itemValue"] = obs_property_list_item_string(property, i);
} else {
itemData["itemValue"] = nullptr;
}
ret.push_back(itemData);
}

View File

@ -0,0 +1,110 @@
#include "RequestHandler.h"
bool IsMediaTimeValid(obs_source_t *input)
{
auto mediaState = obs_source_media_get_state(input);
return mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED;
}
RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputState(input);
if (IsMediaTimeValid(input)) {
responseData["mediaDuration"] = obs_source_media_get_duration(input);
responseData["mediaCursor"] = obs_source_media_get_time(input);
} else {
responseData["mediaDuration"] = nullptr;
responseData["mediaCursor"] = nullptr;
}
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateNumber("mediaCursor", statusCode, comment, 0)))
return RequestResult::Error(statusCode, comment);
if (!IsMediaTimeValid(input))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position.");
int64_t mediaCursor = request.RequestData["mediaCursor"];
// Yes, we're setting the time without checking if it's valid. Can't baby everything.
obs_source_media_set_time(input, mediaCursor);
return RequestResult::Success();
}
RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateNumber("mediaCursorOffset", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
if (!IsMediaTimeValid(input))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position.");
int64_t mediaCursorOffset = request.RequestData["mediaCursorOffset"];
int64_t mediaCursor = obs_source_media_get_time(input) + mediaCursorOffset;
if (mediaCursor < 0)
mediaCursor = 0;
obs_source_media_set_time(input, mediaCursor);
return RequestResult::Success();
}
RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateString("mediaAction", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
std::string mediaActionString = request.RequestData["mediaAction"];
auto mediaAction = Utils::Obs::EnumHelper::GetMediaInputAction(mediaActionString);
switch (mediaAction) {
default:
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE:
return RequestResult::Error(RequestStatus::InvalidRequestParameter, "You have specified an invalid media input action.");
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
// Shoutout to whoever implemented this API call like this
obs_source_media_play_pause(input, false);
break;
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE:
obs_source_media_play_pause(input, true);
break;
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP:
obs_source_media_stop(input);
break;
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART:
// I'm only implementing this because I'm nice. I think its a really dumb action.
obs_source_media_restart(input);
break;
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT:
obs_source_media_next(input);
break;
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS:
obs_source_media_previous(input);
break;
}
return RequestResult::Success();
}

View File

@ -4,11 +4,13 @@ RequestResult RequestHandler::GetRecordStatus(const Request& request)
{
OBSOutputAutoRelease recordOutput = obs_frontend_get_streaming_output();
uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
json responseData;
responseData["outputActive"] = obs_output_active(recordOutput);
responseData["outputPaused"] = obs_output_paused(recordOutput);
responseData["outputTimecode"] = Utils::Obs::StringHelper::GetOutputTimecodeString(recordOutput);
responseData["outputDuration"] = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
responseData["outputDuration"] = outputDuration;
responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(recordOutput);
return RequestResult::Success(responseData);

View File

@ -4,11 +4,13 @@ RequestResult RequestHandler::GetStreamStatus(const Request& request)
{
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(streamOutput);
json responseData;
responseData["outputActive"] = obs_output_active(streamOutput);
responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput);
responseData["outputTimecode"] = Utils::Obs::StringHelper::GetOutputTimecodeString(streamOutput);
responseData["outputDuration"] = Utils::Obs::NumberHelper::GetOutputDuration(streamOutput);
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
responseData["outputDuration"] = outputDuration;
responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput);
responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput);
responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput);

View File

@ -30,7 +30,7 @@ std::vector<std::string> ConvertStringArray(char **array)
return ret;
}
std::string Utils::Obs::StringHelper::GetObsVersionString()
std::string Utils::Obs::StringHelper::GetObsVersion()
{
uint32_t version = obs_get_version();
@ -77,7 +77,7 @@ std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
return "";
}
std::string Utils::Obs::StringHelper::GetSourceTypeString(obs_source_t *source)
std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
{
obs_source_type sourceType = obs_source_get_type(source);
@ -90,7 +90,7 @@ std::string Utils::Obs::StringHelper::GetSourceTypeString(obs_source_t *source)
}
}
std::string Utils::Obs::StringHelper::GetInputMonitorTypeString(obs_source_t *input)
std::string Utils::Obs::StringHelper::GetInputMonitorType(obs_source_t *input)
{
obs_monitoring_type monitorType = obs_source_get_monitoring_type(input);
@ -102,7 +102,7 @@ std::string Utils::Obs::StringHelper::GetInputMonitorTypeString(obs_source_t *in
}
}
std::string Utils::Obs::StringHelper::GetMediaInputStateString(obs_source_t *input)
std::string Utils::Obs::StringHelper::GetMediaInputState(obs_source_t *input)
{
obs_media_state mediaState = obs_source_media_get_state(input);
@ -130,7 +130,7 @@ std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
return ret;
}
std::string Utils::Obs::StringHelper::GetSceneItemBoundsTypeString(enum obs_bounds_type type)
std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type)
{
switch (type) {
default:
@ -144,16 +144,8 @@ std::string Utils::Obs::StringHelper::GetSceneItemBoundsTypeString(enum obs_boun
}
}
std::string Utils::Obs::StringHelper::GetOutputTimecodeString(obs_output_t *output)
std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
{
if (!output || !obs_output_active(output))
return "00:00:00.000";
video_t* video = obs_output_video(output);
uint64_t frameTimeNs = video_output_get_frame_time(video);
int totalFrames = obs_output_get_total_frames(output);
uint64_t ms = util_mul_div64(totalFrames, frameTimeNs, 1000000ULL);
uint64_t secs = ms / 1000ULL;
uint64_t minutes = secs / 60ULL;
@ -179,6 +171,18 @@ enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string
return OBS_BOUNDS_NONE;
}
enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
{
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS);
return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
}
uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
{
if (!output || !obs_output_active(output))
@ -288,11 +292,15 @@ std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, b
if (!enumData->second) {
OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
item["sourceName"] = obs_source_get_name(itemSource);
item["sourceType"] = StringHelper::GetSourceTypeString(itemSource);
item["sourceType"] = StringHelper::GetSourceType(itemSource);
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
item["inputKind"] = obs_source_get_id(itemSource);
else if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
else
item["inputKind"] = nullptr;
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
item["isGroup"] = obs_source_is_group(itemSource);
else
item["isGroup"] = nullptr;
}
enumData->first.push_back(item);
@ -433,7 +441,7 @@ json Utils::Obs::DataHelper::GetSceneItemTransform(obs_sceneitem_t *item)
ret["alignment"] = osi.alignment;
ret["boundsType"] = StringHelper::GetSceneItemBoundsTypeString(osi.bounds_type);
ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type);
ret["boundsAlignment"] = osi.bounds_alignment;
ret["boundsWidth"] = osi.bounds.x;
ret["boundsHeight"] = osi.bounds.y;

View File

@ -16,10 +16,11 @@ enum ObsOutputState {
};
enum ObsMediaInputAction {
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS
};
@ -27,21 +28,22 @@ enum ObsMediaInputAction {
namespace Utils {
namespace Obs {
namespace StringHelper {
std::string GetObsVersionString();
std::string GetObsVersion();
std::string GetCurrentSceneCollection();
std::string GetCurrentProfile();
std::string GetCurrentProfilePath();
std::string GetCurrentRecordOutputPath();
std::string GetSourceTypeString(obs_source_t *source);
std::string GetInputMonitorTypeString(obs_source_t *input);
std::string GetMediaInputStateString(obs_source_t *input);
std::string GetSourceType(obs_source_t *source);
std::string GetInputMonitorType(obs_source_t *input);
std::string GetMediaInputState(obs_source_t *input);
std::string GetLastReplayBufferFilePath();
std::string GetSceneItemBoundsTypeString(enum obs_bounds_type type);
std::string GetOutputTimecodeString(obs_output_t *output);
std::string GetSceneItemBoundsType(enum obs_bounds_type type);
std::string DurationToTimecode(uint64_t);
}
namespace EnumHelper {
enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
}
namespace NumberHelper {