From 8251005ad3acb54a67b68e2cb0846a783f0aeed7 Mon Sep 17 00:00:00 2001 From: derrod Date: Tue, 23 Apr 2024 05:05:50 +0200 Subject: [PATCH] UI: Add migration for relative coordinate system --- UI/data/locale/en-US.ini | 10 ++ UI/forms/OBSBasic.ui | 6 + UI/window-basic-main-scene-collections.cpp | 91 ++++++++++++++++ UI/window-basic-main.cpp | 121 ++++++++++++++++++++- UI/window-basic-main.hpp | 10 +- 5 files changed, 232 insertions(+), 6 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index a1a3d446c..2f15c5abd 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -594,6 +594,14 @@ Basic.Main.AddSceneCollection.Text="Please enter the name of the scene collectio # rename scene collection dialog Basic.Main.RenameSceneCollection.Title="Rename Scene Collection" +# remigrate scene collection dialog +Basic.Main.RemigrateSceneCollection.Title="Update Scene Collection Resolution" +Basic.Main.RemigrateSceneCollection.Text="Do you want to update the scene collection resolution of \"%1\" to match the current profile's canvas resolution of %2x%3?" +Basic.Main.RemigrateSceneCollection.CannotMigrate.Active="Cannot update scene collection resolution while outputs are active." +Basic.Main.RemigrateSceneCollection.CannotMigrate.UnknownBaseResolution="Failed to update scene collection resolution. The original resolution is unknown." +Basic.Main.RemigrateSceneCollection.CannotMigrate.FailedVideoReset="Reset not possible: Changing OBS resolution failed." +Basic.Main.RemigrateSceneCollection.CannotMigrate.BaseResolutionMatches="Reset not possible: Current resolution already the base resolution of scene collection." + # add profile dialog AddProfile.Title="Add Profile" AddProfile.Text="Please enter the name of the profile" @@ -835,6 +843,8 @@ Basic.MainMenu.Profile.Import="Import Profile" Basic.MainMenu.Profile.Export="Export Profile" Basic.MainMenu.SceneCollection.Import="Import Scene Collection" Basic.MainMenu.SceneCollection.Export="Export Scene Collection" +Basic.MainMenu.SceneCollection.Remigrate="Reset Base Resolution" +Basic.MainMenu.SceneCollection.Migrate="Set Base Resolution" Basic.MainMenu.Profile.Exists="The profile already exists" # basic mode help menu diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index b97fe0950..5c02445d7 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -777,6 +777,7 @@ + @@ -1934,6 +1935,11 @@ Basic.MainMenu.File.ShowMissingFiles + + + Basic.MainMenu.SceneCollection.Remigrate + + New diff --git a/UI/window-basic-main-scene-collections.cpp b/UI/window-basic-main-scene-collections.cpp index b3d6d1881..a1ce86cbb 100644 --- a/UI/window-basic-main-scene-collections.cpp +++ b/UI/window-basic-main-scene-collections.cpp @@ -434,6 +434,97 @@ void OBSBasic::on_actionExportSceneCollection_triggered() } } +void OBSBasic::on_actionRemigrateSceneCollection_triggered() +{ + if (Active()) { + OBSMessageBox::warning( + this, + QTStr("Basic.Main.RemigrateSceneCollection.Title"), + QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.Active")); + return; + } + + OBSDataAutoRelease priv = obs_get_private_data(); + + if (!usingAbsoluteCoordinates && !migrationBaseResolution) { + OBSMessageBox::warning( + this, + QTStr("Basic.Main.RemigrateSceneCollection.Title"), + QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.UnknownBaseResolution")); + return; + } + + obs_video_info ovi; + obs_get_video_info(&ovi); + + if (!usingAbsoluteCoordinates && + migrationBaseResolution->first == ovi.base_width && + migrationBaseResolution->second == ovi.base_height) { + OBSMessageBox::warning( + this, + QTStr("Basic.Main.RemigrateSceneCollection.Title"), + QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.BaseResolutionMatches")); + return; + } + + QString name = config_get_string(App()->GlobalConfig(), "Basic", + "SceneCollection"); + QString message = QTStr("Basic.Main.RemigrateSceneCollection.Text") + .arg(name) + .arg(ovi.base_width) + .arg(ovi.base_height); + + auto answer = OBSMessageBox::question( + this, QTStr("Basic.Main.RemigrateSceneCollection.Title"), + message); + + if (answer == QMessageBox::No) + return; + + lastOutputResolution = {ovi.base_width, ovi.base_height}; + + if (!usingAbsoluteCoordinates) { + /* Temporarily change resolution to migration resolution */ + ovi.base_width = migrationBaseResolution->first; + ovi.base_height = migrationBaseResolution->second; + + if (obs_reset_video(&ovi) != OBS_VIDEO_SUCCESS) { + OBSMessageBox::critical( + this, + QTStr("Basic.Main.RemigrateSceneCollection.Title"), + QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.FailedVideoReset")); + return; + } + } + + char path[512]; + int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/"); + if (ret <= 0) { + blog(LOG_WARNING, "Failed to get scene collection config path"); + return; + } + + std::string fileName = path; + fileName += config_get_string(App()->GlobalConfig(), "Basic", + "SceneCollectionFile"); + fileName += ".json"; + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING); + + /* Save and immediately reload to (re-)run migrations. */ + SaveProjectNow(); + /* Reset video if we potentially changed to a temporary resolution */ + if (!usingAbsoluteCoordinates) + ResetVideo(); + + Load(fileName.c_str(), !usingAbsoluteCoordinates); + RefreshSceneCollections(); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); +} + void OBSBasic::ChangeSceneCollection() { QAction *action = reinterpret_cast(sender()); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 0d0b0df80..5e03d5544 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -953,7 +953,16 @@ void OBSBasic::Save(const char *file) obs_data_set_obj(saveData, "resolution", res); } - if (!obs_data_save_json_safe(saveData, file, "tmp", "bak")) + obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); + + if (migrationBaseResolution && !usingAbsoluteCoordinates) { + OBSDataAutoRelease res = obs_data_create(); + obs_data_set_int(res, "x", migrationBaseResolution->first); + obs_data_set_int(res, "y", migrationBaseResolution->second); + obs_data_set_obj(saveData, "migration_resolution", res); + } + + if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) blog(LOG_ERROR, "Could not save scene data to %s", file); } @@ -1038,6 +1047,20 @@ void OBSBasic::CreateFirstRunSources() Str("Basic.AuxDevice1"), 3); } +void OBSBasic::DisableRelativeCoordinates(bool enable) +{ + /* Allow disabling relative positioning to allow loading collections + * that cannot yet be migrated. */ + OBSDataAutoRelease priv = obs_get_private_data(); + obs_data_set_bool(priv, "AbsoluteCoordinates", enable); + usingAbsoluteCoordinates = enable; + + ui->actionRemigrateSceneCollection->setText( + enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") + : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); + ui->actionRemigrateSceneCollection->setEnabled(enable); +} + void OBSBasic::CreateDefaultScene(bool firstStart) { disableSaving++; @@ -1048,6 +1071,7 @@ void OBSBasic::CreateDefaultScene(bool firstStart) ui->transitionDuration->setValue(300); SetTransition(fadeTransition); + DisableRelativeCoordinates(false); OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); if (firstStart) @@ -1188,10 +1212,11 @@ void OBSBasic::LogScenes() blog(LOG_INFO, "------------------------------------------------"); } -void OBSBasic::Load(const char *file) +void OBSBasic::Load(const char *file, bool remigrate) { disableSaving++; lastOutputResolution.reset(); + migrationBaseResolution.reset(); obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); if (!data) { @@ -1229,7 +1254,7 @@ void OBSBasic::Load(const char *file) return; } - LoadData(data, file); + LoadData(data, file, remigrate); } static inline void AddMissingFiles(void *data, obs_source_t *source) @@ -1241,7 +1266,27 @@ static inline void AddMissingFiles(void *data, obs_source_t *source) obs_missing_files_destroy(sf); } -void OBSBasic::LoadData(obs_data_t *data, const char *file) +static void ClearRelativePosCb(obs_data_t *data, void *) +{ + const string_view id = obs_data_get_string(data, "id"); + if (id != "scene" && id != "group") + return; + + OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); + OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); + + obs_data_array_enum( + items, + [](obs_data_t *data, void *) { + obs_data_unset_user_value(data, "pos_rel"); + obs_data_unset_user_value(data, "scale_rel"); + obs_data_unset_user_value(data, "scale_ref"); + obs_data_unset_user_value(data, "bounds_rel"); + }, + nullptr); +} + +void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) { ClearSceneData(); ClearContextBar(); @@ -1322,9 +1367,70 @@ void OBSBasic::LoadData(obs_data_t *data, const char *file) obs_data_array_push_back_array(sources, groups); } + /* Reset relative coordinate data if forcefully remigrating. */ + if (remigrate) { + obs_data_set_int(data, "version", 1); + obs_data_array_enum(sources, ClearRelativePosCb, nullptr); + } + + bool resetVideo = false; + bool disableRelativeCoords = false; + obs_video_info ovi; + + int64_t version = obs_data_get_int(data, "version"); + OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); + if (res) { + lastOutputResolution = {obs_data_get_int(res, "x"), + obs_data_get_int(res, "y")}; + } + + /* Only migrate legacy collection if resolution is saved. */ + if (version < 2 && lastOutputResolution) { + obs_get_video_info(&ovi); + + uint32_t width = obs_data_get_int(res, "x"); + uint32_t height = obs_data_get_int(res, "y"); + + migrationBaseResolution = {width, height}; + + if (ovi.base_height != height || ovi.base_width != width) { + ovi.base_width = width; + ovi.base_height = height; + + /* Attempt to reset to last known canvas resolution for migration. */ + resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; + disableRelativeCoords = !resetVideo; + } + + /* If migration is possible, and it wasn't forced, back up the original file. */ + if (!disableRelativeCoords && !remigrate) { + auto path = filesystem::u8path(file); + auto backupPath = path.concat(".v1"); + if (!filesystem::exists(backupPath)) { + if (!obs_data_save_json_pretty_safe( + data, backupPath.u8string().c_str(), + "tmp", NULL)) { + blog(LOG_WARNING, + "Failed to create a backup of existing scene collection data!"); + } + } + } + } else if (version < 2) { + disableRelativeCoords = true; + } else if (OBSDataAutoRelease migration_res = + obs_data_get_obj(data, "migration_resolution")) { + migrationBaseResolution = {obs_data_get_int(migration_res, "x"), + obs_data_get_int(migration_res, + "y")}; + } + + DisableRelativeCoordinates(disableRelativeCoords); + obs_missing_files_t *files = obs_missing_files_create(); obs_load_sources(sources, AddMissingFiles, files); + if (resetVideo) + ResetVideo(); if (transitions) LoadTransitions(transitions, AddMissingFiles, files); if (sceneOrder) @@ -4953,6 +5059,13 @@ int OBSBasic::ResetVideo() OBSBasicStats::InitializeValues(); OBSProjector::UpdateMultiviewProjectors(); + bool canMigrate = + usingAbsoluteCoordinates || + (migrationBaseResolution && + (migrationBaseResolution->first != ovi.base_width || + migrationBaseResolution->second != ovi.base_height)); + ui->actionRemigrateSceneCollection->setEnabled(canMigrate); + emit CanvasResized(ovi.base_width, ovi.base_height); emit OutputResized(ovi.output_width, ovi.output_height); } diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 5f6fea052..3f2134832 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -358,6 +358,10 @@ private: std::atomic currentScene = nullptr; std::optional> lastOutputResolution; + std::optional> migrationBaseResolution; + bool usingAbsoluteCoordinates = false; + + void DisableRelativeCoordinates(bool disable); void OnEvent(enum obs_frontend_event event); @@ -377,8 +381,9 @@ private: void UploadLog(const char *subdir, const char *file, const bool crash); void Save(const char *file); - void LoadData(obs_data_t *data, const char *file); - void Load(const char *file); + void LoadData(obs_data_t *data, const char *file, + bool remigrate = false); + void Load(const char *file, bool remigrate = false); void InitHotkeys(); void CreateHotkeys(); @@ -1149,6 +1154,7 @@ private slots: void on_actionRemoveSceneCollection_triggered(); void on_actionImportSceneCollection_triggered(); void on_actionExportSceneCollection_triggered(); + void on_actionRemigrateSceneCollection_triggered(); void on_actionNewProfile_triggered(); void on_actionDupProfile_triggered();