#include "AppConfigProvider.h" #include "RuntimeJson.h" #include #include #include #include #include #include #include namespace RenderCadenceCompositor { namespace { std::filesystem::path ExecutableDirectory() { char path[MAX_PATH] = {}; const DWORD length = GetModuleFileNameA(nullptr, path, static_cast(sizeof(path))); if (length == 0 || length >= sizeof(path)) return std::filesystem::current_path(); return std::filesystem::path(path).parent_path(); } std::string ReadTextFile(const std::filesystem::path& path, std::string& error) { std::ifstream input(path, std::ios::binary); if (!input) { error = "Could not open config file: " + path.string(); return std::string(); } std::ostringstream buffer; buffer << input.rdbuf(); return buffer.str(); } const JsonValue* Find(const JsonValue& root, const char* key) { return root.find(key); } void ApplyString(const JsonValue& root, const char* key, std::string& target) { const JsonValue* value = Find(root, key); if (value && value->isString()) target = value->asString(); } void ApplyBool(const JsonValue& root, const char* key, bool& target) { const JsonValue* value = Find(root, key); if (value && value->isBoolean()) target = value->asBoolean(); } void ApplyDouble(const JsonValue& root, const char* key, double& target) { const JsonValue* value = Find(root, key); if (value && value->isNumber()) target = value->asNumber(); } void ApplySize(const JsonValue& root, const char* key, std::size_t& target) { const JsonValue* value = Find(root, key); if (value && value->isNumber() && value->asNumber() >= 0.0) target = static_cast(value->asNumber()); } void ApplyPort(const JsonValue& root, const char* key, unsigned short& target) { const JsonValue* value = Find(root, key); if (!value || !value->isNumber()) return; const double port = value->asNumber(); if (port >= 1.0 && port <= 65535.0) target = static_cast(port); } } AppConfigProvider::AppConfigProvider() : mConfig(DefaultAppConfig()) { } bool AppConfigProvider::LoadDefault(std::string& error) { const std::filesystem::path path = FindConfigFile(); if (path.empty()) { error = "Could not locate config/runtime-host.json from current directory or executable directory."; return false; } return Load(path, error); } bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& error) { mConfig = DefaultAppConfig(); mSourcePath = path; mLoadedFromFile = false; std::string fileError; const std::string text = ReadTextFile(path, fileError); if (!fileError.empty()) { error = fileError; return false; } JsonValue root; std::string parseError; if (!ParseJson(text, root, parseError) || !root.isObject()) { error = parseError.empty() ? "Config root must be a JSON object." : parseError; return false; } ApplyString(root, "shaderLibrary", mConfig.shaderLibrary); ApplyPort(root, "serverPort", mConfig.http.preferredPort); ApplyString(root, "oscBindAddress", mConfig.oscBindAddress); ApplyPort(root, "oscPort", mConfig.oscPort); ApplyDouble(root, "oscSmoothing", mConfig.oscSmoothing); ApplyString(root, "inputVideoFormat", mConfig.inputVideoFormat); ApplyString(root, "inputFrameRate", mConfig.inputFrameRate); ApplyString(root, "outputVideoFormat", mConfig.outputVideoFormat); ApplyString(root, "outputFrameRate", mConfig.outputFrameRate); ApplyBool(root, "autoReload", mConfig.autoReload); ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames); ApplyDouble(root, "previewFps", mConfig.previewFps); ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled); std::size_t startupSettleMilliseconds = static_cast(mConfig.startupSettle.count()); ApplySize(root, "startupSettleMs", startupSettleMilliseconds); mConfig.startupSettle = std::chrono::milliseconds(startupSettleMilliseconds); mLoadedFromFile = true; error.clear(); return true; } void AppConfigProvider::ApplyCommandLine(int argc, char** argv) { for (int index = 1; index < argc; ++index) { const std::string argument = argv[index]; if (argument == "--shader" && index + 1 < argc) { mConfig.runtimeShaderId = argv[++index]; continue; } if (argument == "--no-shader") { mConfig.runtimeShaderId.clear(); continue; } if (argument == "--port" && index + 1 < argc) { const int port = std::atoi(argv[++index]); if (port >= 1 && port <= 65535) mConfig.http.preferredPort = static_cast(port); continue; } } } double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate) { double rate = fallbackRate; try { rate = std::stod(rateText); } catch (...) { rate = fallbackRate; } if (rate <= 0.0) rate = fallbackRate; return 1000.0 / rate; } void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height) { std::string normalized = formatName; std::transform(normalized.begin(), normalized.end(), normalized.begin(), [](unsigned char character) { return static_cast(std::tolower(character)); }); if (normalized == "720p") { width = 1280; height = 720; return; } if (normalized == "2160p" || normalized == "4k" || normalized == "uhd") { width = 3840; height = 2160; return; } width = 1920; height = 1080; } std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath) { return FindRepoPath(relativePath); } std::filesystem::path FindRepoPath(const std::filesystem::path& relativePath) { std::vector starts; starts.push_back(std::filesystem::current_path()); starts.push_back(ExecutableDirectory()); for (std::filesystem::path start : starts) { for (;;) { const std::filesystem::path candidate = start / relativePath; if (std::filesystem::exists(candidate)) return candidate; const std::filesystem::path parent = start.parent_path(); if (parent.empty() || parent == start) break; start = parent; } } return std::filesystem::path(); } }