core/FilterGraph.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "avs/config.h" | ||
| 2 | #include "FilterGraph.h" | ||
| 3 | #include "DeviceManager.h" | ||
| 4 | #include "InternalEnvironment.h" | ||
| 5 | #include "strings.h" | ||
| 6 | |||
| 7 | #ifdef AVS_WINDOWS | ||
| 8 | #include <avs/win.h> | ||
| 9 | #else | ||
| 10 | #include <avs/posix.h> | ||
| 11 | #endif | ||
| 12 | |||
| 13 | #include <map> | ||
| 14 | #include <iostream> | ||
| 15 | #include <string> | ||
| 16 | #include <sstream> | ||
| 17 | #include <iomanip> | ||
| 18 | |||
| 19 | #include <avs/filesystem.h> | ||
| 20 | |||
| 21 | ✗ | static AVSValue DeepCopyValue(std::vector<std::unique_ptr<AVSValue[]>>& arrays, const AVSValue& src) { | |
| 22 | ✗ | if (src.IsArray()) { | |
| 23 | ✗ | AVSValue* copy = new AVSValue[src.ArraySize()]; | |
| 24 | ✗ | for (int i = 0; i < src.ArraySize(); ++i) { | |
| 25 | ✗ | copy[i] = src[i]; // NEW_AVSVALUE is already doing deep copy | |
| 26 | // copy[i] = DeepCopyValue(arrays, src[i]); | ||
| 27 | } | ||
| 28 | ✗ | arrays.emplace_back(std::unique_ptr<AVSValue[]>(copy)); | |
| 29 | ✗ | return AVSValue(copy, src.ArraySize()); | |
| 30 | } | ||
| 31 | ✗ | return src; | |
| 32 | } | ||
| 33 | |||
| 34 | ✗ | FilterGraphNode::FilterGraphNode(PClip child, const char* name, | |
| 35 | const AVSValue& last_, const AVSValue& args_, const char* const* argnames_, | ||
| 36 | ✗ | IScriptEnvironment* env) | |
| 37 | ✗ | : Env(env) | |
| 38 | ✗ | , child(child) | |
| 39 | ✗ | , name(name) | |
| 40 | ✗ | , memory(new GraphMemoryNode()) | |
| 41 | { | ||
| 42 | ✗ | if (last_.Defined()) { | |
| 43 | ✗ | std::vector<AVSValue> argstmp; | |
| 44 | ✗ | argstmp.push_back(last_); | |
| 45 | ✗ | if (argnames_) { | |
| 46 | ✗ | argnames.push_back(std::string()); | |
| 47 | } | ||
| 48 | ✗ | for (int i = 0; i < args_.ArraySize(); ++i) { | |
| 49 | ✗ | argstmp.push_back(args_[i]); | |
| 50 | } | ||
| 51 | ✗ | args = DeepCopyValue(arrays, AVSValue(argstmp.data(), (int)argstmp.size())); | |
| 52 | ✗ | } | |
| 53 | else { | ||
| 54 | ✗ | args = DeepCopyValue(arrays, args_.IsArray() ? args_ : AVSValue(args_, 1)); | |
| 55 | } | ||
| 56 | |||
| 57 | ✗ | if (argnames_) { | |
| 58 | ✗ | for (int i = 0; i < args_.ArraySize(); ++i) { | |
| 59 | ✗ | argnames.push_back(argnames_[i] ? std::string(argnames_[i]) : std::string()); | |
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | ✗ | Env->ManageCache(MC_RegisterGraphNode, this); | |
| 64 | ✗ | } | |
| 65 | |||
| 66 | ✗ | FilterGraphNode::~FilterGraphNode() | |
| 67 | { | ||
| 68 | ✗ | Env->ManageCache(MC_UnRegisterGraphNode, this); | |
| 69 | ✗ | } | |
| 70 | |||
| 71 | struct ScopedGraphNode { | ||
| 72 | FilterGraphNode*& target; | ||
| 73 | FilterGraphNode* prev; | ||
| 74 | ✗ | ScopedGraphNode(FilterGraphNode*& target, FilterGraphNode* node) : target(target) { | |
| 75 | ✗ | prev = target; | |
| 76 | ✗ | target = node; | |
| 77 | ✗ | } | |
| 78 | ✗ | ~ScopedGraphNode() { | |
| 79 | ✗ | target = prev; | |
| 80 | ✗ | } | |
| 81 | }; | ||
| 82 | |||
| 83 | ✗ | PVideoFrame __stdcall FilterGraphNode::GetFrame(int n, IScriptEnvironment* env_) | |
| 84 | { | ||
| 85 | ✗ | InternalEnvironment* env = GetAndRevealCamouflagedEnv(env_); | |
| 86 | |||
| 87 | ✗ | ScopedGraphNode scope(env->GetCurrentGraphNode(), this); | |
| 88 | ✗ | return child->GetFrame(n, env); | |
| 89 | ✗ | } | |
| 90 | |||
| 91 | ✗ | void GraphMemoryNode::OnAllocate(size_t bytes, Device* dev) | |
| 92 | { | ||
| 93 | ✗ | auto it = memory.find(dev); | |
| 94 | ✗ | if (it == memory.end()) { | |
| 95 | ✗ | memory[dev] = MemoryInfo(); | |
| 96 | ✗ | it = memory.find(dev); | |
| 97 | } | ||
| 98 | ✗ | it->second.numAllocation++; | |
| 99 | ✗ | it->second.totalBytes += bytes; | |
| 100 | ✗ | } | |
| 101 | |||
| 102 | ✗ | void GraphMemoryNode::OnFree(size_t bytes, Device* dev) | |
| 103 | { | ||
| 104 | ✗ | auto it = memory.find(dev); | |
| 105 | ✗ | if (it == memory.end()) { | |
| 106 | ✗ | printf("Unexpected behavior ...\n"); | |
| 107 | ✗ | return; | |
| 108 | } | ||
| 109 | ✗ | it->second.numAllocation--; | |
| 110 | ✗ | it->second.totalBytes -= bytes; | |
| 111 | } | ||
| 112 | |||
| 113 | class FilterGraph | ||
| 114 | { | ||
| 115 | protected: | ||
| 116 | IScriptEnvironment * env; | ||
| 117 | |||
| 118 | struct NodeInfo { | ||
| 119 | bool isFunction; | ||
| 120 | int number; | ||
| 121 | std::string name; | ||
| 122 | std::string args; | ||
| 123 | std::vector<void*> refNodes; | ||
| 124 | |||
| 125 | int cacheSize; | ||
| 126 | int cacheCapacity; | ||
| 127 | std::map<Device*, GraphMemoryNode::MemoryInfo> memory; | ||
| 128 | |||
| 129 | ✗ | NodeInfo() { } | |
| 130 | ✗ | NodeInfo(int number) : number(number) { } | |
| 131 | }; | ||
| 132 | |||
| 133 | std::map<void*, NodeInfo> nodeMap; | ||
| 134 | |||
| 135 | ✗ | int DoClip(IClip* pclip) { | |
| 136 | ✗ | if (nodeMap.find(pclip) == nodeMap.end()) { | |
| 137 | ✗ | nodeMap.insert(std::make_pair(pclip, (int)nodeMap.size())); | |
| 138 | ✗ | FilterGraphNode* node = dynamic_cast<FilterGraphNode*>(pclip); | |
| 139 | ✗ | if (node != nullptr) { | |
| 140 | ✗ | NodeInfo& info = nodeMap[node]; | |
| 141 | ✗ | info.isFunction = false; | |
| 142 | ✗ | info.name = node->name; | |
| 143 | ✗ | info.args = "(" + DoArray(info, nullptr, node->argnames.data(), node->args) + ")"; | |
| 144 | ✗ | info.cacheSize = node->SetCacheHints(CACHE_GET_SIZE, 0); | |
| 145 | ✗ | info.cacheCapacity = node->SetCacheHints(CACHE_GET_CAPACITY, 0); | |
| 146 | ✗ | info.memory = node->memory->memory; | |
| 147 | } | ||
| 148 | ✗ | OutClip(nodeMap[node]); | |
| 149 | } | ||
| 150 | ✗ | return nodeMap[pclip].number; | |
| 151 | } | ||
| 152 | |||
| 153 | ✗ | int DoFunc(IFunction* pfunc) { | |
| 154 | ✗ | if (nodeMap.find(pfunc) == nodeMap.end()) { | |
| 155 | ✗ | nodeMap.insert(std::make_pair(pfunc, (int)nodeMap.size())); | |
| 156 | ✗ | NodeInfo& info = nodeMap[pfunc]; | |
| 157 | ✗ | info.isFunction = true; | |
| 158 | ✗ | auto captures = pfunc->GetCaptures(); | |
| 159 | ✗ | info.name = pfunc->ToString(env); | |
| 160 | ✗ | info.args = "[" + DoArray(info, captures.var_names, nullptr, AVSValue(captures.var_data, captures.count)) + "]"; | |
| 161 | ✗ | info.cacheSize = 0; | |
| 162 | ✗ | info.cacheCapacity = 0; | |
| 163 | ✗ | OutFunc(info); | |
| 164 | } | ||
| 165 | ✗ | return nodeMap[pfunc].number; | |
| 166 | } | ||
| 167 | |||
| 168 | ✗ | std::string DoArray(NodeInfo& info, const char** argnames_c, std::string* argnames_s, const AVSValue& arr) { | |
| 169 | ✗ | std::stringstream ss; | |
| 170 | ✗ | int breakpos = 0; | |
| 171 | ✗ | int maxlen = 60; | |
| 172 | |||
| 173 | ✗ | for (int i = 0; i < arr.ArraySize(); ++i) { | |
| 174 | ✗ | if (i != 0) { | |
| 175 | ✗ | ss << ","; | |
| 176 | } | ||
| 177 | ✗ | if (argnames_c && argnames_c[i]) { | |
| 178 | ✗ | ss << argnames_c[i] << "="; | |
| 179 | } | ||
| 180 | ✗ | if (argnames_s && argnames_s[i].size() > 0) { | |
| 181 | ✗ | ss << argnames_s[i] << "="; | |
| 182 | } | ||
| 183 | ✗ | const AVSValue& v = arr[i]; | |
| 184 | ✗ | if (!v.Defined()) { | |
| 185 | ✗ | ss << "default"; | |
| 186 | } | ||
| 187 | ✗ | else if (v.IsClip()) { | |
| 188 | ✗ | IClip* pclip = (IClip*)(void*)v.AsClip(); | |
| 189 | ✗ | int clipnum = DoClip(pclip); | |
| 190 | ✗ | ss << "clip" << (clipnum + 1); | |
| 191 | ✗ | info.refNodes.push_back(pclip); | |
| 192 | } | ||
| 193 | ✗ | else if (v.IsFunction()) { | |
| 194 | ✗ | IFunction* pfunc = (IFunction*)(void*)v.AsFunction(); | |
| 195 | ✗ | int funcnum = DoFunc(pfunc); | |
| 196 | ✗ | ss << "func" << (funcnum + 1); | |
| 197 | ✗ | info.refNodes.push_back(pfunc); | |
| 198 | } | ||
| 199 | ✗ | else if (v.IsArray()) { | |
| 200 | ✗ | ss << OutArray("(" + DoArray(info, nullptr, nullptr, v) + ")"); | |
| 201 | } | ||
| 202 | ✗ | else if (v.IsBool()) { | |
| 203 | ✗ | ss << (v.AsBool() ? "True" : "False"); | |
| 204 | } | ||
| 205 | ✗ | else if (v.IsInt()) { | |
| 206 | ✗ | ss << v.AsInt(); | |
| 207 | } | ||
| 208 | ✗ | else if (v.IsFloat()) { | |
| 209 | ✗ | ss << std::setprecision(8) << v.AsFloat(); | |
| 210 | } | ||
| 211 | ✗ | else if (v.IsString()) { | |
| 212 | ✗ | ss << "\"" << v.AsString() << "\""; | |
| 213 | } | ||
| 214 | else { | ||
| 215 | ✗ | ss << "<error>"; | |
| 216 | } | ||
| 217 | ✗ | if ((int)ss.tellp() - breakpos > maxlen) { | |
| 218 | ✗ | ss << "\n"; | |
| 219 | ✗ | breakpos = (int)ss.tellp(); | |
| 220 | } | ||
| 221 | } | ||
| 222 | ✗ | return ss.str(); | |
| 223 | ✗ | } | |
| 224 | |||
| 225 | virtual void OutClip(const NodeInfo& info) = 0; | ||
| 226 | virtual void OutFunc(const NodeInfo& info) = 0; | ||
| 227 | virtual std::string OutArray(const std::string& args) = 0; | ||
| 228 | |||
| 229 | public: | ||
| 230 | ✗ | int Construct(FilterGraphNode* root, IScriptEnvironment* env_) | |
| 231 | { | ||
| 232 | ✗ | env = env_; | |
| 233 | ✗ | nodeMap.clear(); | |
| 234 | ✗ | return DoClip(root); | |
| 235 | } | ||
| 236 | ✗ | void Construct(const std::vector<FilterGraphNode*>& roots, IScriptEnvironment* env_) | |
| 237 | { | ||
| 238 | ✗ | env = env_; | |
| 239 | ✗ | nodeMap.clear(); | |
| 240 | ✗ | for (auto node : roots) { | |
| 241 | ✗ | if (node != nullptr) { | |
| 242 | ✗ | DoClip(node); | |
| 243 | } | ||
| 244 | } | ||
| 245 | ✗ | } | |
| 246 | }; | ||
| 247 | |||
| 248 | ✗ | static void ReplaceAll(std::string& str, const std::string& from, const std::string& to) { | |
| 249 | ✗ | size_t start_pos = 0; | |
| 250 | ✗ | while ((start_pos = str.find(from, start_pos)) != std::string::npos) { | |
| 251 | ✗ | str.replace(start_pos, from.length(), to); | |
| 252 | ✗ | start_pos += to.length(); | |
| 253 | } | ||
| 254 | ✗ | } | |
| 255 | |||
| 256 | class AvsScriptFilterGraph : private FilterGraph | ||
| 257 | { | ||
| 258 | std::stringstream ss; | ||
| 259 | |||
| 260 | protected: | ||
| 261 | ✗ | virtual void OutClip(const NodeInfo& info) { | |
| 262 | ✗ | int num = info.number + 1; | |
| 263 | ✗ | if (info.name.size() == 0) { | |
| 264 | ✗ | ss << "clip" << num << ": Failed to get information" << std::endl; | |
| 265 | } | ||
| 266 | else { | ||
| 267 | ✗ | auto args = info.args; | |
| 268 | ✗ | ReplaceAll(args, "\n", ""); | |
| 269 | ✗ | ss << "clip" << num << " = " << info.name << args << std::endl; | |
| 270 | ✗ | } | |
| 271 | ✗ | } | |
| 272 | ✗ | virtual void OutFunc(const NodeInfo& info) { | |
| 273 | ✗ | int num = info.number + 1; | |
| 274 | ✗ | auto args = info.args; | |
| 275 | ✗ | ReplaceAll(args, "\n", ""); | |
| 276 | ✗ | ss << "func" << num << " = function" << args << "(){ " << info.name << " }" << std::endl; | |
| 277 | ✗ | } | |
| 278 | |||
| 279 | int nextArrayNumber = 0; | ||
| 280 | ✗ | virtual std::string OutArray(const std::string& args) { | |
| 281 | ✗ | std::string name = std::string("array") + std::to_string(++nextArrayNumber); | |
| 282 | ✗ | ss << name; | |
| 283 | ✗ | ss << " = ArrayCreate" << args << std::endl; | |
| 284 | ✗ | return name; | |
| 285 | ✗ | } | |
| 286 | public: | ||
| 287 | |||
| 288 | ✗ | void Construct(FilterGraphNode* root, IScriptEnvironment* env) { | |
| 289 | ✗ | int last = FilterGraph::Construct(root, env); | |
| 290 | ✗ | ss << "return clip" << (last + 1) << std::endl; | |
| 291 | ✗ | } | |
| 292 | |||
| 293 | ✗ | std::string GetOutput() { | |
| 294 | ✗ | return ss.str(); | |
| 295 | } | ||
| 296 | }; | ||
| 297 | |||
| 298 | class DotFilterGraph : private FilterGraph | ||
| 299 | { | ||
| 300 | bool enableArgs; | ||
| 301 | bool enableMemory; | ||
| 302 | std::stringstream ss; | ||
| 303 | |||
| 304 | protected: | ||
| 305 | |||
| 306 | ✗ | void printfcomma(size_t n) { | |
| 307 | ✗ | if (n < 1000) { | |
| 308 | ✗ | ss << n; | |
| 309 | ✗ | return; | |
| 310 | } | ||
| 311 | ✗ | printfcomma(n / 1000); | |
| 312 | ✗ | ss << ',' << std::setfill('0') << std::setw(3) << (n % 1000); | |
| 313 | } | ||
| 314 | |||
| 315 | ✗ | virtual void OutClip(const NodeInfo& info) { | |
| 316 | ✗ | int num = info.number + 1; | |
| 317 | ✗ | ss << "clip" << num; | |
| 318 | ✗ | if (info.name.size() == 0) { | |
| 319 | ✗ | ss << " [label = \"...\"];" << std::endl; | |
| 320 | } | ||
| 321 | else { | ||
| 322 | ✗ | if (enableArgs) { | |
| 323 | ✗ | std::string label = info.name + info.args; | |
| 324 | ✗ | ReplaceAll(label, "\\", "\\\\"); | |
| 325 | ✗ | ReplaceAll(label, "\"", "\\\""); | |
| 326 | ✗ | ReplaceAll(label, "\n", "\\n"); | |
| 327 | ✗ | ss << " [label = \"" << label << "\"];" << std::endl; | |
| 328 | ✗ | } | |
| 329 | else { | ||
| 330 | ✗ | if (info.cacheCapacity != 0) { | |
| 331 | ✗ | ss << " [label = \"" << info.name << "(caching " << info.cacheSize << " frames with capacity " << info.cacheCapacity << ")"; | |
| 332 | } | ||
| 333 | else { | ||
| 334 | ✗ | ss << " [label = \"" << info.name; | |
| 335 | } | ||
| 336 | ✗ | if (enableMemory) { | |
| 337 | ✗ | for (auto entry : info.memory) { | |
| 338 | ✗ | ss << "\\n" << entry.first->GetName() << ": " << entry.second.numAllocation << " frames, "; | |
| 339 | ✗ | printfcomma(entry.second.totalBytes); | |
| 340 | ✗ | ss << " bytes"; | |
| 341 | } | ||
| 342 | } | ||
| 343 | ✗ | ss << "\"];" << std::endl;; | |
| 344 | } | ||
| 345 | } | ||
| 346 | ✗ | for (void* pclip : info.refNodes) { | |
| 347 | ✗ | auto& node = nodeMap[pclip]; | |
| 348 | ✗ | int refnum = node.number + 1; | |
| 349 | ✗ | if (node.isFunction) { | |
| 350 | ✗ | ss << "func" << refnum << " -> " << "clip" << num << ";" << std::endl; | |
| 351 | } | ||
| 352 | else { | ||
| 353 | ✗ | ss << "clip" << refnum << " -> " << "clip" << num << ";" << std::endl; | |
| 354 | } | ||
| 355 | } | ||
| 356 | ✗ | } | |
| 357 | ✗ | virtual void OutFunc(const NodeInfo& info) { | |
| 358 | ✗ | int num = info.number + 1; | |
| 359 | ✗ | ss << "func" << num; | |
| 360 | ✗ | if (enableArgs) { | |
| 361 | ✗ | std::string label = info.name + "\n" + info.args; | |
| 362 | ✗ | ReplaceAll(label, "\\", "\\\\"); | |
| 363 | ✗ | ReplaceAll(label, "\"", "\\\""); | |
| 364 | ✗ | ReplaceAll(label, "\n", "\\n"); | |
| 365 | ✗ | ss << " [label = \"" << label << "\"];" << std::endl; | |
| 366 | ✗ | } | |
| 367 | else { | ||
| 368 | ✗ | ss << " [label = \"" << info.name << "\"];" << std::endl; | |
| 369 | } | ||
| 370 | ✗ | for (void* pclip : info.refNodes) { | |
| 371 | ✗ | auto& node = nodeMap[pclip]; | |
| 372 | ✗ | int refnum = node.number + 1; | |
| 373 | ✗ | if (node.isFunction) { | |
| 374 | ✗ | ss << "func" << refnum << " -> " << "func" << num << ";" << std::endl; | |
| 375 | } | ||
| 376 | else { | ||
| 377 | ✗ | ss << "clip" << refnum << " -> " << "func" << num << ";" << std::endl; | |
| 378 | } | ||
| 379 | } | ||
| 380 | ✗ | } | |
| 381 | |||
| 382 | int nextArrayNumber = 0; | ||
| 383 | ✗ | virtual std::string OutArray(const std::string& args) { | |
| 384 | ✗ | std::string name = std::string("array") + std::to_string(++nextArrayNumber); | |
| 385 | //ss << name; | ||
| 386 | //ss << " = ArrayCreate" << args << std::endl; | ||
| 387 | ✗ | return name; | |
| 388 | } | ||
| 389 | public: | ||
| 390 | |||
| 391 | ✗ | void Construct(FilterGraphNode* root, bool enableArgs, bool enableMemory, IScriptEnvironment* env) { | |
| 392 | ✗ | this->enableArgs = enableArgs; | |
| 393 | ✗ | this->enableMemory = enableMemory; | |
| 394 | ✗ | ss << "digraph avs_filter_graph {" << std::endl; | |
| 395 | ✗ | ss << "node [ shape = box ];" << std::endl; | |
| 396 | ✗ | int last = FilterGraph::Construct(root, env); | |
| 397 | ✗ | ss << "GOAL;" << std::endl; | |
| 398 | ✗ | ss << "clip" << (last + 1) << " -> GOAL" << std::endl; | |
| 399 | ✗ | ss << "}" << std::endl; | |
| 400 | ✗ | } | |
| 401 | |||
| 402 | ✗ | void Construct(const std::vector<FilterGraphNode*>& roots, bool enableArgs, bool enableMemory, IScriptEnvironment* env) { | |
| 403 | ✗ | this->enableArgs = enableArgs; | |
| 404 | ✗ | this->enableMemory = enableMemory; | |
| 405 | ✗ | ss << "digraph avs_filter_graph {" << std::endl; | |
| 406 | ✗ | ss << "node [ shape = box ];" << std::endl; | |
| 407 | ✗ | FilterGraph::Construct(roots, env); | |
| 408 | ✗ | ss << "}" << std::endl; | |
| 409 | ✗ | } | |
| 410 | |||
| 411 | ✗ | std::string GetOutput() { | |
| 412 | ✗ | return ss.str(); | |
| 413 | } | ||
| 414 | }; | ||
| 415 | |||
| 416 | // Helper: open a file whose path is encoded as UTF-8. | ||
| 417 | // On Windows convert to wide string and use _wfopen; on POSIX just call fopen. | ||
| 418 | ✗ | static FILE* OpenFileUtf8(const std::string& path_utf8, const char* mode) { | |
| 419 | #ifdef AVS_WINDOWS | ||
| 420 | if (path_utf8.empty() || mode == nullptr) return nullptr; | ||
| 421 | |||
| 422 | // Convert UTF-8 path to wide string using helper from strings.h | ||
| 423 | std::wstring wpath = Utf8ToWideChar(path_utf8.c_str()).get(); | ||
| 424 | if (wpath.empty()) return nullptr; | ||
| 425 | |||
| 426 | // Convert mode to wide string. Mode is usually ASCII but convert via Utf8 helper for consistency. | ||
| 427 | std::wstring wmode = Utf8ToWideChar(mode).get(); | ||
| 428 | if (wmode.empty()) return nullptr; | ||
| 429 | |||
| 430 | return _wfopen(wpath.c_str(), wmode.c_str()); | ||
| 431 | #else | ||
| 432 | ✗ | return fopen(path_utf8.c_str(), mode); | |
| 433 | #endif | ||
| 434 | } | ||
| 435 | |||
| 436 | ✗ | void DoDumpGraph(const std::vector<FilterGraphNode*>& roots, const char* path_utf8, IScriptEnvironment* env) | |
| 437 | { | ||
| 438 | ✗ | DotFilterGraph graph; | |
| 439 | ✗ | graph.Construct(roots, true, true, env); | |
| 440 | ✗ | std::string ret = graph.GetOutput(); | |
| 441 | |||
| 442 | ✗ | FILE* fp = OpenFileUtf8(std::string(path_utf8), "w"); | |
| 443 | ✗ | if (fp == nullptr) { | |
| 444 | ✗ | env->ThrowError("Could not open output file ..."); | |
| 445 | } | ||
| 446 | ✗ | fwrite(ret.data(), ret.size(), 1, fp); | |
| 447 | ✗ | fclose(fp); | |
| 448 | ✗ | } | |
| 449 | |||
| 450 | ✗ | static void DoDumpGraph(PClip clip, int mode, const char* path_utf8, IScriptEnvironment* env) | |
| 451 | { | ||
| 452 | ✗ | FilterGraphNode* root = dynamic_cast<FilterGraphNode*>((IClip*)(void*)clip); | |
| 453 | |||
| 454 | ✗ | std::string ret; | |
| 455 | |||
| 456 | ✗ | if (mode == 0) { | |
| 457 | ✗ | AvsScriptFilterGraph graph; | |
| 458 | ✗ | graph.Construct(root, env); | |
| 459 | ✗ | ret = graph.GetOutput(); | |
| 460 | ✗ | } | |
| 461 | ✗ | else if (mode == 1 || mode == 2) { | |
| 462 | ✗ | DotFilterGraph graph; | |
| 463 | ✗ | graph.Construct(root, mode == 1, true, env); | |
| 464 | ✗ | ret = graph.GetOutput(); | |
| 465 | ✗ | } | |
| 466 | else { | ||
| 467 | ✗ | env->ThrowError("Unknown mode (%d)", mode); | |
| 468 | } | ||
| 469 | |||
| 470 | ✗ | FILE* fp = OpenFileUtf8(std::string(path_utf8), "w"); | |
| 471 | ✗ | if (fp == nullptr) { | |
| 472 | ✗ | env->ThrowError("Could not open output file ..."); | |
| 473 | } | ||
| 474 | ✗ | fwrite(ret.data(), ret.size(), 1, fp); | |
| 475 | ✗ | fclose(fp); | |
| 476 | ✗ | } | |
| 477 | |||
| 478 | class DelayedDump : public GenericVideoFilter | ||
| 479 | { | ||
| 480 | std::string outpath_utf8; | ||
| 481 | int mode; | ||
| 482 | int nframes; | ||
| 483 | bool repeat; | ||
| 484 | std::vector<bool> fired; | ||
| 485 | public: | ||
| 486 | ✗ | DelayedDump(PClip clip, const std::string& outpath_utf8, int mode, int nframes, bool repeat) | |
| 487 | ✗ | : GenericVideoFilter(clip) | |
| 488 | ✗ | , outpath_utf8(outpath_utf8) | |
| 489 | ✗ | , mode(mode) | |
| 490 | ✗ | , nframes(nframes) | |
| 491 | ✗ | , repeat(repeat) | |
| 492 | { | ||
| 493 | ✗ | if (repeat) { | |
| 494 | ✗ | fired.resize((clip->GetVideoInfo().num_frames + nframes - 1) / nframes); | |
| 495 | } | ||
| 496 | else { | ||
| 497 | ✗ | fired.resize(1); | |
| 498 | } | ||
| 499 | ✗ | } | |
| 500 | |||
| 501 | ✗ | PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) | |
| 502 | { | ||
| 503 | ✗ | if (repeat) { | |
| 504 | ✗ | int slot = std::max(0, std::min(n / nframes, (int)fired.size() - 1)); | |
| 505 | ✗ | if (fired[slot] == false) { | |
| 506 | ✗ | fired[slot] = true; | |
| 507 | // Build path by inserting "-<n>" before extension (if any), preserve UTF-8 | ||
| 508 | ✗ | std::string basename = outpath_utf8; | |
| 509 | ✗ | size_t pos = basename.find_last_of('.'); | |
| 510 | ✗ | std::string path; | |
| 511 | ✗ | if (pos != std::string::npos) { | |
| 512 | ✗ | path = basename.substr(0, pos) + "-" + std::to_string(n) + basename.substr(pos); | |
| 513 | } else { | ||
| 514 | ✗ | path = basename + "-" + std::to_string(n); | |
| 515 | } | ||
| 516 | ✗ | DoDumpGraph(child, mode, path.c_str(), env); | |
| 517 | ✗ | } | |
| 518 | } | ||
| 519 | else { | ||
| 520 | ✗ | if (n == nframes && fired[0] == false) { | |
| 521 | ✗ | fired[0] = true; | |
| 522 | ✗ | DoDumpGraph(child, mode, outpath_utf8.c_str(), env); | |
| 523 | } | ||
| 524 | } | ||
| 525 | ✗ | return child->GetFrame(n, env); | |
| 526 | } | ||
| 527 | }; | ||
| 528 | |||
| 529 | ✗ | static AVSValue DumpFilterGraph(AVSValue args, void* user_data, IScriptEnvironment* env) { | |
| 530 | ✗ | PClip clip = args[0].AsClip(); | |
| 531 | ✗ | FilterGraphNode* root = dynamic_cast<FilterGraphNode*>((IClip*)(void*)clip); | |
| 532 | ✗ | if (root == nullptr) { | |
| 533 | ✗ | env->ThrowError("clip is not a FilterChainNode. Ensure you have enabled the chain analysis by SetGraphAnalysis(true)."); | |
| 534 | } | ||
| 535 | |||
| 536 | ✗ | int mode = args[2].AsInt(0); | |
| 537 | ✗ | const char* path = args[1].AsString(""); | |
| 538 | ✗ | int nframes = args[3].AsInt(-1); | |
| 539 | ✗ | bool repeat = args[4].AsBool(false); | |
| 540 | |||
| 541 | ✗ | const bool utf8 = args[5].AsBool(false); | |
| 542 | // DumpFiltergraph can also support yet forced UTF-8 mode in ANSI-only processes. | ||
| 543 | |||
| 544 | ✗ | std::string path_utf8; | |
| 545 | #ifdef AVS_WINDOWS | ||
| 546 | // internally use UTF-8 path | ||
| 547 | if (!utf8) | ||
| 548 | path_utf8 = AnsiToUtf8(path).get(); | ||
| 549 | else | ||
| 550 | path_utf8 = path; | ||
| 551 | #else | ||
| 552 | ✗ | path_utf8 = path; | |
| 553 | #endif | ||
| 554 | |||
| 555 | ✗ | if (nframes >= 0) { | |
| 556 | ✗ | return new DelayedDump(clip, GetFullPathNameWrapUtf8(path_utf8), mode, nframes, repeat); | |
| 557 | } | ||
| 558 | |||
| 559 | // For immediate dumps, normalize and let DoDumpGraph handle UTF-8 aware opening. | ||
| 560 | ✗ | DoDumpGraph(clip, mode, path_utf8.c_str(), env); | |
| 561 | |||
| 562 | ✗ | return clip; | |
| 563 | ✗ | } | |
| 564 | |||
| 565 | ✗ | static AVSValue __cdecl SetGraphAnalysis(AVSValue args, void* user_data, IScriptEnvironment* env_) { | |
| 566 | ✗ | InternalEnvironment* env = GetAndRevealCamouflagedEnv(env_); | |
| 567 | ✗ | env->SetGraphAnalysis(args[0].AsBool()); | |
| 568 | ✗ | return AVSValue(); | |
| 569 | } | ||
| 570 | |||
| 571 | extern const AVSFunction FilterGraph_filters[] = { | ||
| 572 | { "SetGraphAnalysis", BUILTIN_FUNC_PREFIX, "b", SetGraphAnalysis, nullptr }, | ||
| 573 | { "DumpFilterGraph", BUILTIN_FUNC_PREFIX, "c[outfile]s[mode]i[nframes]i[repeat]b[utf8]b", DumpFilterGraph, nullptr }, | ||
| 574 | { 0 } | ||
| 575 | }; | ||
| 576 |