#include #include #include #include #include #include "Utils.h" #include "WSRequestHandler.h" bool isTextGDIPlusSource(const QString& sourceKind) { return (sourceKind == "text_gdiplus" || sourceKind == "text_gdiplus_v2"); } bool isTextFreeType2Source(const QString& sourceKind) { return (sourceKind == "text_ft2_source" || sourceKind == "text_ft2_source_v2"); } /** * Create a source and add it as a sceneitem to a scene. * * @param {String} `sourceName` Source name. * @param {String} `sourceKind` Source kind, Eg. `vlc_source`. * @param {String} `sceneName` Scene to add the new source to. * @param {Object (optional)} `sourceSettings` Source settings data. * @param {boolean (optional)} `setVisible` Set the created SceneItem as visible or not. Defaults to true * * @return {int} `itemId` ID of the SceneItem in the scene. * * @api requests * @name CreateSource * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::CreateSource(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("sourceKind") || !request.hasField("sceneName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind"); if (sourceName.isEmpty() || sourceKind.isEmpty()) { return request.failed("empty sourceKind or sourceName parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (source) { return request.failed("a source with that name already exists"); } const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); OBSSourceAutoRelease sceneSource = obs_get_source_by_name(sceneName); OBSScene scene = obs_scene_from_source(sceneSource); if (!scene) { return request.failed("requested scene is invalid or doesnt exist"); } OBSDataAutoRelease sourceSettings = nullptr; if (request.hasField("sourceSettings")) { sourceSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); } OBSSourceAutoRelease newSource = obs_source_create(sourceKind.toUtf8(), sourceName.toUtf8(), sourceSettings, nullptr); if (!newSource) { return request.failed("failed to create the source"); } obs_source_set_enabled(newSource, true); Utils::AddSourceData data; data.source = newSource; data.setVisible = true; if (request.hasField("setVisible")) { data.setVisible = obs_data_get_bool(request.parameters(), "setVisible"); } obs_enter_graphics(); obs_scene_atomic_update(scene, Utils::AddSourceHelper, &data); obs_leave_graphics(); OBSDataAutoRelease responseData = obs_data_create(); obs_data_set_int(responseData, "itemId", obs_sceneitem_get_id(data.sceneItem)); return request.success(responseData); } /** * List all sources available in the running OBS instance * * @return {Array} `sources` Array of sources * @return {String} `sources.*.name` Unique source name * @return {String} `sources.*.typeId` Non-unique source internal type (a.k.a kind) * @return {String} `sources.*.type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" * * @api requests * @name GetSourcesList * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::GetSourcesList(const RpcRequest& request) { OBSDataArrayAutoRelease sourcesArray = obs_data_array_create(); auto sourceEnumProc = [](void* privateData, obs_source_t* source) -> bool { obs_data_array_t* sourcesArray = (obs_data_array_t*)privateData; OBSDataAutoRelease sourceData = obs_data_create(); obs_data_set_string(sourceData, "name", obs_source_get_name(source)); obs_data_set_string(sourceData, "typeId", obs_source_get_id(source)); QString typeString = ""; enum obs_source_type sourceType = obs_source_get_type(source); switch (sourceType) { case OBS_SOURCE_TYPE_INPUT: typeString = "input"; break; case OBS_SOURCE_TYPE_FILTER: typeString = "filter"; break; case OBS_SOURCE_TYPE_TRANSITION: typeString = "transition"; break; case OBS_SOURCE_TYPE_SCENE: typeString = "scene"; break; default: typeString = "unknown"; break; } obs_data_set_string(sourceData, "type", typeString.toUtf8()); obs_data_array_push_back(sourcesArray, sourceData); return true; }; obs_enum_sources(sourceEnumProc, sourcesArray); OBSDataAutoRelease response = obs_data_create(); obs_data_set_array(response, "sources", sourcesArray); return request.success(response); } /** * Get a list of all available sources types * * @return {Array} `types` Array of source types * @return {String} `types.*.typeId` Non-unique internal source type ID * @return {String} `types.*.displayName` Display name of the source type * @return {String} `types.*.type` Type. Value is one of the following: "input", "filter", "transition" or "other" * @return {Object} `types.*.defaultSettings` Default settings of this source type * @return {Object} `types.*.caps` Source type capabilities * @return {Boolean} `types.*.caps.isAsync` True if source of this type provide frames asynchronously * @return {Boolean} `types.*.caps.hasVideo` True if sources of this type provide video * @return {Boolean} `types.*.caps.hasAudio` True if sources of this type provide audio * @return {Boolean} `types.*.caps.canInteract` True if interaction with this sources of this type is possible * @return {Boolean} `types.*.caps.isComposite` True if sources of this type composite one or more sub-sources * @return {Boolean} `types.*.caps.doNotDuplicate` True if sources of this type should not be fully duplicated * @return {Boolean} `types.*.caps.doNotSelfMonitor` True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be * * @api requests * @name GetSourceTypesList * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::GetSourceTypesList(const RpcRequest& request) { OBSDataArrayAutoRelease idsArray = obs_data_array_create(); const char* id; size_t idx = 0; QHash idTypes; idx = 0; while (obs_enum_input_types(idx++, &id)) { idTypes.insert(id, "input"); } idx = 0; while (obs_enum_filter_types(idx++, &id)) { idTypes.insert(id, "filter"); } idx = 0; while (obs_enum_transition_types(idx++, &id)) { idTypes.insert(id, "transition"); } idx = 0; while (obs_enum_source_types(idx++, &id)) { OBSDataAutoRelease item = obs_data_create(); obs_data_set_string(item, "typeId", id); obs_data_set_string(item, "displayName", obs_source_get_display_name(id)); obs_data_set_string(item, "type", idTypes.value(id, "other").toUtf8()); uint32_t caps = obs_get_source_output_flags(id); OBSDataAutoRelease capsData = obs_data_create(); obs_data_set_bool(capsData, "isAsync", caps & OBS_SOURCE_ASYNC); obs_data_set_bool(capsData, "hasVideo", caps & OBS_SOURCE_VIDEO); obs_data_set_bool(capsData, "hasAudio", caps & OBS_SOURCE_AUDIO); obs_data_set_bool(capsData, "canInteract", caps & OBS_SOURCE_INTERACTION); obs_data_set_bool(capsData, "isComposite", caps & OBS_SOURCE_COMPOSITE); obs_data_set_bool(capsData, "doNotDuplicate", caps & OBS_SOURCE_DO_NOT_DUPLICATE); obs_data_set_bool(capsData, "doNotSelfMonitor", caps & OBS_SOURCE_DO_NOT_SELF_MONITOR); obs_data_set_bool(capsData, "isDeprecated", caps & OBS_SOURCE_DEPRECATED); obs_data_set_obj(item, "caps", capsData); OBSDataAutoRelease defaultSettings = obs_get_source_defaults(id); obs_data_set_obj(item, "defaultSettings", defaultSettings); obs_data_array_push_back(idsArray, item); } OBSDataAutoRelease response = obs_data_create(); obs_data_set_array(response, "types", idsArray); return request.success(response); } /** * Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE. * * @param {String} `source` Source name. * @param {boolean (optional)} `useDecibel` Output volume in decibels of attenuation instead of amplitude/mul. * * @return {String} `name` Source name. * @return {double} `volume` Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB. * @return {boolean} `muted` Indicates whether the source is muted. * * @api requests * @name GetVolume * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::GetVolume(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } float volume = obs_source_get_volume(source); bool useDecibel = obs_data_get_bool(request.parameters(), "useDecibel"); if (useDecibel) { volume = obs_mul_to_db(volume); } if (volume == -INFINITY) { volume = -100.0; } OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "name", obs_source_get_name(source)); obs_data_set_double(response, "volume", volume); obs_data_set_bool(response, "muted", obs_source_muted(source)); return request.success(response); } /** * Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE. * * @param {String} `source` Source name. * @param {double} `volume` 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. * @param {boolean (optional)} `useDecibel` Interperet `volume` data as decibels instead of amplitude/mul. * * @api requests * @name SetVolume * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::SetVolume(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("volume")) { return request.failed("missing request parameters"); } bool useDecibel = obs_data_get_bool(request.parameters(), "useDecibel"); QString sourceName = obs_data_get_string(request.parameters(), "source"); float sourceVolume = obs_data_get_double(request.parameters(), "volume"); bool isNotValidDecibel = (useDecibel && sourceVolume > 26.0); bool isNotValidMul = (!useDecibel && (sourceVolume < 0.0 || sourceVolume > 20.0)); if (sourceName.isEmpty() || isNotValidDecibel || isNotValidMul) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } if (useDecibel) { sourceVolume = obs_db_to_mul(sourceVolume); } obs_source_set_volume(source, sourceVolume); return request.success(); } /** * Get the mute status of a specified source. * * @param {String} `source` Source name. * * @return {String} `name` Source name. * @return {boolean} `muted` Mute status of the source. * * @api requests * @name GetMute * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::GetMute(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "name", obs_source_get_name(source)); obs_data_set_bool(response, "muted", obs_source_muted(source)); return request.success(response); } /** * Sets the mute status of a specified source. * * @param {String} `source` Source name. * @param {boolean} `mute` Desired mute status. * * @api requests * @name SetMute * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::SetMute(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("mute")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); bool mute = obs_data_get_bool(request.parameters(), "mute"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } obs_source_set_muted(source, mute); return request.success(); } /** * Inverts the mute status of a specified source. * * @param {String} `source` Source name. * * @api requests * @name ToggleMute * @category sources * @since 4.0.0 */ RpcResponse WSRequestHandler::ToggleMute(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("invalid request parameters"); } obs_source_set_muted(source, !obs_source_muted(source)); return request.success(); } /** * Get the audio's active status of a specified source. * * @param {String} `sourceName` Source name. * * @return {boolean} `audioActive` Audio active status of the source. * * @api requests * @name GetAudioActive * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::GetAudioActive(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } OBSDataAutoRelease response = obs_data_create(); obs_data_set_bool(response, "audioActive", obs_source_audio_active(source)); return request.success(response); } /** * Sets (aka rename) the name of a source. Also works with scenes since scenes are technically sources in OBS. * * Note: If the new name already exists as a source, obs-websocket will return an error. * * @param {String} `sourceName` Source name. * @param {String} `newName` New source name. * * @api requests * @name SetSourceName * @category sources * @since 4.8.0 */ RpcResponse WSRequestHandler::SetSourceName(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("newName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); QString newName = obs_data_get_string(request.parameters(), "newName"); if (sourceName.isEmpty() || newName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease existingSource = obs_get_source_by_name(newName.toUtf8()); if (!existingSource) { // OBS is supposed to automatically rename colliding source names, but it doesn't. So this gets to be the solution for now. obs_source_set_name(source, newName.toUtf8()); return request.success(); } else { return request.failed("a source with that name already exists"); } } /** * Set the audio sync offset of a specified source. * * @param {String} `source` Source name. * @param {int} `offset` The desired audio sync offset (in nanoseconds). * * @api requests * @name SetSyncOffset * @category sources * @since 4.2.0 */ RpcResponse WSRequestHandler::SetSyncOffset(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("offset")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); int64_t sourceSyncOffset = (int64_t)obs_data_get_int(request.parameters(), "offset"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } obs_source_set_sync_offset(source, sourceSyncOffset); return request.success(); } /** * Get the audio sync offset of a specified source. * * @param {String} `source` Source name. * * @return {String} `name` Source name. * @return {int} `offset` The audio sync offset (in nanoseconds). * * @api requests * @name GetSyncOffset * @category sources * @since 4.2.0 */ RpcResponse WSRequestHandler::GetSyncOffset(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "source"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "name", obs_source_get_name(source)); obs_data_set_int(response, "offset", obs_source_get_sync_offset(source)); return request.success(response); } /** * Get settings of the specified source * * @param {String} `sourceName` Source name. * @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking if you expect a specific settings schema. * * @return {String} `sourceName` Source name * @return {String} `sourceType` Type of the specified source * @return {Object} `sourceSettings` Source settings (varies between source types, may require some probing around). * * @api requests * @name GetSourceSettings * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::GetSourceSettings(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } if (request.hasField("sourceType")) { QString actualSourceType = obs_source_get_id(source); QString requestedType = obs_data_get_string(request.parameters(), "sourceType"); if (actualSourceType != requestedType) { return request.failed("specified source exists but is not of expected type"); } } OBSDataAutoRelease sourceSettings = obs_source_get_settings(source); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceName", obs_source_get_name(source)); obs_data_set_string(response, "sourceType", obs_source_get_id(source)); obs_data_set_obj(response, "sourceSettings", sourceSettings); return request.success(response); } /** * Set settings of the specified source. * * @param {String} `sourceName` Source name. * @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type. * @param {Object} `sourceSettings` Source settings (varies between source types, may require some probing around). * * @return {String} `sourceName` Source name * @return {String} `sourceType` Type of the specified source * @return {Object} `sourceSettings` Updated source settings * * @api requests * @name SetSourceSettings * @category sources * @since 4.3.0 */ RpcResponse WSRequestHandler::SetSourceSettings(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("sourceSettings")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } if (request.hasField("sourceType")) { QString actualSourceType = obs_source_get_id(source); QString requestedType = obs_data_get_string(request.parameters(), "sourceType"); if (actualSourceType != requestedType) { return request.failed("specified source exists but is not of expected type"); } } OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); obs_source_update(source, newSettings); obs_source_update_properties(source); OBSDataAutoRelease updatedSettings = obs_source_get_settings(source); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceName", obs_source_get_name(source)); obs_data_set_string(response, "sourceType", obs_source_get_id(source)); obs_data_set_obj(response, "sourceSettings", updatedSettings); return request.success(response); } /** * Get the current properties of a Text GDI Plus source. * * @param {String} `source` Source name. * * @return {String} `source` Source name. * @return {String} `align` Text Alignment ("left", "center", "right"). * @return {int} `bk_color` Background color. * @return {int} `bk_opacity` Background opacity (0-100). * @return {boolean} `chatlog` Chat log. * @return {int} `chatlog_lines` Chat log lines. * @return {int} `color` Text color. * @return {boolean} `extents` Extents wrap. * @return {int} `extents_cx` Extents cx. * @return {int} `extents_cy` Extents cy. * @return {String} `file` File path name. * @return {boolean} `read_from_file` Read text from the specified file. * @return {Object} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @return {String} `font.face` Font face. * @return {int} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @return {int} `font.size` Font text size. * @return {String} `font.style` Font Style (unknown function). * @return {boolean} `gradient` Gradient enabled. * @return {int} `gradient_color` Gradient color. * @return {float} `gradient_dir` Gradient direction. * @return {int} `gradient_opacity` Gradient opacity (0-100). * @return {boolean} `outline` Outline. * @return {int} `outline_color` Outline color. * @return {int} `outline_size` Outline size. * @return {int} `outline_opacity` Outline opacity (0-100). * @return {String} `text` Text content to be displayed. * @return {String} `valign` Text vertical alignment ("top", "center", "bottom"). * @return {boolean} `vertical` Vertical text enabled. * * @api requests * @name GetTextGDIPlusProperties * @category sources * @since 4.1.0 */ RpcResponse WSRequestHandler::GetTextGDIPlusProperties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextGDIPlusSource(sourceKind)) { return request.failed("not a text gdi plus source"); } OBSDataAutoRelease response = obs_source_get_settings(source); obs_data_set_string(response, "source", obs_source_get_name(source)); return request.success(response); } /** * Set the current properties of a Text GDI Plus source. * * @param {String} `source` Name of the source. * @param {String (optional)} `align` Text Alignment ("left", "center", "right"). * @param {int (optional)} `bk_color` Background color. * @param {int (optional)} `bk_opacity` Background opacity (0-100). * @param {boolean (optional)} `chatlog` Chat log. * @param {int (optional)} `chatlog_lines` Chat log lines. * @param {int (optional)} `color` Text color. * @param {boolean (optional)} `extents` Extents wrap. * @param {int (optional)} `extents_cx` Extents cx. * @param {int (optional)} `extents_cy` Extents cy. * @param {String (optional)} `file` File path name. * @param {boolean (optional)} `read_from_file` Read text from the specified file. * @param {Object (optional)} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @param {String (optional)} `font.face` Font face. * @param {int (optional)} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @param {int (optional)} `font.size` Font text size. * @param {String (optional)} `font.style` Font Style (unknown function). * @param {boolean (optional)} `gradient` Gradient enabled. * @param {int (optional)} `gradient_color` Gradient color. * @param {float (optional)} `gradient_dir` Gradient direction. * @param {int (optional)} `gradient_opacity` Gradient opacity (0-100). * @param {boolean (optional)} `outline` Outline. * @param {int (optional)} `outline_color` Outline color. * @param {int (optional)} `outline_size` Outline size. * @param {int (optional)} `outline_opacity` Outline opacity (0-100). * @param {String (optional)} `text` Text content to be displayed. * @param {String (optional)} `valign` Text vertical alignment ("top", "center", "bottom"). * @param {boolean (optional)} `vertical` Vertical text enabled. * @param {boolean (optional)} `render` Visibility of the scene item. * * @api requests * @name SetTextGDIPlusProperties * @category sources * @since 4.1.0 */ RpcResponse WSRequestHandler::SetTextGDIPlusProperties(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextGDIPlusSource(sourceKind)) { return request.failed("not a text gdi plus source"); } OBSDataAutoRelease settings = obs_source_get_settings(source); if (request.hasField("align")) { obs_data_set_string(settings, "align", obs_data_get_string(request.parameters(), "align")); } if (request.hasField("bk_color")) { obs_data_set_int(settings, "bk_color", obs_data_get_int(request.parameters(), "bk_color")); } if (request.hasField("bk-opacity")) { obs_data_set_int(settings, "bk_opacity", obs_data_get_int(request.parameters(), "bk_opacity")); } if (request.hasField("chatlog")) { obs_data_set_bool(settings, "chatlog", obs_data_get_bool(request.parameters(), "chatlog")); } if (request.hasField("chatlog_lines")) { obs_data_set_int(settings, "chatlog_lines", obs_data_get_int(request.parameters(), "chatlog_lines")); } if (request.hasField("color")) { obs_data_set_int(settings, "color", obs_data_get_int(request.parameters(), "color")); } if (request.hasField("extents")) { obs_data_set_bool(settings, "extents", obs_data_get_bool(request.parameters(), "extents")); } if (request.hasField("extents_wrap")) { obs_data_set_bool(settings, "extents_wrap", obs_data_get_bool(request.parameters(), "extents_wrap")); } if (request.hasField("extents_cx")) { obs_data_set_int(settings, "extents_cx", obs_data_get_int(request.parameters(), "extents_cx")); } if (request.hasField("extents_cy")) { obs_data_set_int(settings, "extents_cy", obs_data_get_int(request.parameters(), "extents_cy")); } if (request.hasField("file")) { obs_data_set_string(settings, "file", obs_data_get_string(request.parameters(), "file")); } if (request.hasField("font")) { OBSDataAutoRelease font_obj = obs_data_get_obj(settings, "font"); if (font_obj) { OBSDataAutoRelease req_font_obj = obs_data_get_obj(request.parameters(), "font"); if (obs_data_has_user_value(req_font_obj, "face")) { obs_data_set_string(font_obj, "face", obs_data_get_string(req_font_obj, "face")); } if (obs_data_has_user_value(req_font_obj, "flags")) { obs_data_set_int(font_obj, "flags", obs_data_get_int(req_font_obj, "flags")); } if (obs_data_has_user_value(req_font_obj, "size")) { obs_data_set_int(font_obj, "size", obs_data_get_int(req_font_obj, "size")); } if (obs_data_has_user_value(req_font_obj, "style")) { obs_data_set_string(font_obj, "style", obs_data_get_string(req_font_obj, "style")); } } } if (request.hasField("gradient")) { obs_data_set_bool(settings, "gradient", obs_data_get_bool(request.parameters(), "gradient")); } if (request.hasField("gradient_color")) { obs_data_set_int(settings, "gradient_color", obs_data_get_int(request.parameters(), "gradient_color")); } if (request.hasField("gradient_dir")) { obs_data_set_double(settings, "gradient_dir", obs_data_get_double(request.parameters(), "gradient_dir")); } if (request.hasField("gradient_opacity")) { obs_data_set_int(settings, "gradient_opacity", obs_data_get_int(request.parameters(), "gradient_opacity")); } if (request.hasField("outline")) { obs_data_set_bool(settings, "outline", obs_data_get_bool(request.parameters(), "outline")); } if (request.hasField("outline_size")) { obs_data_set_int(settings, "outline_size", obs_data_get_int(request.parameters(), "outline_size")); } if (request.hasField("outline_color")) { obs_data_set_int(settings, "outline_color", obs_data_get_int(request.parameters(), "outline_color")); } if (request.hasField("outline_opacity")) { obs_data_set_int(settings, "outline_opacity", obs_data_get_int(request.parameters(), "outline_opacity")); } if (request.hasField("read_from_file")) { obs_data_set_bool(settings, "read_from_file", obs_data_get_bool(request.parameters(), "read_from_file")); } if (request.hasField("text")) { obs_data_set_string(settings, "text", obs_data_get_string(request.parameters(), "text")); } if (request.hasField("valign")) { obs_data_set_string(settings, "valign", obs_data_get_string(request.parameters(), "valign")); } if (request.hasField("vertical")) { obs_data_set_bool(settings, "vertical", obs_data_get_bool(request.parameters(), "vertical")); } obs_source_update(source, settings); return request.success(); } /** * Get the current properties of a Text Freetype 2 source. * * @param {String} `source` Source name. * * @return {String} `source` Source name * @return {int} `color1` Gradient top color. * @return {int} `color2` Gradient bottom color. * @return {int} `custom_width` Custom width (0 to disable). * @return {boolean} `drop_shadow` Drop shadow. * @return {Object} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @return {String} `font.face` Font face. * @return {int} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @return {int} `font.size` Font text size. * @return {String} `font.style` Font Style (unknown function). * @return {boolean} `from_file` Read text from the specified file. * @return {boolean} `log_mode` Chat log. * @return {boolean} `outline` Outline. * @return {String} `text` Text content to be displayed. * @return {String} `text_file` File path. * @return {boolean} `word_wrap` Word wrap. * * @api requests * @name GetTextFreetype2Properties * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::GetTextFreetype2Properties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextFreeType2Source(sourceKind)) { return request.failed("not a freetype 2 source"); } OBSDataAutoRelease response = obs_source_get_settings(source); obs_data_set_string(response, "source", sourceName); return request.success(response); } /** * Set the current properties of a Text Freetype 2 source. * * @param {String} `source` Source name. * @param {int (optional)} `color1` Gradient top color. * @param {int (optional)} `color2` Gradient bottom color. * @param {int (optional)} `custom_width` Custom width (0 to disable). * @param {boolean (optional)} `drop_shadow` Drop shadow. * @param {Object (optional)} `font` Holds data for the font. Ex: `"font": { "face": "Arial", "flags": 0, "size": 150, "style": "" }` * @param {String (optional)} `font.face` Font face. * @param {int (optional)} `font.flags` Font text styling flag. `Bold=1, Italic=2, Bold Italic=3, Underline=5, Strikeout=8` * @param {int (optional)} `font.size` Font text size. * @param {String (optional)} `font.style` Font Style (unknown function). * @param {boolean (optional)} `from_file` Read text from the specified file. * @param {boolean (optional)} `log_mode` Chat log. * @param {boolean (optional)} `outline` Outline. * @param {String (optional)} `text` Text content to be displayed. * @param {String (optional)} `text_file` File path. * @param {boolean (optional)} `word_wrap` Word wrap. * * @api requests * @name SetTextFreetype2Properties * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::SetTextFreetype2Properties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceKind = obs_source_get_id(source); if (!isTextFreeType2Source(sourceKind)) { return request.failed("not text freetype 2 source"); } OBSDataAutoRelease settings = obs_source_get_settings(source); if (request.hasField("color1")) { obs_data_set_int(settings, "color1", obs_data_get_int(request.parameters(), "color1")); } if (request.hasField("color2")) { obs_data_set_int(settings, "color2", obs_data_get_int(request.parameters(), "color2")); } if (request.hasField("custom_width")) { obs_data_set_int(settings, "custom_width", obs_data_get_int(request.parameters(), "custom_width")); } if (request.hasField("drop_shadow")) { obs_data_set_bool(settings, "drop_shadow", obs_data_get_bool(request.parameters(), "drop_shadow")); } if (request.hasField("font")) { OBSDataAutoRelease font_obj = obs_data_get_obj(settings, "font"); if (font_obj) { OBSDataAutoRelease req_font_obj = obs_data_get_obj(request.parameters(), "font"); if (obs_data_has_user_value(req_font_obj, "face")) { obs_data_set_string(font_obj, "face", obs_data_get_string(req_font_obj, "face")); } if (obs_data_has_user_value(req_font_obj, "flags")) { obs_data_set_int(font_obj, "flags", obs_data_get_int(req_font_obj, "flags")); } if (obs_data_has_user_value(req_font_obj, "size")) { obs_data_set_int(font_obj, "size", obs_data_get_int(req_font_obj, "size")); } if (obs_data_has_user_value(req_font_obj, "style")) { obs_data_set_string(font_obj, "style", obs_data_get_string(req_font_obj, "style")); } } } if (request.hasField("from_file")) { obs_data_set_bool(settings, "from_file", obs_data_get_bool(request.parameters(), "from_file")); } if (request.hasField("log_mode")) { obs_data_set_bool(settings, "log_mode", obs_data_get_bool(request.parameters(), "log_mode")); } if (request.hasField("outline")) { obs_data_set_bool(settings, "outline", obs_data_get_bool(request.parameters(), "outline")); } if (request.hasField("text")) { obs_data_set_string(settings, "text", obs_data_get_string(request.parameters(), "text")); } if (request.hasField("text_file")) { obs_data_set_string(settings, "text_file", obs_data_get_string(request.parameters(), "text_file")); } if (request.hasField("word_wrap")) { obs_data_set_bool(settings, "word_wrap", obs_data_get_bool(request.parameters(), "word_wrap")); } obs_source_update(source, settings); return request.success(); } /** * Get current properties for a Browser Source. * * @param {String} `source` Source name. * * @return {String} `source` Source name. * @return {boolean} `is_local_file` Indicates that a local file is in use. * @return {String} `local_file` file path. * @return {String} `url` Url. * @return {String} `css` CSS to inject. * @return {int} `width` Width. * @return {int} `height` Height. * @return {int} `fps` Framerate. * @return {boolean} `shutdown` Indicates whether the source should be shutdown when not visible. * * @api requests * @name GetBrowserSourceProperties * @category sources * @since 4.1.0 * @deprecated Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0 */ RpcResponse WSRequestHandler::GetBrowserSourceProperties(const RpcRequest& request) { const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceId = obs_source_get_id(source); if (sourceId != "browser_source" && sourceId != "linuxbrowser-source") { return request.failed("not a browser source"); } OBSDataAutoRelease response = obs_source_get_settings(source); obs_data_set_string(response, "source", obs_source_get_name(source)); return request.success(response); } /** * Set current properties for a Browser Source. * * @param {String} `source` Name of the source. * @param {boolean (optional)} `is_local_file` Indicates that a local file is in use. * @param {String (optional)} `local_file` file path. * @param {String (optional)} `url` Url. * @param {String (optional)} `css` CSS to inject. * @param {int (optional)} `width` Width. * @param {int (optional)} `height` Height. * @param {int (optional)} `fps` Framerate. * @param {boolean (optional)} `shutdown` Indicates whether the source should be shutdown when not visible. * @param {boolean (optional)} `render` Visibility of the scene item. * * @api requests * @name SetBrowserSourceProperties * @category sources * @deprecated Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0 * @since 4.1.0 */ RpcResponse WSRequestHandler::SetBrowserSourceProperties(const RpcRequest& request) { if (!request.hasField("source")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "source"); if (!sourceName) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } QString sourceId = obs_source_get_id(source); if(sourceId != "browser_source" && sourceId != "linuxbrowser-source") { return request.failed("not a browser source"); } OBSDataAutoRelease settings = obs_source_get_settings(source); if (request.hasField("restart_when_active")) { obs_data_set_bool(settings, "restart_when_active", obs_data_get_bool(request.parameters(), "restart_when_active")); } if (request.hasField("shutdown")) { obs_data_set_bool(settings, "shutdown", obs_data_get_bool(request.parameters(), "shutdown")); } if (request.hasField("is_local_file")) { obs_data_set_bool(settings, "is_local_file", obs_data_get_bool(request.parameters(), "is_local_file")); } if (request.hasField("local_file")) { obs_data_set_string(settings, "local_file", obs_data_get_string(request.parameters(), "local_file")); } if (request.hasField("url")) { obs_data_set_string(settings, "url", obs_data_get_string(request.parameters(), "url")); } if (request.hasField("css")) { obs_data_set_string(settings, "css", obs_data_get_string(request.parameters(), "css")); } if (request.hasField("width")) { obs_data_set_int(settings, "width", obs_data_get_int(request.parameters(), "width")); } if (request.hasField("height")) { obs_data_set_int(settings, "height", obs_data_get_int(request.parameters(), "height")); } if (request.hasField("fps")) { obs_data_set_int(settings, "fps", obs_data_get_int(request.parameters(), "fps")); } obs_source_update(source, settings); return request.success(); } /** * Get configured special sources like Desktop Audio and Mic/Aux sources. * * @return {String (optional)} `desktop-1` Name of the first Desktop Audio capture source. * @return {String (optional)} `desktop-2` Name of the second Desktop Audio capture source. * @return {String (optional)} `mic-1` Name of the first Mic/Aux input source. * @return {String (optional)} `mic-2` Name of the second Mic/Aux input source. * @return {String (optional)} `mic-3` NAme of the third Mic/Aux input source. * * @api requests * @name GetSpecialSources * @category sources * @since 4.1.0 */ RpcResponse WSRequestHandler::GetSpecialSources(const RpcRequest& request) { OBSDataAutoRelease response = obs_data_create(); QMap sources; sources["desktop-1"] = 1; sources["desktop-2"] = 2; sources["mic-1"] = 3; sources["mic-2"] = 4; sources["mic-3"] = 5; QMapIterator i(sources); while (i.hasNext()) { i.next(); const char* id = i.key(); OBSSourceAutoRelease source = obs_get_output_source(i.value()); if (source) { obs_data_set_string(response, id, obs_source_get_name(source)); } } return request.success(response); } /** * List filters applied to a source * * @param {String} `sourceName` Source name * * @return {Array} `filters` List of filters for the specified source * @return {Boolean} `filters.*.enabled` Filter status (enabled or not) * @return {String} `filters.*.type` Filter type * @return {String} `filters.*.name` Filter name * @return {Object} `filters.*.settings` Filter settings * * @api requests * @name GetSourceFilters * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::GetSourceFilters(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSDataArrayAutoRelease filters = Utils::GetSourceFiltersList(source, true); OBSDataAutoRelease response = obs_data_create(); obs_data_set_array(response, "filters", filters); return request.success(response); } /** * List filters applied to a source * * @param {String} `sourceName` Source name * @param {String} `filterName` Source filter name * * @return {Boolean} `enabled` Filter status (enabled or not) * @return {String} `type` Filter type * @return {String} `name` Filter name * @return {Object} `settings` Filter settings * * @api requests * @name GetSourceFilterInfo * @category sources * @since 4.7.0 */ RpcResponse WSRequestHandler::GetSourceFilterInfo(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist on specified source"); } OBSDataAutoRelease response = Utils::GetSourceFilterInfo(filter, true); return request.success(response); } /** * Add a new filter to a source. Available source types along with their settings properties are available from `GetSourceTypesList`. * * @param {String} `sourceName` Name of the source on which the filter is added * @param {String} `filterName` Name of the new filter * @param {String} `filterType` Filter type * @param {Object} `filterSettings` Filter settings * * @api requests * @name AddFilterToSource * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::AddFilterToSource(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("filterType") || !request.hasField("filterSettings")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); const char* filterType = obs_data_get_string(request.parameters(), "filterType"); OBSDataAutoRelease filterSettings = obs_data_get_obj(request.parameters(), "filterSettings"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(source, filterName); if (existingFilter) { return request.failed("filter name already taken"); } OBSSourceAutoRelease filter = obs_source_create_private(filterType, filterName, filterSettings); if (!filter) { return request.failed("filter creation failed"); } if (obs_source_get_type(filter) != OBS_SOURCE_TYPE_FILTER) { return request.failed("invalid filter type"); } obs_source_filter_add(source, filter); return request.success(); } /** * Remove a filter from a source * * @param {String} `sourceName` Name of the source from which the specified filter is removed * @param {String} `filterName` Name of the filter to remove * * @api requests * @name RemoveFilterFromSource * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::RemoveFilterFromSource(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } obs_source_filter_remove(source, filter); return request.success(); } /** * Move a filter in the chain (absolute index positioning) * * @param {String} `sourceName` Name of the source to which the filter belongs * @param {String} `filterName` Name of the filter to reorder * @param {Integer} `newIndex` Desired position of the filter in the chain * * @api requests * @name ReorderSourceFilter * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::ReorderSourceFilter(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("newIndex")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); int newIndex = obs_data_get_int(request.parameters(), "newIndex"); if (newIndex < 0) { return request.failed("invalid index"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } struct filterSearch { int i; int filterIndex; obs_source_t* filter; }; struct filterSearch ctx = { 0, 0, filter }; obs_source_enum_filters(source, [](obs_source_t *parent, obs_source_t *child, void *param) { struct filterSearch* ctx = (struct filterSearch*)param; if (child == ctx->filter) { ctx->filterIndex = ctx->i; } ctx->i++; }, &ctx); int lastFilterIndex = ctx.i + 1; if (newIndex > lastFilterIndex) { return request.failed("index out of bounds"); } int currentIndex = ctx.filterIndex; if (newIndex > currentIndex) { int downSteps = newIndex - currentIndex; for (int i = 0; i < downSteps; i++) { obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_DOWN); } } else if (newIndex < currentIndex) { int upSteps = currentIndex - newIndex; for (int i = 0; i < upSteps; i++) { obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_UP); } } return request.success(); } /** * Move a filter in the chain (relative positioning) * * @param {String} `sourceName` Name of the source to which the filter belongs * @param {String} `filterName` Name of the filter to reorder * @param {String} `movementType` How to move the filter around in the source's filter chain. Either "up", "down", "top" or "bottom". * * @api requests * @name MoveSourceFilter * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::MoveSourceFilter(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("movementType")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); QString movementType(obs_data_get_string(request.parameters(), "movementType")); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } obs_order_movement movement; if (movementType == "up") { movement = OBS_ORDER_MOVE_UP; } else if (movementType == "down") { movement = OBS_ORDER_MOVE_DOWN; } else if (movementType == "top") { movement = OBS_ORDER_MOVE_TOP; } else if (movementType == "bottom") { movement = OBS_ORDER_MOVE_BOTTOM; } else { return request.failed("invalid value for movementType: must be either 'up', 'down', 'top' or 'bottom'."); } obs_source_filter_set_order(source, filter, movement); return request.success(); } /** * Update settings of a filter * * @param {String} `sourceName` Name of the source to which the filter belongs * @param {String} `filterName` Name of the filter to reconfigure * @param {Object} `filterSettings` New settings. These will be merged to the current filter settings. * * @api requests * @name SetSourceFilterSettings * @category sources * @since 4.5.0 */ RpcResponse WSRequestHandler::SetSourceFilterSettings(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("filterSettings")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSDataAutoRelease newFilterSettings = obs_data_get_obj(request.parameters(), "filterSettings"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist"); } OBSDataAutoRelease settings = obs_source_get_settings(filter); obs_data_apply(settings, newFilterSettings); obs_source_update(filter, settings); return request.success(); } /** * Change the visibility/enabled state of a filter * * @param {String} `sourceName` Source name * @param {String} `filterName` Source filter name * @param {Boolean} `filterEnabled` New filter state * * @api requests * @name SetSourceFilterVisibility * @category sources * @since 4.7.0 */ RpcResponse WSRequestHandler::SetSourceFilterVisibility(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("filterName") || !request.hasField("filterEnabled")) { return request.failed("missing request parameters"); } const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } const char* filterName = obs_data_get_string(request.parameters(), "filterName"); OBSSourceAutoRelease filter = obs_source_get_filter_by_name(source, filterName); if (!filter) { return request.failed("specified filter doesn't exist on specified source"); } bool filterEnabled = obs_data_get_bool(request.parameters(), "filterEnabled"); obs_source_set_enabled(filter, filterEnabled); return request.success(); } /** * Get the audio monitoring type of the specified source. * * @param {String} `sourceName` Source name. * * @return {String} `monitorType` The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`. * * @api requests * @name GetAudioMonitorType * @category sources * @since 4.8.0 */ RpcResponse WSRequestHandler::GetAudioMonitorType(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } OBSDataAutoRelease response = obs_data_create(); QString monitorType; enum obs_monitoring_type mtype = obs_source_get_monitoring_type(source); switch (mtype) { case OBS_MONITORING_TYPE_NONE: monitorType = "none"; break; case OBS_MONITORING_TYPE_MONITOR_ONLY: monitorType = "monitorOnly"; break; case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT: monitorType = "monitorAndOutput"; break; default: monitorType = "unknown"; break; } obs_data_set_string(response, "monitorType", monitorType.toUtf8()); return request.success(response); } /** * Set the audio monitoring type of the specified source. * * @param {String} `sourceName` Source name. * @param {String} `monitorType` The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`. * * @api requests * @name SetAudioMonitorType * @category sources * @since 4.8.0 */ RpcResponse WSRequestHandler::SetAudioMonitorType(const RpcRequest& request) { if (!request.hasField("sourceName") || !request.hasField("monitorType")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); QString monitorType = obs_data_get_string(request.parameters(), "monitorType"); if (sourceName.isEmpty() || monitorType.isEmpty()) { return request.failed("invalid request parameters"); } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } if (monitorType == "none") { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_NONE); } else if (monitorType == "monitorOnly") { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY); } else if (monitorType == "monitorAndOutput") { obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT); } else { return request.failed("invalid monitorType"); } return request.success(); } /** * Get the default settings for a given source type. * * @param {String} `sourceKind` Source kind. Also called "source id" in libobs terminology. * * @return {String} `sourceKind` Source kind. Same value as the `sourceKind` parameter. * @return {Object} `defaultSettings` Settings object for source. * * @api requests * @name GetSourceDefaultSettings * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::GetSourceDefaultSettings(const RpcRequest& request) { if (!request.hasField("sourceKind")) { return request.failed("missing request parameters"); } QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind"); if (sourceKind.isEmpty()) { return request.failed("invalid request parameters"); } OBSDataAutoRelease defaultData = obs_get_source_defaults(sourceKind.toUtf8()); if (!defaultData) { return request.failed("invalid sourceKind"); } OBSDataAutoRelease defaultSettings = Utils::OBSDataGetDefaults(defaultData); OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceKind", sourceKind.toUtf8().constData()); obs_data_set_obj(response, "defaultSettings", defaultSettings); return request.success(response); } /** * Takes a picture snapshot of a source and then can either or both: * - Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) * - Save it to disk (by specifying `saveToFilePath` in the request) * * At least `embedPictureFormat` or `saveToFilePath` must be specified. * * Clients can specify `width` and `height` parameters to receive scaled pictures. Aspect ratio is * preserved if only one of these two parameters is specified. * * @param {String (optional)} `sourceName` Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used. * @param {String (optional)} `embedPictureFormat` Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other value supported by Qt's Image module) * @param {String (optional)} `saveToFilePath` Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path. * @param {String (optional)} `fileFormat` Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension. * @param {int (optional)} `compressionQuality` 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. * @param {int (optional)} `width` Screenshot width. Defaults to the source's base width. * @param {int (optional)} `height` Screenshot height. Defaults to the source's base height. * * @return {String} `sourceName` Source name * @return {String} `img` Image Data URI (if `embedPictureFormat` was specified in the request) * @return {String} `imageFile` Absolute path to the saved image file (if `saveToFilePath` was specified in the request) * * @api requests * @name TakeSourceScreenshot * @category sources * @since 4.6.0 */ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { if (!request.hasField("embedPictureFormat") && !request.hasField("saveToFilePath")) { return request.failed("At least 'embedPictureFormat' or 'saveToFilePath' must be specified"); } OBSSourceAutoRelease source; if (!request.hasField("sourceName")) { source = obs_frontend_get_current_scene(); } else { const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist");; } } const uint32_t sourceWidth = obs_source_get_base_width(source); const uint32_t sourceHeight = obs_source_get_base_height(source); const double sourceAspectRatio = ((double)sourceWidth / (double)sourceHeight); uint32_t imgWidth = sourceWidth; uint32_t imgHeight = sourceHeight; if (request.hasField("width")) { imgWidth = obs_data_get_int(request.parameters(), "width"); if (!request.hasField("height")) { imgHeight = ((double)imgWidth / sourceAspectRatio); } } if (request.hasField("height")) { imgHeight = obs_data_get_int(request.parameters(), "height"); if (!request.hasField("width")) { imgWidth = ((double)imgHeight * sourceAspectRatio); } } QImage sourceImage(imgWidth, imgHeight, QImage::Format::Format_RGBA8888); sourceImage.fill(0); uint8_t* videoData = nullptr; uint32_t videoLinesize = 0; obs_enter_graphics(); gs_texrender_t* texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE); gs_stagesurf_t* stagesurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA); bool renderSuccess = false; gs_texrender_reset(texrender); if (gs_texrender_begin(texrender, imgWidth, imgHeight)) { vec4 background; vec4_zero(&background); gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0); gs_ortho(0.0f, (float)sourceWidth, 0.0f, (float)sourceHeight, -100.0f, 100.0f); gs_blend_state_push(); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); obs_source_inc_showing(source); obs_source_video_render(source); obs_source_dec_showing(source); gs_blend_state_pop(); gs_texrender_end(texrender); gs_stage_texture(stagesurface, gs_texrender_get_texture(texrender)); if (gs_stagesurface_map(stagesurface, &videoData, &videoLinesize)) { int linesize = sourceImage.bytesPerLine(); for (uint y = 0; y < imgHeight; y++) { memcpy(sourceImage.scanLine(y), videoData + (y * videoLinesize), linesize); } gs_stagesurface_unmap(stagesurface); renderSuccess = true; } } gs_stagesurface_destroy(stagesurface); gs_texrender_destroy(texrender); obs_leave_graphics(); if (!renderSuccess) { return request.failed("Source render failed"); } OBSDataAutoRelease response = obs_data_create(); int compressionQuality {-1}; if (request.hasField("compressionQuality")) { compressionQuality = obs_data_get_int(request.parameters(), "compressionQuality"); if (compressionQuality < -1 || compressionQuality > 100) { QString errorMessage = QString("compression quality out of range: %1").arg(compressionQuality); return request.failed(errorMessage.toUtf8()); } } if (request.hasField("embedPictureFormat")) { const char* pictureFormat = obs_data_get_string(request.parameters(), "embedPictureFormat"); QByteArrayList supportedFormats = QImageWriter::supportedImageFormats(); if (!supportedFormats.contains(pictureFormat)) { QString errorMessage = QString("unsupported picture format: %1").arg(pictureFormat); return request.failed(errorMessage.toUtf8()); } QByteArray encodedImgBytes; QBuffer buffer(&encodedImgBytes); buffer.open(QBuffer::WriteOnly); if (!sourceImage.save(&buffer, pictureFormat, compressionQuality)) { return request.failed("embed image encoding failed"); } buffer.close(); QString imgBase64(encodedImgBytes.toBase64()); imgBase64.prepend( QString("data:image/%1;base64,").arg(pictureFormat) ); obs_data_set_string(response, "img", imgBase64.toUtf8()); } if (request.hasField("saveToFilePath")) { QString filePathStr = obs_data_get_string(request.parameters(), "saveToFilePath"); QFileInfo filePathInfo(filePathStr); QString absoluteFilePath = filePathInfo.absoluteFilePath(); const char* fileFormat = nullptr; if (request.hasField("fileFormat")) { fileFormat = obs_data_get_string(request.parameters(), "fileFormat"); QByteArrayList supportedFormats = QImageWriter::supportedImageFormats(); if (!supportedFormats.contains(fileFormat)) { QString errorMessage = QString("unsupported file format: %1").arg(fileFormat); return request.failed(errorMessage.toUtf8()); } } if (!sourceImage.save(absoluteFilePath, fileFormat, compressionQuality)) { return request.failed("Image save failed"); } obs_data_set_string(response, "imageFile", absoluteFilePath.toUtf8()); } obs_data_set_string(response, "sourceName", obs_source_get_name(source)); return request.success(response); } /** * Refreshes the specified browser source. * * @param {String} `sourceName` Source name. * * @api requests * @name RefreshBrowserSource * @category sources * @since 4.9.0 */ RpcResponse WSRequestHandler::RefreshBrowserSource(const RpcRequest& request) { if (!request.hasField("sourceName")) { return request.failed("missing request parameters"); } QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { return request.failed("specified source doesn't exist"); } if (strcmp(obs_source_get_id(source), "browser_source") != 0) { return request.failed("specified source is not a browser"); } obs_properties_t *sourceProperties = obs_source_properties(source); obs_property_t *property = obs_properties_get(sourceProperties, "refreshnocache"); obs_property_button_clicked(property, source); // This returns a boolean but we ignore it because the browser plugin always returns `false`. obs_properties_destroy(sourceProperties); return request.success(); }