316 lines
10 KiB
C++
316 lines
10 KiB
C++
#include "AppConfigJson.h"
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
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<std::size_t>(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<int>(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<unsigned>(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<unsigned short>(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<unsigned short>(port);
|
|
}
|
|
|
|
JsonValue NumberValue(double value)
|
|
{
|
|
return JsonValue(value);
|
|
}
|
|
|
|
JsonValue SizeValue(std::size_t value)
|
|
{
|
|
return JsonValue(static_cast<double>(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<double>(window.x)));
|
|
value.set("y", JsonValue(static_cast<double>(window.y)));
|
|
value.set("width", JsonValue(static_cast<double>(window.width)));
|
|
value.set("height", JsonValue(static_cast<double>(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;
|
|
}
|
|
}
|