Added config editor in front end
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m46s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-30 19:33:40 +10:00
parent f0f8b080ca
commit 8ffc011ca0
26 changed files with 1201 additions and 55 deletions

212
src/app/AppConfigJson.cpp Normal file
View File

@@ -0,0 +1,212 @@
#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 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);
}
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);
}
}
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;
}
}