obs-source-copy-0.2.2/0000755000000000000000000000000014352314334013244 5ustar rootrootobs-source-copy-0.2.2/.gitignore0000644000000000000000000000021214352314334015227 0ustar rootroot*~ .DS_Store /build/ /build_*/ /release/ /installer/Output/ .vscode .idea # ignore generated files *.generated.* **/.Brewfile.lock.json obs-source-copy-0.2.2/source-copy.cpp0000644000000000000000000007237214352314334016233 0ustar rootroot#include "source-copy.hpp" #include #include #include #include #include #include #include #include #include "version.h" #include "util/config-file.h" #include "util/platform.h" #define QT_UTF8(str) QString::fromUtf8(str) #define QT_TO_UTF8(str) str.toUtf8().constData() OBS_DECLARE_MODULE() OBS_MODULE_AUTHOR("Exeldro"); OBS_MODULE_USE_DEFAULT_LOCALE("source-copy", "en-US") #define MAX_PATH 260 static bool replace(std::string &str, const char *from, const char *to) { size_t start_pos = str.find(from); if (start_pos == std::string::npos) return false; str.replace(start_pos, strlen(from), to); return true; } static void try_fix_paths(obs_data_t *data, const char *dir, char *path_buffer) { obs_data_item_t *item = obs_data_first(data); while (item) { const enum obs_data_type type = obs_data_item_gettype(item); if (type == OBS_DATA_STRING) { std::string str = obs_data_item_get_string(item); bool edit = false; if (replace(str, "[U_COMBOBULATOR_PATH]", dir)) { obs_data_item_set_string(&item, str.c_str()); edit = true; } bool local_url = false; if (str.substr(0, 7) == "file://") { str = str.substr(7); local_url = true; } std::size_t found = str.find_last_of("/\\"); if (str.length() < MAX_PATH && found != std::string::npos && !os_file_exists(str.c_str())) { while (found != std::string::npos) { auto file = found == 0 && str[0] != '/' && str[0] != '\\' ? str : str.substr(found + 1); if (file.find('.') == std::string::npos) break; auto oldDir = str.substr(0, found + 1); std::string newFile = dir; newFile += file; if (os_file_exists(newFile.c_str())) { if (local_url) { str = "file://"; if (os_get_abs_path( newFile.c_str(), path_buffer, MAX_PATH)) { for (auto i = 0; path_buffer [i] != '\0'; i++) if (path_buffer [i] == '\\') path_buffer[i] = '/'; str += path_buffer; } } else { if (os_get_abs_path( newFile.c_str(), path_buffer, MAX_PATH)) { for (auto i = 0; path_buffer [i] != '\0'; i++) if (path_buffer [i] == '\\') path_buffer[i] = '/'; str = path_buffer; } else { str = ""; } } obs_data_item_set_string( &item, str.c_str()); edit = true; break; } if (found == 0) { found = std::string::npos; } else { found = str.find_last_of( "/\\", found - 1); if (found == std::string::npos) { found = 0; } } } } if (edit) { item = obs_data_first(data); continue; } } else if (type == OBS_DATA_OBJECT) { if (obs_data_t *obj = obs_data_item_get_obj(item)) { try_fix_paths(obj, dir, path_buffer); obs_data_release(obj); } } else if (type == OBS_DATA_ARRAY) { const auto array = obs_data_item_get_array(item); const auto count = obs_data_array_count(array); for (size_t i = 0; i < count; i++) { if (obs_data_t *obj = obs_data_array_item(array, i)) { try_fix_paths(obj, dir, path_buffer); obs_data_release(obj); } } } obs_data_item_next(&item); } } static void try_fix_paths(obs_data_t *data, QString fileName) { char path_buffer[MAX_PATH]; std::string dir = QT_TO_UTF8(fileName); const std::size_t slash = dir.find_last_of("/\\"); if (slash != std::string::npos) { auto point = dir.find_last_of('.'); if (point != std::string::npos && point > slash) { dir = dir.substr(0, point); dir += "/"; try_fix_paths(data, dir.c_str(), path_buffer); } dir = dir.substr(0, slash + 1); } try_fix_paths(data, dir.c_str(), path_buffer); } static void LoadSourceMenu(QMenu *menu, obs_source_t *source, obs_sceneitem_t *item); static void LoadSources(obs_data_array_t *data) { const size_t count = obs_data_array_count(data); std::vector sources; sources.reserve(count); for (size_t i = 0; i < count; i++) { obs_data_t *sourceData = obs_data_array_item(data, i); const char *name = obs_data_get_string(sourceData, "name"); obs_source_t *s = obs_get_source_by_name(name); if (!s) s = obs_load_source(sourceData); if (s) sources.push_back(s); obs_scene_t *scene = obs_scene_from_source(s); if (!scene) scene = obs_group_from_source(s); if (scene) { obs_data_t *scene_settings = obs_data_get_obj(sourceData, "settings"); obs_source_update(s, scene_settings); obs_data_release(scene_settings); } obs_data_release(sourceData); } for (obs_source_t *source : sources) obs_source_load(source); for (obs_source_t *source : sources) obs_source_release(source); } static void LoadScene(obs_data_t *data) { if (!data) return; obs_data_array_t *sourcesData = obs_data_get_array(data, "sources"); if (!sourcesData) return; LoadSources(sourcesData); obs_data_array_release(sourcesData); } obs_data_array_t *GetScriptsData() { const auto config = obs_frontend_get_global_config(); if (!config) return nullptr; const std::string sceneCollection = config_get_string(config, "Basic", "SceneCollection"); const std::string filename = config_get_string(config, "Basic", "SceneCollectionFile"); std::string path = obs_module_config_path("../../basic/scenes/"); path += filename; path += ".json"; obs_frontend_save(); auto data = obs_data_create_from_json_file(path.c_str()); if (!data) return nullptr; auto modules = obs_data_get_obj(data, "modules"); auto scripts = obs_data_get_array(modules, "scripts-tool"); obs_data_release(modules); obs_data_release(data); return scripts; } void LoadScriptData(obs_data_t *script_data) { const auto config = obs_frontend_get_global_config(); if (!config) return; obs_frontend_save(); const std::string sceneCollection = config_get_string(config, "Basic", "SceneCollection"); const std::string filename = config_get_string(config, "Basic", "SceneCollectionFile"); std::string path = obs_module_config_path("../../basic/scenes/"); path += filename; path += ".json"; auto data = obs_data_create_from_json_file(path.c_str()); if (!data) return; auto modules = obs_data_get_obj(data, "modules"); auto scripts = obs_data_get_array(modules, "scripts-tool"); obs_data_release(modules); if (scripts) { obs_data_array_push_back(scripts, script_data); obs_data_array_release(scripts); obs_data_save_json_safe(data, path.c_str(), "tmp", "bak"); obs_data_release(data); config_set_string(config, "Basic", "SceneCollection", ""); config_set_string(config, "Basic", "SceneCollectionFile", "source_copy_temp"); obs_frontend_set_current_scene_collection( sceneCollection.c_str()); std::string temp_path = obs_module_config_path( "../../basic/scenes/scene_collection_manager_temp.json"); os_unlink(temp_path.c_str()); } else { obs_data_release(data); } } static void LoadScriptMenu(QMenu *menu) { menu->clear(); auto a = menu->addAction(QT_UTF8(obs_module_text("LoadScript"))); QObject::connect(a, &QAction::triggered, [] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadScript")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file(QT_TO_UTF8(fileName)); if (!data) return; try_fix_paths(data, fileName); LoadScriptData(data); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("PasteScript"))); QObject::connect(a, &QAction::triggered, [] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; const auto data = obs_data_create_from_json(QT_TO_UTF8(strData)); if (!data) return; LoadScriptData(data); obs_data_release(data); }); const auto scripts = GetScriptsData(); if (!scripts) return; menu->addSeparator(); const size_t size = obs_data_array_count(scripts); for (size_t i = 0; i < size; i++) { auto script = obs_data_array_item(scripts, i); const char *script_path = obs_data_get_string(script, "path"); const char *slash = script_path && *script_path ? strrchr(script_path, '/') : nullptr; QMenu *m; if (slash) { slash++; m = menu->addMenu(QT_UTF8(slash)); } else { m = menu->addMenu(QT_UTF8(script_path)); } QString scriptData = QT_UTF8(obs_data_get_json(script)); a = m->addAction(QT_UTF8(obs_module_text("SaveScript"))); QObject::connect(a, &QAction::triggered, [scriptData] { const QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_module_text("SaveScript")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; auto d = QT_TO_UTF8(scriptData); os_quick_write_utf8_file(QT_TO_UTF8(fileName), d, strlen(d), false); }); a = m->addAction(QT_UTF8(obs_module_text("CopyScript"))); QObject::connect(a, &QAction::triggered, [scriptData] { QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(scriptData); }); } obs_data_array_release(scripts); } static void LoadMenu(QMenu *menu) { menu->clear(); QAction *a = menu->addAction(obs_module_text("LoadScene")); QObject::connect(a, &QAction::triggered, [] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadScene")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file(QT_TO_UTF8(fileName)); try_fix_paths(data, fileName); LoadScene(data); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("PasteScene"))); QObject::connect(a, &QAction::triggered, [] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; obs_data_t *data = obs_data_create_from_json(QT_TO_UTF8(strData)); LoadScene(data); obs_data_release(data); }); auto label = new QLabel("" + QT_UTF8(obs_module_text("Scenes")) + ""); label->setAlignment(Qt::AlignCenter); auto wa = new QWidgetAction(menu); wa->setDefaultWidget(label); menu->addAction(wa); struct obs_frontend_source_list scenes = {}; obs_frontend_get_scenes(&scenes); for (size_t i = 0; i < scenes.sources.num; i++) { obs_source_t *source = scenes.sources.array[i]; QMenu *submenu = menu->addMenu( obs_source_get_name(scenes.sources.array[i])); QObject::connect( submenu, &QMenu::aboutToShow, [submenu, source] { LoadSourceMenu(submenu, source, nullptr); }); } obs_frontend_source_list_free(&scenes); menu->addSeparator(); QMenu *submenu = menu->addMenu(QT_UTF8(obs_module_text("Scripts"))); QObject::connect(submenu, &QMenu::aboutToShow, [submenu] { LoadScriptMenu(submenu); }); } void CopyTransform(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { UNUSED_PARAMETER(data); UNUSED_PARAMETER(id); UNUSED_PARAMETER(hotkey); if (!pressed) return; const auto main_window = static_cast(obs_frontend_get_main_window()); if (!main_window->isActiveWindow()) return; QAction *t = main_window->findChild("actionCopyTransform"); if (t) t->trigger(); } void PasteTransform(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed) { UNUSED_PARAMETER(data); UNUSED_PARAMETER(id); UNUSED_PARAMETER(hotkey); if (!pressed) return; const auto main_window = static_cast(obs_frontend_get_main_window()); if (!main_window->isActiveWindow()) return; QAction *t = main_window->findChild("actionPasteTransform"); if (t) t->trigger(); } obs_hotkey_id copyTransformHotkey = OBS_INVALID_HOTKEY_ID; obs_hotkey_id pasteTransformHotkey = OBS_INVALID_HOTKEY_ID; static void frontend_save_load(obs_data_t *save_data, bool saving, void *) { if (saving) { obs_data_array_t *hotkey_save_array = obs_hotkey_save(copyTransformHotkey); obs_data_set_array(save_data, "copyTransformHotkey", hotkey_save_array); obs_data_array_release(hotkey_save_array); hotkey_save_array = obs_hotkey_save(pasteTransformHotkey); obs_data_set_array(save_data, "pasteTransformHotkey", hotkey_save_array); obs_data_array_release(hotkey_save_array); } else { obs_data_array_t *hotkey_save_array = obs_data_get_array(save_data, "copyTransformHotkey"); obs_hotkey_load(copyTransformHotkey, hotkey_save_array); obs_data_array_release(hotkey_save_array); hotkey_save_array = obs_data_get_array(save_data, "pasteTransformHotkey"); obs_hotkey_load(pasteTransformHotkey, hotkey_save_array); obs_data_array_release(hotkey_save_array); } } bool obs_module_load() { blog(LOG_INFO, "[Source Copy] loaded version %s", PROJECT_VERSION); copyTransformHotkey = obs_hotkey_register_frontend( "actionCopyTransform", obs_module_text("CopyTransform"), CopyTransform, nullptr); pasteTransformHotkey = obs_hotkey_register_frontend( "actionPasteTransform", obs_module_text("PasteTransform"), PasteTransform, nullptr); obs_frontend_add_save_callback(frontend_save_load, nullptr); QAction *action = static_cast(obs_frontend_add_tools_menu_qaction( obs_module_text("SourceCopy"))); QMenu *menu = new QMenu(); action->setMenu(menu); QObject::connect(menu, &QMenu::aboutToShow, [menu] { LoadMenu(menu); }); return true; } void obs_module_unload() { obs_frontend_remove_save_callback(frontend_save_load, nullptr); obs_hotkey_unregister(copyTransformHotkey); obs_hotkey_unregister(pasteTransformHotkey); } MODULE_EXPORT const char *obs_module_description(void) { return obs_module_text("Description"); } MODULE_EXPORT const char *obs_module_name(void) { return obs_module_text("SourceCopy"); } static void AddFilterMenu(obs_source_t *parent, obs_source_t *child, void *data) { UNUSED_PARAMETER(parent); QMenu *menu = static_cast(data); QMenu *submenu = menu->addMenu(QT_UTF8(obs_source_get_name(child))); QAction *a = submenu->addAction(QT_UTF8(obs_module_text("SaveFilter"))); QObject::connect(a, &QAction::triggered, [child] { QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_module_text("SaveFilter")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_save_source(child); obs_data_save_json(data, QT_TO_UTF8(fileName)); obs_data_release(data); }); a = submenu->addAction(QT_UTF8(obs_module_text("CopyFilter"))); QObject::connect(a, &QAction::triggered, [child] { obs_data_t *data = obs_save_source(child); QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(QT_UTF8(obs_data_get_json(data))); obs_data_release(data); }); } static bool AddSceneItemToMenu(obs_scene_t *scene, obs_sceneitem_t *item, void *data) { UNUSED_PARAMETER(scene); QMenu *menu = static_cast(data); obs_source_t *source = obs_sceneitem_get_source(item); QMenu *submenu = menu->addMenu(obs_source_get_name(source)); QObject::connect(submenu, &QMenu::aboutToShow, [submenu, source, item] { LoadSourceMenu(submenu, source, item); }); return true; } static bool SaveSource(obs_scene_t *scene, obs_sceneitem_t *item, void *data) { UNUSED_PARAMETER(scene); obs_data_array_t *sources = static_cast(data); obs_source_t *source = obs_sceneitem_get_source(item); if (!source) return true; const char *name = obs_source_get_name(source); const size_t count = obs_data_array_count(sources); for (size_t i = 0; i < count; i++) { obs_data_t *sourceData = obs_data_array_item(sources, i); obs_data_release(sourceData); if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) return true; } obs_scene_t *nested_scene = obs_scene_from_source(source); if (!nested_scene) nested_scene = obs_group_from_source(source); if (nested_scene) obs_scene_enum_items(nested_scene, SaveSource, sources); obs_data_t *sceneData = obs_save_source(source); obs_data_array_push_back(sources, sceneData); obs_data_release(sceneData); return true; } static void LoadSource(obs_scene_t *scene, obs_data_t *data) { if (!data) return; obs_data_array_t *sourcesData = obs_data_get_array(data, "sources"); if (sourcesData) { LoadSources(sourcesData); obs_data_array_release(sourcesData); } else { const char *name = obs_data_get_string(data, "name"); obs_source_t *source = obs_get_source_by_name(name); if (!source) source = obs_load_source(data); if (source) { if (obs_source_get_type(source) == OBS_SOURCE_TYPE_INPUT) { obs_scene_add(scene, source); obs_source_load(source); } obs_source_release(source); } } obs_data_release(data); } static obs_data_t *GetTransformData(obs_sceneitem_t *item) { obs_data_t *temp = obs_data_create(); obs_transform_info info{}; obs_sceneitem_get_info(item, &info); obs_data_set_vec2(temp, "pos", &info.pos); obs_data_set_vec2(temp, "scale", &info.scale); obs_data_set_double(temp, "rot", info.rot); obs_data_set_int(temp, "alignment", info.alignment); obs_data_set_int(temp, "bounds_type", info.bounds_type); obs_data_set_vec2(temp, "bounds", &info.bounds); obs_data_set_int(temp, "bounds_alignment", info.bounds_alignment); obs_sceneitem_crop crop{}; obs_sceneitem_get_crop(item, &crop); obs_data_set_int(temp, "top", crop.top); obs_data_set_int(temp, "bottom", crop.bottom); obs_data_set_int(temp, "left", crop.left); obs_data_set_int(temp, "right", crop.right); return temp; } void LoadTransform(obs_sceneitem_t *item, obs_data_t *data) { obs_transform_info info{}; obs_sceneitem_get_info(item, &info); obs_data_get_vec2(data, "pos", &info.pos); obs_data_get_vec2(data, "scale", &info.scale); info.rot = obs_data_get_double(data, "rot"); info.alignment = obs_data_get_int(data, "alignment"); info.bounds_type = (enum obs_bounds_type)obs_data_get_int(data, "bounds_type"); obs_data_get_vec2(data, "bounds", &info.bounds); info.bounds_alignment = obs_data_get_int(data, "bounds_alignment"); obs_sceneitem_set_info(item, &info); obs_sceneitem_crop crop{}; crop.top = obs_data_get_int(data, "top"); crop.bottom = obs_data_get_int(data, "bottom"); crop.left = obs_data_get_int(data, "left"); crop.right = obs_data_get_int(data, "right"); obs_sceneitem_set_crop(item, &crop); } static void LoadSourceMenu(QMenu *menu, obs_source_t *source, obs_sceneitem_t *item) { menu->clear(); obs_scene_t *scene = obs_scene_from_source(source); if (!scene) scene = obs_group_from_source(source); QAction *a; if (scene) { a = menu->addAction( QT_UTF8(obs_scene_is_group(scene) ? obs_module_text("SaveGroup") : obs_module_text("SaveScene"))); QObject::connect(a, &QAction::triggered, [scene, source] { QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_scene_is_group(scene) ? obs_module_text("SaveGroup") : obs_module_text("SaveScene")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create(); obs_data_array_t *sources = obs_data_array_create(); obs_data_set_array(data, "sources", sources); obs_scene_enum_items(scene, SaveSource, sources); obs_data_t *sceneData = obs_save_source(source); obs_data_array_push_back(sources, sceneData); obs_data_release(sceneData); obs_data_save_json(data, QT_TO_UTF8(fileName)); obs_data_release(data); }); a = menu->addAction( QT_UTF8(obs_scene_is_group(scene) ? obs_module_text("CopyGroup") : obs_module_text("CopyScene"))); QObject::connect(a, &QAction::triggered, [scene, source] { obs_data_t *data = obs_data_create(); obs_data_array_t *sources = obs_data_array_create(); obs_data_set_array(data, "sources", sources); obs_scene_enum_items(scene, SaveSource, sources); obs_data_t *sceneData = obs_save_source(source); obs_data_array_push_back(sources, sceneData); obs_data_release(sceneData); QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(QT_UTF8(obs_data_get_json(data))); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("LoadSource"))); QObject::connect(a, &QAction::triggered, [scene] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadSource")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file( QT_TO_UTF8(fileName)); try_fix_paths(data, fileName); LoadSource(scene, data); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("PasteSource"))); QObject::connect(a, &QAction::triggered, [scene] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; obs_data_t *data = obs_data_create_from_json(QT_TO_UTF8(strData)); LoadSource(scene, data); obs_data_release(data); }); } else { a = menu->addAction(QT_UTF8(obs_module_text("SaveSource"))); QObject::connect(a, &QAction::triggered, [source] { QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_module_text("SaveSource")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_save_source(source); obs_data_save_json(data, QT_TO_UTF8(fileName)); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("CopySource"))); QObject::connect(a, &QAction::triggered, [source] { obs_data_t *data = obs_save_source(source); QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(QT_UTF8(obs_data_get_json(data))); obs_data_release(data); }); } if (item) { menu->addSeparator(); a = menu->addAction(obs_module_text("LoadTransform")); QObject::connect(a, &QAction::triggered, [item] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadTransform")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file( QT_TO_UTF8(fileName)); LoadTransform(item, data); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("PasteTransform"))); QObject::connect(a, &QAction::triggered, [item] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; obs_data_t *data = obs_data_create_from_json(QT_TO_UTF8(strData)); LoadTransform(item, data); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("SaveTransform"))); QObject::connect(a, &QAction::triggered, [item] { QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_module_text("SaveSource")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *temp = GetTransformData(item); obs_data_save_json(temp, QT_TO_UTF8(fileName)); obs_data_release(temp); }); a = menu->addAction(QT_UTF8(obs_module_text("CopyTransform"))); QObject::connect(a, &QAction::triggered, [item] { obs_data_t *temp = GetTransformData(item); QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(QT_UTF8(obs_data_get_json(temp))); obs_data_release(temp); }); menu->addSeparator(); a = menu->addAction(obs_module_text("LoadShowTransition")); QObject::connect(a, &QAction::triggered, [item] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadShowTransition")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file( QT_TO_UTF8(fileName)); if (const auto t = obs_load_private_source(data)) { obs_sceneitem_set_show_transition(item, t); obs_source_release(t); } obs_data_release(data); }); a = menu->addAction( QT_UTF8(obs_module_text("PasteShowTransition"))); QObject::connect(a, &QAction::triggered, [item] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; obs_data_t *data = obs_data_create_from_json(QT_TO_UTF8(strData)); if (const auto t = obs_load_private_source(data)) { obs_sceneitem_set_show_transition(item, t); obs_source_release(t); } obs_data_release(data); }); a = menu->addAction(obs_module_text("LoadHideTransition")); QObject::connect(a, &QAction::triggered, [item] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadHideTransition")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file( QT_TO_UTF8(fileName)); if (const auto t = obs_load_private_source(data)) { obs_sceneitem_set_transition(item, false, t); obs_source_release(t); } obs_data_release(data); }); a = menu->addAction( QT_UTF8(obs_module_text("PasteHideTransition"))); QObject::connect(a, &QAction::triggered, [item] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; obs_data_t *data = obs_data_create_from_json(QT_TO_UTF8(strData)); if (const auto t = obs_load_private_source(data)) { obs_sceneitem_set_transition(item, false, t); obs_source_release(t); } obs_data_release(data); }); auto st = obs_sceneitem_get_transition(item, true); if (st) { a = menu->addAction( QT_UTF8(obs_module_text("SaveShowTransition"))); QObject::connect(a, &QAction::triggered, [st] { QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_module_text( "SaveShowTransition")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *temp = obs_save_source(st); obs_data_save_json(temp, QT_TO_UTF8(fileName)); obs_data_release(temp); }); a = menu->addAction( QT_UTF8(obs_module_text("CopyShowTransition"))); QObject::connect(a, &QAction::triggered, [st] { obs_data_t *temp = obs_save_source(st); QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText( QT_UTF8(obs_data_get_json(temp))); obs_data_release(temp); }); } auto ht = obs_sceneitem_get_transition(item, false); if (ht) { a = menu->addAction( QT_UTF8(obs_module_text("SaveHideTransition"))); QObject::connect(a, &QAction::triggered, [ht] { QString fileName = QFileDialog::getSaveFileName( nullptr, QT_UTF8(obs_module_text( "SaveHideTransition")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *temp = obs_save_source(ht); obs_data_save_json(temp, QT_TO_UTF8(fileName)); obs_data_release(temp); }); a = menu->addAction( QT_UTF8(obs_module_text("CopyHideTransition"))); QObject::connect(a, &QAction::triggered, [ht] { obs_data_t *temp = obs_save_source(ht); QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText( QT_UTF8(obs_data_get_json(temp))); obs_data_release(temp); }); } } menu->addSeparator(); a = menu->addAction(QT_UTF8(obs_module_text("LoadFilter"))); QObject::connect(a, &QAction::triggered, [source] { QString fileName = QFileDialog::getOpenFileName( nullptr, QT_UTF8(obs_module_text("LoadFilter")), QString(), "JSON File (*.json)"); if (fileName.isEmpty()) return; obs_data_t *data = obs_data_create_from_json_file(QT_TO_UTF8(fileName)); if (!data) return; const char *name = obs_data_get_string(data, "name"); obs_source_t *filter = obs_source_get_filter_by_name(source, name); if (!filter) { try_fix_paths(data, fileName); filter = obs_load_source(data); if (filter && obs_source_get_type(filter) == OBS_SOURCE_TYPE_FILTER) { obs_source_filter_add(source, filter); obs_source_load(filter); } } obs_source_release(filter); obs_data_release(data); }); a = menu->addAction(QT_UTF8(obs_module_text("PasteFilter"))); QObject::connect(a, &QAction::triggered, [source] { QClipboard *clipboard = QGuiApplication::clipboard(); const QString strData = clipboard->text(); if (strData.isEmpty()) return; obs_data_t *data = obs_data_create_from_json(QT_TO_UTF8(strData)); if (!data) return; const char *name = obs_data_get_string(data, "name"); obs_source_t *filter = obs_source_get_filter_by_name(source, name); if (!filter) { filter = obs_load_source(data); if (filter && obs_source_get_type(filter) == OBS_SOURCE_TYPE_FILTER) { obs_source_filter_add(source, filter); obs_source_load(filter); } } obs_source_release(filter); obs_data_release(data); }); if (scene) { auto label = new QLabel( "" + QT_UTF8(obs_module_text("Sources")) + ""); label->setAlignment(Qt::AlignCenter); auto wa = new QWidgetAction(menu); wa->setDefaultWidget(label); menu->addAction(wa); obs_scene_enum_items(scene, AddSceneItemToMenu, menu); if (menu->actions().last() == wa) { menu->removeAction(wa); delete wa; } } auto label = new QLabel("" + QT_UTF8(obs_module_text("Filters")) + ""); label->setAlignment(Qt::AlignCenter); auto wa = new QWidgetAction(menu); wa->setDefaultWidget(label); menu->addAction(wa); obs_source_enum_filters(source, AddFilterMenu, menu); if (menu->actions().last() == wa) { menu->removeAction(wa); delete wa; } } obs-source-copy-0.2.2/.github/0000755000000000000000000000000014352314334014604 5ustar rootrootobs-source-copy-0.2.2/.github/scripts/0000755000000000000000000000000014352314334016273 5ustar rootrootobs-source-copy-0.2.2/.github/scripts/package-linux.sh0000755000000000000000000000040614352314334021362 0ustar rootroot#!/bin/sh if ! type zsh > /dev/null 2>&1; then echo ' => Installing script dependency Zsh.' sudo apt-get update sudo apt-get install zsh fi SCRIPT=$(readlink -f "${0}") SCRIPT_DIR=$(dirname "${SCRIPT}") zsh ${SCRIPT_DIR}/package-linux.zsh "${@}" obs-source-copy-0.2.2/.github/scripts/.package.zsh0000755000000000000000000001364514352314334020506 0ustar rootroot#!/usr/bin/env zsh builtin emulate -L zsh setopt EXTENDED_GLOB setopt PUSHD_SILENT setopt ERR_EXIT setopt ERR_RETURN setopt NO_UNSET setopt PIPE_FAIL setopt NO_AUTO_PUSHD setopt NO_PUSHD_IGNORE_DUPS setopt FUNCTION_ARGZERO ## Enable for script debugging # setopt WARN_CREATE_GLOBAL # setopt WARN_NESTED_VAR # setopt XTRACE autoload -Uz is-at-least && if ! is-at-least 5.2; then print -u2 -PR "%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue." exit 1 fi _trap_error() { print -u2 -PR '%F{1} ✖︎ script execution error%f' print -PR -e " Callstack: ${(j:\n :)funcfiletrace} " exit 2 } package() { if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h} local host_os=${${(s:-:)ZSH_ARGZERO:t:r}[2]} local target="${host_os}-${CPUTYPE}" local project_root=${SCRIPT_HOME:A:h:h} local buildspec_file="${project_root}/buildspec.json" trap '_trap_error' ZERR fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath}) autoload -Uz set_loglevel log_info log_error log_output check_${host_os} local -i _verbosity=1 local -r _version='1.0.0' local -r -a _valid_targets=( macos-x86_64 macos-arm64 macos-universal linux-x86_64 ) local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel) local -r _usage=" Usage: %B${functrace[1]%:*}%b