2015-06-23 19:29:07 -07:00
|
|
|
/******************************************************************************
|
2023-05-18 17:37:26 -07:00
|
|
|
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
|
2015-06-23 19:29:07 -07:00
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
#include "OBSBasic.hpp"
|
|
|
|
|
|
|
|
|
|
#include <dialogs/OBSMissingFiles.hpp>
|
|
|
|
|
#include <importer/OBSImporter.hpp>
|
2025-01-22 20:44:35 +01:00
|
|
|
#include <models/SceneCollection.hpp>
|
2025-01-08 17:35:16 +01:00
|
|
|
#include <utility/item-widget-helpers.hpp>
|
|
|
|
|
|
|
|
|
|
#include <qt-wrappers.hpp>
|
|
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
#include <filesystem>
|
|
|
|
|
#include <string>
|
2025-01-08 17:35:16 +01:00
|
|
|
#include <vector>
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
extern bool safe_mode;
|
|
|
|
|
extern bool opt_start_streaming;
|
|
|
|
|
extern bool opt_start_recording;
|
|
|
|
|
extern bool opt_start_virtualcam;
|
|
|
|
|
extern bool opt_start_replaybuffer;
|
|
|
|
|
extern std::string opt_starting_scene;
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
using SceneCoordinateMode = OBS::SceneCoordinateMode;
|
|
|
|
|
using SceneCollection = OBS::SceneCollection;
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
// MARK: Constant Expressions
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
static constexpr std::string_view SceneCollectionPath = "/obs-studio/basic/scenes/";
|
|
|
|
|
|
|
|
|
|
namespace DataKeys {
|
|
|
|
|
static constexpr std::string_view AbsoluteCoordinates = "AbsoluteCoordinates";
|
|
|
|
|
static constexpr std::string_view MigrationResolution = "migration_resolution";
|
|
|
|
|
} // namespace DataKeys
|
|
|
|
|
|
|
|
|
|
namespace L10N {
|
|
|
|
|
static constexpr std::string_view Migrate = "Basic.MainMenu.SceneCollection.Migrate";
|
|
|
|
|
static constexpr std::string_view Remigrate = "Basic.MainMenu.SceneCollection.Remigrate";
|
|
|
|
|
} // namespace L10N
|
2016-09-06 04:49:26 -07:00
|
|
|
|
2024-10-18 14:07:38 +02:00
|
|
|
// MARK: - Anonymous Namespace
|
|
|
|
|
namespace {
|
|
|
|
|
QList<QString> sortedSceneCollections{};
|
|
|
|
|
|
|
|
|
|
void updateSortedSceneCollections(const OBSSceneCollectionCache &collections)
|
|
|
|
|
{
|
|
|
|
|
const QLocale locale = QLocale::system();
|
|
|
|
|
QList<QString> newList{};
|
|
|
|
|
|
|
|
|
|
for (auto [collectionName, _] : collections) {
|
|
|
|
|
QString entry = QString::fromStdString(collectionName);
|
|
|
|
|
newList.append(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::sort(newList.begin(), newList.end(), [&locale](const QString &lhs, const QString &rhs) -> bool {
|
|
|
|
|
int result = QString::localeAwareCompare(locale.toLower(lhs), locale.toLower(rhs));
|
|
|
|
|
|
|
|
|
|
return (result < 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
sortedSceneCollections.swap(newList);
|
|
|
|
|
}
|
2024-11-25 20:28:50 +01:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
void cleanBackupCollision(const SceneCollection &collection)
|
2024-11-25 20:28:50 +01:00
|
|
|
{
|
2025-01-22 20:44:35 +01:00
|
|
|
std::filesystem::path backupFilePath = collection.getFilePath();
|
2024-11-25 20:28:50 +01:00
|
|
|
backupFilePath.replace_extension(".json.bak");
|
|
|
|
|
|
|
|
|
|
if (std::filesystem::exists(backupFilePath)) {
|
|
|
|
|
try {
|
|
|
|
|
std::filesystem::remove(backupFilePath);
|
|
|
|
|
} catch (std::filesystem::filesystem_error &) {
|
|
|
|
|
throw std::logic_error("Failed to remove pre-existing scene collection backup file: " +
|
|
|
|
|
backupFilePath.u8string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-22 20:44:35 +01:00
|
|
|
|
|
|
|
|
void updateRemigrationMenuItem(SceneCoordinateMode mode, QAction *menuItem)
|
|
|
|
|
{
|
|
|
|
|
bool isAbsoluteCoordinateMode = mode == SceneCoordinateMode::Absolute;
|
|
|
|
|
|
|
|
|
|
OBSDataAutoRelease privateData = obs_get_private_data();
|
|
|
|
|
obs_data_set_bool(privateData, DataKeys::AbsoluteCoordinates.data(), isAbsoluteCoordinateMode);
|
|
|
|
|
|
|
|
|
|
if (isAbsoluteCoordinateMode) {
|
|
|
|
|
menuItem->setText(QTStr(L10N::Migrate.data()));
|
|
|
|
|
} else {
|
|
|
|
|
menuItem->setText(QTStr(L10N::Remigrate.data()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
menuItem->setEnabled(isAbsoluteCoordinateMode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void removeRelativePositionData(obs_data_t *settings)
|
|
|
|
|
{
|
|
|
|
|
OBSDataArrayAutoRelease sources = obs_data_get_array(settings, "sources");
|
|
|
|
|
|
|
|
|
|
auto iterateCallback = [](obs_data_t *data, void *) {
|
|
|
|
|
const std::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");
|
|
|
|
|
|
|
|
|
|
auto cleanupCallback = [](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");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
obs_data_array_enum(items, cleanupCallback, nullptr);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
obs_data_array_enum(sources, iterateCallback, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-18 14:07:38 +02:00
|
|
|
} // namespace
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
// MARK: - Main Scene Collection Management Functions
|
|
|
|
|
|
|
|
|
|
void OBSBasic::SetupNewSceneCollection(const std::string &collectionName)
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2025-01-06 18:48:11 +01:00
|
|
|
if (collectionName.empty()) {
|
|
|
|
|
throw std::logic_error("Cannot create new scene collection with empty collection name");
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection &newCollection = CreateSceneCollection(collectionName);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
|
|
|
|
|
2024-11-25 20:28:50 +01:00
|
|
|
cleanBackupCollision(newCollection);
|
2024-09-03 16:29:58 +02:00
|
|
|
ActivateSceneCollection(newCollection);
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
blog(LOG_INFO, "Created scene collection '%s' (clean, %s)", newCollection.getName().c_str(),
|
|
|
|
|
newCollection.getFileName().c_str());
|
2024-09-03 16:29:58 +02:00
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBSBasic::SetupDuplicateSceneCollection(const std::string &collectionName)
|
|
|
|
|
{
|
2025-01-22 20:44:35 +01:00
|
|
|
const SceneCollection ¤tCollection = GetCurrentSceneCollection();
|
|
|
|
|
SceneCollection &newCollection = CreateSceneCollection(collectionName);
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
SaveProjectNow();
|
|
|
|
|
|
|
|
|
|
const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
|
|
|
|
|
|
|
|
|
|
try {
|
2025-01-22 20:44:35 +01:00
|
|
|
std::filesystem::copy(currentCollection.getFilePath(), newCollection.getFilePath(), copyOptions);
|
2024-09-03 16:29:58 +02:00
|
|
|
} catch (const std::filesystem::filesystem_error &error) {
|
|
|
|
|
blog(LOG_DEBUG, "%s", error.what());
|
2025-01-22 20:44:35 +01:00
|
|
|
throw std::logic_error("Failed to copy file for cloned scene collection: " + newCollection.getName());
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
OBSDataAutoRelease collection = obs_data_create_from_json_file(newCollection.getFileName().c_str());
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
obs_data_set_string(collection, "name", newCollection.getName().c_str());
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources");
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (sources) {
|
|
|
|
|
obs_data_erase(collection, "sources");
|
|
|
|
|
|
|
|
|
|
obs_data_array_enum(
|
|
|
|
|
sources,
|
|
|
|
|
[](obs_data_t *data, void *) -> void {
|
|
|
|
|
const char *uuid = os_generate_uuid();
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
obs_data_set_string(data, "uuid", uuid);
|
|
|
|
|
|
|
|
|
|
bfree((void *)uuid);
|
|
|
|
|
},
|
|
|
|
|
nullptr);
|
|
|
|
|
|
|
|
|
|
obs_data_set_array(collection, "sources", sources);
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
obs_data_save_json_safe(collection, newCollection.getFileName().c_str(), "tmp", nullptr);
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2024-11-25 20:28:50 +01:00
|
|
|
cleanBackupCollision(newCollection);
|
2024-09-03 16:29:58 +02:00
|
|
|
ActivateSceneCollection(newCollection);
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
blog(LOG_INFO, "Created scene collection '%s' (duplicate, %s)", newCollection.getName().c_str(),
|
|
|
|
|
newCollection.getFileName().c_str());
|
2024-09-03 16:29:58 +02:00
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::SetupRenameSceneCollection(const std::string &collectionName)
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2025-01-22 20:44:35 +01:00
|
|
|
const SceneCollection currentCollection = GetCurrentSceneCollection();
|
|
|
|
|
SceneCollection &newCollection = CreateSceneCollection(collectionName);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
SaveProjectNow();
|
|
|
|
|
|
|
|
|
|
const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
|
|
|
|
|
|
|
|
|
|
try {
|
2025-01-22 20:44:35 +01:00
|
|
|
std::filesystem::copy(currentCollection.getFilePath(), newCollection.getFilePath(), copyOptions);
|
2024-09-03 16:29:58 +02:00
|
|
|
} catch (const std::filesystem::filesystem_error &error) {
|
|
|
|
|
blog(LOG_DEBUG, "%s", error.what());
|
2025-01-22 20:44:35 +01:00
|
|
|
throw std::logic_error("Failed to copy file for scene collection: " + currentCollection.getName());
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
collections.erase(currentCollection.getName());
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
OBSDataAutoRelease collection = obs_data_create_from_json_file(newCollection.getFileName().c_str());
|
|
|
|
|
obs_data_set_string(collection, "name", newCollection.getName().c_str());
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
obs_data_save_json_safe(collection, newCollection.getFileName().c_str(), "tmp", nullptr);
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2024-11-25 20:28:50 +01:00
|
|
|
cleanBackupCollision(newCollection);
|
2024-09-03 16:29:58 +02:00
|
|
|
ActivateSceneCollection(newCollection);
|
|
|
|
|
RemoveSceneCollection(currentCollection);
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
blog(LOG_INFO, "Renamed scene collection '%s' to '%s' (%s)", currentCollection.getName().c_str(),
|
|
|
|
|
newCollection.getName().c_str(), newCollection.getFileName().c_str());
|
2024-09-03 16:29:58 +02:00
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
|
|
|
|
|
|
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_RENAMED);
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
// MARK: - Scene Collection File Management Functions
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection &OBSBasic::CreateSceneCollection(const std::string &collectionName)
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2024-09-03 16:29:58 +02:00
|
|
|
if (const auto &foundCollection = GetSceneCollectionByName(collectionName)) {
|
|
|
|
|
throw std::invalid_argument("Scene collection already exists: " + collectionName);
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
std::string fileName;
|
|
|
|
|
if (!GetFileSafeName(collectionName.c_str(), fileName)) {
|
|
|
|
|
throw std::invalid_argument("Failed to create safe directory for new scene collection: " +
|
|
|
|
|
collectionName);
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
std::string collectionFile;
|
2025-01-22 20:44:35 +01:00
|
|
|
collectionFile.reserve(App()->userScenesLocation.u8string().size() + SceneCollectionPath.size() +
|
2024-09-03 16:29:58 +02:00
|
|
|
fileName.size());
|
2025-01-22 20:44:35 +01:00
|
|
|
collectionFile.append(App()->userScenesLocation.u8string()).append(SceneCollectionPath).append(fileName);
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
if (!GetClosestUnusedFileName(collectionFile, "json")) {
|
|
|
|
|
throw std::invalid_argument("Failed to get closest file name for new scene collection: " + fileName);
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
std::filesystem::path collectionFilePath = std::filesystem::u8path(collectionFile);
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
auto [iterator, success] =
|
|
|
|
|
collections.try_emplace(collectionName, collectionName, std::move(collectionFilePath));
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
return iterator->second;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
void OBSBasic::RemoveSceneCollection(SceneCollection collection)
|
2024-09-03 16:29:58 +02:00
|
|
|
{
|
|
|
|
|
try {
|
2025-01-22 20:44:35 +01:00
|
|
|
std::filesystem::remove(collection.getFilePath());
|
2024-09-03 16:29:58 +02:00
|
|
|
} catch (const std::filesystem::filesystem_error &error) {
|
|
|
|
|
blog(LOG_DEBUG, "%s", error.what());
|
2025-01-22 20:44:35 +01:00
|
|
|
throw std::logic_error("Failed to remove scene collection file: " + collection.getFileName());
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
blog(LOG_INFO, "Removed scene collection '%s' (%s)", collection.getName().c_str(),
|
|
|
|
|
collection.getFileName().c_str());
|
2024-09-03 16:29:58 +02:00
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Scene Collection UI Handling Functions
|
|
|
|
|
|
|
|
|
|
bool OBSBasic::CreateNewSceneCollection(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
SetupNewSceneCollection(name.toStdString());
|
|
|
|
|
return true;
|
|
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
return false;
|
|
|
|
|
} catch (const std::logic_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
bool OBSBasic::CreateDuplicateSceneCollection(const QString &name)
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2024-09-03 16:29:58 +02:00
|
|
|
try {
|
|
|
|
|
SetupDuplicateSceneCollection(name.toStdString());
|
|
|
|
|
return true;
|
|
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
return false;
|
|
|
|
|
} catch (const std::logic_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::DeleteSceneCollection(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
const std::string_view currentCollectionName{
|
|
|
|
|
config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
2019-12-04 15:47:16 -08:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (currentCollectionName == name.toStdString()) {
|
|
|
|
|
on_actionRemoveSceneCollection_triggered();
|
|
|
|
|
return;
|
2018-05-04 15:36:01 -07:00
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection currentCollection = GetCurrentSceneCollection();
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
RemoveSceneCollection(currentCollection);
|
2023-03-09 20:33:36 +01:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
collections.erase(name.toStdString());
|
2023-03-09 20:33:36 +01:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
RefreshSceneCollections();
|
2021-03-22 01:27:10 -04:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
|
|
|
|
|
}
|
2021-10-07 02:59:20 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::ChangeSceneCollection()
|
|
|
|
|
{
|
|
|
|
|
QAction *action = reinterpret_cast<QAction *>(sender());
|
2021-03-22 01:27:10 -04:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (!action) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
const std::string_view currentCollectionName{
|
|
|
|
|
config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
2024-10-18 16:46:04 +02:00
|
|
|
const QVariant qCollectionName = action->property("collection_name");
|
|
|
|
|
const std::string selectedCollectionName{qCollectionName.toString().toStdString()};
|
2016-08-28 14:24:14 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (currentCollectionName == selectedCollectionName) {
|
|
|
|
|
action->setChecked(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
auto foundCollection = GetSceneCollectionByName(selectedCollectionName);
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
if (!foundCollection) {
|
|
|
|
|
const std::string errorMessage{"Selected scene collection not found: "};
|
|
|
|
|
|
|
|
|
|
throw std::invalid_argument(errorMessage + currentCollectionName.data());
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection &selectedCollection = foundCollection.value();
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
|
|
|
|
|
|
|
|
|
ActivateSceneCollection(selectedCollection);
|
2018-05-04 15:36:01 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
blog(LOG_INFO, "Switched to scene collection '%s' (%s)", selectedCollection.getName().c_str(),
|
|
|
|
|
selectedCollection.getFileName().c_str());
|
2024-09-03 16:29:58 +02:00
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::RefreshSceneCollections(bool refreshCache)
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2024-09-03 16:29:58 +02:00
|
|
|
std::string_view currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
|
|
|
|
|
2015-06-23 19:29:07 -07:00
|
|
|
QList<QAction *> menuActions = ui->sceneCollectionMenu->actions();
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
for (auto &action : menuActions) {
|
|
|
|
|
QVariant variant = action->property("file_name");
|
|
|
|
|
if (variant.typeName() != nullptr) {
|
|
|
|
|
delete action;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (refreshCache) {
|
|
|
|
|
RefreshSceneCollectionCache();
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-10-18 14:07:38 +02:00
|
|
|
updateSortedSceneCollections(collections);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-10-18 14:07:38 +02:00
|
|
|
size_t numAddedCollections = 0;
|
|
|
|
|
for (auto &name : sortedSceneCollections) {
|
|
|
|
|
const std::string collectionName = name.toStdString();
|
|
|
|
|
try {
|
2025-01-22 20:44:35 +01:00
|
|
|
const SceneCollection &collection = collections.at(collectionName);
|
2024-10-18 16:46:04 +02:00
|
|
|
const QString qCollectionName = QString().fromStdString(collectionName);
|
2024-10-18 14:07:38 +02:00
|
|
|
|
2024-10-18 16:46:04 +02:00
|
|
|
QAction *action = new QAction(qCollectionName, this);
|
|
|
|
|
action->setProperty("collection_name", qCollectionName);
|
2025-01-22 20:44:35 +01:00
|
|
|
action->setProperty("file_name", QString().fromStdString(collection.getFileName()));
|
2024-10-18 14:07:38 +02:00
|
|
|
connect(action, &QAction::triggered, this, &OBSBasic::ChangeSceneCollection);
|
|
|
|
|
action->setCheckable(true);
|
|
|
|
|
action->setChecked(collectionName == currentCollectionName);
|
|
|
|
|
|
|
|
|
|
ui->sceneCollectionMenu->addAction(action);
|
|
|
|
|
|
|
|
|
|
numAddedCollections += 1;
|
|
|
|
|
} catch (const std::out_of_range &error) {
|
|
|
|
|
blog(LOG_ERROR, "No scene collection with name %s found in scene collection cache.\n%s",
|
|
|
|
|
collectionName.c_str(), error.what());
|
|
|
|
|
}
|
2017-05-17 23:29:02 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
ui->actionRemoveSceneCollection->setEnabled(numAddedCollections > 1);
|
2016-12-29 09:21:53 -06:00
|
|
|
|
2025-01-25 20:21:42 -06:00
|
|
|
OBSBasic *main = OBSBasic::Get();
|
2017-05-17 23:29:02 -07:00
|
|
|
|
2017-03-25 07:08:11 -07:00
|
|
|
main->ui->actionPasteFilters->setEnabled(false);
|
2017-03-25 04:19:29 -07:00
|
|
|
main->ui->actionPasteRef->setEnabled(false);
|
|
|
|
|
main->ui->actionPasteDup->setEnabled(false);
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
// MARK: - Scene Collection Cache Functions
|
|
|
|
|
|
|
|
|
|
void OBSBasic::RefreshSceneCollectionCache()
|
|
|
|
|
{
|
|
|
|
|
OBSSceneCollectionCache foundCollections{};
|
|
|
|
|
|
|
|
|
|
const std::filesystem::path collectionsPath =
|
2025-01-22 20:44:35 +01:00
|
|
|
App()->userScenesLocation / std::filesystem::u8path(SceneCollectionPath.substr(1));
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
if (!std::filesystem::exists(collectionsPath)) {
|
|
|
|
|
blog(LOG_WARNING, "Failed to get scene collections config path");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto &entry : std::filesystem::directory_iterator(collectionsPath)) {
|
|
|
|
|
if (entry.is_directory()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entry.path().extension().u8string() != ".json") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OBSDataAutoRelease collectionData =
|
|
|
|
|
obs_data_create_from_json_file_safe(entry.path().u8string().c_str(), "bak");
|
|
|
|
|
|
|
|
|
|
std::string candidateName;
|
2025-01-17 16:39:19 +01:00
|
|
|
std::string collectionName = obs_data_get_string(collectionData, "name");
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2025-01-17 16:39:19 +01:00
|
|
|
if (collectionName.empty()) {
|
|
|
|
|
candidateName = entry.path().stem().u8string();
|
2024-09-03 16:29:58 +02:00
|
|
|
} else {
|
2025-01-17 16:39:19 +01:00
|
|
|
candidateName = std::move(collectionName);
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
foundCollections.try_emplace(candidateName, candidateName, entry.path());
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
collections.swap(foundCollections);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection &OBSBasic::GetCurrentSceneCollection()
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2024-09-03 16:29:58 +02:00
|
|
|
std::string currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
|
|
|
|
|
|
|
|
|
if (currentCollectionName.empty()) {
|
|
|
|
|
throw std::invalid_argument("No valid scene collection name in configuration Basic->SceneCollection");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto &foundCollection = collections.find(currentCollectionName);
|
|
|
|
|
|
|
|
|
|
if (foundCollection != collections.end()) {
|
|
|
|
|
return foundCollection->second;
|
|
|
|
|
} else {
|
|
|
|
|
throw std::invalid_argument("Scene collection not found in collection list: " + currentCollectionName);
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
std::optional<SceneCollection> OBSBasic::GetSceneCollectionByName(const std::string &collectionName) const
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2024-09-03 16:29:58 +02:00
|
|
|
auto foundCollection = collections.find(collectionName);
|
|
|
|
|
|
|
|
|
|
if (foundCollection == collections.end()) {
|
|
|
|
|
return {};
|
|
|
|
|
} else {
|
|
|
|
|
return foundCollection->second;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
std::optional<SceneCollection> OBSBasic::GetSceneCollectionByFileName(const std::string &fileName) const
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2024-09-03 16:29:58 +02:00
|
|
|
for (auto &[iterator, collection] : collections) {
|
2025-01-22 20:44:35 +01:00
|
|
|
if (collection.getFileName() == fileName) {
|
2024-09-03 16:29:58 +02:00
|
|
|
return collection;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Qt Slot Functions
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::on_actionNewSceneCollection_triggered()
|
|
|
|
|
{
|
|
|
|
|
const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
|
|
|
|
|
if (GetSceneCollectionByName(result.promptValue)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const OBSPromptRequest request{Str("Basic.Main.AddSceneCollection.Title"),
|
|
|
|
|
Str("Basic.Main.AddSceneCollection.Text")};
|
|
|
|
|
|
|
|
|
|
OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
try {
|
|
|
|
|
SetupNewSceneCollection(result.promptValue);
|
|
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
} catch (const std::logic_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBSBasic::on_actionDupSceneCollection_triggered()
|
|
|
|
|
{
|
|
|
|
|
const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
|
|
|
|
|
if (GetSceneCollectionByName(result.promptValue)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
return true;
|
|
|
|
|
};
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
const OBSPromptRequest request{Str("Basic.Main.AddSceneCollection.Title"),
|
|
|
|
|
Str("Basic.Main.AddSceneCollection.Text")};
|
2016-08-28 14:24:14 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
SetupDuplicateSceneCollection(result.promptValue);
|
|
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
} catch (const std::logic_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::on_actionRenameSceneCollection_triggered()
|
2015-06-23 19:29:07 -07:00
|
|
|
{
|
2025-01-22 20:44:35 +01:00
|
|
|
const SceneCollection ¤tCollection = GetCurrentSceneCollection();
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
|
|
|
|
|
if (GetSceneCollectionByName(result.promptValue)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
return true;
|
|
|
|
|
};
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
const OBSPromptRequest request{Str("Basic.Main.RenameSceneCollection.Title"),
|
2025-01-22 20:44:35 +01:00
|
|
|
Str("Basic.Main.AddSceneCollection.Text"), currentCollection.getName()};
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (!result.success) {
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
SetupRenameSceneCollection(result.promptValue);
|
|
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
} catch (const std::logic_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
void OBSBasic::on_actionRemoveSceneCollection_triggered(bool skipConfirmation)
|
|
|
|
|
{
|
|
|
|
|
if (collections.size() < 2) {
|
2015-06-23 19:29:07 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection currentCollection;
|
2021-10-07 02:59:20 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
try {
|
|
|
|
|
currentCollection = GetCurrentSceneCollection();
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (!skipConfirmation) {
|
|
|
|
|
const QString confirmationText =
|
2025-01-22 20:44:35 +01:00
|
|
|
QTStr("ConfirmRemove.Text").arg(QString::fromStdString(currentCollection.getName()));
|
2024-09-03 16:29:58 +02:00
|
|
|
const QMessageBox::StandardButton button =
|
|
|
|
|
OBSMessageBox::question(this, QTStr("ConfirmRemove.Title"), confirmationText);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (button == QMessageBox::No) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
collections.erase(currentCollection.getName());
|
2024-09-03 16:29:58 +02:00
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
} catch (const std::logic_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
|
|
|
|
}
|
2016-08-28 14:24:14 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection &newCollection = collections.begin()->second;
|
2024-09-03 16:29:58 +02:00
|
|
|
|
|
|
|
|
ActivateSceneCollection(newCollection);
|
|
|
|
|
RemoveSceneCollection(currentCollection);
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
blog(LOG_INFO, "Switched to scene collection '%s' (%s)", newCollection.getName().c_str(),
|
|
|
|
|
newCollection.getFileName().c_str());
|
2024-09-03 16:29:58 +02:00
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2016-12-08 17:09:22 -06:00
|
|
|
void OBSBasic::on_actionImportSceneCollection_triggered()
|
|
|
|
|
{
|
2022-05-16 21:51:01 +02:00
|
|
|
OBSImporter imp(this);
|
|
|
|
|
imp.exec();
|
2018-01-29 11:30:47 +01:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
RefreshSceneCollections(true);
|
2016-12-08 17:09:22 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBSBasic::on_actionExportSceneCollection_triggered()
|
|
|
|
|
{
|
2018-12-21 11:26:13 +13:00
|
|
|
SaveProjectNow();
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
const SceneCollection ¤tCollection = GetCurrentSceneCollection();
|
2016-12-08 17:09:22 -06:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
const QString home = QDir::homePath();
|
2016-12-08 17:09:22 -06:00
|
|
|
|
2020-07-17 06:32:38 -05:00
|
|
|
const QString destinationFileName = SaveFile(this, QTStr("Basic.MainMenu.SceneCollection.Export"),
|
2025-01-22 20:44:35 +01:00
|
|
|
home + "/" + currentCollection.getFileName().c_str(),
|
2024-09-03 16:29:58 +02:00
|
|
|
"JSON Files (*.json)");
|
2016-12-08 17:09:22 -06:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (!destinationFileName.isEmpty() && !destinationFileName.isNull()) {
|
2025-01-22 20:44:35 +01:00
|
|
|
const std::filesystem::path sourceFile = currentCollection.getFilePath();
|
2024-09-03 16:29:58 +02:00
|
|
|
const std::filesystem::path destinationFile =
|
|
|
|
|
std::filesystem::u8path(destinationFileName.toStdString());
|
2017-07-13 18:55:30 +02:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OBSDataAutoRelease collection = obs_data_create_from_json_file(sourceFile.u8string().c_str());
|
2023-03-04 18:22:21 +01:00
|
|
|
|
|
|
|
|
OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources");
|
|
|
|
|
if (!sources) {
|
|
|
|
|
blog(LOG_WARNING, "No sources in exported scene collection");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-09-03 16:29:58 +02:00
|
|
|
|
2023-03-04 18:22:21 +01:00
|
|
|
obs_data_erase(collection, "sources");
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
using OBSDataVector = std::vector<OBSData>;
|
|
|
|
|
|
|
|
|
|
OBSDataVector sourceItems;
|
2023-03-04 18:22:21 +01:00
|
|
|
obs_data_array_enum(
|
|
|
|
|
sources,
|
2024-09-03 16:29:58 +02:00
|
|
|
[](obs_data_t *data, void *vector) -> void {
|
|
|
|
|
OBSDataVector &sourceItems{*static_cast<OBSDataVector *>(vector)};
|
2023-03-04 18:22:21 +01:00
|
|
|
sourceItems.push_back(data);
|
|
|
|
|
},
|
|
|
|
|
&sourceItems);
|
|
|
|
|
|
|
|
|
|
std::sort(sourceItems.begin(), sourceItems.end(), [](const OBSData &a, const OBSData &b) {
|
|
|
|
|
return astrcmpi(obs_data_get_string(a, "name"), obs_data_get_string(b, "name")) < 0;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
OBSDataArrayAutoRelease newSources = obs_data_array_create();
|
2024-09-03 16:29:58 +02:00
|
|
|
for (auto &item : sourceItems) {
|
2023-03-04 18:22:21 +01:00
|
|
|
obs_data_array_push_back(newSources, item);
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
2023-03-04 18:22:21 +01:00
|
|
|
|
|
|
|
|
obs_data_set_array(collection, "sources", newSources);
|
2024-09-03 16:29:58 +02:00
|
|
|
obs_data_save_json_pretty_safe(collection, destinationFile.u8string().c_str(), "tmp", "bak");
|
2017-07-13 18:55:30 +02:00
|
|
|
}
|
2016-12-08 17:09:22 -06:00
|
|
|
}
|
|
|
|
|
|
2024-04-23 05:05:50 +02:00
|
|
|
void OBSBasic::on_actionRemigrateSceneCollection_triggered()
|
|
|
|
|
{
|
|
|
|
|
if (Active()) {
|
|
|
|
|
OBSMessageBox::warning(this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
|
|
|
|
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.Active"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
SceneCollection ¤tCollection = GetCurrentSceneCollection();
|
|
|
|
|
SceneCoordinateMode currentCoordinateMode = currentCollection.getCoordinateMode();
|
|
|
|
|
OBS::Rect currentMigrationResolution = currentCollection.getMigrationResolution();
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
if (currentCoordinateMode == SceneCoordinateMode::Relative && currentMigrationResolution.isZero()) {
|
2024-04-23 05:05:50 +02:00
|
|
|
OBSMessageBox::warning(
|
|
|
|
|
this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
|
|
|
|
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.UnknownBaseResolution"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
obs_video_info ovi;
|
|
|
|
|
obs_get_video_info(&ovi);
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
OBS::Rect videoResolution = OBS::Rect(ovi.base_width, ovi.base_height);
|
|
|
|
|
|
|
|
|
|
if (currentCoordinateMode == SceneCoordinateMode::Relative && currentMigrationResolution == videoResolution) {
|
2024-04-23 05:05:50 +02:00
|
|
|
OBSMessageBox::warning(
|
|
|
|
|
this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
|
|
|
|
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.BaseResolutionMatches"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
QString name = QString::fromStdString(currentCollection.getName());
|
2024-04-23 05:05:50 +02:00
|
|
|
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};
|
2025-01-22 20:44:35 +01:00
|
|
|
if (currentCoordinateMode == SceneCoordinateMode::Relative) {
|
2024-04-23 05:05:50 +02:00
|
|
|
/* Temporarily change resolution to migration resolution */
|
2025-01-22 20:44:35 +01:00
|
|
|
ovi.base_width = currentMigrationResolution.getWidth<uint32_t>();
|
|
|
|
|
ovi.base_height = currentMigrationResolution.getHeight<uint32_t>();
|
2024-04-23 05:05:50 +02:00
|
|
|
|
|
|
|
|
if (obs_reset_video(&ovi) != OBS_VIDEO_SUCCESS) {
|
|
|
|
|
OBSMessageBox::critical(
|
|
|
|
|
this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
|
|
|
|
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.FailedVideoReset"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
// Temporarily switch the coordinate mode of the current scene collection to "absolute" mode to force the
|
|
|
|
|
// collection to be saved as a "version 1" variant. By resetting the coordinate mode to its original mode after
|
|
|
|
|
// saving the collection, the activation process will migrate it automatically.
|
|
|
|
|
|
|
|
|
|
currentCollection.setCoordinateMode(SceneCoordinateMode::Absolute);
|
2024-04-23 05:05:50 +02:00
|
|
|
SaveProjectNow();
|
2025-01-22 20:44:35 +01:00
|
|
|
currentCollection.setCoordinateMode(currentCoordinateMode);
|
|
|
|
|
|
2024-04-23 05:05:50 +02:00
|
|
|
/* Reset video if we potentially changed to a temporary resolution */
|
2025-01-22 20:44:35 +01:00
|
|
|
if (currentCoordinateMode == SceneCoordinateMode::Relative) {
|
2024-04-23 05:05:50 +02:00
|
|
|
ResetVideo();
|
2024-09-03 16:29:58 +02:00
|
|
|
}
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
ActivateSceneCollection(currentCollection);
|
2024-04-23 05:05:50 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
// MARK: - Scene Collection Management Helper Functions
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
void OBSBasic::ActivateSceneCollection(SceneCollection &collection)
|
2024-09-03 16:29:58 +02:00
|
|
|
{
|
|
|
|
|
const std::string currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
2021-03-22 01:27:10 -04:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
if (auto foundCollection = GetSceneCollectionByName(currentCollectionName)) {
|
2025-01-22 20:44:35 +01:00
|
|
|
if (collection.getName() != foundCollection.value().getName()) {
|
2024-09-03 16:29:58 +02:00
|
|
|
SaveProjectNow();
|
|
|
|
|
}
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", collection.getName().c_str());
|
|
|
|
|
config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", collection.getFileName().c_str());
|
2021-10-07 02:59:20 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
Load(collection);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
|
|
|
|
RefreshSceneCollections();
|
|
|
|
|
|
|
|
|
|
UpdateTitleBar();
|
2016-08-28 14:24:14 -07:00
|
|
|
|
2024-09-03 16:29:58 +02:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
|
2024-06-18 14:18:22 -05:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
|
2015-06-23 19:29:07 -07:00
|
|
|
}
|
2013-12-29 09:17:00 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
// MARK: - OBSBasic Scene Collection Functions
|
2020-08-19 15:09:26 -03:00
|
|
|
|
2013-12-29 08:54:06 -07:00
|
|
|
using namespace std;
|
2013-11-22 16:20:52 -07:00
|
|
|
|
2015-12-05 05:58:05 -08:00
|
|
|
static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector<OBSSource> &audioSources)
|
2014-05-03 22:54:38 -07:00
|
|
|
{
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSSourceAutoRelease source = obs_get_output_source(channel);
|
2014-05-03 22:54:38 -07:00
|
|
|
if (!source)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
audioSources.push_back(source.Get());
|
2015-12-05 05:58:05 -08:00
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSDataAutoRelease data = obs_save_source(source);
|
2014-05-03 22:54:38 -07:00
|
|
|
|
2014-08-05 11:09:29 -07:00
|
|
|
obs_data_set_obj(parent, name, data);
|
2014-05-03 22:54:38 -07:00
|
|
|
}
|
|
|
|
|
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData,
|
2016-02-27 02:50:04 -08:00
|
|
|
int transitionDuration, obs_data_array_t *transitions, OBSScene &scene,
|
2018-02-28 17:49:47 -03:00
|
|
|
OBSSource &curProgramScene, obs_data_array_t *savedProjectorList)
|
2014-04-26 23:47:50 -07:00
|
|
|
{
|
2015-12-05 05:58:05 -08:00
|
|
|
obs_data_t *saveData = obs_data_create();
|
|
|
|
|
|
|
|
|
|
vector<OBSSource> audioSources;
|
2020-02-25 00:24:05 +01:00
|
|
|
audioSources.reserve(6);
|
2015-12-05 05:58:05 -08:00
|
|
|
|
|
|
|
|
SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources);
|
|
|
|
|
SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources);
|
|
|
|
|
SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources);
|
|
|
|
|
SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources);
|
|
|
|
|
SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources);
|
2018-06-26 20:41:32 +02:00
|
|
|
SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources);
|
2015-12-05 05:58:05 -08:00
|
|
|
|
2018-07-15 18:50:33 -07:00
|
|
|
/* -------------------------------- */
|
|
|
|
|
/* save non-group sources */
|
|
|
|
|
|
2015-12-05 05:58:05 -08:00
|
|
|
auto FilterAudioSources = [&](obs_source_t *source) {
|
2018-07-15 18:50:33 -07:00
|
|
|
if (obs_source_is_group(source))
|
|
|
|
|
return false;
|
|
|
|
|
|
2015-12-05 05:58:05 -08:00
|
|
|
return find(begin(audioSources), end(audioSources), source) == end(audioSources);
|
|
|
|
|
};
|
|
|
|
|
using FilterAudioSources_t = decltype(FilterAudioSources);
|
|
|
|
|
|
|
|
|
|
obs_data_array_t *sourcesArray = obs_save_sources_filtered(
|
|
|
|
|
[](void *data, obs_source_t *source) {
|
2021-10-13 02:44:31 -07:00
|
|
|
auto &func = *static_cast<FilterAudioSources_t *>(data);
|
|
|
|
|
return func(source);
|
2015-12-05 05:58:05 -08:00
|
|
|
},
|
|
|
|
|
static_cast<void *>(&FilterAudioSources));
|
|
|
|
|
|
2018-07-15 18:50:33 -07:00
|
|
|
/* -------------------------------- */
|
|
|
|
|
/* save group sources separately */
|
|
|
|
|
|
|
|
|
|
/* saving separately ensures they won't be loaded in older versions */
|
|
|
|
|
obs_data_array_t *groupsArray = obs_save_sources_filtered(
|
|
|
|
|
[](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr);
|
|
|
|
|
|
|
|
|
|
/* -------------------------------- */
|
|
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSSourceAutoRelease transition = obs_get_output_source(0);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
obs_source_t *currentScene = obs_scene_get_source(scene);
|
2015-12-05 05:58:05 -08:00
|
|
|
const char *sceneName = obs_source_get_name(currentScene);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
const char *programName = obs_source_get_name(curProgramScene);
|
2014-04-26 23:47:50 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection");
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2014-08-05 11:09:29 -07:00
|
|
|
obs_data_set_string(saveData, "current_scene", sceneName);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
obs_data_set_string(saveData, "current_program_scene", programName);
|
2015-06-25 03:53:48 -07:00
|
|
|
obs_data_set_array(saveData, "scene_order", sceneOrder);
|
2015-06-23 19:29:07 -07:00
|
|
|
obs_data_set_string(saveData, "name", sceneCollection);
|
2014-08-05 11:09:29 -07:00
|
|
|
obs_data_set_array(saveData, "sources", sourcesArray);
|
2018-07-15 18:50:33 -07:00
|
|
|
obs_data_set_array(saveData, "groups", groupsArray);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
|
2016-02-27 02:50:04 -08:00
|
|
|
obs_data_set_array(saveData, "transitions", transitions);
|
2016-12-29 09:21:53 -06:00
|
|
|
obs_data_set_array(saveData, "saved_projectors", savedProjectorList);
|
2014-04-26 23:47:50 -07:00
|
|
|
obs_data_array_release(sourcesArray);
|
2018-07-15 18:50:33 -07:00
|
|
|
obs_data_array_release(groupsArray);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
|
|
|
|
|
obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition));
|
|
|
|
|
obs_data_set_int(saveData, "transition_duration", transitionDuration);
|
2014-04-26 23:47:50 -07:00
|
|
|
|
|
|
|
|
return saveData;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
void OBSBasic::Save(SceneCollection &collection)
|
2014-04-26 23:47:50 -07:00
|
|
|
{
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
OBSScene scene = GetCurrentScene();
|
|
|
|
|
OBSSource curProgramScene = OBSGetStrongRef(programScene);
|
|
|
|
|
if (!curProgramScene)
|
|
|
|
|
curProgramScene = obs_scene_get_source(scene);
|
|
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder();
|
|
|
|
|
OBSDataArrayAutoRelease transitions = SaveTransitions();
|
|
|
|
|
OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions();
|
|
|
|
|
OBSDataArrayAutoRelease savedProjectorList = SaveProjectors();
|
2016-02-27 02:50:04 -08:00
|
|
|
OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(),
|
2018-02-28 17:49:47 -03:00
|
|
|
transitions, scene, curProgramScene, savedProjectorList);
|
2015-08-21 17:56:37 -07:00
|
|
|
|
2016-07-26 01:32:43 -07:00
|
|
|
obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked());
|
2017-05-13 13:13:55 -04:00
|
|
|
obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling());
|
|
|
|
|
obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel());
|
|
|
|
|
obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX());
|
|
|
|
|
obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY());
|
2016-07-26 01:32:43 -07:00
|
|
|
|
2022-08-22 16:04:13 +02:00
|
|
|
if (vcamEnabled) {
|
|
|
|
|
OBSDataAutoRelease obj = obs_data_create();
|
|
|
|
|
|
2022-11-15 10:00:41 +01:00
|
|
|
obs_data_set_int(obj, "type2", (int)vcamConfig.type);
|
2022-08-22 16:04:13 +02:00
|
|
|
switch (vcamConfig.type) {
|
2022-11-15 10:00:41 +01:00
|
|
|
case VCamOutputType::Invalid:
|
|
|
|
|
case VCamOutputType::ProgramView:
|
|
|
|
|
case VCamOutputType::PreviewOutput:
|
2022-08-22 16:04:13 +02:00
|
|
|
break;
|
|
|
|
|
case VCamOutputType::SceneOutput:
|
|
|
|
|
obs_data_set_string(obj, "scene", vcamConfig.scene.c_str());
|
|
|
|
|
break;
|
|
|
|
|
case VCamOutputType::SourceOutput:
|
|
|
|
|
obs_data_set_string(obj, "source", vcamConfig.source.c_str());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
obs_data_set_obj(saveData, "virtual-camera", obj);
|
|
|
|
|
}
|
2022-08-18 10:50:13 +02:00
|
|
|
|
2016-08-28 14:24:14 -07:00
|
|
|
if (api) {
|
2024-04-07 20:31:15 +02:00
|
|
|
if (!collectionModuleData)
|
|
|
|
|
collectionModuleData = obs_data_create();
|
|
|
|
|
|
|
|
|
|
api->on_save(collectionModuleData);
|
|
|
|
|
obs_data_set_obj(saveData, "modules", collectionModuleData);
|
2016-08-28 14:24:14 -07:00
|
|
|
}
|
|
|
|
|
|
2023-12-19 00:04:46 +01:00
|
|
|
if (lastOutputResolution) {
|
|
|
|
|
OBSDataAutoRelease res = obs_data_create();
|
|
|
|
|
obs_data_set_int(res, "x", lastOutputResolution->first);
|
|
|
|
|
obs_data_set_int(res, "y", lastOutputResolution->second);
|
|
|
|
|
obs_data_set_obj(saveData, "resolution", res);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
int sceneCollectionVersion = collection.getVersion();
|
|
|
|
|
obs_data_set_int(saveData, "version", sceneCollectionVersion);
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
OBS::Rect migrationResolution = collection.getMigrationResolution();
|
|
|
|
|
SceneCoordinateMode coordinateMode = collection.getCoordinateMode();
|
|
|
|
|
|
|
|
|
|
if (coordinateMode == SceneCoordinateMode::Absolute) {
|
|
|
|
|
removeRelativePositionData(saveData);
|
2024-04-23 05:05:50 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
if (!migrationResolution.isZero() && coordinateMode == SceneCoordinateMode::Relative) {
|
|
|
|
|
OBSDataAutoRelease resolutionData = obs_data_create();
|
|
|
|
|
|
|
|
|
|
obs_data_set_int(resolutionData, "x", migrationResolution.getWidth<long long>());
|
|
|
|
|
obs_data_set_int(resolutionData, "y", migrationResolution.getHeight<long long>());
|
|
|
|
|
|
|
|
|
|
obs_data_set_obj(saveData, DataKeys::MigrationResolution.data(), resolutionData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string collectionFileName = collection.getFilePathString();
|
|
|
|
|
bool success = obs_data_save_json_pretty_safe(saveData, collectionFileName.c_str(), "tmp", "bak");
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
blog(LOG_ERROR, "Could not save scene data to %s", collectionFileName.c_str());
|
|
|
|
|
}
|
2014-04-26 23:47:50 -07:00
|
|
|
}
|
|
|
|
|
|
2018-02-04 23:36:56 +02:00
|
|
|
void OBSBasic::DeferSaveBegin()
|
|
|
|
|
{
|
|
|
|
|
os_atomic_inc_long(&disableSaving);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBSBasic::DeferSaveEnd()
|
|
|
|
|
{
|
|
|
|
|
long result = os_atomic_dec_long(&disableSaving);
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
SaveProject();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 21:11:29 +02:00
|
|
|
static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val);
|
|
|
|
|
|
2014-09-25 17:44:05 -07:00
|
|
|
static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
|
2014-05-03 22:54:38 -07:00
|
|
|
{
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSDataAutoRelease data = obs_data_get_obj(parent, name);
|
2014-05-03 22:54:38 -07:00
|
|
|
if (!data)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSSourceAutoRelease source = obs_load_source(data);
|
|
|
|
|
if (!source)
|
|
|
|
|
return;
|
2020-04-07 21:11:29 +02:00
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
obs_set_output_source(channel, source);
|
2014-05-03 22:54:38 -07:00
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
const char *source_name = obs_source_get_name(source);
|
|
|
|
|
blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name);
|
|
|
|
|
obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1);
|
|
|
|
|
obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source);
|
|
|
|
|
if (monitoring_type != OBS_MONITORING_TYPE_NONE) {
|
|
|
|
|
const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only"
|
|
|
|
|
: "monitor and output";
|
|
|
|
|
|
|
|
|
|
blog(LOG_INFO, " - monitoring: %s", type);
|
|
|
|
|
}
|
2014-05-03 22:54:38 -07:00
|
|
|
}
|
|
|
|
|
|
2015-09-11 21:51:09 -07:00
|
|
|
void OBSBasic::CreateDefaultScene(bool firstStart)
|
2015-09-11 21:49:21 -07:00
|
|
|
{
|
|
|
|
|
disableSaving++;
|
|
|
|
|
|
|
|
|
|
ClearSceneData();
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
InitDefaultTransitions();
|
|
|
|
|
CreateDefaultQuickTransitions();
|
|
|
|
|
ui->transitionDuration->setValue(300);
|
|
|
|
|
SetTransition(fadeTransition);
|
2015-09-11 21:49:21 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
updateRemigrationMenuItem(SceneCoordinateMode::Relative, ui->actionRemigrateSceneCollection);
|
|
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene"));
|
2015-09-11 21:49:21 -07:00
|
|
|
|
2015-09-11 21:51:09 -07:00
|
|
|
if (firstStart)
|
2015-09-23 08:41:00 -07:00
|
|
|
CreateFirstRunSources();
|
2015-09-11 21:49:21 -07:00
|
|
|
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
SetCurrentScene(scene, true);
|
2015-06-30 05:49:31 -07:00
|
|
|
|
|
|
|
|
disableSaving--;
|
2014-05-03 22:54:38 -07:00
|
|
|
}
|
|
|
|
|
|
2016-08-05 17:43:30 -07:00
|
|
|
static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val)
|
|
|
|
|
{
|
|
|
|
|
const char *name = obs_source_get_name(filter);
|
|
|
|
|
const char *id = obs_source_get_id(filter);
|
|
|
|
|
int val = (int)(intptr_t)v_val;
|
|
|
|
|
string indent;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < val; i++)
|
|
|
|
|
indent += " ";
|
|
|
|
|
|
|
|
|
|
blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 21:30:09 +11:00
|
|
|
static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val)
|
2016-08-05 17:43:30 -07:00
|
|
|
{
|
|
|
|
|
obs_source_t *source = obs_sceneitem_get_source(item);
|
|
|
|
|
const char *name = obs_source_get_name(source);
|
|
|
|
|
const char *id = obs_source_get_id(source);
|
2019-03-31 21:30:09 +11:00
|
|
|
int indent_count = (int)(intptr_t)v_val;
|
|
|
|
|
string indent;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < indent_count; i++)
|
|
|
|
|
indent += " ";
|
2016-08-05 17:43:30 -07:00
|
|
|
|
2019-03-31 21:30:09 +11:00
|
|
|
blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id);
|
2016-08-05 17:43:30 -07:00
|
|
|
|
2017-03-02 13:05:38 -08:00
|
|
|
obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source);
|
|
|
|
|
|
|
|
|
|
if (monitoring_type != OBS_MONITORING_TYPE_NONE) {
|
|
|
|
|
const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only"
|
|
|
|
|
: "monitor and output";
|
|
|
|
|
|
2019-03-31 21:30:09 +11:00
|
|
|
blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type);
|
2017-03-02 13:05:38 -08:00
|
|
|
}
|
2019-03-31 21:30:09 +11:00
|
|
|
int child_indent = 1 + indent_count;
|
|
|
|
|
obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent);
|
2021-06-17 15:20:33 +10:00
|
|
|
|
2022-03-24 22:32:41 -07:00
|
|
|
obs_source_t *show_tn = obs_sceneitem_get_transition(item, true);
|
|
|
|
|
obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false);
|
2021-06-17 15:20:33 +10:00
|
|
|
if (show_tn)
|
|
|
|
|
blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn),
|
|
|
|
|
obs_source_get_id(show_tn));
|
|
|
|
|
if (hide_tn)
|
|
|
|
|
blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn),
|
|
|
|
|
obs_source_get_id(hide_tn));
|
|
|
|
|
|
2019-03-31 21:30:09 +11:00
|
|
|
if (obs_sceneitem_is_group(item))
|
|
|
|
|
obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent);
|
2016-08-05 17:43:30 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OBSBasic::LogScenes()
|
|
|
|
|
{
|
|
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
|
|
|
|
blog(LOG_INFO, "Loaded scenes:");
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < ui->scenes->count(); i++) {
|
|
|
|
|
QListWidgetItem *item = ui->scenes->item(i);
|
|
|
|
|
OBSScene scene = GetOBSRef<OBSScene>(item);
|
|
|
|
|
|
|
|
|
|
obs_source_t *source = obs_scene_get_source(scene);
|
|
|
|
|
const char *name = obs_source_get_name(source);
|
|
|
|
|
|
|
|
|
|
blog(LOG_INFO, "- scene '%s':", name);
|
2019-03-31 21:30:09 +11:00
|
|
|
obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1);
|
2016-08-05 17:43:30 -07:00
|
|
|
obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
void OBSBasic::Load(SceneCollection &collection)
|
2014-04-26 23:47:50 -07:00
|
|
|
{
|
2015-08-21 17:56:37 -07:00
|
|
|
disableSaving++;
|
2025-01-22 20:44:35 +01:00
|
|
|
|
2023-12-19 00:04:46 +01:00
|
|
|
lastOutputResolution.reset();
|
2025-01-22 20:44:35 +01:00
|
|
|
collection.setMigrationResolution(0, 0);
|
|
|
|
|
|
|
|
|
|
obs_data_t *data = obs_data_create_from_json_file_safe(collection.getFilePathString().c_str(), "bak");
|
2015-08-21 17:56:37 -07:00
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
|
disableSaving--;
|
2025-01-22 20:44:35 +01:00
|
|
|
const std::filesystem::path filePath = collection.getFilePath();
|
|
|
|
|
|
|
|
|
|
if (std::filesystem::exists(filePath)) {
|
|
|
|
|
std::filesystem::path backupFilePath = filePath;
|
|
|
|
|
backupFilePath.replace_extension(".json.invalid");
|
2024-07-29 22:46:38 +02:00
|
|
|
|
|
|
|
|
blog(LOG_WARNING,
|
|
|
|
|
"File exists but appears to be corrupt, renaming "
|
|
|
|
|
"to \"%s\" before continuing.",
|
2025-01-22 20:44:35 +01:00
|
|
|
backupFilePath.filename().u8string().c_str());
|
2024-07-29 22:46:38 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
try {
|
|
|
|
|
std::filesystem::rename(filePath, backupFilePath);
|
|
|
|
|
} catch (const std::filesystem::filesystem_error &error) {
|
|
|
|
|
blog(LOG_ERROR, "Failed renaming corrupt file:\n%s", error.what());
|
2024-07-29 22:46:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blog(LOG_INFO, "No scene file found, creating default scene");
|
2024-09-03 16:28:36 +02:00
|
|
|
|
|
|
|
|
bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun");
|
|
|
|
|
|
|
|
|
|
CreateDefaultScene(!hasFirstRun);
|
2015-07-02 16:15:51 -07:00
|
|
|
SaveProject();
|
2014-04-26 23:47:50 -07:00
|
|
|
return;
|
2014-05-03 22:54:38 -07:00
|
|
|
}
|
2014-04-26 23:47:50 -07:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
LoadData(data, collection);
|
2021-03-22 01:20:11 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-20 03:44:43 -05:00
|
|
|
static inline void AddMissingFiles(void *data, obs_source_t *source)
|
|
|
|
|
{
|
|
|
|
|
obs_missing_files_t *f = (obs_missing_files_t *)data;
|
|
|
|
|
obs_missing_files_t *sf = obs_source_get_missing_files(source);
|
|
|
|
|
|
|
|
|
|
obs_missing_files_append(f, sf);
|
|
|
|
|
obs_missing_files_destroy(sf);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
void OBSBasic::LoadData(obs_data_t *data, SceneCollection &collection)
|
2021-03-22 01:20:11 -04:00
|
|
|
{
|
2015-07-02 16:17:55 -07:00
|
|
|
ClearSceneData();
|
2020-08-25 00:39:06 -07:00
|
|
|
ClearContextBar();
|
2015-07-02 16:17:55 -07:00
|
|
|
|
2023-05-03 14:59:48 +02:00
|
|
|
/* Exit OBS if clearing scene data failed for some reason. */
|
|
|
|
|
if (clearingFailed) {
|
|
|
|
|
OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text"));
|
|
|
|
|
close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InitDefaultTransitions();
|
|
|
|
|
|
2020-08-25 06:30:59 -07:00
|
|
|
if (devicePropertiesThread && devicePropertiesThread->isRunning()) {
|
|
|
|
|
devicePropertiesThread->wait();
|
|
|
|
|
devicePropertiesThread.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-21 01:38:08 -08:00
|
|
|
QApplication::sendPostedEvents(nullptr);
|
2020-08-25 06:30:59 -07:00
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules");
|
2017-12-25 13:23:39 -08:00
|
|
|
if (api)
|
|
|
|
|
api->on_preload(modulesObj);
|
|
|
|
|
|
2024-04-07 20:31:15 +02:00
|
|
|
/* Keep a reference to "modules" data so plugins that are not loaded do
|
|
|
|
|
* not have their collection specific data lost. */
|
|
|
|
|
collectionModuleData = obs_data_get_obj(data, "modules");
|
2023-03-13 03:09:52 +01:00
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order");
|
|
|
|
|
OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources");
|
|
|
|
|
OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups");
|
|
|
|
|
OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions");
|
2014-08-05 11:09:29 -07:00
|
|
|
const char *sceneName = obs_data_get_string(data, "current_scene");
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
const char *programSceneName = obs_data_get_string(data, "current_program_scene");
|
|
|
|
|
const char *transitionName = obs_data_get_string(data, "current_transition");
|
|
|
|
|
|
2016-04-13 18:59:27 -07:00
|
|
|
if (!opt_starting_scene.empty()) {
|
|
|
|
|
programSceneName = opt_starting_scene.c_str();
|
|
|
|
|
if (!IsPreviewProgramMode())
|
|
|
|
|
sceneName = opt_starting_scene.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
int newDuration = obs_data_get_int(data, "transition_duration");
|
|
|
|
|
if (!newDuration)
|
|
|
|
|
newDuration = 300;
|
|
|
|
|
|
|
|
|
|
if (!transitionName)
|
|
|
|
|
transitionName = obs_source_get_name(fadeTransition);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection");
|
2015-06-23 19:29:07 -07:00
|
|
|
|
|
|
|
|
obs_data_set_default_string(data, "name", curSceneCollection);
|
|
|
|
|
|
|
|
|
|
const char *name = obs_data_get_string(data, "name");
|
2021-11-26 22:25:39 +13:00
|
|
|
OBSSourceAutoRelease curScene;
|
|
|
|
|
OBSSourceAutoRelease curProgramScene;
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
obs_source_t *curTransition;
|
2014-04-26 23:47:50 -07:00
|
|
|
|
2015-06-23 19:29:07 -07:00
|
|
|
if (!name || !*name)
|
|
|
|
|
name = curSceneCollection;
|
|
|
|
|
|
2014-05-03 22:54:38 -07:00
|
|
|
LoadAudioDevice(DESKTOP_AUDIO_1, 1, data);
|
|
|
|
|
LoadAudioDevice(DESKTOP_AUDIO_2, 2, data);
|
|
|
|
|
LoadAudioDevice(AUX_AUDIO_1, 3, data);
|
|
|
|
|
LoadAudioDevice(AUX_AUDIO_2, 4, data);
|
|
|
|
|
LoadAudioDevice(AUX_AUDIO_3, 5, data);
|
2018-06-26 20:41:32 +02:00
|
|
|
LoadAudioDevice(AUX_AUDIO_4, 6, data);
|
2014-05-03 22:54:38 -07:00
|
|
|
|
2018-07-15 18:50:33 -07:00
|
|
|
if (!sources) {
|
2021-12-30 21:10:50 -08:00
|
|
|
sources = std::move(groups);
|
2018-07-15 18:50:33 -07:00
|
|
|
} else {
|
|
|
|
|
obs_data_array_push_back_array(sources, groups);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-23 05:05:50 +02:00
|
|
|
bool resetVideo = false;
|
|
|
|
|
obs_video_info ovi;
|
|
|
|
|
|
|
|
|
|
int64_t version = obs_data_get_int(data, "version");
|
|
|
|
|
OBSDataAutoRelease res = obs_data_get_obj(data, "resolution");
|
2025-01-22 20:44:35 +01:00
|
|
|
|
|
|
|
|
OBS::Rect collectionSize{};
|
|
|
|
|
|
2024-04-23 05:05:50 +02:00
|
|
|
if (res) {
|
2025-01-22 20:44:35 +01:00
|
|
|
collectionSize.setWidth(obs_data_get_int(res, "x"));
|
|
|
|
|
collectionSize.setHeight(obs_data_get_int(res, "y"));
|
2024-04-23 05:05:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Only migrate legacy collection if resolution is saved. */
|
2025-01-22 20:44:35 +01:00
|
|
|
if (version < 2 && !collectionSize.isZero()) {
|
2024-04-23 05:05:50 +02:00
|
|
|
obs_get_video_info(&ovi);
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
collection.setMigrationResolution(collectionSize);
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
OBS::Rect outputSize{ovi.base_width, ovi.base_height};
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
if (outputSize != collectionSize) {
|
|
|
|
|
ovi.base_width = collectionSize.getWidth<uint32_t>();
|
|
|
|
|
ovi.base_height = collectionSize.getHeight<uint32_t>();
|
2024-04-23 05:05:50 +02:00
|
|
|
|
|
|
|
|
/* Attempt to reset to last known canvas resolution for migration. */
|
|
|
|
|
resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS;
|
2025-01-22 20:44:35 +01:00
|
|
|
|
|
|
|
|
if (!resetVideo) {
|
|
|
|
|
collection.setCoordinateMode(OBS::SceneCoordinateMode::Absolute);
|
|
|
|
|
}
|
2024-04-23 05:05:50 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
// If migration is necessary, attempt to back up version 1 variant of the scene ecollection.
|
|
|
|
|
if (collection.getCoordinateMode() == SceneCoordinateMode::Absolute) {
|
|
|
|
|
std::filesystem::path backupFilePath = collection.getFilePath();
|
|
|
|
|
backupFilePath.replace_extension(".json.v1");
|
|
|
|
|
|
|
|
|
|
if (!std::filesystem::exists(backupFilePath)) {
|
|
|
|
|
bool success = obs_data_save_json_pretty_safe(data, backupFilePath.u8string().c_str(),
|
|
|
|
|
"tmp", nullptr);
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
2024-04-23 05:05:50 +02:00
|
|
|
blog(LOG_WARNING,
|
2025-01-22 20:44:35 +01:00
|
|
|
"Failed to create a backup of existing scene collection data");
|
2024-04-23 05:05:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-22 20:44:35 +01:00
|
|
|
collection.setCoordinateMode(SceneCoordinateMode::Relative);
|
2024-04-23 05:05:50 +02:00
|
|
|
} else if (version < 2) {
|
2025-01-22 20:44:35 +01:00
|
|
|
collection.setCoordinateMode(SceneCoordinateMode::Absolute);
|
|
|
|
|
} else {
|
|
|
|
|
OBSDataAutoRelease migrationResolution = obs_data_get_obj(data, "migration_resolution");
|
|
|
|
|
|
|
|
|
|
if (migrationResolution) {
|
|
|
|
|
collection.setMigrationResolution(obs_data_get_int(migrationResolution, "x"),
|
|
|
|
|
obs_data_get_int(migrationResolution, "y"));
|
|
|
|
|
}
|
2024-04-23 05:05:50 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
// FIXME: Migrate to actual SceneCollection container with reference-based API
|
|
|
|
|
// The collection passed as function argument might be a copy generated via std::optional and thus
|
|
|
|
|
// might not represent the actual collection within the collection, meaning all changes would be lost after
|
|
|
|
|
// the function stack is unwound (i.e., the change would not be written into the scene collection file
|
|
|
|
|
// during OBS shutdown).
|
|
|
|
|
// Explicitly updating the collection inside the container ensures the changes "stick".
|
|
|
|
|
|
|
|
|
|
collections[collection.getName()] = collection;
|
|
|
|
|
|
|
|
|
|
updateRemigrationMenuItem(collection.getCoordinateMode(), ui->actionRemigrateSceneCollection);
|
2024-04-23 05:05:50 +02:00
|
|
|
|
2022-07-20 03:44:43 -05:00
|
|
|
obs_missing_files_t *files = obs_missing_files_create();
|
|
|
|
|
obs_load_sources(sources, AddMissingFiles, files);
|
2014-04-26 23:47:50 -07:00
|
|
|
|
2024-04-23 05:05:50 +02:00
|
|
|
if (resetVideo)
|
|
|
|
|
ResetVideo();
|
2016-02-27 02:50:04 -08:00
|
|
|
if (transitions)
|
2022-07-20 03:44:43 -05:00
|
|
|
LoadTransitions(transitions, AddMissingFiles, files);
|
2015-06-25 03:53:48 -07:00
|
|
|
if (sceneOrder)
|
|
|
|
|
LoadSceneListOrder(sceneOrder);
|
|
|
|
|
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
curTransition = FindTransition(transitionName);
|
|
|
|
|
if (!curTransition)
|
|
|
|
|
curTransition = fadeTransition;
|
|
|
|
|
|
|
|
|
|
ui->transitionDuration->setValue(newDuration);
|
|
|
|
|
SetTransition(curTransition);
|
|
|
|
|
|
2016-04-13 18:59:27 -07:00
|
|
|
retryScene:
|
2014-04-26 23:47:50 -07:00
|
|
|
curScene = obs_get_source_by_name(sceneName);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
curProgramScene = obs_get_source_by_name(programSceneName);
|
2016-04-13 18:59:27 -07:00
|
|
|
|
|
|
|
|
/* if the starting scene command line parameter is bad at all,
|
|
|
|
|
* fall back to original settings */
|
|
|
|
|
if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) {
|
|
|
|
|
sceneName = obs_data_get_string(data, "current_scene");
|
|
|
|
|
programSceneName = obs_data_get_string(data, "current_program_scene");
|
|
|
|
|
opt_starting_scene.clear();
|
|
|
|
|
goto retryScene;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-30 20:31:34 +01:00
|
|
|
if (!curScene) {
|
|
|
|
|
auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) {
|
2024-07-20 10:17:43 +09:00
|
|
|
*static_cast<OBSSourceAutoRelease *>(source_ptr) = obs_source_get_ref(scene);
|
2023-11-30 20:31:34 +01:00
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
obs_enum_scenes(find_scene_cb, &curScene);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-26 22:25:39 +13:00
|
|
|
SetCurrentScene(curScene.Get(), true);
|
2021-12-30 21:10:50 -08:00
|
|
|
|
|
|
|
|
if (!curProgramScene)
|
|
|
|
|
curProgramScene = std::move(curScene);
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
if (IsPreviewProgramMode())
|
2021-11-26 22:25:39 +13:00
|
|
|
TransitionToScene(curProgramScene.Get(), true);
|
2015-06-23 19:29:07 -07:00
|
|
|
|
2018-02-27 20:26:22 -03:00
|
|
|
/* ------------------- */
|
|
|
|
|
|
2024-09-03 16:28:36 +02:00
|
|
|
bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors");
|
2018-02-27 20:26:22 -03:00
|
|
|
|
|
|
|
|
if (projectorSave) {
|
|
|
|
|
OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors");
|
|
|
|
|
|
2018-04-18 19:15:52 -07:00
|
|
|
if (savedProjectors) {
|
2018-02-27 20:26:22 -03:00
|
|
|
LoadSavedProjectors(savedProjectors);
|
2018-04-18 19:15:52 -07:00
|
|
|
activateWindow();
|
|
|
|
|
}
|
2018-02-27 20:26:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ------------------- */
|
|
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", collection.getName().c_str());
|
|
|
|
|
config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", collection.getFileName().c_str());
|
2015-06-23 19:29:07 -07:00
|
|
|
|
UI: Implement transitions and preview/program mode
Implements transitions, and introduces "Studio Mode" which allows live
editing of the same or different scenes while preserving what's
currently being displayed.
Studio Mode offers a number of new features:
- The ability to edit different scenes or the same scene without
modifying what's currently being displayed (of course)
- The ability to set up "quick transitions" with a desired transition
and duration that can be assigned hotkeys
- The option to create full copies of all sources in the program scene
to allow editing of source properties of the same scene live without
modifying the output, or (by default) just use references. (Note
however that certain sources cannot be duplicated, such as capture
sources, media sources, and device sources)
- Swap Mode (enabled by default) which swaps the program scene with
the preview scene when a transition completes
Currently, only non-configurable transitions (transitions without
properties) are listed, and the only transitions available as of this
writing are fade and cut. In future versions more transitions will be
added, such as swipe, stingers, and many other various sort of
transitions, and the UI will support being able to add/configure/remove
those sort of configurable transitions.
2016-01-23 11:19:29 -08:00
|
|
|
OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions");
|
|
|
|
|
LoadQuickTransitions(quickTransitionData);
|
|
|
|
|
|
|
|
|
|
RefreshQuickTransitions();
|
|
|
|
|
|
2016-07-26 01:32:43 -07:00
|
|
|
bool previewLocked = obs_data_get_bool(data, "preview_locked");
|
|
|
|
|
ui->preview->SetLocked(previewLocked);
|
|
|
|
|
ui->actionLockPreview->setChecked(previewLocked);
|
|
|
|
|
|
2017-05-13 13:13:55 -04:00
|
|
|
/* ---------------------- */
|
|
|
|
|
|
|
|
|
|
bool fixedScaling = obs_data_get_bool(data, "scaling_enabled");
|
|
|
|
|
int scalingLevel = (int)obs_data_get_int(data, "scaling_level");
|
|
|
|
|
float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x");
|
|
|
|
|
float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y");
|
|
|
|
|
|
|
|
|
|
if (fixedScaling) {
|
|
|
|
|
ui->preview->SetScalingLevel(scalingLevel);
|
|
|
|
|
ui->preview->SetScrollingOffset(scrollOffX, scrollOffY);
|
2016-11-05 12:48:46 -04:00
|
|
|
}
|
2017-05-13 13:13:55 -04:00
|
|
|
ui->preview->SetFixedScaling(fixedScaling);
|
2024-07-31 10:53:59 -04:00
|
|
|
|
2020-03-11 13:48:44 -07:00
|
|
|
emit ui->preview->DisplayResized();
|
2016-11-05 12:48:46 -04:00
|
|
|
|
2022-08-22 16:04:13 +02:00
|
|
|
if (vcamEnabled) {
|
|
|
|
|
OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera");
|
|
|
|
|
|
2022-11-15 10:00:41 +01:00
|
|
|
vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2");
|
|
|
|
|
if (vcamConfig.type == VCamOutputType::Invalid)
|
|
|
|
|
vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type");
|
|
|
|
|
|
|
|
|
|
if (vcamConfig.type == VCamOutputType::Invalid) {
|
|
|
|
|
VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal");
|
|
|
|
|
|
|
|
|
|
switch (internal) {
|
|
|
|
|
case VCamInternalType::Default:
|
|
|
|
|
vcamConfig.type = VCamOutputType::ProgramView;
|
|
|
|
|
break;
|
|
|
|
|
case VCamInternalType::Preview:
|
|
|
|
|
vcamConfig.type = VCamOutputType::PreviewOutput;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-22 16:04:13 +02:00
|
|
|
vcamConfig.scene = obs_data_get_string(obj, "scene");
|
|
|
|
|
vcamConfig.source = obs_data_get_string(obj, "source");
|
|
|
|
|
}
|
2022-08-18 10:50:13 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
if (!collectionSize.isZero()) {
|
|
|
|
|
lastOutputResolution = {collectionSize.getWidth<uint32_t>(), collectionSize.getHeight<uint32_t>()};
|
2024-06-01 18:42:34 +02:00
|
|
|
}
|
|
|
|
|
|
2017-05-13 13:13:55 -04:00
|
|
|
/* ---------------------- */
|
2016-11-05 12:48:46 -04:00
|
|
|
|
2017-12-25 13:23:39 -08:00
|
|
|
if (api)
|
2016-08-28 14:24:14 -07:00
|
|
|
api->on_load(modulesObj);
|
|
|
|
|
|
2014-04-26 23:47:50 -07:00
|
|
|
obs_data_release(data);
|
2015-06-30 05:49:31 -07:00
|
|
|
|
2016-04-13 18:59:27 -07:00
|
|
|
if (!opt_starting_scene.empty())
|
|
|
|
|
opt_starting_scene.clear();
|
|
|
|
|
|
2023-03-13 03:09:52 +01:00
|
|
|
if (opt_start_streaming && !safe_mode) {
|
2017-10-12 17:20:19 +02:00
|
|
|
blog(LOG_INFO, "Starting stream due to command line parameter");
|
2016-04-13 19:00:12 -07:00
|
|
|
QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection);
|
|
|
|
|
opt_start_streaming = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 03:09:52 +01:00
|
|
|
if (opt_start_recording && !safe_mode) {
|
2017-10-12 17:20:19 +02:00
|
|
|
blog(LOG_INFO, "Starting recording due to command line parameter");
|
2016-04-13 19:00:12 -07:00
|
|
|
QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection);
|
|
|
|
|
opt_start_recording = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 03:09:52 +01:00
|
|
|
if (opt_start_replaybuffer && !safe_mode) {
|
2017-01-11 13:19:17 -06:00
|
|
|
QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection);
|
|
|
|
|
opt_start_replaybuffer = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 03:09:52 +01:00
|
|
|
if (opt_start_virtualcam && !safe_mode) {
|
2020-06-20 07:42:14 -07:00
|
|
|
QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection);
|
|
|
|
|
opt_start_virtualcam = false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-05 17:43:30 -07:00
|
|
|
LogScenes();
|
|
|
|
|
|
2021-08-17 19:33:38 -05:00
|
|
|
if (!App()->IsMissingFilesCheckDisabled())
|
2022-07-20 03:44:43 -05:00
|
|
|
ShowMissingFilesDialog(files);
|
2020-02-18 21:06:17 +13:00
|
|
|
|
2015-06-30 05:49:31 -07:00
|
|
|
disableSaving--;
|
2017-12-25 13:38:02 -08:00
|
|
|
|
2023-05-13 17:19:00 +02:00
|
|
|
if (vcamEnabled)
|
2022-08-22 16:04:13 +02:00
|
|
|
outputHandler->UpdateVirtualCamOutputSource();
|
|
|
|
|
|
2024-06-18 14:18:22 -05:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED);
|
|
|
|
|
OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
|
2014-04-26 23:47:50 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
void OBSBasic::SaveProjectNow()
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
{
|
2025-01-08 17:35:16 +01:00
|
|
|
if (disableSaving)
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
return;
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
projectChanged = true;
|
|
|
|
|
SaveProjectDeferred();
|
|
|
|
|
}
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
void OBSBasic::SaveProject()
|
|
|
|
|
{
|
|
|
|
|
if (disableSaving)
|
|
|
|
|
return;
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
projectChanged = true;
|
|
|
|
|
QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection);
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
void OBSBasic::SaveProjectDeferred()
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
{
|
2025-01-08 17:35:16 +01:00
|
|
|
if (disableSaving)
|
|
|
|
|
return;
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
if (!projectChanged)
|
|
|
|
|
return;
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
projectChanged = false;
|
obs-studio UI: Implement stream settings UI
- Updated the services API so that it links up with an output and
the output gets data from that service rather than via settings.
This allows the service context to have control over how an output is
used, and makes it so that the URL/key/etc isn't necessarily some
static setting.
Also, if the service is attached to an output, it will stick around
until the output is destroyed.
- The settings interface has been updated so that it can allow the
usage of service plugins. What this means is that now you can create
a service plugin that can control aspects of the stream, and it
allows each service to create their own user interface if they create
a service plugin module.
- Testing out saving of current service information. Saves/loads from
JSON in to obs_data_t, seems to be working quite nicely, and the
service object information is saved/preserved on exit, and loaded
again on startup.
- I agonized over the settings user interface for days, and eventually
I just decided that the only way that users weren't going to be
fumbling over options was to split up the settings in to simple/basic
output, pre-configured, and then advanced for advanced use (such as
multiple outputs or services, which I'll implement later).
This was particularly painful to really design right, I wanted more
features and wanted to include everything in one interface but
ultimately just realized from experience that users are just not
technically knowledgable about it and will end up fumbling with the
settings rather than getting things done.
Basically, what this means is that casual users only have to enter in
about 3 things to configure their stream: Stream key, audio bitrate,
and video bitrate. I am really happy with this interface for those
types of users, but it definitely won't be sufficient for advanced
usage or for custom outputs, so that stuff will have to be separated.
- Improved the JSON usage for the 'common streaming services' context,
I realized that JSON arrays are there to ensure sorting, while
forgetting that general items are optimized for hashing. So
basically I'm just using arrays now to sort items in it.
2014-04-24 01:49:07 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
try {
|
2025-01-22 20:44:35 +01:00
|
|
|
OBS::SceneCollection ¤tCollection = GetCurrentSceneCollection();
|
2024-09-03 16:29:30 +02:00
|
|
|
|
2025-01-22 20:44:35 +01:00
|
|
|
Save(currentCollection);
|
2025-01-08 17:35:16 +01:00
|
|
|
} catch (const std::invalid_argument &error) {
|
|
|
|
|
blog(LOG_ERROR, "%s", error.what());
|
2019-07-07 15:47:29 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
void OBSBasic::ClearSceneData()
|
2019-07-07 15:47:29 -07:00
|
|
|
{
|
2025-01-08 17:35:16 +01:00
|
|
|
disableSaving++;
|
2019-07-07 15:47:29 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
setCursor(Qt::WaitCursor);
|
2019-07-07 15:47:29 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
CloseDialogs();
|
2024-04-03 17:08:14 +02:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
ClearVolumeControls();
|
|
|
|
|
ClearListItems(ui->scenes);
|
|
|
|
|
ui->sources->Clear();
|
|
|
|
|
ClearQuickTransitions();
|
|
|
|
|
ui->transitions->clear();
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
ClearProjectors();
|
2019-09-27 15:04:07 +02:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++)
|
|
|
|
|
obs_set_output_source(i, nullptr);
|
2019-09-27 15:04:07 +02:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
/* Reset VCam to default to clear its private scene and any references
|
|
|
|
|
* it holds. It will be reconfigured during loading. */
|
|
|
|
|
if (vcamEnabled) {
|
|
|
|
|
vcamConfig.type = VCamOutputType::ProgramView;
|
|
|
|
|
outputHandler->UpdateVirtualCamOutputSource();
|
2019-09-27 15:04:07 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
collectionModuleData = nullptr;
|
|
|
|
|
lastScene = nullptr;
|
|
|
|
|
swapScene = nullptr;
|
|
|
|
|
programScene = nullptr;
|
|
|
|
|
prevFTBSource = nullptr;
|
2020-09-08 20:29:40 -07:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
clipboard.clear();
|
|
|
|
|
copyFiltersSource = nullptr;
|
|
|
|
|
copyFilter = nullptr;
|
2024-03-28 16:43:12 -04:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
auto cb = [](void *, obs_source_t *source) {
|
|
|
|
|
obs_source_remove(source);
|
2024-03-28 16:43:12 -04:00
|
|
|
return true;
|
2025-01-08 17:35:16 +01:00
|
|
|
};
|
2024-03-28 16:43:12 -04:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
obs_enum_scenes(cb, nullptr);
|
|
|
|
|
obs_enum_sources(cb, nullptr);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP);
|
2019-09-27 15:04:07 +02:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
undo_s.clear();
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
/* using QEvent::DeferredDelete explicitly is the only way to ensure
|
|
|
|
|
* that deleteLater events are processed at this point */
|
|
|
|
|
QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
do {
|
|
|
|
|
QApplication::sendPostedEvents(nullptr);
|
|
|
|
|
} while (obs_wait_for_destroy_queue());
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
/* Pump Qt events one final time to give remaining signals time to be
|
|
|
|
|
* processed (since this happens after the destroy thread finishes and
|
|
|
|
|
* the audio/video threads have processed their tasks). */
|
|
|
|
|
QApplication::sendPostedEvents(nullptr);
|
2019-06-12 04:56:00 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
unsetCursor();
|
2019-10-10 20:12:20 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
/* If scene data wasn't actually cleared, e.g. faulty plugin holding a
|
|
|
|
|
* reference, they will still be in the hash table, enumerate them and
|
|
|
|
|
* store the names for logging purposes. */
|
|
|
|
|
auto cb2 = [](void *param, obs_source_t *source) {
|
|
|
|
|
auto orphans = static_cast<vector<string> *>(param);
|
|
|
|
|
orphans->push_back(obs_source_get_name(source));
|
|
|
|
|
return true;
|
|
|
|
|
};
|
2019-10-15 23:20:35 -05:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
vector<string> orphan_sources;
|
|
|
|
|
obs_enum_sources(cb2, &orphan_sources);
|
2020-02-14 23:30:34 +11:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
if (!orphan_sources.empty()) {
|
|
|
|
|
/* Avoid logging list twice in case it gets called after
|
|
|
|
|
* setting the flag the first time. */
|
|
|
|
|
if (!clearingFailed) {
|
|
|
|
|
/* This ugly mess exists to join a vector of strings
|
|
|
|
|
* with a user-defined delimiter. */
|
|
|
|
|
string orphan_names =
|
|
|
|
|
std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""),
|
|
|
|
|
[](string a, string b) { return std::move(a) + "\n- " + b; });
|
2022-02-22 00:40:17 +11:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n",
|
|
|
|
|
orphan_names.c_str());
|
2022-02-22 00:40:17 +11:00
|
|
|
}
|
2021-07-03 17:55:47 +12:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
/* We do not decrement disableSaving here to avoid OBS
|
|
|
|
|
* overwriting user data with garbage. */
|
|
|
|
|
clearingFailed = true;
|
2021-07-03 17:55:47 +12:00
|
|
|
} else {
|
2025-01-08 17:35:16 +01:00
|
|
|
disableSaving--;
|
2021-07-03 17:55:47 +12:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
blog(LOG_INFO, "All scene data cleared");
|
|
|
|
|
blog(LOG_INFO, "------------------------------------------------");
|
2021-07-03 17:55:47 +12:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files)
|
2021-07-03 17:55:47 +12:00
|
|
|
{
|
2025-01-08 17:35:16 +01:00
|
|
|
if (obs_missing_files_count(files) > 0) {
|
|
|
|
|
/* When loading the missing files dialog on launch, the
|
|
|
|
|
* window hasn't fully initialized by this point on macOS,
|
|
|
|
|
* so put this at the end of the current task queue. Fixes
|
|
|
|
|
* a bug where the window is behind OBS on startup. */
|
|
|
|
|
QTimer::singleShot(0, [this, files] {
|
|
|
|
|
missDialog = new OBSMissingFiles(files, this);
|
|
|
|
|
missDialog->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
|
missDialog->show();
|
|
|
|
|
missDialog->raise();
|
|
|
|
|
});
|
2021-07-03 17:55:47 +12:00
|
|
|
} else {
|
2025-01-08 17:35:16 +01:00
|
|
|
obs_missing_files_destroy(files);
|
2024-07-31 10:53:59 -04:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
/* Only raise dialog if triggered manually */
|
|
|
|
|
if (!disableSaving)
|
|
|
|
|
OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"),
|
|
|
|
|
QTStr("MissingFiles.NoMissing.Text"));
|
2024-07-31 10:53:59 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
void OBSBasic::on_actionShowMissingFiles_triggered()
|
2024-07-31 10:53:59 -04:00
|
|
|
{
|
2025-01-08 17:35:16 +01:00
|
|
|
obs_missing_files_t *files = obs_missing_files_create();
|
2024-07-31 10:53:59 -04:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
auto cb_sources = [](void *data, obs_source_t *source) {
|
|
|
|
|
AddMissingFiles(data, source);
|
|
|
|
|
return true;
|
2024-07-31 10:53:59 -04:00
|
|
|
};
|
2024-09-03 16:29:30 +02:00
|
|
|
|
2025-01-08 17:35:16 +01:00
|
|
|
obs_enum_all_sources(cb_sources, files);
|
|
|
|
|
ShowMissingFilesDialog(files);
|
2024-09-03 16:29:30 +02:00
|
|
|
}
|