UI: Rewrite profile system to enable user-provided storage location

This change enables loading profiles from locations different than
OBS' own configuration directory.

It also rewrites profile management in the app to work off an in-memory
collection of profiles found on disk and does not require iterating
over directory contents for most profile interactions by the app.
This commit is contained in:
PatTheMav 2024-09-03 16:29:30 +02:00 committed by Ryan Foster
parent 2635cf3a2a
commit 607d37b423
9 changed files with 1772 additions and 1601 deletions

View File

@ -16,7 +16,6 @@ template<typename T> static T GetOBSRef(QListWidgetItem *item)
return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>(); return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
} }
void EnumProfiles(function<bool(const char *, const char *)> &&cb);
void EnumSceneCollections(function<bool(const char *, const char *)> &&cb); void EnumSceneCollections(function<bool(const char *, const char *)> &&cb);
extern volatile bool streaming_active; extern volatile bool streaming_active;
@ -218,29 +217,24 @@ struct OBSStudioAPI : obs_frontend_callbacks {
void void
obs_frontend_get_profiles(std::vector<std::string> &strings) override obs_frontend_get_profiles(std::vector<std::string> &strings) override
{ {
auto addProfile = [&](const char *name, const char *) { const OBSProfileCache &profiles = main->GetProfileCache();
strings.emplace_back(name);
return true;
};
EnumProfiles(addProfile); for (auto &[profileName, profile] : profiles) {
strings.emplace_back(profileName);
}
} }
char *obs_frontend_get_current_profile(void) override char *obs_frontend_get_current_profile(void) override
{ {
const char *name = config_get_string(App()->GlobalConfig(), const OBSProfile &profile = main->GetCurrentProfile();
"Basic", "Profile"); return bstrdup(profile.name.c_str());
return bstrdup(name);
} }
char *obs_frontend_get_current_profile_path(void) override char *obs_frontend_get_current_profile_path(void) override
{ {
char profilePath[512]; const OBSProfile &profile = main->GetCurrentProfile();
int ret = GetProfilePath(profilePath, sizeof(profilePath), "");
if (ret <= 0)
return nullptr;
return bstrdup(profilePath); return bstrdup(profile.path.u8string().c_str());
} }
void obs_frontend_set_current_profile(const char *profile) override void obs_frontend_set_current_profile(const char *profile) override
@ -510,7 +504,9 @@ struct OBSStudioAPI : obs_frontend_callbacks {
config_t *obs_frontend_get_profile_config(void) override config_t *obs_frontend_get_profile_config(void) override
{ {
return main->basicConfig; return main->activeConfiguration;
}
config_t *obs_frontend_get_global_config(void) override config_t *obs_frontend_get_global_config(void) override
{ {
blog(LOG_WARNING, blog(LOG_WARNING,
@ -544,20 +540,16 @@ struct OBSStudioAPI : obs_frontend_callbacks {
else if (astrcmpi(type, "Scene") == 0) else if (astrcmpi(type, "Scene") == 0)
proj.type = ProjectorType::Scene; proj.type = ProjectorType::Scene;
else if (astrcmpi(type, "StudioProgram") == 0) else if (astrcmpi(type, "StudioProgram") == 0)
proj.type = proj.type = ProjectorType::StudioProgram;
ProjectorType::StudioProgram;
else if (astrcmpi(type, "Multiview") == 0) else if (astrcmpi(type, "Multiview") == 0)
proj.type = ProjectorType::Multiview; proj.type = ProjectorType::Multiview;
} }
QMetaObject::invokeMethod( QMetaObject::invokeMethod(main, "OpenSavedProjector",
main, "OpenSavedProjector", WaitConnection(), WaitConnection(),
Q_ARG(SavedProjectorInfo *, &proj)); Q_ARG(SavedProjectorInfo *, &proj));
} }
void obs_frontend_save(void) override void obs_frontend_save(void) override { main->SaveProject(); }
{
main->SaveProject();
}
void obs_frontend_defer_save_begin(void) override void obs_frontend_defer_save_begin(void) override
{ {
@ -569,43 +561,37 @@ struct OBSStudioAPI : obs_frontend_callbacks {
QMetaObject::invokeMethod(main, "DeferSaveEnd"); QMetaObject::invokeMethod(main, "DeferSaveEnd");
} }
void obs_frontend_add_save_callback( void obs_frontend_add_save_callback(obs_frontend_save_cb callback,
obs_frontend_save_cb callback, void *private_data) void *private_data) override
override
{ {
size_t idx = GetCallbackIdx(saveCallbacks, callback, size_t idx =
private_data); GetCallbackIdx(saveCallbacks, callback, private_data);
if (idx == (size_t)-1) if (idx == (size_t)-1)
saveCallbacks.emplace_back(callback, saveCallbacks.emplace_back(callback, private_data);
private_data);
} }
void obs_frontend_remove_save_callback( void obs_frontend_remove_save_callback(obs_frontend_save_cb callback,
obs_frontend_save_cb callback, void *private_data) void *private_data) override
override
{ {
size_t idx = GetCallbackIdx(saveCallbacks, callback, size_t idx =
private_data); GetCallbackIdx(saveCallbacks, callback, private_data);
if (idx == (size_t)-1) if (idx == (size_t)-1)
return; return;
saveCallbacks.erase(saveCallbacks.begin() + idx); saveCallbacks.erase(saveCallbacks.begin() + idx);
} }
void obs_frontend_add_preload_callback( void obs_frontend_add_preload_callback(obs_frontend_save_cb callback,
obs_frontend_save_cb callback, void *private_data) void *private_data) override
override
{ {
size_t idx = GetCallbackIdx(preloadCallbacks, callback, size_t idx = GetCallbackIdx(preloadCallbacks, callback,
private_data); private_data);
if (idx == (size_t)-1) if (idx == (size_t)-1)
preloadCallbacks.emplace_back(callback, preloadCallbacks.emplace_back(callback, private_data);
private_data);
} }
void obs_frontend_remove_preload_callback( void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback,
obs_frontend_save_cb callback, void *private_data) void *private_data) override
override
{ {
size_t idx = GetCallbackIdx(preloadCallbacks, callback, size_t idx = GetCallbackIdx(preloadCallbacks, callback,
private_data); private_data);
@ -626,8 +612,7 @@ struct OBSStudioAPI : obs_frontend_callbacks {
App()->PopUITranslation(); App()->PopUITranslation();
} }
void obs_frontend_set_streaming_service(obs_service_t * service) void obs_frontend_set_streaming_service(obs_service_t *service) override
override
{ {
main->SetService(service); main->SetService(service);
} }
@ -652,8 +637,7 @@ struct OBSStudioAPI : obs_frontend_callbacks {
main->SetPreviewProgramMode(enable); main->SetPreviewProgramMode(enable);
} }
void obs_frontend_preview_program_trigger_transition(void) void obs_frontend_preview_program_trigger_transition(void) override
override
{ {
QMetaObject::invokeMethod(main, "TransitionClicked"); QMetaObject::invokeMethod(main, "TransitionClicked");
} }
@ -669,25 +653,23 @@ struct OBSStudioAPI : obs_frontend_callbacks {
main->EnablePreviewDisplay(enable); main->EnablePreviewDisplay(enable);
} }
obs_source_t *obs_frontend_get_current_preview_scene(void) obs_source_t *obs_frontend_get_current_preview_scene(void) override
override
{ {
if (main->IsPreviewProgramMode()) { if (main->IsPreviewProgramMode()) {
OBSSource source = OBSSource source = main->GetCurrentSceneSource();
main->GetCurrentSceneSource();
return obs_source_get_ref(source); return obs_source_get_ref(source);
} }
return nullptr; return nullptr;
} }
void obs_frontend_set_current_preview_scene(obs_source_t * void
scene) override obs_frontend_set_current_preview_scene(obs_source_t *scene) override
{ {
if (main->IsPreviewProgramMode()) { if (main->IsPreviewProgramMode()) {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(main, "SetCurrentScene",
main, "SetCurrentScene", Q_ARG(OBSSource,
Q_ARG(OBSSource, OBSSource(scene)), OBSSource(scene)),
Q_ARG(bool, false)); Q_ARG(bool, false));
} }
} }
@ -697,18 +679,15 @@ struct OBSStudioAPI : obs_frontend_callbacks {
QMetaObject::invokeMethod(main, "Screenshot"); QMetaObject::invokeMethod(main, "Screenshot");
} }
void obs_frontend_take_source_screenshot(obs_source_t * source) void obs_frontend_take_source_screenshot(obs_source_t *source) override
override
{ {
QMetaObject::invokeMethod(main, "Screenshot", QMetaObject::invokeMethod(main, "Screenshot",
Q_ARG(OBSSource, Q_ARG(OBSSource, OBSSource(source)));
OBSSource(source)));
} }
obs_output_t *obs_frontend_get_virtualcam_output(void) override obs_output_t *obs_frontend_get_virtualcam_output(void) override
{ {
OBSOutput output = OBSOutput output = main->outputHandler->virtualCam.Get();
main->outputHandler->virtualCam.Get();
return obs_output_get_ref(output); return obs_output_get_ref(output);
} }
@ -727,37 +706,28 @@ struct OBSStudioAPI : obs_frontend_callbacks {
return os_atomic_load_bool(&virtualcam_active); return os_atomic_load_bool(&virtualcam_active);
} }
void obs_frontend_reset_video(void) override void obs_frontend_reset_video(void) override { main->ResetVideo(); }
{
main->ResetVideo();
}
void obs_frontend_open_source_properties(obs_source_t * source) void obs_frontend_open_source_properties(obs_source_t *source) override
override
{ {
QMetaObject::invokeMethod(main, "OpenProperties", QMetaObject::invokeMethod(main, "OpenProperties",
Q_ARG(OBSSource, Q_ARG(OBSSource, OBSSource(source)));
OBSSource(source)));
} }
void obs_frontend_open_source_filters(obs_source_t * source) void obs_frontend_open_source_filters(obs_source_t *source) override
override
{ {
QMetaObject::invokeMethod(main, "OpenFilters", QMetaObject::invokeMethod(main, "OpenFilters",
Q_ARG(OBSSource, Q_ARG(OBSSource, OBSSource(source)));
OBSSource(source)));
} }
void obs_frontend_open_source_interaction(obs_source_t * source) void obs_frontend_open_source_interaction(obs_source_t *source) override
override
{ {
QMetaObject::invokeMethod(main, "OpenInteraction", QMetaObject::invokeMethod(main, "OpenInteraction",
Q_ARG(OBSSource, Q_ARG(OBSSource, OBSSource(source)));
OBSSource(source)));
} }
void obs_frontend_open_sceneitem_edit_transform( void obs_frontend_open_sceneitem_edit_transform(
obs_sceneitem_t * item) override obs_sceneitem_t *item) override
{ {
QMetaObject::invokeMethod(main, "OpenEditTransform", QMetaObject::invokeMethod(main, "OpenEditTransform",
Q_ARG(OBSSceneItem, Q_ARG(OBSSceneItem,
@ -766,14 +736,12 @@ struct OBSStudioAPI : obs_frontend_callbacks {
char *obs_frontend_get_current_record_output_path(void) override char *obs_frontend_get_current_record_output_path(void) override
{ {
const char *recordOutputPath = const char *recordOutputPath = main->GetCurrentOutputPath();
main->GetCurrentOutputPath();
return bstrdup(recordOutputPath); return bstrdup(recordOutputPath);
} }
const char *obs_frontend_get_locale_string(const char *string) const char *obs_frontend_get_locale_string(const char *string) override
override
{ {
return Str(string); return Str(string);
} }
@ -785,8 +753,7 @@ struct OBSStudioAPI : obs_frontend_callbacks {
char *obs_frontend_get_last_recording(void) override char *obs_frontend_get_last_recording(void) override
{ {
return bstrdup( return bstrdup(main->outputHandler->lastRecordingPath.c_str());
main->outputHandler->lastRecordingPath.c_str());
} }
char *obs_frontend_get_last_screenshot(void) override char *obs_frontend_get_last_screenshot(void) override
@ -799,23 +766,21 @@ struct OBSStudioAPI : obs_frontend_callbacks {
return bstrdup(main->lastReplay.c_str()); return bstrdup(main->lastReplay.c_str());
} }
void obs_frontend_add_undo_redo_action( void obs_frontend_add_undo_redo_action(const char *name,
const char *name, const undo_redo_cb undo, const undo_redo_cb undo,
const undo_redo_cb redo, const char *undo_data, const undo_redo_cb redo,
const char *redo_data, bool repeatable) override const char *undo_data,
const char *redo_data,
bool repeatable) override
{ {
main->undo_s.add_action( main->undo_s.add_action(
name, name,
[undo](const std::string &data) { [undo](const std::string &data) { undo(data.c_str()); },
undo(data.c_str()); [redo](const std::string &data) { redo(data.c_str()); },
},
[redo](const std::string &data) {
redo(data.c_str());
},
undo_data, redo_data, repeatable); undo_data, redo_data, repeatable);
} }
void on_load(obs_data_t * settings) override void on_load(obs_data_t *settings) override
{ {
for (size_t i = saveCallbacks.size(); i > 0; i--) { for (size_t i = saveCallbacks.size(); i > 0; i--) {
auto cb = saveCallbacks[i - 1]; auto cb = saveCallbacks[i - 1];
@ -823,7 +788,7 @@ struct OBSStudioAPI : obs_frontend_callbacks {
} }
} }
void on_preload(obs_data_t * settings) override void on_preload(obs_data_t *settings) override
{ {
for (size_t i = preloadCallbacks.size(); i > 0; i--) { for (size_t i = preloadCallbacks.size(); i > 0; i--) {
auto cb = preloadCallbacks[i - 1]; auto cb = preloadCallbacks[i - 1];
@ -831,7 +796,7 @@ struct OBSStudioAPI : obs_frontend_callbacks {
} }
} }
void on_save(obs_data_t * settings) override void on_save(obs_data_t *settings) override
{ {
for (size_t i = saveCallbacks.size(); i > 0; i--) { for (size_t i = saveCallbacks.size(); i > 0; i--) {
auto cb = saveCallbacks[i - 1]; auto cb = saveCallbacks[i - 1];
@ -842,8 +807,7 @@ struct OBSStudioAPI : obs_frontend_callbacks {
void on_event(enum obs_frontend_event event) override void on_event(enum obs_frontend_event event) override
{ {
if (main->disableSaving && if (main->disableSaving &&
event != event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
event != OBS_FRONTEND_EVENT_EXIT) event != OBS_FRONTEND_EVENT_EXIT)
return; return;
@ -852,11 +816,11 @@ struct OBSStudioAPI : obs_frontend_callbacks {
cb.callback(event, cb.private_data); cb.callback(event, cb.private_data);
} }
} }
}; };
obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main) obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main)
{ {
obs_frontend_callbacks *api = new OBSStudioAPI(main); obs_frontend_callbacks *api = new OBSStudioAPI(main);
obs_frontend_set_callbacks_internal(api); obs_frontend_set_callbacks_internal(api);
return api; return api;
} }

View File

@ -609,111 +609,43 @@ static bool MakeUserDirs()
return true; return true;
} }
constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles";
constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes";
static bool MakeUserProfileDirs() static bool MakeUserProfileDirs()
{ {
char path[512]; const std::filesystem::path userProfilePath =
App()->userProfilesLocation /
std::filesystem::u8path(OBSProfileSubDirectory);
const std::filesystem::path userScenesPath =
App()->userScenesLocation /
std::filesystem::u8path(OBSScenesSubDirectory);
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0) if (!std::filesystem::exists(userProfilePath)) {
return false; try {
if (!do_mkdir(path)) std::filesystem::create_directories(userProfilePath);
} catch (const std::filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create user profile directory '%s'\n%s",
userProfilePath.u8string().c_str(), error.what());
return false; return false;
}
}
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0) if (!std::filesystem::exists(userScenesPath)) {
return false; try {
if (!do_mkdir(path)) std::filesystem::create_directories(userScenesPath);
} catch (const std::filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create user scene collection directory '%s'\n%s",
userScenesPath.u8string().c_str(), error.what());
return false; return false;
}
}
return true; return true;
} }
static string GetProfileDirFromName(const char *name)
{
string outputPath;
os_glob_t *glob;
char path[512];
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0)
return outputPath;
strcat(path, "/*");
if (os_glob(path, 0, &glob) != 0)
return outputPath;
for (size_t i = 0; i < glob->gl_pathc; i++) {
struct os_globent ent = glob->gl_pathv[i];
if (!ent.directory)
continue;
strcpy(path, ent.path);
strcat(path, "/basic.ini");
ConfigFile config;
if (config.Open(path, CONFIG_OPEN_EXISTING) != 0)
continue;
const char *curName =
config_get_string(config, "General", "Name");
if (astrcmpi(curName, name) == 0) {
outputPath = ent.path;
break;
}
}
os_globfree(glob);
if (!outputPath.empty()) {
replace(outputPath.begin(), outputPath.end(), '\\', '/');
const char *start = strrchr(outputPath.c_str(), '/');
if (start)
outputPath.erase(0, start - outputPath.c_str() + 1);
}
return outputPath;
}
static string GetSceneCollectionFileFromName(const char *name)
{
string outputPath;
os_glob_t *glob;
char path[512];
if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0)
return outputPath;
strcat(path, "/*.json");
if (os_glob(path, 0, &glob) != 0)
return outputPath;
for (size_t i = 0; i < glob->gl_pathc; i++) {
struct os_globent ent = glob->gl_pathv[i];
if (ent.directory)
continue;
OBSDataAutoRelease data =
obs_data_create_from_json_file_safe(ent.path, "bak");
const char *curName = obs_data_get_string(data, "name");
if (astrcmpi(name, curName) == 0) {
outputPath = ent.path;
break;
}
}
os_globfree(glob);
if (!outputPath.empty()) {
outputPath.resize(outputPath.size() - 5);
replace(outputPath.begin(), outputPath.end(), '\\', '/');
const char *start = strrchr(outputPath.c_str(), '/');
if (start)
outputPath.erase(0, start - outputPath.c_str() + 1);
}
return outputPath;
}
bool OBSApp::UpdatePre22MultiviewLayout(const char *layout) bool OBSApp::UpdatePre22MultiviewLayout(const char *layout)
{ {
if (!layout) if (!layout)
@ -1212,56 +1144,77 @@ OBSApp::~OBSApp()
static void move_basic_to_profiles(void) static void move_basic_to_profiles(void)
{ {
char path[512]; char path[512];
char new_path[512];
os_glob_t *glob;
/* if not first time use */ if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) {
if (GetConfigPath(path, 512, "obs-studio/basic") <= 0)
return; return;
if (!os_file_exists(path))
return;
/* if the profiles directory doesn't already exist */
if (GetConfigPath(new_path, 512, "obs-studio/basic/profiles") <= 0)
return;
if (os_file_exists(new_path))
return;
if (os_mkdir(new_path) == MKDIR_ERROR)
return;
strcat(new_path, "/");
strcat(new_path, Str("Untitled"));
if (os_mkdir(new_path) == MKDIR_ERROR)
return;
strcat(path, "/*.*");
if (os_glob(path, 0, &glob) != 0)
return;
strcpy(path, new_path);
for (size_t i = 0; i < glob->gl_pathc; i++) {
struct os_globent ent = glob->gl_pathv[i];
char *file;
if (ent.directory)
continue;
file = strrchr(ent.path, '/');
if (!file++)
continue;
if (astrcmpi(file, "scenes.json") == 0)
continue;
strcpy(new_path, path);
strcat(new_path, "/");
strcat(new_path, file);
os_rename(ent.path, new_path);
} }
os_globfree(glob); const std::filesystem::path basicPath = std::filesystem::u8path(path);
if (!std::filesystem::exists(basicPath)) {
return;
}
const std::filesystem::path profilesPath =
App()->userProfilesLocation /
std::filesystem::u8path("obs-studio/basic/profiles");
if (std::filesystem::exists(profilesPath)) {
return;
}
try {
std::filesystem::create_directories(profilesPath);
} catch (const std::filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create profiles directory for migration from basic profile\n%s",
error.what());
return;
}
const std::filesystem::path newProfilePath =
profilesPath / std::filesystem::u8path(Str("Untitled"));
for (auto &entry : std::filesystem::directory_iterator(basicPath)) {
if (entry.is_directory()) {
continue;
}
if (entry.path().filename().u8string() == "scenes.json") {
continue;
}
if (!std::filesystem::exists(newProfilePath)) {
try {
std::filesystem::create_directory(
newProfilePath);
} catch (
const std::filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create profile directory for 'Untitled'\n%s",
error.what());
return;
}
}
const filesystem::path destinationFile =
newProfilePath / entry.path().filename();
const auto copyOptions =
std::filesystem::copy_options::overwrite_existing;
try {
std::filesystem::copy(entry.path(), destinationFile,
copyOptions);
} catch (const std::filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to copy basic profile file '%s' to new profile 'Untitled'\n%s",
entry.path().filename().u8string().c_str(),
error.what());
return;
}
}
} }
static void move_basic_to_scene_collections(void) static void move_basic_to_scene_collections(void)

View File

@ -39,18 +39,24 @@ extern QCefCookieManager *panel_cookies;
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
#define SERVICE_PATH "service.json" constexpr std::string_view OBSServiceFileName = "service.json";
static OBSData OpenServiceSettings(std::string &type) static OBSData OpenServiceSettings(std::string &type)
{ {
char serviceJsonPath[512]; const OBSBasic *basic =
int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath), reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
SERVICE_PATH); const OBSProfile &currentProfile = basic->GetCurrentProfile();
if (ret <= 0)
return OBSData();
OBSDataAutoRelease data = const std::filesystem::path jsonFilePath =
obs_data_create_from_json_file_safe(serviceJsonPath, "bak"); currentProfile.path /
std::filesystem::u8path(OBSServiceFileName);
if (!std::filesystem::exists(jsonFilePath)) {
return OBSData();
}
OBSDataAutoRelease data = obs_data_create_from_json_file_safe(
jsonFilePath.u8string().c_str(), "bak");
obs_data_set_default_string(data, "type", "rtmp_common"); obs_data_set_default_string(data, "type", "rtmp_common");
type = obs_data_get_string(data, "type"); type = obs_data_get_string(data, "type");

View File

@ -1616,19 +1616,28 @@ struct AdvancedOutput : BasicOutputHandler {
static OBSData GetDataFromJsonFile(const char *jsonFile) static OBSData GetDataFromJsonFile(const char *jsonFile)
{ {
char fullPath[512]; const OBSBasic *basic =
reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = basic->GetCurrentProfile();
const std::filesystem::path jsonFilePath =
currentProfile.path / std::filesystem::u8path(jsonFile);
OBSDataAutoRelease data = nullptr; OBSDataAutoRelease data = nullptr;
int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile); if (!jsonFilePath.empty()) {
if (ret > 0) { BPtr<char> jsonData = os_quick_read_utf8_file(
BPtr<char> jsonData = os_quick_read_utf8_file(fullPath); jsonFilePath.u8string().c_str());
if (!!jsonData) { if (!!jsonData) {
data = obs_data_create_from_json(jsonData); data = obs_data_create_from_json(jsonData);
} }
} }
if (!data) if (!data) {
data = obs_data_create(); data = obs_data_create();
}
return data.Get(); return data.Get();
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -134,6 +134,37 @@ private:
std::shared_ptr<OBSSignal> renamedSignal; std::shared_ptr<OBSSignal> renamedSignal;
}; };
struct OBSProfile {
std::string name;
std::string directoryName;
std::filesystem::path path;
std::filesystem::path profileFile;
};
struct OBSSceneCollection {
std::string name;
std::string fileName;
std::filesystem::path collectionFile;
};
struct OBSPromptResult {
bool success;
std::string promptValue;
bool optionValue;
};
struct OBSPromptRequest {
std::string title;
std::string prompt;
std::string promptValue;
bool withOption;
std::string optionPrompt;
bool optionValue;
};
using OBSPromptCallback = std::function<bool(const OBSPromptResult &result)>;
using OBSProfileCache = std::map<std::string, OBSProfile>;
class ColorSelect : public QWidget { class ColorSelect : public QWidget {
public: public:
@ -443,17 +474,6 @@ private:
void RefreshSceneCollections(); void RefreshSceneCollections();
void ChangeSceneCollection(); void ChangeSceneCollection();
void LogScenes(); void LogScenes();
void ResetProfileData();
bool AddProfile(bool create_new, const char *title, const char *text,
const char *init_text = nullptr, bool rename = false);
bool CreateProfile(const std::string &newName, bool create_new,
bool showWizardChecked, bool rename = false);
void DeleteProfile(const char *profile_name, const char *profile_dir);
void RefreshProfiles();
void ChangeProfile();
void CheckForSimpleModeX264Fallback();
void SaveProjectNow(); void SaveProjectNow();
int GetTopSelectedSourceItem(); int GetTopSelectedSourceItem();
@ -742,11 +762,6 @@ public slots:
bool AddSceneCollection(bool create_new, bool AddSceneCollection(bool create_new,
const QString &name = QString()); const QString &name = QString());
bool NewProfile(const QString &name);
bool DuplicateProfile(const QString &name);
void DeleteProfile(const QString &profileName);
void UpdatePatronJson(const QString &text, const QString &error); void UpdatePatronJson(const QString &text, const QString &error);
void ShowContextBar(); void ShowContextBar();
@ -1156,13 +1171,6 @@ private slots:
void on_actionExportSceneCollection_triggered(); void on_actionExportSceneCollection_triggered();
void on_actionRemigrateSceneCollection_triggered(); void on_actionRemigrateSceneCollection_triggered();
void on_actionNewProfile_triggered();
void on_actionDupProfile_triggered();
void on_actionRenameProfile_triggered();
void on_actionRemoveProfile_triggered(bool skipConfirmation = false);
void on_actionImportProfile_triggered();
void on_actionExportProfile_triggered();
void on_actionShowSettingsFolder_triggered(); void on_actionShowSettingsFolder_triggered();
void on_actionShowProfileFolder_triggered(); void on_actionShowProfileFolder_triggered();
@ -1337,6 +1345,59 @@ public:
void DeleteYouTubeAppDock(); void DeleteYouTubeAppDock();
YouTubeAppDock *GetYouTubeAppDock(); YouTubeAppDock *GetYouTubeAppDock();
#endif #endif
// MARK: - Generic UI Helper Functions
OBSPromptResult PromptForName(const OBSPromptRequest &request,
const OBSPromptCallback &callback);
// MARK: - OBS Profile Management
private:
OBSProfileCache profiles{};
void SetupNewProfile(const std::string &profileName,
bool useWizard = false);
void SetupDuplicateProfile(const std::string &profileName);
void SetupRenameProfile(const std::string &profileName);
const OBSProfile &CreateProfile(const std::string &profileName);
void RemoveProfile(OBSProfile profile);
void ChangeProfile();
void RefreshProfileCache();
void RefreshProfiles(bool refreshCache = false);
void ActivateProfile(const OBSProfile &profile, bool reset = false);
std::vector<std::string>
GetRestartRequirements(const ConfigFile &config) const;
void ResetProfileData();
void CheckForSimpleModeX264Fallback();
public:
inline const OBSProfileCache &GetProfileCache() const noexcept
{
return profiles;
};
const OBSProfile &GetCurrentProfile() const;
std::optional<OBSProfile>
GetProfileByName(const std::string &profileName) const;
std::optional<OBSProfile>
GetProfileByDirectoryName(const std::string &directoryName) const;
private slots:
void on_actionNewProfile_triggered();
void on_actionDupProfile_triggered();
void on_actionRenameProfile_triggered();
void on_actionRemoveProfile_triggered(bool skipConfirmation = false);
void on_actionImportProfile_triggered();
void on_actionExportProfile_triggered();
public slots:
bool CreateNewProfile(const QString &name);
bool CreateDuplicateProfile(const QString &name);
void DeleteProfile(const QString &profileName);
}; };
extern bool cef_js_avail; extern bool cef_js_avail;

View File

@ -2136,12 +2136,16 @@ OBSBasicSettings::CreateEncoderPropertyView(const char *encoder,
OBSPropertiesView *view; OBSPropertiesView *view;
if (path) { if (path) {
char encoderJsonPath[512]; const OBSBasic *basic =
int ret = GetProfilePath(encoderJsonPath, reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
sizeof(encoderJsonPath), path); const OBSProfile &currentProfile = basic->GetCurrentProfile();
if (ret > 0) {
const std::filesystem::path jsonFilePath =
currentProfile.path / std::filesystem::u8path(path);
if (!jsonFilePath.empty()) {
obs_data_t *data = obs_data_create_from_json_file_safe( obs_data_t *data = obs_data_create_from_json_file_safe(
encoderJsonPath, "bak"); jsonFilePath.u8string().c_str(), "bak");
obs_data_apply(settings, data); obs_data_apply(settings, data);
obs_data_release(data); obs_data_release(data);
} }
@ -3748,17 +3752,22 @@ static inline const char *SplitFileTypeFromIdx(int idx)
static void WriteJsonData(OBSPropertiesView *view, const char *path) static void WriteJsonData(OBSPropertiesView *view, const char *path)
{ {
char full_path[512];
if (!view || !WidgetChanged(view)) if (!view || !WidgetChanged(view))
return; return;
int ret = GetProfilePath(full_path, sizeof(full_path), path); const OBSBasic *basic =
if (ret > 0) { reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = basic->GetCurrentProfile();
const std::filesystem::path jsonFilePath =
currentProfile.path / std::filesystem::u8path(path);
if (!jsonFilePath.empty()) {
obs_data_t *settings = view->GetSettings(); obs_data_t *settings = view->GetSettings();
if (settings) { if (settings) {
obs_data_save_json_safe(settings, full_path, "tmp", obs_data_save_json_safe(settings,
"bak"); jsonFilePath.u8string().c_str(),
"tmp", "bak");
} }
} }
} }
@ -5691,14 +5700,16 @@ void OBSBasicSettings::AdvReplayBufferChanged()
if (!settings) if (!settings)
return; return;
char encoderJsonPath[512]; const OBSProfile &currentProfile = main->GetCurrentProfile();
int ret = GetProfilePath(encoderJsonPath,
sizeof(encoderJsonPath), const std::filesystem::path jsonFilePath =
"recordEncoder.json"); currentProfile.path /
if (ret > 0) { std::filesystem::u8path("recordEncoder.json");
if (!jsonFilePath.empty()) {
OBSDataAutoRelease data = OBSDataAutoRelease data =
obs_data_create_from_json_file_safe( obs_data_create_from_json_file_safe(
encoderJsonPath, "bak"); jsonFilePath.u8string().c_str(), "bak");
obs_data_apply(settings, data); obs_data_apply(settings, data);
} }
} }

View File

@ -287,8 +287,8 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth,
workerThread->start(); workerThread->start();
OBSBasic *main = OBSBasic::Get(); OBSBasic *main = OBSBasic::Get();
bool rememberSettings = config_get_bool(main->basicConfig, "YouTube", bool rememberSettings = config_get_bool(main->activeConfiguration,
"RememberSettings"); "YouTube", "RememberSettings");
if (rememberSettings) if (rememberSettings)
LoadSettings(); LoadSettings();
@ -749,83 +749,85 @@ void OBSYoutubeActions::SaveSettings(BroadcastDescription &broadcast)
{ {
OBSBasic *main = OBSBasic::Get(); OBSBasic *main = OBSBasic::Get();
config_set_string(main->basicConfig, "YouTube", "Title", config_set_string(main->activeConfiguration, "YouTube", "Title",
QT_TO_UTF8(broadcast.title)); QT_TO_UTF8(broadcast.title));
config_set_string(main->basicConfig, "YouTube", "Description", config_set_string(main->activeConfiguration, "YouTube", "Description",
QT_TO_UTF8(broadcast.description)); QT_TO_UTF8(broadcast.description));
config_set_string(main->basicConfig, "YouTube", "Privacy", config_set_string(main->activeConfiguration, "YouTube", "Privacy",
QT_TO_UTF8(broadcast.privacy)); QT_TO_UTF8(broadcast.privacy));
config_set_string(main->basicConfig, "YouTube", "CategoryID", config_set_string(main->activeConfiguration, "YouTube", "CategoryID",
QT_TO_UTF8(broadcast.category.id)); QT_TO_UTF8(broadcast.category.id));
config_set_string(main->basicConfig, "YouTube", "Latency", config_set_string(main->activeConfiguration, "YouTube", "Latency",
QT_TO_UTF8(broadcast.latency)); QT_TO_UTF8(broadcast.latency));
config_set_bool(main->basicConfig, "YouTube", "MadeForKids", config_set_bool(main->activeConfiguration, "YouTube", "MadeForKids",
broadcast.made_for_kids); broadcast.made_for_kids);
config_set_bool(main->basicConfig, "YouTube", "AutoStart", config_set_bool(main->activeConfiguration, "YouTube", "AutoStart",
broadcast.auto_start); broadcast.auto_start);
config_set_bool(main->basicConfig, "YouTube", "AutoStop", config_set_bool(main->activeConfiguration, "YouTube", "AutoStop",
broadcast.auto_start); broadcast.auto_start);
config_set_bool(main->basicConfig, "YouTube", "DVR", broadcast.dvr); config_set_bool(main->activeConfiguration, "YouTube", "DVR",
config_set_bool(main->basicConfig, "YouTube", "ScheduleForLater", broadcast.dvr);
broadcast.schedul_for_later); config_set_bool(main->activeConfiguration, "YouTube",
config_set_string(main->basicConfig, "YouTube", "Projection", "ScheduleForLater", broadcast.schedul_for_later);
config_set_string(main->activeConfiguration, "YouTube", "Projection",
QT_TO_UTF8(broadcast.projection)); QT_TO_UTF8(broadcast.projection));
config_set_string(main->basicConfig, "YouTube", "ThumbnailFile", config_set_string(main->activeConfiguration, "YouTube", "ThumbnailFile",
QT_TO_UTF8(thumbnailFile)); QT_TO_UTF8(thumbnailFile));
config_set_bool(main->basicConfig, "YouTube", "RememberSettings", true); config_set_bool(main->activeConfiguration, "YouTube",
"RememberSettings", true);
} }
void OBSYoutubeActions::LoadSettings() void OBSYoutubeActions::LoadSettings()
{ {
OBSBasic *main = OBSBasic::Get(); OBSBasic *main = OBSBasic::Get();
const char *title = const char *title = config_get_string(main->activeConfiguration,
config_get_string(main->basicConfig, "YouTube", "Title"); "YouTube", "Title");
ui->title->setText(QT_UTF8(title)); ui->title->setText(QT_UTF8(title));
const char *desc = const char *desc = config_get_string(main->activeConfiguration,
config_get_string(main->basicConfig, "YouTube", "Description"); "YouTube", "Description");
ui->description->setPlainText(QT_UTF8(desc)); ui->description->setPlainText(QT_UTF8(desc));
const char *priv = const char *priv = config_get_string(main->activeConfiguration,
config_get_string(main->basicConfig, "YouTube", "Privacy"); "YouTube", "Privacy");
int index = ui->privacyBox->findData(priv); int index = ui->privacyBox->findData(priv);
ui->privacyBox->setCurrentIndex(index); ui->privacyBox->setCurrentIndex(index);
const char *catID = const char *catID = config_get_string(main->activeConfiguration,
config_get_string(main->basicConfig, "YouTube", "CategoryID"); "YouTube", "CategoryID");
index = ui->categoryBox->findData(catID); index = ui->categoryBox->findData(catID);
ui->categoryBox->setCurrentIndex(index); ui->categoryBox->setCurrentIndex(index);
const char *latency = const char *latency = config_get_string(main->activeConfiguration,
config_get_string(main->basicConfig, "YouTube", "Latency"); "YouTube", "Latency");
index = ui->latencyBox->findData(latency); index = ui->latencyBox->findData(latency);
ui->latencyBox->setCurrentIndex(index); ui->latencyBox->setCurrentIndex(index);
bool dvr = config_get_bool(main->basicConfig, "YouTube", "DVR"); bool dvr = config_get_bool(main->activeConfiguration, "YouTube", "DVR");
ui->checkDVR->setChecked(dvr); ui->checkDVR->setChecked(dvr);
bool forKids = bool forKids = config_get_bool(main->activeConfiguration, "YouTube",
config_get_bool(main->basicConfig, "YouTube", "MadeForKids"); "MadeForKids");
if (forKids) if (forKids)
ui->yesMakeForKids->setChecked(true); ui->yesMakeForKids->setChecked(true);
else else
ui->notMakeForKids->setChecked(true); ui->notMakeForKids->setChecked(true);
bool schedLater = config_get_bool(main->basicConfig, "YouTube", bool schedLater = config_get_bool(main->activeConfiguration, "YouTube",
"ScheduleForLater"); "ScheduleForLater");
ui->checkScheduledLater->setChecked(schedLater); ui->checkScheduledLater->setChecked(schedLater);
bool autoStart = bool autoStart = config_get_bool(main->activeConfiguration, "YouTube",
config_get_bool(main->basicConfig, "YouTube", "AutoStart"); "AutoStart");
ui->checkAutoStart->setChecked(autoStart); ui->checkAutoStart->setChecked(autoStart);
bool autoStop = bool autoStop = config_get_bool(main->activeConfiguration, "YouTube",
config_get_bool(main->basicConfig, "YouTube", "AutoStop"); "AutoStop");
ui->checkAutoStop->setChecked(autoStop); ui->checkAutoStop->setChecked(autoStop);
const char *projection = const char *projection = config_get_string(main->activeConfiguration,
config_get_string(main->basicConfig, "YouTube", "Projection"); "YouTube", "Projection");
if (projection && *projection) { if (projection && *projection) {
if (strcmp(projection, "360") == 0) if (strcmp(projection, "360") == 0)
ui->check360Video->setChecked(true); ui->check360Video->setChecked(true);
@ -833,8 +835,8 @@ void OBSYoutubeActions::LoadSettings()
ui->check360Video->setChecked(false); ui->check360Video->setChecked(false);
} }
const char *thumbFile = config_get_string(main->basicConfig, "YouTube", const char *thumbFile = config_get_string(main->activeConfiguration,
"ThumbnailFile"); "YouTube", "ThumbnailFile");
if (thumbFile && *thumbFile) { if (thumbFile && *thumbFile) {
QFileInfo tFile(thumbFile); QFileInfo tFile(thumbFile);
// Re-check validity before setting path again // Re-check validity before setting path again