#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 ApplyInt(const JsonValue& root, const char* key, int& target) { const JsonValue* value = Find(root, key); if (value && value->isNumber()) target = static_cast(value->asNumber()); } void ApplyUnsigned(const JsonValue& root, const char* key, unsigned& 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); } void ApplyOptionalPort(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 >= 0.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; } void ApplyWindowOutputConfig(const JsonValue& root, AppConfig& config) { const JsonValue* window = Find(root, "windowOutput"); if (!window || !window->isObject()) return; ApplyBool(*window, "fullscreen", config.windowOutput.fullscreen); ApplyBool(*window, "borderless", config.windowOutput.borderless); ApplyString(*window, "display", config.windowOutput.display); ApplyInt(*window, "x", config.windowOutput.x); ApplyInt(*window, "y", config.windowOutput.y); ApplyUnsigned(*window, "width", config.windowOutput.width); ApplyUnsigned(*window, "height", config.windowOutput.height); ApplyBool(*window, "vsync", config.windowOutput.vsync); ApplyBool(*window, "allowTearing", config.windowOutput.allowTearing); } void ApplyColorPipelineConfig(const JsonValue& root, AppConfig& config) { const JsonValue* color = Find(root, "colorPipeline"); if (!color || !color->isObject()) return; ApplyBool(*color, "ocioEnabled", config.colorPipeline.ocioEnabled); ApplyString(*color, "ocioConfig", config.colorPipeline.ocioConfig); ApplyString(*color, "inputColorSpace", config.colorPipeline.inputColorSpace); ApplyString(*color, "workingColorSpace", config.colorPipeline.workingColorSpace); ApplyString(*color, "outputColorSpace", config.colorPipeline.outputColorSpace); ApplyString(*color, "display", config.colorPipeline.display); ApplyString(*color, "view", config.colorPipeline.view); ApplyString(*color, "look", config.colorPipeline.look); ApplyDouble(*color, "exposure", config.colorPipeline.exposure); ApplyDouble(*color, "gamma", config.colorPipeline.gamma); ApplyString(*color, "workingFormat", config.colorPipeline.workingFormat); ApplyBool(*color, "linearWorkingSpace", config.colorPipeline.linearWorkingSpace); } 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; } JsonValue WindowOutputConfigToJson(const WindowOutputAppConfig& window) { JsonValue value = JsonValue::MakeObject(); value.set("fullscreen", JsonValue(window.fullscreen)); value.set("borderless", JsonValue(window.borderless)); value.set("display", JsonValue(window.display)); value.set("x", JsonValue(static_cast(window.x))); value.set("y", JsonValue(static_cast(window.y))); value.set("width", JsonValue(static_cast(window.width))); value.set("height", JsonValue(static_cast(window.height))); value.set("vsync", JsonValue(window.vsync)); value.set("allowTearing", JsonValue(window.allowTearing)); return value; } JsonValue ColorPipelineConfigToJson(const ColorPipelineAppConfig& color) { JsonValue value = JsonValue::MakeObject(); value.set("ocioEnabled", JsonValue(color.ocioEnabled)); value.set("ocioConfig", JsonValue(color.ocioConfig)); value.set("inputColorSpace", JsonValue(color.inputColorSpace)); value.set("workingColorSpace", JsonValue(color.workingColorSpace)); value.set("outputColorSpace", JsonValue(color.outputColorSpace)); value.set("display", JsonValue(color.display)); value.set("view", JsonValue(color.view)); value.set("look", JsonValue(color.look)); value.set("exposure", JsonValue(color.exposure)); value.set("gamma", JsonValue(color.gamma)); value.set("workingFormat", JsonValue(color.workingFormat)); value.set("linearWorkingSpace", JsonValue(color.linearWorkingSpace)); 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); ApplyOptionalPort(root, "oscPort", config.oscPort); ApplyDouble(root, "oscSmoothing", config.oscSmoothing); ApplyInputConfig(root, config); ApplyOutputConfig(root, config); ApplyWindowOutputConfig(root, config); ApplyColorPipelineConfig(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("windowOutput", WindowOutputConfigToJson(config.windowOutput)); root.set("colorPipeline", ColorPipelineConfigToJson(config.colorPipeline)); 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; } }