238 lines
5.8 KiB
C++
238 lines
5.8 KiB
C++
#include "AppConfigProvider.h"
|
|
|
|
#include "RuntimeJson.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <windows.h>
|
|
|
|
namespace RenderCadenceCompositor
|
|
{
|
|
namespace
|
|
{
|
|
std::filesystem::path ExecutableDirectory()
|
|
{
|
|
char path[MAX_PATH] = {};
|
|
const DWORD length = GetModuleFileNameA(nullptr, path, static_cast<DWORD>(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<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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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<unsigned short>(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<char>(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<std::filesystem::path> 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();
|
|
}
|
|
}
|