#include "AppConfigJson.h" #include #include namespace RenderCadenceCompositor { namespace { 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); } JsonValue NumberValue(double value) { return JsonValue(value); } JsonValue SizeValue(std::size_t value) { return JsonValue(static_cast(value)); } void ApplyInputConfig(const JsonValue& root, AppConfig& config) { const JsonValue* input = Find(root, "input"); if (!input || !input->isObject()) return; ApplyString(*input, "backend", config.input.backend); ApplyString(*input, "device", config.input.device); ApplyString(*input, "resolution", config.input.resolution); ApplyString(*input, "frameRate", config.input.frameRate); } void ApplyOutputConfig(const JsonValue& root, AppConfig& config) { const JsonValue* output = Find(root, "output"); if (!output || !output->isObject()) return; ApplyString(*output, "backend", config.output.backend); ApplyString(*output, "device", config.output.device); ApplyString(*output, "resolution", config.output.resolution); ApplyString(*output, "frameRate", config.output.frameRate); ApplyString(*output, "pixelFormat", config.output.pixelFormat); const JsonValue* keying = Find(*output, "keying"); if (keying && keying->isObject()) { ApplyBool(*keying, "external", config.output.externalKeyingEnabled); ApplyBool(*keying, "alphaRequired", config.output.outputAlphaRequired); } if (config.output.externalKeyingEnabled) config.output.outputAlphaRequired = true; if (config.output.backend != "decklink") config.output.externalKeyingEnabled = false; } JsonValue InputConfigToJson(const VideoInputAppConfig& input) { JsonValue value = JsonValue::MakeObject(); value.set("backend", JsonValue(input.backend)); value.set("device", JsonValue(input.device)); value.set("resolution", JsonValue(input.resolution)); value.set("frameRate", JsonValue(input.frameRate)); return value; } JsonValue OutputConfigToJson(const VideoOutputAppConfig& output) { JsonValue keying = JsonValue::MakeObject(); keying.set("external", JsonValue(output.externalKeyingEnabled)); keying.set("alphaRequired", JsonValue(output.outputAlphaRequired)); JsonValue value = JsonValue::MakeObject(); value.set("backend", JsonValue(output.backend)); value.set("device", JsonValue(output.device)); value.set("resolution", JsonValue(output.resolution)); value.set("frameRate", JsonValue(output.frameRate)); value.set("pixelFormat", JsonValue(output.pixelFormat)); value.set("keying", keying); return value; } } bool ApplyAppConfigJson(const JsonValue& root, AppConfig& config, std::string* error) { if (!root.isObject()) { if (error) *error = "Config root must be a JSON object."; return false; } ApplyString(root, "shaderLibrary", config.shaderLibrary); ApplyPort(root, "serverPort", config.http.preferredPort); ApplyString(root, "oscBindAddress", config.oscBindAddress); ApplyPort(root, "oscPort", config.oscPort); ApplyDouble(root, "oscSmoothing", config.oscSmoothing); ApplyInputConfig(root, config); ApplyOutputConfig(root, config); ApplyBool(root, "autoReload", config.autoReload); ApplySize(root, "maxTemporalHistoryFrames", config.maxTemporalHistoryFrames); ApplyBool(root, "previewEnabled", config.previewEnabled); ApplyDouble(root, "previewFps", config.previewFps); ApplyString(root, "runtimeShaderId", config.runtimeShaderId); if (error) error->clear(); return true; } bool ParseAppConfigJson(const std::string& text, AppConfig& config, std::string& error) { JsonValue root; std::string parseError; if (!ParseJson(text, root, parseError)) { error = parseError.empty() ? "Config JSON could not be parsed." : parseError; return false; } config = DefaultAppConfig(); return ApplyAppConfigJson(root, config, &error); } JsonValue AppConfigToJsonValue(const AppConfig& config) { JsonValue root = JsonValue::MakeObject(); root.set("$schema", JsonValue("./runtime-host.schema.json")); root.set("shaderLibrary", JsonValue(config.shaderLibrary)); root.set("serverPort", NumberValue(config.http.preferredPort)); root.set("oscBindAddress", JsonValue(config.oscBindAddress)); root.set("oscPort", NumberValue(config.oscPort)); root.set("oscSmoothing", NumberValue(config.oscSmoothing)); root.set("input", InputConfigToJson(config.input)); root.set("output", OutputConfigToJson(config.output)); root.set("autoReload", JsonValue(config.autoReload)); root.set("maxTemporalHistoryFrames", SizeValue(config.maxTemporalHistoryFrames)); root.set("previewEnabled", JsonValue(config.previewEnabled)); root.set("previewFps", NumberValue(config.previewFps)); root.set("runtimeShaderId", JsonValue(config.runtimeShaderId)); return root; } std::string AppConfigToJson(const AppConfig& config) { return SerializeJson(AppConfigToJsonValue(config), true) + "\n"; } bool SaveAppConfigToFile(const AppConfig& config, const std::filesystem::path& path, std::string& error) { if (path.empty()) { error = "Config path is not available."; return false; } std::ofstream output(path, std::ios::binary | std::ios::trunc); if (!output) { error = "Could not open config file for writing: " + path.string(); return false; } output << AppConfigToJson(config); if (!output) { error = "Could not write config file: " + path.string(); return false; } error.clear(); return true; } }