1
0

OBSBasic_Profiles.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include "OBSBasic.hpp"
  15. #ifdef YOUTUBE_ENABLED
  16. #include <docks/YouTubeAppDock.hpp>
  17. #endif
  18. #include <wizards/AutoConfig.hpp>
  19. #include <qt-wrappers.hpp>
  20. #include <QDir>
  21. #include <QFile>
  22. // MARK: Constant Expressions
  23. constexpr std::string_view OBSProfilePath = "/obs-studio/basic/profiles/";
  24. constexpr std::string_view OBSProfileSettingsFile = "basic.ini";
  25. // MARK: Forward Declarations
  26. extern bool restart;
  27. extern void DestroyPanelCookieManager();
  28. extern void DuplicateCurrentCookieProfile(ConfigFile &config);
  29. extern void CheckExistingCookieId();
  30. extern void DeleteCookies();
  31. // MARK: - Anonymous Namespace
  32. namespace {
  33. QList<QString> sortedProfiles{};
  34. void updateSortedProfiles(const OBSProfileCache &profiles)
  35. {
  36. const QLocale locale = QLocale::system();
  37. QList<QString> newList{};
  38. for (auto [profileName, _] : profiles) {
  39. QString entry = QString::fromStdString(profileName);
  40. newList.append(entry);
  41. }
  42. std::sort(newList.begin(), newList.end(), [&locale](const QString &lhs, const QString &rhs) -> bool {
  43. int result = QString::localeAwareCompare(locale.toLower(lhs), locale.toLower(rhs));
  44. return (result < 0);
  45. });
  46. sortedProfiles.swap(newList);
  47. }
  48. } // namespace
  49. // MARK: - Main Profile Management Functions
  50. void OBSBasic::SetupNewProfile(const std::string &profileName, bool useWizard)
  51. {
  52. const OBSProfile &newProfile = CreateProfile(profileName);
  53. config_set_bool(App()->GetUserConfig(), "Basic", "ConfigOnNewProfile", useWizard);
  54. ActivateProfile(newProfile, true);
  55. blog(LOG_INFO, "Created profile '%s' (clean, %s)", newProfile.name.c_str(), newProfile.directoryName.c_str());
  56. blog(LOG_INFO, "------------------------------------------------");
  57. if (useWizard) {
  58. AutoConfig wizard(this);
  59. wizard.setModal(true);
  60. wizard.show();
  61. wizard.exec();
  62. }
  63. }
  64. void OBSBasic::SetupDuplicateProfile(const std::string &profileName)
  65. {
  66. const OBSProfile &newProfile = CreateProfile(profileName);
  67. const OBSProfile &currentProfile = GetCurrentProfile();
  68. const auto copyOptions = std::filesystem::copy_options::recursive |
  69. std::filesystem::copy_options::overwrite_existing;
  70. try {
  71. std::filesystem::copy(currentProfile.path, newProfile.path, copyOptions);
  72. } catch (const std::filesystem::filesystem_error &error) {
  73. blog(LOG_DEBUG, "%s", error.what());
  74. throw std::logic_error("Failed to copy files for cloned profile: " + newProfile.name);
  75. }
  76. ActivateProfile(newProfile);
  77. blog(LOG_INFO, "Created profile '%s' (duplicate, %s)", newProfile.name.c_str(),
  78. newProfile.directoryName.c_str());
  79. blog(LOG_INFO, "------------------------------------------------");
  80. }
  81. void OBSBasic::SetupRenameProfile(const std::string &profileName)
  82. {
  83. const OBSProfile &newProfile = CreateProfile(profileName);
  84. const OBSProfile currentProfile = GetCurrentProfile();
  85. const auto copyOptions = std::filesystem::copy_options::recursive |
  86. std::filesystem::copy_options::overwrite_existing;
  87. try {
  88. std::filesystem::copy(currentProfile.path, newProfile.path, copyOptions);
  89. } catch (const std::filesystem::filesystem_error &error) {
  90. blog(LOG_DEBUG, "%s", error.what());
  91. throw std::logic_error("Failed to copy files for profile: " + currentProfile.name);
  92. }
  93. profiles.erase(currentProfile.name);
  94. ActivateProfile(newProfile);
  95. RemoveProfile(currentProfile);
  96. blog(LOG_INFO, "Renamed profile '%s' to '%s' (%s)", currentProfile.name.c_str(), newProfile.name.c_str(),
  97. newProfile.directoryName.c_str());
  98. blog(LOG_INFO, "------------------------------------------------");
  99. OnEvent(OBS_FRONTEND_EVENT_PROFILE_RENAMED);
  100. }
  101. // MARK: - Profile File Management Functions
  102. const OBSProfile &OBSBasic::CreateProfile(const std::string &profileName)
  103. {
  104. if (const auto &foundProfile = GetProfileByName(profileName)) {
  105. throw std::invalid_argument("Profile already exists: " + profileName);
  106. }
  107. std::string directoryName;
  108. if (!GetFileSafeName(profileName.c_str(), directoryName)) {
  109. throw std::invalid_argument("Failed to create safe directory for new profile: " + profileName);
  110. }
  111. std::string profileDirectory;
  112. profileDirectory.reserve(App()->userProfilesLocation.u8string().size() + OBSProfilePath.size() +
  113. directoryName.size());
  114. profileDirectory.append(App()->userProfilesLocation.u8string()).append(OBSProfilePath).append(directoryName);
  115. if (!GetClosestUnusedFileName(profileDirectory, nullptr)) {
  116. throw std::invalid_argument("Failed to get closest directory name for new profile: " + profileName);
  117. }
  118. const std::filesystem::path profileDirectoryPath = std::filesystem::u8path(profileDirectory);
  119. try {
  120. std::filesystem::create_directory(profileDirectoryPath);
  121. } catch (const std::filesystem::filesystem_error error) {
  122. throw std::logic_error("Failed to create directory for new profile: " + profileDirectory);
  123. }
  124. const std::filesystem::path profileFile =
  125. profileDirectoryPath / std::filesystem::u8path(OBSProfileSettingsFile);
  126. auto [iterator, success] =
  127. profiles.try_emplace(profileName, OBSProfile{profileName, profileDirectoryPath.filename().u8string(),
  128. profileDirectoryPath, profileFile});
  129. OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED);
  130. return iterator->second;
  131. }
  132. void OBSBasic::RemoveProfile(OBSProfile profile)
  133. {
  134. try {
  135. std::filesystem::remove_all(profile.path);
  136. } catch (const std::filesystem::filesystem_error &error) {
  137. blog(LOG_DEBUG, "%s", error.what());
  138. throw std::logic_error("Failed to remove profile directory: " + profile.directoryName);
  139. }
  140. blog(LOG_INFO, "------------------------------------------------");
  141. blog(LOG_INFO, "Removed profile '%s' (%s)", profile.name.c_str(), profile.directoryName.c_str());
  142. blog(LOG_INFO, "------------------------------------------------");
  143. }
  144. // MARK: - Profile UI Handling Functions
  145. bool OBSBasic::CreateNewProfile(const QString &name)
  146. {
  147. try {
  148. SetupNewProfile(name.toStdString());
  149. return true;
  150. } catch (const std::invalid_argument &error) {
  151. blog(LOG_ERROR, "%s", error.what());
  152. return false;
  153. } catch (const std::logic_error &error) {
  154. blog(LOG_ERROR, "%s", error.what());
  155. return false;
  156. }
  157. }
  158. bool OBSBasic::CreateDuplicateProfile(const QString &name)
  159. {
  160. try {
  161. SetupDuplicateProfile(name.toStdString());
  162. return true;
  163. } catch (const std::invalid_argument &error) {
  164. blog(LOG_ERROR, "%s", error.what());
  165. return false;
  166. } catch (const std::logic_error &error) {
  167. blog(LOG_ERROR, "%s", error.what());
  168. return false;
  169. }
  170. }
  171. void OBSBasic::DeleteProfile(const QString &name)
  172. {
  173. const std::string_view currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")};
  174. if (currentProfileName == name.toStdString()) {
  175. on_actionRemoveProfile_triggered();
  176. return;
  177. }
  178. auto foundProfile = GetProfileByName(name.toStdString());
  179. if (!foundProfile) {
  180. blog(LOG_ERROR, "Invalid profile name: %s", QT_TO_UTF8(name));
  181. return;
  182. }
  183. RemoveProfile(foundProfile.value());
  184. profiles.erase(name.toStdString());
  185. RefreshProfiles();
  186. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  187. OnEvent(OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED);
  188. }
  189. void OBSBasic::ChangeProfile()
  190. {
  191. QAction *action = reinterpret_cast<QAction *>(sender());
  192. if (!action) {
  193. return;
  194. }
  195. const std::string_view currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")};
  196. const QVariant qProfileName = action->property("profile_name");
  197. const std::string selectedProfileName{qProfileName.toString().toStdString()};
  198. if (currentProfileName == selectedProfileName) {
  199. action->setChecked(true);
  200. return;
  201. }
  202. const std::optional<OBSProfile> foundProfile = GetProfileByName(selectedProfileName);
  203. if (!foundProfile) {
  204. const std::string errorMessage{"Selected profile not found: "};
  205. throw std::invalid_argument(errorMessage + selectedProfileName.data());
  206. }
  207. const OBSProfile &selectedProfile = foundProfile.value();
  208. OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING);
  209. ActivateProfile(selectedProfile, true);
  210. blog(LOG_INFO, "Switched to profile '%s' (%s)", selectedProfile.name.c_str(),
  211. selectedProfile.directoryName.c_str());
  212. blog(LOG_INFO, "------------------------------------------------");
  213. }
  214. void OBSBasic::RefreshProfiles(bool refreshCache)
  215. {
  216. std::string_view currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")};
  217. QList<QAction *> menuActions = ui->profileMenu->actions();
  218. for (auto &action : menuActions) {
  219. QVariant variant = action->property("file_name");
  220. if (variant.typeName() != nullptr) {
  221. delete action;
  222. }
  223. }
  224. if (refreshCache) {
  225. RefreshProfileCache();
  226. }
  227. updateSortedProfiles(profiles);
  228. size_t numAddedProfiles = 0;
  229. for (auto &name : sortedProfiles) {
  230. const std::string profileName = name.toStdString();
  231. try {
  232. const OBSProfile &profile = profiles.at(profileName);
  233. const QString qProfileName = QString().fromStdString(profileName);
  234. QAction *action = new QAction(qProfileName, this);
  235. action->setProperty("profile_name", qProfileName);
  236. action->setProperty("file_name", QString().fromStdString(profile.directoryName));
  237. connect(action, &QAction::triggered, this, &OBSBasic::ChangeProfile);
  238. action->setCheckable(true);
  239. action->setChecked(profileName == currentProfileName);
  240. ui->profileMenu->addAction(action);
  241. numAddedProfiles += 1;
  242. } catch (const std::out_of_range &error) {
  243. blog(LOG_ERROR, "No profile with name %s found in profile cache.\n%s", profileName.c_str(),
  244. error.what());
  245. }
  246. }
  247. ui->actionRemoveProfile->setEnabled(numAddedProfiles > 1);
  248. }
  249. // MARK: - Profile Cache Functions
  250. /// Refreshes profile cache data with profile state found on local file system.
  251. void OBSBasic::RefreshProfileCache()
  252. {
  253. std::map<std::string, OBSProfile> foundProfiles{};
  254. const std::filesystem::path profilesPath =
  255. App()->userProfilesLocation / std::filesystem::u8path(OBSProfilePath.substr(1));
  256. const std::filesystem::path profileSettingsFile = std::filesystem::u8path(OBSProfileSettingsFile);
  257. if (!std::filesystem::exists(profilesPath)) {
  258. blog(LOG_WARNING, "Failed to get profiles config path");
  259. return;
  260. }
  261. for (const auto &entry : std::filesystem::directory_iterator(profilesPath)) {
  262. if (!entry.is_directory()) {
  263. continue;
  264. }
  265. const auto profileCandidate = entry.path() / profileSettingsFile;
  266. ConfigFile config;
  267. if (config.Open(profileCandidate.u8string().c_str(), CONFIG_OPEN_EXISTING) != CONFIG_SUCCESS) {
  268. continue;
  269. }
  270. std::string candidateName;
  271. const char *configName = config_get_string(config, "General", "Name");
  272. if (configName) {
  273. candidateName = configName;
  274. } else {
  275. candidateName = entry.path().filename().u8string();
  276. }
  277. foundProfiles.try_emplace(candidateName, OBSProfile{candidateName, entry.path().filename().u8string(),
  278. entry.path(), profileCandidate});
  279. }
  280. profiles.swap(foundProfiles);
  281. }
  282. const OBSProfile &OBSBasic::GetCurrentProfile() const
  283. {
  284. std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")};
  285. if (currentProfileName.empty()) {
  286. throw std::invalid_argument("No valid profile name in configuration Basic->Profile");
  287. }
  288. const auto &foundProfile = profiles.find(currentProfileName);
  289. if (foundProfile != profiles.end()) {
  290. return foundProfile->second;
  291. } else {
  292. throw std::invalid_argument("Profile not found in profile list: " + currentProfileName);
  293. }
  294. }
  295. std::optional<OBSProfile> OBSBasic::GetProfileByName(const std::string &profileName) const
  296. {
  297. auto foundProfile = profiles.find(profileName);
  298. if (foundProfile == profiles.end()) {
  299. return {};
  300. } else {
  301. return foundProfile->second;
  302. }
  303. }
  304. std::optional<OBSProfile> OBSBasic::GetProfileByDirectoryName(const std::string &directoryName) const
  305. {
  306. for (auto &[iterator, profile] : profiles) {
  307. if (profile.directoryName == directoryName) {
  308. return profile;
  309. }
  310. }
  311. return {};
  312. }
  313. // MARK: - Qt Slot Functions
  314. void OBSBasic::on_actionNewProfile_triggered()
  315. {
  316. bool useProfileWizard = config_get_bool(App()->GetUserConfig(), "Basic", "ConfigOnNewProfile");
  317. const OBSPromptCallback profilePromptCallback = [this](const OBSPromptResult &result) {
  318. if (GetProfileByName(result.promptValue)) {
  319. return false;
  320. }
  321. return true;
  322. };
  323. const OBSPromptRequest request{Str("AddProfile.Title"), Str("AddProfile.Text"), "", true,
  324. Str("AddProfile.WizardCheckbox"), useProfileWizard};
  325. OBSPromptResult result = PromptForName(request, profilePromptCallback);
  326. if (!result.success) {
  327. return;
  328. }
  329. try {
  330. SetupNewProfile(result.promptValue, result.optionValue);
  331. } catch (const std::invalid_argument &error) {
  332. blog(LOG_ERROR, "%s", error.what());
  333. } catch (const std::logic_error &error) {
  334. blog(LOG_ERROR, "%s", error.what());
  335. }
  336. }
  337. void OBSBasic::on_actionDupProfile_triggered()
  338. {
  339. const OBSPromptCallback profilePromptCallback = [this](const OBSPromptResult &result) {
  340. if (GetProfileByName(result.promptValue)) {
  341. return false;
  342. }
  343. return true;
  344. };
  345. const OBSPromptRequest request{Str("AddProfile.Title"), Str("AddProfile.Text")};
  346. OBSPromptResult result = PromptForName(request, profilePromptCallback);
  347. if (!result.success) {
  348. return;
  349. }
  350. try {
  351. SetupDuplicateProfile(result.promptValue);
  352. } catch (const std::invalid_argument &error) {
  353. blog(LOG_ERROR, "%s", error.what());
  354. } catch (const std::logic_error &error) {
  355. blog(LOG_ERROR, "%s", error.what());
  356. }
  357. }
  358. void OBSBasic::on_actionRenameProfile_triggered()
  359. {
  360. const std::string currentProfileName = config_get_string(App()->GetUserConfig(), "Basic", "Profile");
  361. const OBSPromptCallback profilePromptCallback = [this](const OBSPromptResult &result) {
  362. if (GetProfileByName(result.promptValue)) {
  363. return false;
  364. }
  365. return true;
  366. };
  367. const OBSPromptRequest request{Str("RenameProfile.Title"), Str("AddProfile.Text"), currentProfileName};
  368. OBSPromptResult result = PromptForName(request, profilePromptCallback);
  369. if (!result.success) {
  370. return;
  371. }
  372. try {
  373. SetupRenameProfile(result.promptValue);
  374. } catch (const std::invalid_argument &error) {
  375. blog(LOG_ERROR, "%s", error.what());
  376. } catch (const std::logic_error &error) {
  377. blog(LOG_ERROR, "%s", error.what());
  378. }
  379. }
  380. void OBSBasic::on_actionRemoveProfile_triggered(bool skipConfirmation)
  381. {
  382. if (profiles.size() < 2) {
  383. return;
  384. }
  385. OBSProfile currentProfile;
  386. try {
  387. currentProfile = GetCurrentProfile();
  388. if (!skipConfirmation) {
  389. const QString confirmationText =
  390. QTStr("ConfirmRemove.Text").arg(QString::fromStdString(currentProfile.name));
  391. const QMessageBox::StandardButton button =
  392. OBSMessageBox::question(this, QTStr("ConfirmRemove.Title"), confirmationText);
  393. if (button == QMessageBox::No) {
  394. return;
  395. }
  396. }
  397. OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGING);
  398. profiles.erase(currentProfile.name);
  399. } catch (const std::invalid_argument &error) {
  400. blog(LOG_ERROR, "%s", error.what());
  401. } catch (const std::logic_error &error) {
  402. blog(LOG_ERROR, "%s", error.what());
  403. }
  404. const OBSProfile &newProfile = profiles.begin()->second;
  405. ActivateProfile(newProfile, true);
  406. RemoveProfile(currentProfile);
  407. #ifdef YOUTUBE_ENABLED
  408. if (YouTubeAppDock::IsYTServiceSelected() && !youtubeAppDock)
  409. NewYouTubeAppDock();
  410. #endif
  411. blog(LOG_INFO, "Switched to profile '%s' (%s)", newProfile.name.c_str(), newProfile.directoryName.c_str());
  412. blog(LOG_INFO, "------------------------------------------------");
  413. }
  414. void OBSBasic::on_actionImportProfile_triggered()
  415. {
  416. const QString home = QDir::homePath();
  417. const QString sourceDirectory = SelectDirectory(this, QTStr("Basic.MainMenu.Profile.Import"), home);
  418. if (!sourceDirectory.isEmpty() && !sourceDirectory.isNull()) {
  419. const std::filesystem::path sourcePath = std::filesystem::u8path(sourceDirectory.toStdString());
  420. const std::string directoryName = sourcePath.filename().u8string();
  421. if (auto profile = GetProfileByDirectoryName(directoryName)) {
  422. OBSMessageBox::warning(this, QTStr("Basic.MainMenu.Profile.Import"),
  423. QTStr("Basic.MainMenu.Profile.Exists"));
  424. return;
  425. }
  426. std::string destinationPathString;
  427. destinationPathString.reserve(App()->userProfilesLocation.u8string().size() + OBSProfilePath.size() +
  428. directoryName.size());
  429. destinationPathString.append(App()->userProfilesLocation.u8string())
  430. .append(OBSProfilePath)
  431. .append(directoryName);
  432. const std::filesystem::path destinationPath = std::filesystem::u8path(destinationPathString);
  433. try {
  434. std::filesystem::create_directory(destinationPath);
  435. } catch (const std::filesystem::filesystem_error &error) {
  436. blog(LOG_WARNING, "Failed to create profile directory '%s':\n%s", directoryName.c_str(),
  437. error.what());
  438. return;
  439. }
  440. const std::array<std::pair<std::string, bool>, 4> profileFiles{{
  441. {"basic.ini", true},
  442. {"service.json", false},
  443. {"streamEncoder.json", false},
  444. {"recordEncoder.json", false},
  445. }};
  446. for (auto &[file, isMandatory] : profileFiles) {
  447. const std::filesystem::path sourceFile = sourcePath / std::filesystem::u8path(file);
  448. if (!std::filesystem::exists(sourceFile)) {
  449. if (isMandatory) {
  450. blog(LOG_ERROR,
  451. "Failed to import profile from directory '%s' - necessary file '%s' not found",
  452. directoryName.c_str(), file.c_str());
  453. return;
  454. }
  455. continue;
  456. }
  457. const std::filesystem::path destinationFile = destinationPath / std::filesystem::u8path(file);
  458. try {
  459. std::filesystem::copy(sourceFile, destinationFile);
  460. } catch (const std::filesystem::filesystem_error &error) {
  461. blog(LOG_WARNING, "Failed to copy import file '%s' for profile '%s':\n%s", file.c_str(),
  462. directoryName.c_str(), error.what());
  463. return;
  464. }
  465. }
  466. RefreshProfiles(true);
  467. }
  468. }
  469. void OBSBasic::on_actionExportProfile_triggered()
  470. {
  471. const OBSProfile &currentProfile = GetCurrentProfile();
  472. const QString home = QDir::homePath();
  473. const QString destinationDirectory = SelectDirectory(this, QTStr("Basic.MainMenu.Profile.Export"), home);
  474. const std::array<std::string, 4> profileFiles{"basic.ini", "service.json", "streamEncoder.json",
  475. "recordEncoder.json"};
  476. if (!destinationDirectory.isEmpty() && !destinationDirectory.isNull()) {
  477. const std::filesystem::path sourcePath = currentProfile.path;
  478. const std::filesystem::path destinationPath =
  479. std::filesystem::u8path(destinationDirectory.toStdString()) /
  480. std::filesystem::u8path(currentProfile.directoryName);
  481. if (!std::filesystem::exists(destinationPath)) {
  482. std::filesystem::create_directory(destinationPath);
  483. }
  484. std::filesystem::copy_options copyOptions = std::filesystem::copy_options::overwrite_existing;
  485. for (auto &file : profileFiles) {
  486. const std::filesystem::path sourceFile = sourcePath / std::filesystem::u8path(file);
  487. if (!std::filesystem::exists(sourceFile)) {
  488. continue;
  489. }
  490. const std::filesystem::path destinationFile = destinationPath / std::filesystem::u8path(file);
  491. try {
  492. std::filesystem::copy(sourceFile, destinationFile, copyOptions);
  493. } catch (const std::filesystem::filesystem_error &error) {
  494. blog(LOG_WARNING, "Failed to copy export file '%s' for profile '%s'\n%s", file.c_str(),
  495. currentProfile.name.c_str(), error.what());
  496. return;
  497. }
  498. }
  499. }
  500. }
  501. // MARK: - Profile Management Helper Functions
  502. void OBSBasic::ActivateProfile(const OBSProfile &profile, bool reset)
  503. {
  504. ConfigFile config;
  505. if (config.Open(profile.profileFile.u8string().c_str(), CONFIG_OPEN_ALWAYS) != CONFIG_SUCCESS) {
  506. throw std::logic_error("failed to open configuration file of new profile: " +
  507. profile.profileFile.string());
  508. }
  509. config_set_string(config, "General", "Name", profile.name.c_str());
  510. config.SaveSafe("tmp");
  511. std::vector<std::string> restartRequirements;
  512. if (activeConfiguration) {
  513. Auth::Save();
  514. if (reset) {
  515. auth.reset();
  516. DestroyPanelCookieManager();
  517. #ifdef YOUTUBE_ENABLED
  518. if (youtubeAppDock) {
  519. DeleteYouTubeAppDock();
  520. }
  521. #endif
  522. }
  523. restartRequirements = GetRestartRequirements(config);
  524. activeConfiguration.SaveSafe("tmp");
  525. }
  526. activeConfiguration.Swap(config);
  527. config_set_string(App()->GetUserConfig(), "Basic", "Profile", profile.name.c_str());
  528. config_set_string(App()->GetUserConfig(), "Basic", "ProfileDir", profile.directoryName.c_str());
  529. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  530. InitBasicConfigDefaults();
  531. if (reset) {
  532. UpdateProfileEncoders();
  533. ResetProfileData();
  534. }
  535. RefreshProfiles();
  536. UpdateTitleBar();
  537. UpdateVolumeControlsDecayRate();
  538. Auth::Load();
  539. #ifdef YOUTUBE_ENABLED
  540. if (YouTubeAppDock::IsYTServiceSelected() && !youtubeAppDock)
  541. NewYouTubeAppDock();
  542. #endif
  543. OnEvent(OBS_FRONTEND_EVENT_PROFILE_CHANGED);
  544. if (!restartRequirements.empty()) {
  545. std::string requirements = std::accumulate(
  546. std::next(restartRequirements.begin()), restartRequirements.end(), restartRequirements[0],
  547. [](std::string a, std::string b) { return std::move(a) + "\n" + b; });
  548. QMessageBox::StandardButton button = OBSMessageBox::question(
  549. this, QTStr("Restart"), QTStr("LoadProfileNeedsRestart").arg(requirements.c_str()));
  550. if (button == QMessageBox::Yes) {
  551. restart = true;
  552. close();
  553. }
  554. }
  555. }
  556. void OBSBasic::UpdateProfileEncoders()
  557. {
  558. InitBasicConfigDefaults2();
  559. CheckForSimpleModeX264Fallback();
  560. }
  561. void OBSBasic::ResetProfileData()
  562. {
  563. ResetVideo();
  564. service = nullptr;
  565. InitService();
  566. ResetOutputs();
  567. ClearHotkeys();
  568. CreateHotkeys();
  569. /* load audio monitoring */
  570. if (obs_audio_monitoring_available()) {
  571. const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName");
  572. const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId");
  573. obs_set_audio_monitoring_device(device_name, device_id);
  574. blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id);
  575. }
  576. }
  577. std::vector<std::string> OBSBasic::GetRestartRequirements(const ConfigFile &config) const
  578. {
  579. std::vector<std::string> result;
  580. const char *oldSpeakers = config_get_string(activeConfiguration, "Audio", "ChannelSetup");
  581. const char *newSpeakers = config_get_string(config, "Audio", "ChannelSetup");
  582. uint64_t oldSampleRate = config_get_uint(activeConfiguration, "Audio", "SampleRate");
  583. uint64_t newSampleRate = config_get_uint(config, "Audio", "SampleRate");
  584. if (oldSpeakers != NULL && newSpeakers != NULL) {
  585. if (std::string_view{oldSpeakers} != std::string_view{newSpeakers}) {
  586. result.emplace_back(Str("Basic.Settings.Audio.Channels"));
  587. }
  588. }
  589. if (oldSampleRate != 0 && newSampleRate != 0) {
  590. if (oldSampleRate != newSampleRate) {
  591. result.emplace_back(Str("Basic.Settings.Audio.SampleRate"));
  592. }
  593. }
  594. return result;
  595. }
  596. void OBSBasic::CheckForSimpleModeX264Fallback()
  597. {
  598. const char *curStreamEncoder = config_get_string(activeConfiguration, "SimpleOutput", "StreamEncoder");
  599. const char *curRecEncoder = config_get_string(activeConfiguration, "SimpleOutput", "RecEncoder");
  600. bool qsv_supported = false;
  601. bool qsv_av1_supported = false;
  602. bool amd_supported = false;
  603. bool nve_supported = false;
  604. #ifdef ENABLE_HEVC
  605. bool amd_hevc_supported = false;
  606. bool nve_hevc_supported = false;
  607. bool apple_hevc_supported = false;
  608. #endif
  609. bool amd_av1_supported = false;
  610. bool apple_supported = false;
  611. bool changed = false;
  612. size_t idx = 0;
  613. const char *id;
  614. while (obs_enum_encoder_types(idx++, &id)) {
  615. if (strcmp(id, "h264_texture_amf") == 0)
  616. amd_supported = true;
  617. else if (strcmp(id, "obs_qsv11") == 0)
  618. qsv_supported = true;
  619. else if (strcmp(id, "obs_qsv11_av1") == 0)
  620. qsv_av1_supported = true;
  621. else if (strcmp(id, "ffmpeg_nvenc") == 0)
  622. nve_supported = true;
  623. #ifdef ENABLE_HEVC
  624. else if (strcmp(id, "h265_texture_amf") == 0)
  625. amd_hevc_supported = true;
  626. else if (strcmp(id, "ffmpeg_hevc_nvenc") == 0)
  627. nve_hevc_supported = true;
  628. #endif
  629. else if (strcmp(id, "av1_texture_amf") == 0)
  630. amd_av1_supported = true;
  631. else if (strcmp(id, "com.apple.videotoolbox.videoencoder.ave.avc") == 0)
  632. apple_supported = true;
  633. #ifdef ENABLE_HEVC
  634. else if (strcmp(id, "com.apple.videotoolbox.videoencoder.ave.hevc") == 0)
  635. apple_hevc_supported = true;
  636. #endif
  637. }
  638. auto CheckEncoder = [&](const char *&name) {
  639. if (strcmp(name, SIMPLE_ENCODER_QSV) == 0) {
  640. if (!qsv_supported) {
  641. changed = true;
  642. name = SIMPLE_ENCODER_X264;
  643. return false;
  644. }
  645. } else if (strcmp(name, SIMPLE_ENCODER_QSV_AV1) == 0) {
  646. if (!qsv_av1_supported) {
  647. changed = true;
  648. name = SIMPLE_ENCODER_X264;
  649. return false;
  650. }
  651. } else if (strcmp(name, SIMPLE_ENCODER_NVENC) == 0) {
  652. if (!nve_supported) {
  653. changed = true;
  654. name = SIMPLE_ENCODER_X264;
  655. return false;
  656. }
  657. } else if (strcmp(name, SIMPLE_ENCODER_NVENC_AV1) == 0) {
  658. if (!nve_supported) {
  659. changed = true;
  660. name = SIMPLE_ENCODER_X264;
  661. return false;
  662. }
  663. #ifdef ENABLE_HEVC
  664. } else if (strcmp(name, SIMPLE_ENCODER_AMD_HEVC) == 0) {
  665. if (!amd_hevc_supported) {
  666. changed = true;
  667. name = SIMPLE_ENCODER_X264;
  668. return false;
  669. }
  670. } else if (strcmp(name, SIMPLE_ENCODER_NVENC_HEVC) == 0) {
  671. if (!nve_hevc_supported) {
  672. changed = true;
  673. name = SIMPLE_ENCODER_X264;
  674. return false;
  675. }
  676. #endif
  677. } else if (strcmp(name, SIMPLE_ENCODER_AMD) == 0) {
  678. if (!amd_supported) {
  679. changed = true;
  680. name = SIMPLE_ENCODER_X264;
  681. return false;
  682. }
  683. } else if (strcmp(name, SIMPLE_ENCODER_AMD_AV1) == 0) {
  684. if (!amd_av1_supported) {
  685. changed = true;
  686. name = SIMPLE_ENCODER_X264;
  687. return false;
  688. }
  689. } else if (strcmp(name, SIMPLE_ENCODER_APPLE_H264) == 0) {
  690. if (!apple_supported) {
  691. changed = true;
  692. name = SIMPLE_ENCODER_X264;
  693. return false;
  694. }
  695. #ifdef ENABLE_HEVC
  696. } else if (strcmp(name, SIMPLE_ENCODER_APPLE_HEVC) == 0) {
  697. if (!apple_hevc_supported) {
  698. changed = true;
  699. name = SIMPLE_ENCODER_X264;
  700. return false;
  701. }
  702. #endif
  703. }
  704. return true;
  705. };
  706. if (!CheckEncoder(curStreamEncoder))
  707. config_set_string(activeConfiguration, "SimpleOutput", "StreamEncoder", curStreamEncoder);
  708. if (!CheckEncoder(curRecEncoder))
  709. config_set_string(activeConfiguration, "SimpleOutput", "RecEncoder", curRecEncoder);
  710. if (changed) {
  711. activeConfiguration.SaveSafe("tmp");
  712. }
  713. }