diff --git a/CI/flatpak/com.obsproject.Studio.json b/CI/flatpak/com.obsproject.Studio.json index 93daeddc8d3547..78d7511ef16d71 100644 --- a/CI/flatpak/com.obsproject.Studio.json +++ b/CI/flatpak/com.obsproject.Studio.json @@ -231,7 +231,8 @@ "-DUSE_XDG=ON", "-DDISABLE_ALSA=ON", "-DENABLE_PULSEAUDIO=ON", - "-DWITH_RTMPS=ON" + "-DWITH_RTMPS=ON", + "-DBUILD_BROWSER=OFF" ], "sources": [ { diff --git a/UI/context-bar-controls.cpp b/UI/context-bar-controls.cpp index 1af2599ab72d3d..dc0d357f6109e8 100644 --- a/UI/context-bar-controls.cpp +++ b/UI/context-bar-controls.cpp @@ -62,7 +62,7 @@ void SourceToolbar::SetUndoProperties(obs_source_t *source) obs_source_t *scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source); + main->SetCurrentScene(scene_source, true); obs_source_release(scene_source); obs_data_release(settings); diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp index 802e74c2b62271..1a14855ddf596b 100644 --- a/UI/source-tree.cpp +++ b/UI/source-tree.cpp @@ -413,7 +413,7 @@ void SourceTreeItem::ExitEditMode(bool save) obs_source_t *scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source); + main->SetCurrentScene(scene_source, true); obs_source_release(scene_source); }; @@ -424,7 +424,7 @@ void SourceTreeItem::ExitEditMode(bool save) obs_source_t *scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source); + main->SetCurrentScene(scene_source, true); obs_source_release(scene_source); }; diff --git a/UI/window-basic-filters.cpp b/UI/window-basic-filters.cpp index a4a55d054ad2f6..81774b000d5735 100644 --- a/UI/window-basic-filters.cpp +++ b/UI/window-basic-filters.cpp @@ -575,7 +575,7 @@ void OBSBasicFilters::AddNewFilter(const char *id) obs_source_t *ssource = obs_get_source_by_name(scene_name.c_str()); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(ssource); + ->SetCurrentScene(ssource, true); obs_source_release(ssource); obs_data_t *dat = @@ -599,7 +599,7 @@ void OBSBasicFilters::AddNewFilter(const char *id) obs_source_t *ssource = obs_get_source_by_name(scene_name.c_str()); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(ssource); + ->SetCurrentScene(ssource, true); obs_source_release(ssource); obs_data_t *dat = @@ -829,7 +829,7 @@ void OBSBasicFilters::on_removeEffectFilter_clicked() scene_name.c_str()); reinterpret_cast( App()->GetMainWindow()) - ->SetCurrentScene(ssource); + ->SetCurrentScene(ssource, true); obs_source_release(ssource); obs_data_t *dat = @@ -854,7 +854,7 @@ void OBSBasicFilters::on_removeEffectFilter_clicked() scene_name.c_str()); reinterpret_cast( App()->GetMainWindow()) - ->SetCurrentScene(ssource); + ->SetCurrentScene(ssource, true); obs_source_release(ssource); obs_data_t *dat = @@ -1134,7 +1134,7 @@ void OBSBasicFilters::FilterNameEdited(QWidget *editor, QListWidget *list) obs_source_t *ssource = obs_get_source_by_name(scene_name.c_str()); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(ssource); + ->SetCurrentScene(ssource, true); obs_source_release(ssource); obs_source_t *source = @@ -1151,7 +1151,7 @@ void OBSBasicFilters::FilterNameEdited(QWidget *editor, QListWidget *list) obs_source_t *ssource = obs_get_source_by_name(scene_name.c_str()); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(ssource); + ->SetCurrentScene(ssource, true); obs_source_release(ssource); obs_source_t *source = diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index d63ffd666bfcc9..b68e359feb4540 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -753,6 +753,8 @@ void OBSBasic::SetCurrentScene(OBSSource scene, bool force) } } + UpdateContextBar(true); + if (scene) { bool userSwitched = (!force && !disableSaving); blog(LOG_INFO, "%s to scene '%s'", diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index ea26503d24bc49..9ed75982f6ba82 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -3739,7 +3739,7 @@ void OBSBasic::RemoveSelectedScene() obs_source_t *scene_source = sources.back(); OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene); + SetCurrentScene(scene, true); /* set original index in list box */ ui->scenes->blockSignals(true); @@ -4927,7 +4927,7 @@ void OBSBasic::on_actionAddScene_triggered() auto redo_fn = [this](const std::string &data) { obs_scene_t *scene = obs_scene_create(data.c_str()); obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source); + SetCurrentScene(source, true); obs_scene_release(scene); }; undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), @@ -4952,12 +4952,12 @@ void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) if (idx == -1 || idx == invalidIdx) return; + ui->scenes->blockSignals(true); QListWidgetItem *item = ui->scenes->takeItem(idx); if (!relative) idx = 0; - ui->scenes->blockSignals(true); ui->scenes->insertItem(idx + offset, item); ui->scenes->setCurrentRow(idx + offset); item->setSelected(true); @@ -7155,7 +7155,7 @@ void undo_redo(const std::string &data) obs_source_t *source = obs_get_source_by_name(obs_data_get_string(dat, "scene_name")); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(source); + ->SetCurrentScene(source, true); obs_source_release(source); obs_data_release(dat); diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index ac03f3850cd1da..cbbdda3880cf00 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -730,7 +730,7 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event) obs_source_t *source = obs_get_source_by_name( obs_data_get_string(dat, "scene_name")); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(source); + ->SetCurrentScene(source, true); obs_source_release(source); obs_data_release(dat); diff --git a/UI/window-basic-properties.cpp b/UI/window-basic-properties.cpp index b6a161ebe0b93d..a616b4c0098491 100644 --- a/UI/window-basic-properties.cpp +++ b/UI/window-basic-properties.cpp @@ -375,7 +375,7 @@ void OBSBasicProperties::on_buttonBox_clicked(QAbstractButton *button) obs_source_t *scene_source = obs_get_source_by_name(scene_name.c_str()); - OBSBasic::Get()->SetCurrentScene(source); + OBSBasic::Get()->SetCurrentScene(source, true); obs_source_release(scene_source); diff --git a/UI/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp index cee30e520355cc..174bdb934d858b 100644 --- a/UI/window-basic-source-select.cpp +++ b/UI/window-basic-source-select.cpp @@ -252,7 +252,7 @@ void OBSBasicSourceSelect::on_buttonBox_accepted() obs_source_t *scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source); + main->SetCurrentScene(scene_source, true); obs_source_release(scene_source); main->RefreshSources(main->GetCurrentScene()); @@ -282,7 +282,7 @@ void OBSBasicSourceSelect::on_buttonBox_accepted() obs_source_t *scene_source = obs_get_source_by_name(scene_name.c_str()); - main->SetCurrentScene(scene_source); + main->SetCurrentScene(scene_source, true); obs_source_release(scene_source); main->RefreshSources(main->GetCurrentScene()); diff --git a/UI/window-basic-transform.cpp b/UI/window-basic-transform.cpp index 4eb38cb185917e..47f2bb8d794064 100644 --- a/UI/window-basic-transform.cpp +++ b/UI/window-basic-transform.cpp @@ -73,7 +73,8 @@ OBSBasicTransform::OBSBasicTransform(OBSBasic *parent) SetScene(scene); SetItem(item); - obs_data_t *wrapper = obs_scene_save_transform_states(scene, false); + obs_data_t *wrapper = + obs_scene_save_transform_states(main->GetCurrentScene(), false); undo_data = std::string(obs_data_get_json(wrapper)); obs_data_release(wrapper); @@ -92,7 +93,7 @@ OBSBasicTransform::~OBSBasicTransform() obs_source_t *source = obs_get_source_by_name( obs_data_get_string(dat, "scene_name")); reinterpret_cast(App()->GetMainWindow()) - ->SetCurrentScene(source); + ->SetCurrentScene(source, true); obs_source_release(source); obs_data_release(dat); obs_scene_load_transform_states(data.c_str()); diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index fb105563ee30d3..e770f6673a3d86 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -2063,6 +2063,7 @@ static void signal_parent(obs_scene_t *parent, const char *command, struct passthrough { obs_data_array_t *ids; + obs_data_array_t *scenes_and_groups; bool all_items; }; @@ -2105,6 +2106,32 @@ bool save_transform_states(obs_scene_t *scene, obs_sceneitem_t *item, obs_data_release(temp); } + obs_source_t *item_source = obs_sceneitem_get_source(item); + + if (obs_source_is_group(item_source)) { + obs_data_t *temp = obs_data_create(); + obs_data_array_t *nids = obs_data_array_create(); + + obs_data_set_string(temp, "scene_name", + obs_source_get_name(item_source)); + obs_data_set_bool(temp, "is_group", true); + obs_data_set_string( + temp, "group_parent", + obs_source_get_name(obs_scene_get_source(scene))); + + struct passthrough npass = {nids, pass->scenes_and_groups, + pass->all_items}; + obs_sceneitem_group_enum_items(item, save_transform_states, + (void *)&npass); + + obs_data_set_array(temp, "items", nids); + + obs_data_array_push_back(pass->scenes_and_groups, temp); + + obs_data_release(temp); + obs_data_array_release(nids); + } + UNUSED_PARAMETER(scene); return true; } @@ -2112,15 +2139,27 @@ bool save_transform_states(obs_scene_t *scene, obs_sceneitem_t *item, obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene, bool all_items) { obs_data_t *wrapper = obs_data_create(); + obs_data_array_t *scenes_and_groups = obs_data_array_create(); obs_data_array_t *item_ids = obs_data_array_create(); - struct passthrough pass = {item_ids, all_items}; - obs_scene_enum_items(scene, save_transform_states, (void *)&pass); - obs_data_set_array(wrapper, "item_ids", item_ids); - obs_data_set_string(wrapper, "scene_name", + struct passthrough pass = {item_ids, scenes_and_groups, all_items}; + + obs_data_t *temp = obs_data_create(); + + obs_data_set_string(temp, "scene_name", obs_source_get_name(obs_scene_get_source(scene))); + obs_data_set_bool(temp, "is_group", false); + + obs_scene_enum_items(scene, save_transform_states, (void *)&pass); + + obs_data_set_array(temp, "items", item_ids); + obs_data_array_push_back(scenes_and_groups, temp); + + obs_data_set_array(wrapper, "scenes_and_groups", scenes_and_groups); obs_data_array_release(item_ids); + obs_data_array_release(scenes_and_groups); + obs_data_release(temp); return wrapper; } @@ -2155,18 +2194,44 @@ void load_transform_states(obs_data_t *temp, void *vp_scene) obs_sceneitem_defer_update_end(item); } +void iterate_scenes_and_groups_transform_states(obs_data_t *data, void *vp) +{ + obs_data_array_t *items = obs_data_get_array(data, "items"); + obs_source_t *scene_source = + obs_get_source_by_name(obs_data_get_string(data, "scene_name")); + obs_scene_t *scene = obs_scene_from_source(scene_source); + + if (obs_data_get_bool(data, "is_group")) { + obs_source_t *parent_source = obs_get_source_by_name( + obs_data_get_string(data, "group_parent")); + obs_scene_t *parent = obs_scene_from_source(parent_source); + obs_sceneitem_t *group = obs_scene_get_group( + parent, obs_data_get_string(data, "scene_name")); + scene = obs_sceneitem_group_get_scene(group); + + obs_source_release(parent_source); + } + + obs_data_array_enum(items, load_transform_states, (void *)scene); + + UNUSED_PARAMETER(vp); + + obs_data_array_release(items); + obs_source_release(scene_source); +} + void obs_scene_load_transform_states(const char *data) { obs_data_t *dat = obs_data_create_from_json(data); - obs_data_array_t *item_ids = obs_data_get_array(dat, "item_ids"); - obs_source_t *source = - obs_get_source_by_name(obs_data_get_string(dat, "scene_name")); - obs_scene_t *scene = obs_scene_from_source(source); - obs_data_array_enum(item_ids, load_transform_states, (void *)scene); + + obs_data_array_t *scenes_and_groups = + obs_data_get_array(dat, "scenes_and_groups"); + + obs_data_array_enum(scenes_and_groups, + iterate_scenes_and_groups_transform_states, NULL); obs_data_release(dat); - obs_data_array_release(item_ids); - obs_source_release(source); + obs_data_array_release(scenes_and_groups); } void obs_sceneitem_select(obs_sceneitem_t *item, bool select) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c index 0fc6a283581570..600ec6d88698eb 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c @@ -284,10 +284,12 @@ static bool nvenc_update(void *data, obs_data_t *settings) "\theight: %d\n" "\t2-pass: %s\n" "\tb-frames: %d\n" + "\tpsycho-aq: %d\n" "\tGPU: %d\n", rc, bitrate, cqp, enc->context->gop_size, preset, profile, enc->context->width, enc->context->height, - twopass ? "true" : "false", enc->context->max_b_frames, gpu); + twopass ? "true" : "false", enc->context->max_b_frames, psycho_aq, + gpu); return nvenc_init_codec(enc); } @@ -578,16 +580,15 @@ obs_properties_t *nvenc_properties_internal(bool ffmpeg) obs_module_text("NVENC.LookAhead")); obs_property_set_long_description( p, obs_module_text("NVENC.LookAhead.ToolTip")); - - p = obs_properties_add_bool( - props, "psycho_aq", - obs_module_text("NVENC.PsychoVisualTuning")); - obs_property_set_long_description( - p, obs_module_text("NVENC.PsychoVisualTuning.ToolTip")); p = obs_properties_add_bool(props, "repeat_headers", "repeat_headers"); obs_property_set_visible(p, false); } + p = obs_properties_add_bool( + props, "psycho_aq", + obs_module_text("NVENC.PsychoVisualTuning")); + obs_property_set_long_description( + p, obs_module_text("NVENC.PsychoVisualTuning.ToolTip")); obs_properties_add_int(props, "gpu", obs_module_text("GPU"), 0, 8, 1); diff --git a/plugins/rtmp-services/data/package.json b/plugins/rtmp-services/data/package.json index eb1ffa8cc7d5d5..416b8cd4588781 100644 --- a/plugins/rtmp-services/data/package.json +++ b/plugins/rtmp-services/data/package.json @@ -1,10 +1,10 @@ { "url": "https://obsproject.com/obs2_update/rtmp-services", - "version": 172, + "version": 173, "files": [ { "name": "services.json", - "version": 172 + "version": 173 } ] } diff --git a/plugins/rtmp-services/data/services.json b/plugins/rtmp-services/data/services.json index 64a35b614d94d6..7a1e7c15513457 100644 --- a/plugins/rtmp-services/data/services.json +++ b/plugins/rtmp-services/data/services.json @@ -1232,6 +1232,12 @@ "keyint": 2, "max video bitrate": 6000, "max audio bitrate": 320, + "supported resolutions": [ + "1280x720", + "852x480", + "480x360" + ], + "max fps": 30, "x264opts": "scenecut=0" } }, diff --git a/plugins/win-dshow/virtualcam-module/placeholder.cpp b/plugins/win-dshow/virtualcam-module/placeholder.cpp index 8b222d1eee95bd..7cfa8631194122 100644 --- a/plugins/win-dshow/virtualcam-module/placeholder.cpp +++ b/plugins/win-dshow/virtualcam-module/placeholder.cpp @@ -1,8 +1,10 @@ #include +#include #include #include #include #include +#include using namespace Gdiplus; @@ -81,24 +83,10 @@ static void convert_placeholder(const uint8_t *rgb_in, int width, int height) } } -static bool load_placeholder_internal() +static bool load_placeholder_common(wchar_t *file) { Status s; - wchar_t file[MAX_PATH]; - if (!GetModuleFileNameW(dll_inst, file, MAX_PATH)) { - return false; - } - - wchar_t *slash = wcsrchr(file, '\\'); - if (!slash) { - return false; - } - - slash[1] = 0; - - StringCbCat(file, sizeof(file), L"placeholder.png"); - Bitmap bmp(file); if (bmp.GetLastStatus() != Status::Ok) { return false; @@ -121,13 +109,58 @@ static bool load_placeholder_internal() return true; } +static bool load_placeholder_external() +{ + wchar_t file[MAX_PATH] = {L""}; + PWSTR pszPath = NULL; + + HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, + &pszPath); + if (hr != S_OK) { + CoTaskMemFree((wchar_t *)pszPath); + return false; + } + + StringCbCat(file, sizeof(file), pszPath); + StringCbCat(file, sizeof(file), + L"\\obs-studio\\plugin_config\\win-dshow\\placeholder.png"); + CoTaskMemFree(pszPath); + + if (std::filesystem::exists(file)) { + return load_placeholder_common(file); + } else { + return false; + } +} + +static bool load_placeholder_internal() +{ + wchar_t file[MAX_PATH]; + if (!GetModuleFileNameW(dll_inst, file, MAX_PATH)) { + return false; + } + + wchar_t *slash = wcsrchr(file, '\\'); + if (!slash) { + return false; + } + + slash[1] = 0; + + StringCbCat(file, sizeof(file), L"placeholder.png"); + + return load_placeholder_common(file); +} + bool initialize_placeholder() { GdiplusStartupInput si; ULONG_PTR token; GdiplusStartup(&token, &si, nullptr); - initialized = load_placeholder_internal(); + initialized = load_placeholder_external(); + if (!initialized) + initialized = load_placeholder_internal(); GdiplusShutdown(token); return initialized;