#include "printgraph.h"
#include <set>
#include <map>
#include <list>
#include <algorithm>
#include <cstring>
#include <climits>
static std::string extendStringRight(const std::string &s, size_t length) {
if (s.length() >= length)
return s;
else
return s + std::string(length - s.length(), ' ');
}
static std::string extendStringLeft(const std::string &s, size_t length) {
if (s.length() >= length)
return s;
else
return std::string(length - s.length(), ' ') + s;
}
static std::string printWithTwoDecimals(double d) {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.2f", d);
return buffer;
}
static std::string filterModeToString(int fm) {
if (fm == fmParallel)
return "parallel";
else if (fm == fmParallelRequests)
return "parreq";
else if (fm == fmUnordered)
return "unordered";
else if (fm == fmFrameState)
return "fstate";
else
return "unordered";
}
static std::string replaceAll(const std::string &s, const std::string &from, const std::string &to) {
std::string r(s);
size_t found_pos;
size_t last_pos = 0;
while ((found_pos = r.find(from, last_pos)) != std::string::npos) {
r.replace(found_pos, from.length(), to);
last_pos = found_pos + to.length();
}
return r;
}
static std::string escapeDotString(const std::string &s) {
return replaceAll(replaceAll(replaceAll(replaceAll(s, "\\", "\\\\"), "\r\n", "\\n"), "\r", "\\n"), "\n", "\\n");
}
static std::string mangleNode(VSNode *node, const VSAPI *vsapi) {
return "n" + std::to_string(reinterpret_cast<uintptr_t>(node));
}
static std::string mangleFrame(VSNode *node, int level, const VSAPI *vsapi) {
return "s" + std::to_string(reinterpret_cast<uintptr_t>(vsapi->getNodeCreationFunctionArguments(node, level)));
}
static int getMaxLevel(VSNode *node, const VSAPI *vsapi) {
for (int i = 0; i < INT_MAX; i++) {
if (!vsapi->getNodeCreationFunctionArguments(node, i))
return i - 1;
}
return 0;
}
static std::string printVSMap(const VSMap *args, int maxPrintLength, const VSAPI *vsapi) {
int numKeys = vsapi->mapNumKeys(args);
std::string setArgsStr;
for (int i = 0; i < numKeys; i++) {
const char *key = vsapi->mapGetKey(args, i);
int numElems = vsapi->mapNumElements(args, key);
setArgsStr += "\\n";
setArgsStr += key;
setArgsStr += "=";
switch (vsapi->mapGetType(args, key)) {
case ptInt:
for (int j = 0; j < std::min(maxPrintLength, numElems); j++)
setArgsStr += (j ? ", " : "") + std::to_string(vsapi->mapGetInt(args, key, j, nullptr));
if (numElems > maxPrintLength)
setArgsStr += ", <" + std::to_string(numElems - maxPrintLength) + ">";
break;
case ptFloat:
for (int j = 0; j < std::min(maxPrintLength, numElems); j++)
setArgsStr += (j ? ", " : "") + std::to_string(vsapi->mapGetFloat(args, key, j, nullptr));
if (numElems > maxPrintLength)
setArgsStr += ", <" + std::to_string(numElems - maxPrintLength) + ">";
break;
case ptData:
for (int j = 0; j < std::min(maxPrintLength, numElems); j++)
setArgsStr += std::string(j ? ", " : "") + (vsapi->mapGetDataTypeHint(args, key, j, nullptr) == dtUtf8 ? escapeDotString(vsapi->mapGetData(args, key, j, nullptr)) : ("[binary data " + std::to_string(vsapi->mapGetDataSize(args, key, j, nullptr)) + " bytes]"));
if (numElems > maxPrintLength)
setArgsStr += ", <" + std::to_string(numElems - maxPrintLength) + ">";
break;
case ptVideoNode:
for (int j = 0; j < std::min(maxPrintLength, numElems); j++) {
VSNode *ref = vsapi->mapGetNode(args, key, j, nullptr);
const VSVideoInfo *vi = vsapi->getVideoInfo(ref);
char formatName[32];
vsapi->getVideoFormatName(&vi->format, formatName);
setArgsStr += (j ? ", [" : "[") + std::string(formatName) + ":" + std::to_string(vi->width) + "x" + std::to_string(vi->height) + "]";
vsapi->freeNode(ref);
}
if (numElems > maxPrintLength)
setArgsStr += ", <" + std::to_string(numElems - maxPrintLength) + ">";
break;
case ptAudioNode:
for (int j = 0; j < std::min(maxPrintLength, numElems); j++) {
VSNode *ref = vsapi->mapGetNode(args, key, j, nullptr);
const VSAudioInfo *ai = vsapi->getAudioInfo(ref);
char formatName[32];
vsapi->getAudioFormatName(&ai->format, formatName);
setArgsStr += (j ? ", [" : "[") + std::string(formatName) + ":" + std::to_string(ai->sampleRate) + ":" + std::to_string(ai->format.channelLayout) + "]";
vsapi->freeNode(ref);
}
if (numElems > maxPrintLength)
setArgsStr += ", <" + std::to_string(numElems - maxPrintLength) + ">";
break;
default:
setArgsStr += "<" + std::to_string(numElems) + ">";
}
}
return setArgsStr;
}
static void printNodeGraphHelper(NodePrintMode mode, std::set<std::string> &lines, std::map<std::string, std::set<std::string>> &nodes, std::set<VSNode *> &visited, double processingTime, VSNode *node, const VSAPI *vsapi) {
if (!visited.insert(node).second)
return;
int maxLevel = getMaxLevel(node, vsapi);
std::string setArgsStr = printVSMap(vsapi->getNodeCreationFunctionArguments(node, 0), 5, vsapi);
std::string thisNode = mangleNode(node, vsapi);
std::string thisFrame = mangleFrame(node, 0, vsapi);
std::string baseFrame = mangleFrame(node, maxLevel, vsapi);
bool simple = (mode == NodePrintMode::Simple);
nodes[simple ? baseFrame : thisFrame].insert("label=\"" + std::string(vsapi->getNodeCreationFunctionName(node, simple ? maxLevel : 0)) + setArgsStr + "\"");
if (mode == NodePrintMode::FullWithTimes)
nodes[simple ? baseFrame : thisFrame].insert(thisNode + " [label=\"" + std::string(vsapi->getNodeName(node)) + "\\nMode: " + filterModeToString(vsapi->getNodeFilterMode(node)) + "\\nTime (%): " + printWithTwoDecimals((vsapi->getNodeProcessingTime(node, 0)) / (processingTime * 10000000)) + "\\nTime (s): " + printWithTwoDecimals(vsapi->getNodeProcessingTime(node, 0) / 1000000000.) + "\", shape=oval]");
else
nodes[simple ? baseFrame : thisFrame].insert(thisNode + " [label=\"" + std::string(vsapi->getNodeName(node)) + "\", shape=oval]");
int numDeps = vsapi->getNumNodeDependencies(node);
for (int i = 0; i < numDeps; i++) {
VSNode *source = vsapi->getNodeDependency(node, i)->source;
lines.insert(mangleNode(source, vsapi) + " -> " + thisNode);
printNodeGraphHelper(mode, lines, nodes, visited, processingTime, source, vsapi);
}
}
std::string printNodeGraph(NodePrintMode mode, VSNode *node, double processingTime, const VSAPI *vsapi) {
std::map<std::string, std::set<std::string>> nodes;
std::set<std::string> lines;
std::set<VSNode *> visited;
std::string s = "digraph {\n";
printNodeGraphHelper(mode, lines, nodes, visited, processingTime, node, vsapi);
for (const auto &iter : nodes) {
if (iter.second.size() > 1) {
s += " subgraph cluster_" + iter.first + " {\n";
for (const auto &iter2 : iter.second)
s += " " + iter2 + "\n";
s += " }\n";
} else if (iter.second.size() <= 1) {
for (const auto &iter2 : iter.second)
s += " " + iter2 + "\n";
}
}
for (const auto &iter : lines)
s += " " + iter + "\n";
s += "}";
return s;
}
struct NodeTimeRecord {
std::string filterName;
int filterMode;
int64_t nanoSeconds;
bool operator<(const NodeTimeRecord &other) const noexcept {
return nanoSeconds > other.nanoSeconds;
}
};
static void printNodeTimesHelper(std::list<NodeTimeRecord> &lines, std::set<VSNode *> &visited, VSNode *node, const VSAPI *vsapi) {
if (!visited.insert(node).second)
return;
lines.push_back(NodeTimeRecord{ vsapi->getNodeName(node), vsapi->getNodeFilterMode(node), vsapi->getNodeProcessingTime(node, 0) } );
int numDeps = vsapi->getNumNodeDependencies(node);
for (int i = 0; i < numDeps; i++)
printNodeTimesHelper(lines, visited, vsapi->getNodeDependency(node, i)->source, vsapi);
}
std::string printNodeTimes(VSNode *node, double processingTime, int64_t freedTime, const VSAPI *vsapi) {
std::list<NodeTimeRecord> lines;
std::set<VSNode *> visited;
std::string s;
printNodeTimesHelper(lines, visited, node, vsapi);
lines.sort();
s += extendStringRight("Filtername", 20) + " " + extendStringRight("Filter mode", 10) + " " + extendStringLeft("Time (%)", 10) + " " + extendStringLeft("Time (s)", 10) + "\n";
for (const auto & it : lines)
s += extendStringRight(it.filterName, 20) + " " + extendStringRight(filterModeToString(it.filterMode), 10) + " " + extendStringLeft(printWithTwoDecimals((it.nanoSeconds) / (processingTime * 10000000)), 10) + " " + extendStringLeft(printWithTwoDecimals(it.nanoSeconds / 1000000000.), 10) + "\n";
s += extendStringRight("<Destroyed>", 20) + " " + extendStringRight("<unknown>", 10) + " " + extendStringLeft(printWithTwoDecimals((freedTime) / (processingTime * 10000000)), 10) + " " + extendStringLeft(printWithTwoDecimals(freedTime / 1000000000.), 10) + "\n";
return s;
}