/* obs-websocket Copyright (C) 2016-2017 Stéphane Lepin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ #include #include #include #include #include #include #include #include #include "obs-websocket.h" #include "Utils.h" #include "Config.h" Q_DECLARE_METATYPE(OBSScene); const QHash boundTypeNames = { { OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH" }, { OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER" }, { OBS_BOUNDS_SCALE_OUTER, "OBS_BOUNDS_SCALE_OUTER" }, { OBS_BOUNDS_SCALE_TO_WIDTH, "OBS_BOUNDS_SCALE_TO_WIDTH" }, { OBS_BOUNDS_SCALE_TO_HEIGHT, "OBS_BOUNDS_SCALE_TO_HEIGHT" }, { OBS_BOUNDS_MAX_ONLY, "OBS_BOUNDS_MAX_ONLY" }, { OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE" }, }; QString getBoundsNameFromType(obs_bounds_type type) { QString fallback = boundTypeNames.value(OBS_BOUNDS_NONE); return boundTypeNames.value(type, fallback); } obs_bounds_type getBoundsTypeFromName(QString name) { return boundTypeNames.key(name); } bool Utils::StringInStringList(char** strings, const char* string) { if (!strings) { return false; } size_t index = 0; while (strings[index] != NULL) { char* value = strings[index]; if (strcmp(value, string) == 0) { return true; } index++; } return false; } obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) { obs_data_array_t* list = obs_data_array_create(); if (!strings || !key) { return list; // empty list } size_t index = 0; char* value = nullptr; do { value = strings[index]; OBSDataAutoRelease item = obs_data_create(); obs_data_set_string(item, key, value); if (value) { obs_data_array_push_back(list, item); } index++; } while (value != nullptr); return list; } obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) { obs_data_array_t* items = obs_data_array_create(); OBSScene scene = obs_scene_from_source(source); if (!scene) { return nullptr; } obs_scene_enum_items(scene, []( obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param) { obs_data_array_t* data = reinterpret_cast(param); OBSDataAutoRelease itemData = GetSceneItemData(currentItem); obs_data_array_insert(data, 0, itemData); return true; }, items); return items; } /** * @typedef {Object} `SceneItem` An OBS Scene Item. * @property {Number} `cy` * @property {Number} `cx` * @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. * @property {String} `name` The name of this Scene Item. * @property {int} `id` Scene item ID * @property {Boolean} `render` Whether or not this Scene Item is set to "visible". * @property {Boolean} `muted` Whether or not this Scene Item is muted. * @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around * @property {Number} `source_cx` * @property {Number} `source_cy` * @property {String} `type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" * @property {Number} `volume` * @property {Number} `x` * @property {Number} `y` * @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group) * @property {Array (optional)} `groupChildren` List of children (if this item is a group) */ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) { if (!item) { return nullptr; } vec2 pos; obs_sceneitem_get_pos(item, &pos); vec2 scale; obs_sceneitem_get_scale(item, &scale); // obs_sceneitem_get_source doesn't increase the refcount OBSSource itemSource = obs_sceneitem_get_source(item); float item_width = float(obs_source_get_width(itemSource)); float item_height = float(obs_source_get_height(itemSource)); obs_data_t* data = obs_data_create(); obs_data_set_string(data, "name", obs_source_get_name(itemSource)); obs_data_set_int(data, "id", obs_sceneitem_get_id(item)); obs_data_set_string(data, "type", obs_source_get_id(itemSource)); obs_data_set_double(data, "volume", obs_source_get_volume(itemSource)); obs_data_set_double(data, "x", pos.x); obs_data_set_double(data, "y", pos.y); obs_data_set_int(data, "source_cx", (int)item_width); obs_data_set_int(data, "source_cy", (int)item_height); obs_data_set_bool(data, "muted", obs_source_muted(itemSource)); obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item)); obs_data_set_double(data, "cx", item_width * scale.x); obs_data_set_double(data, "cy", item_height * scale.y); obs_data_set_bool(data, "render", obs_sceneitem_visible(item)); obs_data_set_bool(data, "locked", obs_sceneitem_locked(item)); obs_scene_t* parent = obs_sceneitem_get_scene(item); if (parent) { OBSSource parentSource = obs_scene_get_source(parent); QString parentKind = obs_source_get_id(parentSource); if (parentKind == "group") { obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource)); } } if (obs_sceneitem_is_group(item)) { OBSDataArrayAutoRelease children = obs_data_array_create(); obs_sceneitem_group_enum_items(item, [](obs_scene_t*, obs_sceneitem_t* currentItem, void* param) { obs_data_array_t* items = reinterpret_cast(param); OBSDataAutoRelease itemData = GetSceneItemData(currentItem); obs_data_array_push_back(items, itemData); return true; }, children); obs_data_set_array(data, "groupChildren", children); } return data; } obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) { if (!scene) { return nullptr; } struct current_search { QString query; obs_sceneitem_t* result; bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*); }; current_search search; search.query = name; search.result = nullptr; search.enumCallback = []( obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param) { current_search* search = reinterpret_cast(param); if (obs_sceneitem_is_group(currentItem)) { obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search); if (search->result) { return false; } } QString currentItemName = obs_source_get_name(obs_sceneitem_get_source(currentItem)); if (currentItemName == search->query) { search->result = currentItem; obs_sceneitem_addref(search->result); return false; } return true; }; obs_scene_enum_items(scene, search.enumCallback, &search); return search.result; } obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) { if (!scene) { return nullptr; } struct current_search { int query; obs_sceneitem_t* result; bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*); }; current_search search; search.query = id; search.result = nullptr; search.enumCallback = []( obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param) { current_search* search = reinterpret_cast(param); if (obs_sceneitem_is_group(currentItem)) { obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search); if (search->result) { return false; } } if (obs_sceneitem_get_id(currentItem) == search->query) { search->result = currentItem; obs_sceneitem_addref(search->result); return false; } return true; }; obs_scene_enum_items(scene, search.enumCallback, &search); return search.result; } obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) { if (!scene) { return nullptr; } OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id"); int id = obs_data_item_get_int(idInfoItem); OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name"); const char* name = obs_data_item_get_string(nameInfoItem); if (idInfoItem) { obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id); obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem); QString sceneItemName = obs_source_get_name(sceneItemSource); if (nameInfoItem && (QString(name) != sceneItemName)) { return nullptr; } return sceneItem; } else if (nameInfoItem) { return GetSceneItemFromName(scene, name); } return nullptr; } obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem) { enum obs_data_type dataType = obs_data_item_gettype(dataItem); if (dataType == OBS_DATA_OBJECT) { OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem); return GetSceneItemFromItem(scene, itemData); } else if (dataType == OBS_DATA_STRING) { QString name = obs_data_item_get_string(dataItem); return GetSceneItemFromName(scene, name); } return nullptr; } bool Utils::IsValidAlignment(const uint32_t alignment) { switch (alignment) { case OBS_ALIGN_CENTER: case OBS_ALIGN_LEFT: case OBS_ALIGN_RIGHT: case OBS_ALIGN_TOP: case OBS_ALIGN_BOTTOM: case OBS_ALIGN_TOP | OBS_ALIGN_LEFT: case OBS_ALIGN_TOP | OBS_ALIGN_RIGHT: case OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT: case OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT: { return true; } } return false; } obs_source_t* Utils::GetTransitionFromName(QString searchName) { obs_source_t* foundTransition = nullptr; obs_frontend_source_list transition_list = {}; obs_frontend_get_transitions(&transition_list); for (size_t i = 0; i < transition_list.sources.num; i++) { obs_source_t* transition = transition_list.sources.array[i]; QString transitionName = obs_source_get_name(transition); if (transitionName == searchName) { foundTransition = transition; obs_source_addref(foundTransition); break; } } obs_frontend_source_list_free(&transition_list); return foundTransition; } obs_scene_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) { // Both obs_frontend_get_current_scene() and obs_get_source_by_name() // increase the returned source's refcount OBSSourceAutoRelease sceneSource = nullptr; if (sceneName.isEmpty() || sceneName.isNull()) { sceneSource = obs_frontend_get_current_scene(); } else { sceneSource = obs_get_source_by_name(sceneName.toUtf8()); } return obs_scene_from_source(sceneSource); } obs_data_array_t* Utils::GetScenes() { obs_frontend_source_list sceneList = {}; obs_frontend_get_scenes(&sceneList); obs_data_array_t* scenes = obs_data_array_create(); for (size_t i = 0; i < sceneList.sources.num; i++) { obs_source_t* scene = sceneList.sources.array[i]; OBSDataAutoRelease sceneData = GetSceneData(scene); obs_data_array_push_back(scenes, sceneData); } obs_frontend_source_list_free(&sceneList); return scenes; } obs_data_t* Utils::GetSceneData(obs_source_t* source) { OBSDataArrayAutoRelease sceneItems = GetSceneItems(source); obs_data_t* sceneData = obs_data_create(); obs_data_set_string(sceneData, "name", obs_source_get_name(source)); obs_data_set_array(sceneData, "sources", sceneItems); return sceneData; } QSpinBox* Utils::GetTransitionDurationControl() { QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window(); return window->findChild("transitionDuration"); } int Utils::GetTransitionDuration(obs_source_t* transition) { if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) { return -1; } QString transitionKind = obs_source_get_id(transition); if (transitionKind == "cut_transition") { // If this is a Cut transition, return 0 return 0; } if (obs_transition_fixed(transition)) { // If this transition has a fixed duration (such as a Stinger), // we don't currently have a way of retrieving that number. // For now, return -1 to indicate that we don't know the actual duration. return -1; } OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene); // Detect if transition is the global transition or a transition override. // Fetching the duration is different depending on the case. obs_data_item_t* transitionDurationItem = obs_data_item_byname(destinationSettings, "transition_duration"); int duration = ( transitionDurationItem ? obs_data_item_get_int(transitionDurationItem) : obs_frontend_get_transition_duration() ); return duration; } bool Utils::SetTransitionByName(QString transitionName) { OBSSourceAutoRelease transition = GetTransitionFromName(transitionName); if (transition) { obs_frontend_set_current_transition(transition); return true; } else { return false; } } obs_data_t* Utils::GetTransitionData(obs_source_t* transition) { int duration = Utils::GetTransitionDuration(transition); if (duration < 0) { blog(LOG_WARNING, "GetTransitionData: duration is negative !"); } OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A); OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); obs_data_t* transitionData = obs_data_create(); obs_data_set_string(transitionData, "name", obs_source_get_name(transition)); obs_data_set_string(transitionData, "type", obs_source_get_id(transition)); obs_data_set_int(transitionData, "duration", duration); // When a transition starts and while it is running, SOURCE_A is the source scene // and SOURCE_B is the destination scene. // Before the transition_end event is triggered on a transition, the destination scene // goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop // we don't know what was the source scene // TODO fix this in libobs bool isTransitionEndEvent = (sourceScene == destinationScene); if (!isTransitionEndEvent) { obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene)); } obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene)); return transitionData; } QString Utils::OBSVersionString() { uint32_t version = obs_get_version(); uint8_t major, minor, patch; major = (version >> 24) & 0xFF; minor = (version >> 16) & 0xFF; patch = version & 0xFF; QString result = QString("%1.%2.%3") .arg(major).arg(minor).arg(patch); return result; } QSystemTrayIcon* Utils::GetTrayIcon() { void* systemTray = obs_frontend_get_system_tray(); return reinterpret_cast(systemTray); } void Utils::SysTrayNotify(QString text, QSystemTrayIcon::MessageIcon icon, QString title) { if (!GetConfig()->AlertsEnabled || !QSystemTrayIcon::isSystemTrayAvailable() || !QSystemTrayIcon::supportsMessages()) { return; } QSystemTrayIcon* trayIcon = GetTrayIcon(); if (trayIcon) trayIcon->showMessage(title, text, icon); } const char* Utils::GetRecordingFolder() { config_t* profile = obs_frontend_get_profile_config(); QString outputMode = config_get_string(profile, "Output", "Mode"); if (outputMode == "Advanced") { // Advanced mode return config_get_string(profile, "AdvOut", "RecFilePath"); } else { // Simple mode return config_get_string(profile, "SimpleOutput", "FilePath"); } } bool Utils::SetRecordingFolder(const char* path) { QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } config_t* profile = obs_frontend_get_profile_config(); config_set_string(profile, "AdvOut", "RecFilePath", path); config_set_string(profile, "SimpleOutput", "FilePath", path); config_save(profile); return true; } QString Utils::ParseDataToQueryString(obs_data_t* data) { if (!data) return QString(); QString query; obs_data_item_t* item = obs_data_first(data); if (item) { bool isFirst = true; do { if (!obs_data_item_has_user_value(item)) continue; if (!isFirst) query += "&"; else isFirst = false; QString attrName = obs_data_item_get_name(item); query += (attrName + "="); switch (obs_data_item_gettype(item)) { case OBS_DATA_BOOLEAN: query += (obs_data_item_get_bool(item) ? "true" : "false"); break; case OBS_DATA_NUMBER: switch (obs_data_item_numtype(item)) { case OBS_DATA_NUM_DOUBLE: query += QString::number(obs_data_item_get_double(item)); break; case OBS_DATA_NUM_INT: query += QString::number(obs_data_item_get_int(item)); break; case OBS_DATA_NUM_INVALID: break; } break; case OBS_DATA_STRING: query += QUrl::toPercentEncoding( QString(obs_data_item_get_string(item))); break; default: //other types are not supported break; } } while (obs_data_item_next(&item)); } return query; } obs_hotkey_t* Utils::FindHotkeyByName(QString name) { struct current_search { QString query; obs_hotkey_t* result; }; current_search search; search.query = name; search.result = nullptr; obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) { current_search* search = reinterpret_cast(data); const char* hk_name = obs_hotkey_get_name(hotkey); if (hk_name == search->query) { search->result = hotkey; return false; } return true; }, &search); return search.result; } bool Utils::ReplayBufferEnabled() { config_t* profile = obs_frontend_get_profile_config(); QString outputMode = config_get_string(profile, "Output", "Mode"); if (outputMode == "Simple") { return config_get_bool(profile, "SimpleOutput", "RecRB"); } else if (outputMode == "Advanced") { return config_get_bool(profile, "AdvOut", "RecRB"); } return false; } void Utils::StartReplayBuffer() { if (obs_frontend_replay_buffer_active()) return; if (!IsRPHotkeySet()) { obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output(); OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput); OBSDataAutoRelease dummyBinding = obs_data_create(); obs_data_set_bool(dummyBinding, "control", true); obs_data_set_bool(dummyBinding, "alt", true); obs_data_set_bool(dummyBinding, "shift", true); obs_data_set_bool(dummyBinding, "command", true); obs_data_set_string(dummyBinding, "key", "OBS_KEY_0"); OBSDataArray rpSaveHotkey = obs_data_get_array( outputHotkeys, "ReplayBuffer.Save"); obs_data_array_push_back(rpSaveHotkey, dummyBinding); obs_hotkeys_load_output(rpOutput, outputHotkeys); obs_frontend_replay_buffer_start(); obs_output_release(rpOutput); } else { obs_frontend_replay_buffer_start(); } } bool Utils::IsRPHotkeySet() { OBSOutputAutoRelease rpOutput = obs_frontend_get_replay_buffer_output(); OBSDataAutoRelease hotkeys = obs_hotkeys_save_output(rpOutput); OBSDataArrayAutoRelease bindings = obs_data_get_array(hotkeys, "ReplayBuffer.Save"); size_t count = obs_data_array_count(bindings); return (count > 0); } const char* Utils::GetFilenameFormatting() { config_t* profile = obs_frontend_get_profile_config(); return config_get_string(profile, "Output", "FilenameFormatting"); } bool Utils::SetFilenameFormatting(const char* filenameFormatting) { config_t* profile = obs_frontend_get_profile_config(); config_set_string(profile, "Output", "FilenameFormatting", filenameFormatting); config_save(profile); return true; } const char* Utils::GetCurrentRecordingFilename() { OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output(); if (!recordingOutput) { return nullptr; } OBSDataAutoRelease settings = obs_output_get_settings(recordingOutput); // mimicks the behavior of BasicOutputHandler::GetRecordingFilename : // try to fetch the path from the "url" property, then try "path" if the first one // didn't yield any result OBSDataItemAutoRelease item = obs_data_item_byname(settings, "url"); if (!item) { item = obs_data_item_byname(settings, "path"); if (!item) { return nullptr; } } return obs_data_item_get_string(item); } // Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet /** * @typedef {Object} `SceneItemTransform` * @property {double} `position.x` The x position of the scene item from the left. * @property {double} `position.y` The y position of the scene item from the top. * @property {int} `position.alignment` The point on the scene item that the item is manipulated from. * @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment. * @property {double} `scale.x` The x-scale factor of the scene item. * @property {double} `scale.y` The y-scale factor of the scene item. * @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling. * @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling. * @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling. * @property {int} `crop.left` The number of pixels cropped off the left of the scene item before scaling. * @property {bool} `visible` If the scene item is visible. * @property {bool} `locked` If the scene item is locked in position. * @property {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". * @property {int} `bounds.alignment` Alignment of the bounding box. * @property {double} `bounds.x` Width of the bounding box. * @property {double} `bounds.y` Height of the bounding box. * @property {int} `sourceWidth` Base width (without scaling) of the source * @property {int} `sourceHeight` Base source (without scaling) of the source * @property {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor) * @property {double} `height` Scene item height (base source height multiplied by the vertical scaling factor) * @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group) * @property {Array (optional)} `groupChildren` List of children (if this item is a group) */ obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) { if (!sceneItem) { return nullptr; } OBSSource source = obs_sceneitem_get_source(sceneItem); uint32_t baseSourceWidth = obs_source_get_width(source); uint32_t baseSourceHeight = obs_source_get_height(source); vec2 pos, scale, bounds; obs_sceneitem_crop crop; obs_sceneitem_get_pos(sceneItem, &pos); obs_sceneitem_get_scale(sceneItem, &scale); obs_sceneitem_get_crop(sceneItem, &crop); obs_sceneitem_get_bounds(sceneItem, &bounds); uint32_t alignment = obs_sceneitem_get_alignment(sceneItem); float rotation = obs_sceneitem_get_rot(sceneItem); bool isVisible = obs_sceneitem_visible(sceneItem); bool isLocked = obs_sceneitem_locked(sceneItem); obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem); uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem); QString boundsTypeName = getBoundsNameFromType(boundsType); OBSDataAutoRelease posData = obs_data_create(); obs_data_set_double(posData, "x", pos.x); obs_data_set_double(posData, "y", pos.y); obs_data_set_int(posData, "alignment", alignment); OBSDataAutoRelease scaleData = obs_data_create(); obs_data_set_double(scaleData, "x", scale.x); obs_data_set_double(scaleData, "y", scale.y); OBSDataAutoRelease cropData = obs_data_create(); obs_data_set_int(cropData, "left", crop.left); obs_data_set_int(cropData, "top", crop.top); obs_data_set_int(cropData, "right", crop.right); obs_data_set_int(cropData, "bottom", crop.bottom); OBSDataAutoRelease boundsData = obs_data_create(); obs_data_set_string(boundsData, "type", boundsTypeName.toUtf8()); obs_data_set_int(boundsData, "alignment", boundsAlignment); obs_data_set_double(boundsData, "x", bounds.x); obs_data_set_double(boundsData, "y", bounds.y); obs_data_t* data = obs_data_create(); obs_data_set_obj(data, "position", posData); obs_data_set_double(data, "rotation", rotation); obs_data_set_obj(data, "scale", scaleData); obs_data_set_obj(data, "crop", cropData); obs_data_set_bool(data, "visible", isVisible); obs_data_set_bool(data, "locked", isLocked); obs_data_set_obj(data, "bounds", boundsData); obs_data_set_int(data, "sourceWidth", baseSourceWidth); obs_data_set_int(data, "sourceHeight", baseSourceHeight); obs_data_set_double(data, "width", baseSourceWidth * scale.x); obs_data_set_double(data, "height", baseSourceHeight * scale.y); obs_scene_t* parent = obs_sceneitem_get_scene(sceneItem); if (parent) { OBSSource parentSource = obs_scene_get_source(parent); QString parentKind = obs_source_get_id(parentSource); if (parentKind == "group") { obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource)); } } if (obs_sceneitem_is_group(sceneItem)) { OBSDataArrayAutoRelease children = obs_data_array_create(); obs_sceneitem_group_enum_items(sceneItem, [](obs_scene_t*, obs_sceneitem_t* subItem, void* param) { obs_data_array_t* items = reinterpret_cast(param); OBSDataAutoRelease itemData = GetSceneItemPropertiesData(subItem); obs_data_array_push_back(items, itemData); return true; }, children); obs_data_set_array(data, "groupChildren", children); } return data; } obs_data_t* Utils::GetSourceFilterInfo(obs_source_t* filter, bool includeSettings) { obs_data_t* data = obs_data_create(); obs_data_set_bool(data, "enabled", obs_source_enabled(filter)); obs_data_set_string(data, "type", obs_source_get_id(filter)); obs_data_set_string(data, "name", obs_source_get_name(filter)); if (includeSettings) { OBSDataAutoRelease settings = obs_source_get_settings(filter); obs_data_set_obj(data, "settings", settings); } return data; } obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings) { struct enum_params { obs_data_array_t* filters; bool includeSettings; }; if (!source) { return nullptr; } struct enum_params enumParams; enumParams.filters = obs_data_array_create(); enumParams.includeSettings = includeSettings; obs_source_enum_filters(source, [](obs_source_t* parent, obs_source_t* child, void* param) { auto enumParams = reinterpret_cast(param); OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings); obs_data_array_push_back(enumParams->filters, filterData); }, &enumParams); return enumParams.filters; } void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr) { void* frontendApi = os_dlopen("obs-frontend-api"); if (recPausedFuncPtr) { *recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused"); } if (pauseRecFuncPtr) { *pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause"); } } QString Utils::nsToTimestamp(uint64_t ns) { uint64_t ms = ns / 1000000ULL; uint64_t secs = ms / 1000ULL; uint64_t minutes = secs / 60ULL; uint64_t hoursPart = minutes / 60ULL; uint64_t minutesPart = minutes % 60ULL; uint64_t secsPart = secs % 60ULL; uint64_t msPart = ms % 1000ULL; return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart); } void Utils::AddSourceHelper(void *_data, obs_scene_t *scene) { auto *data = reinterpret_cast(_data); data->sceneItem = obs_scene_add(scene, data->source); obs_sceneitem_set_visible(data->sceneItem, data->setVisible); } struct obs_data_item { // Used for OBSDataGetDefaults volatile long ref; struct obs_data *parent; struct obs_data_item *next; enum obs_data_type type; size_t name_len; size_t data_len; size_t data_size; size_t default_len; size_t default_size; size_t autoselect_size; size_t capacity; }; obs_data_t *Utils::OBSDataGetDefaults(obs_data_t *data) { obs_data_t *returnData = obs_data_create(); obs_data_item_t *item = NULL; for (item = obs_data_first(data); item; obs_data_item_next(&item)) { enum obs_data_type type = obs_data_item_gettype(item); const char *name = obs_data_item_get_name(item); if (type == OBS_DATA_STRING) { const char *val = obs_data_item_get_string(item); obs_data_set_string(returnData, name, val); } else if (type == OBS_DATA_NUMBER) { enum obs_data_number_type type = obs_data_item_numtype(item); if (type == OBS_DATA_NUM_INT) { long long val = obs_data_item_get_int(item); obs_data_set_int(returnData, name, val); } else { double val = obs_data_item_get_double(item); obs_data_set_double(returnData, name, val); } } else if (type == OBS_DATA_BOOLEAN) { bool val = obs_data_item_get_bool(item); obs_data_set_bool(returnData, name, val); } else if (type == OBS_DATA_OBJECT) { OBSDataAutoRelease obj = obs_data_item_get_obj(item); obs_data_set_obj(returnData, name, obj); } else if (type == OBS_DATA_ARRAY) { OBSDataArrayAutoRelease array = obs_data_item_get_array(item); obs_data_set_array(returnData, name, array); } } return returnData; }