ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
options.cpp
Go to the documentation of this file.
1#include "options.hpp"
2
4#include "../version_info.hpp"
5#include "resource_paths.hpp"
6#include <mxvk/mxvk.hpp>
7#include <mxwrite.hpp>
8
9#include <algorithm>
10#include <cctype>
11#include <cmath>
12#include <cstdlib>
13#include <iostream>
14#include <stdexcept>
15#include <utility>
16
17namespace acmxvk {
18 namespace {
19 constexpr int MAX_FRAME_DIMENSION = 16384;
20 constexpr std::int64_t MAX_FRAME_PIXELS = 67108864;
21
22 [[nodiscard]] std::int64_t parse_video_bitrate(std::string value, std::string_view option) {
23 if (value.empty()) {
24 throw std::runtime_error(std::string(option) + " requires a bitrate");
25 }
26 std::int64_t multiplier = 1;
27 const char suffix = static_cast<char>(std::toupper(static_cast<unsigned char>(value.back())));
28 if (suffix == 'K' || suffix == 'M' || suffix == 'G') {
29 value.pop_back();
30 multiplier = suffix == 'K' ? 1000LL : suffix == 'M' ? 1000000LL : 1000000000LL;
31 }
32 if (value.empty() || !std::ranges::all_of(value, [](unsigned char character) { return std::isdigit(character) != 0; })) {
33 throw std::runtime_error("video bitrate must be an integer with an optional K, M, "
34 "or G suffix (for example 10M)");
35 }
36 std::uint64_t amount = 0;
37 try {
38 amount = std::stoull(value);
39 } catch (const std::exception &) {
40 throw std::runtime_error("video bitrate is out of range");
41 }
42 constexpr std::uint64_t MAX_VIDEO_BITRATE = 100000000000ULL;
43 if (amount == 0U || amount > MAX_VIDEO_BITRATE / static_cast<std::uint64_t>(multiplier)) {
44 throw std::runtime_error("video bitrate must be between 1 bit/s and 100G");
45 }
46 return static_cast<std::int64_t>(amount * static_cast<std::uint64_t>(multiplier));
47 }
48 } // namespace
49
50 [[nodiscard]] bool dimensions_supported(int width, int height) { return width > 0 && height > 0 && width <= MAX_FRAME_DIMENSION && height <= MAX_FRAME_DIMENSION && static_cast<std::int64_t>(width) * height <= MAX_FRAME_PIXELS; }
51
52 [[nodiscard]] bool hasShaderManifest(const fs::path &directory) { return fs::is_regular_file(directory / "library.json") || fs::is_regular_file(directory / "index.txt"); }
53
54 [[nodiscard]] std::string optionValue(int &index, int argc, char **argv, std::string_view option) {
55 if (++index >= argc) {
56 throw std::runtime_error("missing value for " + std::string(option));
57 }
58 const std::string value(argv[index]);
59 input::validate_string(value, input::StringKind::Argument, std::string(option) + " value");
60 return value;
61 }
62
63 void validateLocator(std::string_view value, std::string_view context, bool allow_empty = false) {
64 if (value.empty() && allow_empty) {
65 return;
66 }
67 const input::StringKind kind = value.find("://") == std::string_view::npos ? input::StringKind::Path : input::StringKind::Url;
68 input::validate_string(value, kind, context, allow_empty);
69 }
70
71 void validateOptionStrings(const Options &options) {
72 validateLocator(options.input_file, "--input", true);
74 input::validate_string(options.audio_file, input::StringKind::Path, "--audio-file", true);
82 input::validate_string(options.shader_file, input::StringKind::Path, "--shader-file", true);
86 for (const StableDiffusionLora &lora : options.stable_diffusion_loras) {
88 }
89 for (const std::string &argument : options.stable_diffusion_server_arguments) {
90 input::validate_string(argument, input::StringKind::Argument, "--sd-server-arg");
91 }
102 input::validate_string(options.dream_model, input::StringKind::Path, "--dream-model", true);
103 input::validate_string(options.dream_layer, input::StringKind::Token, "--dream-layer", true);
104
105 for (const std::string &path : options.shader_pass_files) {
106 input::validate_string(path, input::StringKind::Path, "--shader-pass-files entry");
107 }
108 for (const std::string &value : options.custom_uniform_overrides) {
110 }
111 for (const std::string &value : options.midi_cc_mappings) {
113 }
114
116 input::validate_string(options.encode_tune, input::StringKind::Token, "--encode-tune", true);
119 input::validate_string(options.list_encoder_options, input::StringKind::Token, "--list-encoder-options", true);
122 }
123
124 [[nodiscard]] int parseInteger(std::string_view text, std::string_view option) {
126 std::size_t parsed = 0;
127 int value = 0;
128 try {
129 value = std::stoi(std::string(text), &parsed);
130 } catch (const std::exception &) {
131 throw std::runtime_error("invalid integer for " + std::string(option) + ": " + std::string(text));
132 }
133 if (parsed != text.size()) {
134 throw std::runtime_error("invalid integer for " + std::string(option) + ": " + std::string(text));
135 }
136 return value;
137 }
138
139 [[nodiscard]] double parseNumber(std::string_view text, std::string_view option) {
141 std::size_t parsed = 0;
142 double value = 0.0;
143 try {
144 value = std::stod(std::string(text), &parsed);
145 } catch (const std::exception &) {
146 throw std::runtime_error("invalid number for " + std::string(option) + ": " + std::string(text));
147 }
148 if (parsed != text.size() || !std::isfinite(value)) {
149 throw std::runtime_error("invalid number for " + std::string(option) + ": " + std::string(text));
150 }
151 return value;
152 }
153
154 [[nodiscard]] std::vector<int> parseIntegerList(std::string_view text, std::string_view option) {
156 if (text.empty()) {
157 throw std::runtime_error("empty integer list for " + std::string(option));
158 }
159 std::vector<int> values;
160 std::size_t start = 0;
161 while (start <= text.size()) {
162 const std::size_t separator = text.find(',', start);
163 const std::size_t end = separator == std::string_view::npos ? text.size() : separator;
164 if (end == start) {
165 throw std::runtime_error("invalid integer list for " + std::string(option) + ": " + std::string(text));
166 }
167 values.push_back(parseInteger(text.substr(start, end - start), option));
168 if (separator == std::string_view::npos) {
169 break;
170 }
171 start = separator + 1;
172 }
173 return values;
174 }
175
176 [[nodiscard]] std::array<std::uint8_t, 3> parseColor(std::string_view text, std::string_view option) {
177 const std::vector<int> components = parseIntegerList(text, option);
178 if (components.size() != 3U) {
179 throw std::runtime_error(std::string(option) + " requires r,g,b");
180 }
181
182 std::array<std::uint8_t, 3> color{};
183 for (std::size_t index = 0; index < color.size(); ++index) {
184 if (components[index] < 0 || components[index] > 255) {
185 throw std::runtime_error(std::string(option) + " components must be between 0 and 255");
186 }
187 color[index] = static_cast<std::uint8_t>(components[index]);
188 }
189 return color;
190 }
191
192 void parseDimensions(std::string_view text, int &width, int &height, std::string_view option) {
193 const std::size_t separator = text.find_first_of("xX");
194 if (separator == std::string_view::npos) {
195 throw std::runtime_error("invalid dimensions for " + std::string(option) + "; expected WidthxHeight");
196 }
197
198 width = parseInteger(text.substr(0, separator), option);
199 height = parseInteger(text.substr(separator + 1), option);
200 if (!dimensions_supported(width, height)) {
201 throw std::runtime_error("dimensions are outside the supported range for " + std::string(option));
202 }
203 }
204
205 [[nodiscard]] FrameRotation parseFrameRotation(std::string value) {
207 std::transform(value.begin(), value.end(), value.begin(), [](unsigned char character) { return static_cast<char>(std::tolower(character)); });
208 if (value == "clockwise" || value == "cw" || value == "90" || value == "90cw") {
210 }
211 if (value == "180") {
213 }
214 if (value == "counterclockwise" || value == "ccw" || value == "90ccw" || value == "270") {
216 }
217 throw std::runtime_error("--rotate requires clockwise, 180, or counterclockwise");
218 }
219
220 [[nodiscard]] bool isUtilityRequest(const Options &options) { return options.show_help || options.list_audio_devices || options.list_camera_devices || options.check_audio || options.list_midi_devices || options.check_midi || options.list_gpu_filters || options.list_cuda_devices || options.check_cuda || options.check_dnn || options.check_deep_dream || options.check_stable_diffusion || !options.probe_hdr_file.empty() || options.enumerate_camera_device >= 0 || options.list_encoders || !options.list_encoder_options.empty(); }
221
223 if (isUtilityRequest(options)) {
224 return;
225 }
226
227 std::string resource_source;
228 if (!options.resource_directory.empty()) {
229 resource_source = "--path";
230 } else if (const char *environment = std::getenv("ACMXVK_PATH"); environment != nullptr && environment[0] != '\0') {
231 input::validate_string(environment, input::StringKind::Path, "ACMXVK_PATH");
232 options.resource_directory = environment;
233 resource_source = "ACMXVK_PATH";
234 } else if (const char *environment = std::getenv("ACMX2_PATH"); environment != nullptr && environment[0] != '\0') {
235 input::validate_string(environment, input::StringKind::Path, "ACMX2_PATH");
236 const fs::path compatibility_directory = fs::absolute(environment).lexically_normal();
237 if (fs::is_directory(compatibility_directory)) {
238 options.resource_directory = compatibility_directory.string();
239 resource_source = "ACMX2_PATH compatibility fallback";
240 } else {
241 std::cerr << "acmxvk: ignoring unavailable ACMX2_PATH: " << compatibility_directory.string() << '\n';
242 }
243 }
244
245 if (!options.resource_directory.empty()) {
246 const fs::path directory = fs::absolute(options.resource_directory).lexically_normal();
247 if (!fs::is_directory(directory)) {
248 throw std::runtime_error(resource_source + " is not a readable resource directory: " + directory.string());
249 }
250 options.resource_directory = directory.string();
251 std::cout << "acmxvk: resource path (" << resource_source << "): " << options.resource_directory << '\n';
252 }
253
254 if (!options.shader_directory.empty() || !options.fragment_shader.empty() || !options.compute_shader.empty()) {
255 return;
256 }
257
258 if (const char *environment = std::getenv("ACMXVK_SHADER_PATH"); environment != nullptr && environment[0] != '\0') {
259 input::validate_string(environment, input::StringKind::Path, "ACMXVK_SHADER_PATH");
260 fs::path directory = fs::absolute(environment).lexically_normal();
261 if (fs::is_regular_file(directory) && (directory.filename() == "library.json" || directory.filename() == "index.txt")) {
262 directory = directory.parent_path();
263 }
264 if (!fs::is_directory(directory) || !hasShaderManifest(directory)) {
265 throw std::runtime_error("ACMXVK_SHADER_PATH does not contain library.json or index.txt: " + directory.string());
266 }
267 options.shader_directory = directory.string();
268 std::cout << "acmxvk: shader library (ACMXVK_SHADER_PATH): " << options.shader_directory << '\n';
269 return;
270 }
271
272 for (const fs::path &resource_directory : resource_directories(options)) {
273 const fs::path shader_directory = resource_directory / "shaders";
274 if (hasShaderManifest(shader_directory)) {
275 options.shader_directory = shader_directory.string();
276 std::cout << "acmxvk: shader library (resource path): " << options.shader_directory << '\n';
277 return;
278 }
279 }
280 }
281
282 [[nodiscard]] Options parseOptions(int argc, char **argv) {
283 Options options;
284 if (argc < 0 || argv == nullptr || argc > 4096) {
285 throw std::runtime_error("command line contains an invalid number of arguments");
286 }
287 if (argc == 1) {
288 options.show_help = true;
289 return options;
290 }
291
292 const bool library_build_requested = [&] {
293 for (int index = 1; index < argc; ++index) {
294 const std::string_view argument(argv[index]);
295 if (argument == "--build" || argument == "--builddir" || argument == "--fix" || argument == "--prune" || argument == "--force" || argument == "--glslc" || argument == "--parallel") {
296 return true;
297 }
298 }
299 return false;
300 }();
301 if (library_build_requested) {
302 for (int index = 1; index < argc; ++index) {
303 const std::string_view option(argv[index]);
304 input::validate_string(option, input::StringKind::Token, "command-line option");
305 if (option == "-h" || option == "-v" || option == "--help" || option == "--version") {
306 options.show_help = true;
307 } else if (option == "--unbuffered") {
308 options.unbuffered_output = true;
309 } else if (option == "--build") {
310 if (!options.build_manifest.empty()) {
311 throw std::runtime_error("--build may only be supplied once");
312 }
313 options.build_manifest = optionValue(index, argc, argv, option);
314 } else if (option == "--builddir") {
315 if (!options.build_directory.empty()) {
316 throw std::runtime_error("--builddir and --fix are mutually exclusive");
317 }
318 options.build_directory = optionValue(index, argc, argv, option);
319 } else if (option == "--fix") {
320 if (!options.build_directory.empty()) {
321 throw std::runtime_error("--builddir and --fix are mutually exclusive");
322 }
323 options.build_directory = optionValue(index, argc, argv, option);
324 options.build_fix = true;
325 } else if (option == "--prune") {
326 if (options.build_prune) {
327 throw std::runtime_error("--prune may only be supplied once");
328 }
329 options.build_prune = true;
330 } else if (option == "--force") {
331 if (options.build_force) {
332 throw std::runtime_error("--force may only be supplied once");
333 }
334 options.build_force = true;
335 } else if (option == "--glslc") {
336 options.glslc_executable = optionValue(index, argc, argv, option);
337 } else if (option == "--parallel") {
338 if (options.build_parallel_specified) {
339 throw std::runtime_error("--parallel may only be supplied once");
340 }
341 options.build_parallel = parseInteger(optionValue(index, argc, argv, option), option);
342 options.build_parallel_specified = true;
343 } else {
344 throw std::runtime_error("library build mode cannot be combined with option: " + std::string(option));
345 }
346 }
348 input::validate_string(options.build_directory, input::StringKind::Path, options.build_fix ? "--fix" : "--builddir", true);
350 if (options.build_parallel < 1 || options.build_parallel > 256) {
351 throw std::runtime_error("--parallel job count must be between 1 and 256");
352 }
353 if (!options.show_help && options.build_manifest.empty()) {
354 throw std::runtime_error("--builddir/--fix/--prune/--force/--glslc/--parallel requires "
355 "--build <library.json>");
356 }
357 if (!options.show_help && options.build_directory.empty()) {
358 throw std::runtime_error("--build requires --builddir or --fix <output-directory>");
359 }
360 if (!options.show_help && options.build_prune && !options.build_fix) {
361 throw std::runtime_error("--prune requires --fix <output-directory>");
362 }
363 if (!options.show_help && options.build_force && !options.build_prune) {
364 throw std::runtime_error("--force is only valid with --prune");
365 }
366 if (!options.show_help && options.build_prune && !options.build_force) {
367 throw std::runtime_error("WARNING: --prune permanently deletes .frag and .comp "
368 "source files that fail compilation; use --force to "
369 "confirm");
370 }
371 return options;
372 }
373
374 for (int index = 1; index < argc; ++index) {
375 const std::string_view option(argv[index]);
376 input::validate_string(option, input::StringKind::Token, "command-line option");
377 if (option == "-h" || option == "-v" || option == "--help" || option == "--version") {
378 options.show_help = true;
379 } else if (option == "--unbuffered") {
380 options.unbuffered_output = true;
381 } else if (option == "--silent" || option == "--headless") {
382 options.headless = true;
383 } else if (option == "--interface-shm") {
384 options.interface_shm = true;
385 } else if (option == "-p" || option == "--path") {
386 options.resource_directory = optionValue(index, argc, argv, option);
387 if (options.resource_directory.empty()) {
388 throw std::runtime_error("resource path must not be empty");
389 }
390 } else if (option == "-i" || option == "--input") {
391 options.input_file = optionValue(index, argc, argv, option);
392 } else if (option == "-g" || option == "--graphic") {
393 options.graphic_file = optionValue(index, argc, argv, option);
394 } else if (option == "-o" || option == "--output") {
395 options.output_file = optionValue(index, argc, argv, option);
396 } else if (option == "-e" || option == "--prefix") {
397 options.snapshot_directory = optionValue(index, argc, argv, option);
398 if (options.snapshot_directory.empty()) {
399 throw std::runtime_error("snapshot directory must not be empty");
400 }
401 } else if (option == "-d" || option == "--device") {
402 options.camera_device = parseInteger(optionValue(index, argc, argv, option), option);
403 if (options.camera_device < 0 || options.camera_device > 65535) {
404 throw std::runtime_error("camera device index must be between 0 and 65535");
405 }
406 } else if (option == "-c" || option == "--camera-res") {
407 parseDimensions(optionValue(index, argc, argv, option), options.camera_width, options.camera_height, option);
408 } else if (option == "--enumerate-device" || option == "--probe-camera") {
409 options.enumerate_camera_device = parseInteger(optionValue(index, argc, argv, option), option);
410 if (options.enumerate_camera_device < 0 || options.enumerate_camera_device > 65535) {
411 throw std::runtime_error("camera probe device index must be between 0 and 65535");
412 }
413 } else if (option == "--list-camera-devices") {
414 options.list_camera_devices = true;
415 } else if (option == "--use-yuv") {
416 options.use_yuv = true;
417 } else if (option == "--maximize-fps") {
418 options.maximize_fps = true;
419 } else if (option == "--use-source-fps") {
420 options.use_source_fps = true;
421 } else if (option == "--use-source-audio") {
422 options.use_source_audio = true;
423 options.enable_audio = true;
424 } else if (option == "--edge") {
425 options.edge_model = optionValue(index, argc, argv, option);
426 } else if (option == "--human") {
427 options.human_model = optionValue(index, argc, argv, option);
428 } else if (option == "--onnx") {
429 options.onnx_configuration = optionValue(index, argc, argv, option);
430 } else if (option == "--background") {
431 options.human_background = true;
432 } else if (option == "--black") {
433 options.human_black_point = parseNumber(optionValue(index, argc, argv, option), option);
434 options.human_black_specified = true;
435 if (options.human_black_point < 0.0 || options.human_black_point > 1.0) {
436 throw std::runtime_error("--black must be between 0.0 and 1.0");
437 }
438 } else if (option == "--white") {
439 options.human_white_point = parseNumber(optionValue(index, argc, argv, option), option);
440 options.human_white_specified = true;
441 if (options.human_white_point < 0.0 || options.human_white_point > 1.0) {
442 throw std::runtime_error("--white must be between 0.0 and 1.0");
443 }
444 } else if (option == "--check-dnn") {
445 options.check_dnn = true;
446 } else if (option == "--check-deep-dream") {
447 options.check_deep_dream = true;
448 } else if (option == "--check-stable-diffusion") {
449 options.check_stable_diffusion = true;
450 } else if (option == "--dream-model") {
451 options.dream_model = optionValue(index, argc, argv, option);
452 } else if (option == "--dream-layer") {
453 options.dream_layer = optionValue(index, argc, argv, option);
454 } else if (option == "--dream-iterations") {
455 options.dream_iterations = parseInteger(optionValue(index, argc, argv, option), option);
456 options.dream_iterations_specified = true;
457 if (options.dream_iterations < 1 || options.dream_iterations > 100) {
458 throw std::runtime_error("--dream-iterations must be between 1 and 100");
459 }
460 } else if (option == "--dream-strength") {
461 options.dream_strength = parseNumber(optionValue(index, argc, argv, option), option);
462 options.dream_strength_specified = true;
463 if (options.dream_strength <= 0.0 || options.dream_strength > 10.0) {
464 throw std::runtime_error("--dream-strength must be greater than 0 and no more than 10");
465 }
466 } else if (option == "--dream-feedback") {
467 options.dream_feedback = parseNumber(optionValue(index, argc, argv, option), option);
468 options.dream_feedback_specified = true;
469 if (options.dream_feedback < 0.0 || options.dream_feedback > 0.99) {
470 throw std::runtime_error("--dream-feedback must be between 0 and 0.99");
471 }
472 } else if (option == "--dream-zoom") {
473 options.dream_zoom = parseNumber(optionValue(index, argc, argv, option), option);
474 options.dream_zoom_specified = true;
475 if (options.dream_zoom < 0.9 || options.dream_zoom > 1.1) {
476 throw std::runtime_error("--dream-zoom must be between 0.9 and 1.1");
477 }
478 } else if (option == "--dream-rotation") {
479 options.dream_rotation = parseNumber(optionValue(index, argc, argv, option), option);
480 options.dream_rotation_specified = true;
481 if (options.dream_rotation < -5.0 || options.dream_rotation > 5.0) {
482 throw std::runtime_error("--dream-rotation must be between -5 and 5 degrees");
483 }
484 } else if (option == "--dream-size") {
485 options.dream_size = parseInteger(optionValue(index, argc, argv, option), option);
486 options.dream_size_specified = true;
487 if (options.dream_size != 0 && (options.dream_size < 64 || options.dream_size > 4096)) {
488 throw std::runtime_error("--dream-size must be 0 or between 64 and 4096");
489 }
490 } else if (option == "--dream-fp16") {
491 options.dream_fp16 = true;
492 } else if (option == "--dream-channel") {
493 const std::string value = optionValue(index, argc, argv, option);
494 options.dream_channel_specified = true;
495 if (value == "all") {
496 options.dream_channel = -1;
497 } else {
498 options.dream_channel = parseInteger(value, option);
499 if (options.dream_channel < 0 || options.dream_channel > 65535) {
500 throw std::runtime_error("--dream-channel must be 'all' or between 0 and 65535");
501 }
502 }
503 } else if (option == "--dream-octaves") {
504 options.dream_octaves = parseInteger(optionValue(index, argc, argv, option), option);
505 options.dream_octaves_specified = true;
506 if (options.dream_octaves < 1 || options.dream_octaves > 8) {
507 throw std::runtime_error("--dream-octaves must be between 1 and 8");
508 }
509 } else if (option == "--dream-octave-scale") {
510 options.dream_octave_scale = parseNumber(optionValue(index, argc, argv, option), option);
511 options.dream_octave_scale_specified = true;
512 if (options.dream_octave_scale < 1.1 || options.dream_octave_scale > 3.0) {
513 throw std::runtime_error("--dream-octave-scale must be between 1.1 and 3.0");
514 }
515 } else if (option == "--dream-jitter") {
516 options.dream_jitter = parseInteger(optionValue(index, argc, argv, option), option);
517 options.dream_jitter_specified = true;
518 if (options.dream_jitter < 0 || options.dream_jitter > 64) {
519 throw std::runtime_error("--dream-jitter must be between 0 and 64");
520 }
521 } else if (option == "--dream-smoothing") {
522 options.dream_smoothing = parseInteger(optionValue(index, argc, argv, option), option);
523 options.dream_smoothing_specified = true;
524 if (options.dream_smoothing < 0 || options.dream_smoothing > 16) {
525 throw std::runtime_error("--dream-smoothing must be between 0 and 16");
526 }
527 } else if (option == "--random-dream" || option == "--random_dream") {
528 options.random_dream_interval = parseNumber(optionValue(index, argc, argv, option), option);
529 options.random_dream_specified = true;
530 if (options.random_dream_interval <= 0.0) {
531 throw std::runtime_error(std::string(option) + " must be greater than 0");
532 }
533 } else if (option == "--dream-headless") {
534 options.dream_headless = true;
535 } else if (option == "--deep-orig") {
536 options.deep_original = true;
537 } else if (option == "--gpu-filter-before-dream") {
538 options.gpu_filter_before_dream = true;
539 } else if (option == "--sd-model") {
541 options.stable_diffusion_model = optionValue(index, argc, argv, option);
542 } else if (option == "--sd-prompt") {
544 options.stable_diffusion_prompt = optionValue(index, argc, argv, option);
545 } else if (option == "--sd-negative-prompt") {
547 options.stable_diffusion_negative_prompt = optionValue(index, argc, argv, option);
548 } else if (option == "--sd-lora") {
550 options.stable_diffusion_loras.push_back({optionValue(index, argc, argv, option), 1.0});
551 } else if (option == "--sd-lora-strength") {
553 if (options.stable_diffusion_loras.empty()) {
554 throw std::runtime_error("--sd-lora-strength must follow --sd-lora");
555 }
556 const double multiplier = parseNumber(optionValue(index, argc, argv, option), option);
557 if (multiplier < -10.0 || multiplier > 10.0) {
558 throw std::runtime_error("--sd-lora-strength must be between -10 and 10");
559 }
560 options.stable_diffusion_loras.back().multiplier = multiplier;
561 } else if (option == "--sd-server") {
563 options.stable_diffusion_server = optionValue(index, argc, argv, option);
564 } else if (option == "--sd-server-arg") {
566 constexpr std::size_t MAX_SERVER_ARGUMENTS = 256U;
567 if (options.stable_diffusion_server_arguments.size() >= MAX_SERVER_ARGUMENTS) {
568 throw std::runtime_error("--sd-server-arg may be repeated at most 256 times");
569 }
570 options.stable_diffusion_server_arguments.push_back(optionValue(index, argc, argv, option));
571 } else if (option == "--sd-server-port") {
573 options.stable_diffusion_server_port = parseInteger(optionValue(index, argc, argv, option), option);
574 if (options.stable_diffusion_server_port < 1024 || options.stable_diffusion_server_port > 65535) {
575 throw std::runtime_error("--sd-server-port must be between 1024 and 65535");
576 }
577 } else if (option == "--sd-size") {
579 parseDimensions(optionValue(index, argc, argv, option), options.stable_diffusion_width, options.stable_diffusion_height, option);
580 if ((options.stable_diffusion_width % 64) != 0 || (options.stable_diffusion_height % 64) != 0 || options.stable_diffusion_width > 2048 || options.stable_diffusion_height > 2048) {
581 throw std::runtime_error("--sd-size dimensions must be multiples of 64 and no "
582 "larger than 2048");
583 }
584 } else if (option == "--sd-upscale-size") {
588 throw std::runtime_error("--sd-upscale-size dimensions must be between 64 and 4096");
589 }
590 } else if (option == "--sd-steps") {
592 options.stable_diffusion_steps = parseInteger(optionValue(index, argc, argv, option), option);
593 if (options.stable_diffusion_steps < 1 || options.stable_diffusion_steps > 150) {
594 throw std::runtime_error("--sd-steps must be between 1 and 150");
595 }
596 } else if (option == "--sd-strength") {
598 options.stable_diffusion_strength = parseNumber(optionValue(index, argc, argv, option), option);
599 if (options.stable_diffusion_strength <= 0.0 || options.stable_diffusion_strength > 1.0) {
600 throw std::runtime_error("--sd-strength must be greater than 0 and no more than 1");
601 }
602 } else if (option == "--sd-cfg-scale") {
604 options.stable_diffusion_cfg_scale = parseNumber(optionValue(index, argc, argv, option), option);
605 if (options.stable_diffusion_cfg_scale < 0.0 || options.stable_diffusion_cfg_scale > 50.0) {
606 throw std::runtime_error("--sd-cfg-scale must be between 0 and 50");
607 }
608 } else if (option == "--sd-seed") {
610 options.stable_diffusion_seed = parseInteger(optionValue(index, argc, argv, option), option);
611 } else if (option == "--sd-sampler") {
613 options.stable_diffusion_sampler = optionValue(index, argc, argv, option);
614 } else if (option == "--sd-scheduler") {
616 options.stable_diffusion_scheduler = optionValue(index, argc, argv, option);
617 } else if (option == "--sd-after-shaders") {
619 options.stable_diffusion_after_shaders = true;
620 } else if (option == "--sd-upscale") {
622 options.stable_diffusion_upscale = true;
623 } else if (option == "--sd-upscale-only") {
625 options.stable_diffusion_upscale_only = true;
626 } else if (option == "--upscale-model") {
628 options.stable_diffusion_upscale_model = optionValue(index, argc, argv, option);
629 } else if (option == "--sd-quiet") {
631 options.stable_diffusion_quiet = true;
632 } else if (option == "--probe-hdr") {
633 options.probe_hdr_file = optionValue(index, argc, argv, option);
634 } else if (option == "-r" || option == "--resolution") {
635 parseDimensions(optionValue(index, argc, argv, option), options.width, options.height, option);
636 options.resolution_specified = true;
637 } else if (option == "-s" || option == "--shaders") {
638 options.shader_directory = optionValue(index, argc, argv, option);
639 } else if (option == "-f" || option == "--fragment") {
640 options.fragment_shader = optionValue(index, argc, argv, option);
641 } else if (option == "--compute") {
642 options.compute_shader = optionValue(index, argc, argv, option);
643 } else if (option == "--enable-3d") {
644 options.enable_3d = true;
645 } else if (option == "--model") {
646 options.model_file = optionValue(index, argc, argv, option);
647 options.enable_3d = true;
648 } else if (option == "-H" || option == "--shader-index") {
649 options.shader_index = parseInteger(optionValue(index, argc, argv, option), option);
650 if (options.shader_index < 0 || options.shader_index >= static_cast<int>(input::MAX_SHADER_ENTRIES)) {
651 throw std::runtime_error("shader index is outside the supported range");
652 }
653 } else if (option == "--shader-file") {
654 options.shader_file = optionValue(index, argc, argv, option);
655 } else if (option == "--uniform") {
656 options.custom_uniform_overrides.push_back(optionValue(index, argc, argv, option));
657 if (options.custom_uniform_overrides.size() > mxvk::VK_Sprite::MAX_CUSTOM_UNIFORMS) {
658 throw std::runtime_error("too many --uniform overrides were supplied");
659 }
660 } else if (option == "--shader-pass") {
661 const std::string values = optionValue(index, argc, argv, option);
662 std::size_t start = 0;
663 while (start <= values.size()) {
664 const std::size_t separator = values.find(',', start);
665 const std::string_view value(values.data() + start, (separator == std::string::npos ? values.size() : separator) - start);
666 if (value.empty()) {
667 throw std::runtime_error("shader pass list contains an empty entry");
668 }
669 options.shader_pass_indices.push_back(parseInteger(value, option));
670 if (options.shader_pass_indices.back() < 0 || options.shader_pass_indices.size() > input::MAX_SHADER_ENTRIES) {
671 throw std::runtime_error("shader pass list is outside the supported range");
672 }
673 if (separator == std::string::npos) {
674 break;
675 }
676 start = separator + 1;
677 }
678 } else if (option == "--shader-pass-files") {
679 const std::string payload = optionValue(index, argc, argv, option);
680 std::size_t start = 0;
681 while (start < payload.size()) {
682 const std::size_t separator = payload.find(':', start);
683 if (separator == std::string::npos) {
684 throw std::runtime_error("invalid --shader-pass-files payload");
685 }
686 const int length = parseInteger(std::string_view(payload).substr(start, separator - start), option);
687 const std::size_t name_start = separator + 1;
688 if (length < 0 || static_cast<std::size_t>(length) > payload.size() - name_start) {
689 throw std::runtime_error("invalid --shader-pass-files payload");
690 }
691 options.shader_pass_files.push_back(payload.substr(name_start, static_cast<std::size_t>(length)));
692 if (options.shader_pass_files.size() > input::MAX_SHADER_ENTRIES) {
693 throw std::runtime_error("shader pass file list contains too many entries");
694 }
695 start = name_start + static_cast<std::size_t>(length);
696 }
697 } else if (option == "--playlist") {
698 options.playlist_file = optionValue(index, argc, argv, option);
699 } else if (option == "--enable-playlist") {
700 options.enable_playlist = true;
701 } else if (option == "--cross-fade") {
702 options.cross_fade_duration = parseNumber(optionValue(index, argc, argv, option), option);
703 if (options.cross_fade_duration < 0.0 || options.cross_fade_duration > 60.0) {
704 throw std::runtime_error("crossfade duration must be between 0 and 60 seconds");
705 }
706 } else if (option == "--time-speed") {
707 options.time_speed = parseNumber(optionValue(index, argc, argv, option), option);
708 if (options.time_speed < -1000.0 || options.time_speed > 1000.0) {
709 throw std::runtime_error("time speed must be between -1000 and 1000");
710 }
711 } else if (option == "--normalized") {
712 options.normalized_time = true;
713 } else if (option == "--enable-random-autopilot") {
714 options.enable_random_autopilot = true;
715 } else if (option == "--autopilot-frames" || option == "--autopilot-timeout") {
716 options.autopilot_frames = parseInteger(optionValue(index, argc, argv, option), option);
717 if (options.autopilot_frames < 4 || options.autopilot_frames > 1000000000) {
718 throw std::runtime_error("autopilot frame interval must be between 4 and 1000000000");
719 }
720 } else if (option == "--autopilot-random" || option == "--autiopilot-random") {
721 options.autopilot_random_timeout = parseInteger(optionValue(index, argc, argv, option), option);
722 if (options.autopilot_random_timeout < 4 || options.autopilot_random_timeout > 1000000000) {
723 throw std::runtime_error("autopilot random interval must be between 4 and 1000000000");
724 }
725 } else if (option == "-u" || option == "--fps") {
726 options.requested_fps = parseNumber(optionValue(index, argc, argv, option), option);
727 if (options.requested_fps <= 0.0 || options.requested_fps > 1000.0) {
728 throw std::runtime_error("FPS must be between 0 and 1000");
729 }
730 } else if (option == "-w" || option == "--enable-audio") {
731 options.enable_audio = true;
732 } else if (option == "--audio-time" || option == "--enable-audio-time") {
733 options.enable_audio_time = true;
734 } else if (option == "-l" || option == "--channels") {
735 options.audio_channels = parseInteger(optionValue(index, argc, argv, option), option);
736 if (options.audio_channels < 1 || options.audio_channels > 32) {
737 throw std::runtime_error("audio channels must be between 1 and 32");
738 }
739 } else if (option == "-q" || option == "--sense") {
740 options.audio_sensitivity = parseNumber(optionValue(index, argc, argv, option), option);
741 if (options.audio_sensitivity < 0.1 || options.audio_sensitivity > 5.0) {
742 throw std::runtime_error("audio sensitivity must be between 0.1 and 5.0");
743 }
744 } else if (option == "--audio-warm-rate") {
745 options.audio_warm_rate = parseNumber(optionValue(index, argc, argv, option), option);
746 options.audio_warm_rate_specified = true;
747 if (options.audio_warm_rate < 0.0 || options.audio_warm_rate > 1000.0) {
748 throw std::runtime_error("audio warmup rate must be between 0 and 1000");
749 }
750 } else if (option == "--audio-input") {
751 const std::string value = optionValue(index, argc, argv, option);
752 options.audio_input_specified = true;
753 options.audio_input_device = value == "default" ? -1 : parseInteger(value, option);
754 if (options.audio_input_device < -1 || options.audio_input_device > 65535) {
755 throw std::runtime_error("audio input must be default or a non-negative device index");
756 }
757 } else if (option == "--audio-file") {
758 options.audio_file = optionValue(index, argc, argv, option);
759 options.enable_audio = true;
760 } else if (option == "-y" || option == "--pass-through") {
761 options.audio_pass_through = true;
762 options.enable_audio = true;
763 } else if (option == "--audio-output") {
764 const std::string value = optionValue(index, argc, argv, option);
765 options.audio_output_specified = true;
766 options.audio_output_device = value == "default" ? -1 : parseInteger(value, option);
767 if (options.audio_output_device < -1 || options.audio_output_device > 65535) {
768 throw std::runtime_error("audio output must be default or a non-negative device index");
769 }
770 } else if (option == "--pass-through-gain") {
771 options.audio_pass_through_gain = parseNumber(optionValue(index, argc, argv, option), option);
773 if (options.audio_pass_through_gain < 0.0 || options.audio_pass_through_gain > 4.0) {
774 throw std::runtime_error("pass-through gain must be between 0.0 and 4.0");
775 }
776 } else if (option == "--record-gain") {
777 options.audio_recording_gain = parseNumber(optionValue(index, argc, argv, option), option);
778 options.audio_recording_gain_specified = true;
779 if (options.audio_recording_gain < 0.0 || options.audio_recording_gain > 2.0) {
780 throw std::runtime_error("recording gain must be between 0.0 and 2.0");
781 }
782 } else if (option == "--record-audio") {
783 options.record_audio_file = optionValue(index, argc, argv, option);
784 options.enable_audio = true;
785 } else if (option == "--audio-repeat") {
786 options.audio_repeat = true;
787 } else if (option == "--audio-trunc") {
788 options.audio_trunc = true;
789 } else if (option == "--enable-audio-buffers" || option == "--audio-buffers") {
790 options.audio_buffers = parseInteger(optionValue(index, argc, argv, option), option);
791 if (options.audio_buffers < 0 || options.audio_buffers > 64) {
792 throw std::runtime_error("audio history buffers must be between 0 and 64");
793 }
794 } else if (option == "--list-devices") {
795 options.list_audio_devices = true;
796 } else if (option == "--check-audio") {
797 options.check_audio = true;
798 } else if (option == "--midi-device") {
799 options.midi_device = parseInteger(optionValue(index, argc, argv, option), option);
800 options.midi_device_specified = true;
801 if (options.midi_device < 0 || options.midi_device > 65535) {
802 throw std::runtime_error("MIDI device index must be between 0 and 65535");
803 }
804 } else if (option == "--midi-monitor") {
805 options.midi_monitor = true;
806 } else if (option == "--midi-map") {
807 options.midi_map_file = optionValue(index, argc, argv, option);
808 } else if (option == "--midi-cc") {
809 options.midi_cc_mappings.push_back(optionValue(index, argc, argv, option));
810 if (options.midi_cc_mappings.size() > mxvk::VK_Sprite::MAX_CUSTOM_UNIFORMS) {
811 throw std::runtime_error("too many --midi-cc mappings were supplied");
812 }
813 } else if (option == "--list-midi") {
814 options.list_midi_devices = true;
815 } else if (option == "--check-midi") {
816 options.check_midi = true;
817 } else if (option == "--gpu-filter") {
818 options.gpu_filter_indices = parseIntegerList(optionValue(index, argc, argv, option), option);
819 if (options.gpu_filter_indices.size() > 256U || std::any_of(options.gpu_filter_indices.begin(), options.gpu_filter_indices.end(), [](int value) { return value < 0 || value > 65535; })) {
820 throw std::runtime_error("GPU filter list is outside the supported range");
821 }
822 } else if (option == "--gpu-buffer") {
823 options.gpu_frame_buffer_size = parseInteger(optionValue(index, argc, argv, option), option);
824 options.gpu_buffer_specified = true;
825 if (options.gpu_frame_buffer_size < 4 || options.gpu_frame_buffer_size > 32) {
826 throw std::runtime_error("GPU frame buffer must be between 4 and 32");
827 }
828 } else if (option == "-m" || option == "--cuda-device") {
829 options.cuda_device = parseInteger(optionValue(index, argc, argv, option), option);
830 options.cuda_device_specified = true;
831 if (options.cuda_device < 0 || options.cuda_device > 1024) {
832 throw std::runtime_error("CUDA device index must be between 0 and 1024");
833 }
834 } else if (option == "--list-filters") {
835 options.list_gpu_filters = true;
836 } else if (option == "--list-cuda-devices") {
837 options.list_cuda_devices = true;
838 } else if (option == "--check-cuda") {
839 options.check_cuda = true;
840 } else if (option == "--duration") {
841 options.duration = parseNumber(optionValue(index, argc, argv, option), option);
842 if (options.duration <= 0.0 || options.duration > 604800.0) {
843 throw std::runtime_error("duration must be between 0 and 604800 seconds");
844 }
845 } else if (option == "--max-size") {
846 options.max_size_mb = parseNumber(optionValue(index, argc, argv, option), option);
847 if (options.max_size_mb <= 0.0 || options.max_size_mb > 1048576.0) {
848 throw std::runtime_error("maximum output size must be between 0 and 1048576 MB");
849 }
850 } else if (option == "--png") {
851 options.png_output = true;
852 options.png_output_directory = optionValue(index, argc, argv, option);
853 } else if (option == "--png-level") {
854 options.png_level = parseInteger(optionValue(index, argc, argv, option), option);
855 if (options.png_level < 1 || options.png_level > 9) {
856 throw std::runtime_error("--png-level must be between 1 and 9");
857 }
858 } else if (option == "--generate") {
859 options.generate_interval = parseInteger(optionValue(index, argc, argv, option), option);
860 if (options.generate_interval <= 0) {
861 throw std::runtime_error("--generate requires a positive frame interval");
862 }
863 } else if (option == "-b" || option == "--encode-crf") {
864 options.encode_crf = parseInteger(optionValue(index, argc, argv, option), option);
865 if (options.encode_crf < 0 || options.encode_crf > 51) {
866 throw std::runtime_error("encoder CRF must be between 0 and 51");
867 }
868 } else if (option == "--bitrate" || option == "--video-bitrate" || option == "--encode-bitrate") {
869 options.encode_bitrate = parse_video_bitrate(optionValue(index, argc, argv, option), option);
870 } else if (option == "--encode-preset") {
871 options.encode_preset = optionValue(index, argc, argv, option);
872 } else if (option == "--encode-tune") {
873 options.encode_tune = optionValue(index, argc, argv, option);
874 } else if (option == "--encode-codec") {
875 options.encode_codec = optionValue(index, argc, argv, option);
876 } else if (option == "--encode-params") {
877 options.encode_params = optionValue(index, argc, argv, option);
878 } else if (option == "--list-encoders") {
879 options.list_encoders = true;
880 } else if (option == "--list-encoder-options") {
881 options.list_encoder_options = optionValue(index, argc, argv, option);
882 } else if (option == "--encode-realtime") {
883 options.encode_realtime = true;
884 } else if (option == "--no-drop") {
885 options.no_drop = true;
886 } else if (option == "--constant-frame-rate" || option == "--sequential-encode") {
887 options.constant_frame_rate = true;
888 } else if (option == "--fill-pts-gaps") {
889 options.fill_pts_gaps = true;
890 } else if (option == "--display-filter") {
891 options.display_filter = true;
892 } else if (option == "--disable-counter") {
893 options.disable_counter = true;
894 } else if (option == "--use-watermark") {
895 options.watermark_text = optionValue(index, argc, argv, option);
896 if (options.watermark_text.empty()) {
897 throw std::runtime_error("--use-watermark requires non-empty text");
898 }
899 } else if (option == "--use-watermark-color") {
900 options.watermark_color = parseColor(optionValue(index, argc, argv, option), option);
901 } else if (option == "--copy-audio") {
902 options.copy_audio = true;
903 } else if (option == "--mute-output") {
904 options.mute_output = true;
905 } else if (option == "-n" || option == "--fullscreen") {
906 options.fullscreen = true;
907 } else if (option == "-a" || option == "--repeat") {
908 options.repeat = true;
909 } else if (option == "--enable-vsync") {
910 options.enable_vsync = true;
911 } else if (option == "--enable-screenshot") {
912 options.enable_screenshot = true;
913 } else if (option == "--history-test") {
914 options.history_test = true;
915 options.enable_texture_cache = true;
916 } else if (option == "--texture-cache" || option == "--texture-cache-array") {
917 options.enable_texture_cache = true;
918 } else if (option == "--cache-delay") {
919 options.cache_delay = parseInteger(optionValue(index, argc, argv, option), option);
920 if (options.cache_delay < 0 || options.cache_delay > 1000000) {
921 throw std::runtime_error("--cache-delay must be between 0 and 1000000");
922 }
923 } else if (option == "--texture-cache-size") {
924 options.texture_cache_size = parseInteger(optionValue(index, argc, argv, option), option);
925 if (options.texture_cache_size < 1 || options.texture_cache_size > 64) {
926 throw std::runtime_error("--texture-cache-size must be between 1 and 64");
927 }
928 } else if (option == "--flip") {
929 options.flip_output = true;
930 } else if (option == "--rotate") {
931 options.frame_rotation = parseFrameRotation(optionValue(index, argc, argv, option));
932 } else {
933 throw std::runtime_error("unknown option: " + std::string(option));
934 }
935 }
936
937 if ((!options.stable_diffusion_model.empty() || options.stable_diffusion_upscale_only) && !options.output_file.empty()) {
938 options.constant_frame_rate = true;
939 options.no_drop = true;
940 }
941
942 if (options.headless) {
943 options.interface_shm = false;
944 }
945
946 if (options.headless && options.audio_pass_through) {
947 throw std::runtime_error("--pass-through cannot be used with --headless or --silent");
948 }
949
950 if (options.use_source_audio) {
951 if (options.input_file.empty()) {
952 throw std::runtime_error("--use-source-audio requires --input <video>");
953 }
954 const bool offline_constant_rate = options.headless && options.constant_frame_rate;
955 if (!options.use_source_fps && !offline_constant_rate) {
956 throw std::runtime_error("--use-source-audio requires --use-source-fps or "
957 "--headless --constant-frame-rate");
958 }
959 if (!options.audio_file.empty()) {
960 throw std::runtime_error("--use-source-audio cannot be combined with --audio-file");
961 }
962 options.audio_file = options.input_file;
963 options.audio_repeat = options.audio_repeat || options.repeat;
964 }
965
966 validateOptionStrings(options);
967 applyResourceDefaults(options);
968
969 if (!options.input_file.empty() && !options.graphic_file.empty()) {
970 throw std::runtime_error("--input and --graphic cannot be used together");
971 }
972 if (!options.dream_layer.empty() && options.dream_model.empty()) {
973 throw std::runtime_error("--dream-layer requires --dream-model");
974 }
976 throw std::runtime_error("Deep Dream processing options require --dream-model");
977 }
978 if (options.stable_diffusion_upscale_only) {
979 if (options.input_file.empty() || !options.graphic_file.empty()) {
980 throw std::runtime_error("Stable Diffusion upscale-only processing requires --input <video>");
981 }
982 if (options.stable_diffusion_upscale_model.empty()) {
983 throw std::runtime_error("--sd-upscale-only requires --upscale-model <file>");
984 }
985 if (!options.stable_diffusion_model.empty() || !options.stable_diffusion_prompt.empty() || !options.stable_diffusion_negative_prompt.empty() || !options.stable_diffusion_loras.empty()) {
986 throw std::runtime_error("--sd-upscale-only cannot be combined with image-to-image model, prompt, or LoRA options");
987 }
989 throw std::runtime_error("--sd-upscale-only cannot be combined with --sd-after-shaders or --sd-upscale");
990 }
991 if (options.fill_pts_gaps) {
992 throw std::runtime_error("Stable Diffusion processing cannot be combined with --fill-pts-gaps");
993 }
994 } else if (!options.stable_diffusion_model.empty()) {
995 if (options.input_file.empty() || !options.graphic_file.empty()) {
996 throw std::runtime_error("Stable Diffusion processing requires --input <video>");
997 }
998 if (options.stable_diffusion_prompt.empty()) {
999 throw std::runtime_error("--sd-model requires --sd-prompt <text>");
1000 }
1001 if (options.fill_pts_gaps) {
1002 throw std::runtime_error("Stable Diffusion processing cannot be combined with --fill-pts-gaps");
1003 }
1004 if (options.stable_diffusion_after_shaders && (!options.headless || options.output_file.empty())) {
1005 throw std::runtime_error("--sd-after-shaders requires --headless and encoded --output <video>");
1006 }
1008 throw std::runtime_error("--sd-upscale cannot be combined with --sd-after-shaders");
1009 }
1010 if (options.stable_diffusion_upscale && !options.stable_diffusion_upscale_model.empty()) {
1011 throw std::runtime_error("--sd-upscale and --upscale-model are mutually exclusive");
1012 }
1013 } else if (options.stable_diffusion_option_specified) {
1014 throw std::runtime_error("Stable Diffusion options require --sd-model");
1015 }
1016 if (options.dream_headless || options.deep_original) {
1017 if (options.dream_headless && !options.headless) {
1018 throw std::runtime_error("--dream-headless requires --headless or --silent");
1019 }
1020 if (options.dream_headless && options.deep_original) {
1021 throw std::runtime_error("--dream-headless and --deep-orig are mutually exclusive");
1022 }
1023 if (options.input_file.empty()) {
1024 throw std::runtime_error("independent-frame Deep Dream mode requires --input <video>");
1025 }
1026 if (options.output_file.empty()) {
1027 throw std::runtime_error("independent-frame Deep Dream mode requires --output <file>");
1028 }
1029 if (options.random_dream_specified) {
1030 throw std::runtime_error("independent-frame Deep Dream mode cannot be combined with --random-dream");
1031 }
1032 options.dream_feedback = 0.0;
1033 options.dream_zoom = 1.0;
1034 options.dream_rotation = 0.0;
1035 options.no_drop = true;
1036 }
1037 const int shader_source_count = static_cast<int>(!options.shader_directory.empty()) + static_cast<int>(!options.fragment_shader.empty()) + static_cast<int>(!options.compute_shader.empty());
1038 if (shader_source_count > 1) {
1039 throw std::runtime_error("--shaders, --fragment, and --compute are mutually exclusive");
1040 }
1041 if (!options.custom_uniform_overrides.empty() && options.shader_directory.empty()) {
1042 throw std::runtime_error("--uniform requires --shaders <directory>");
1043 }
1044 if (!options.midi_cc_mappings.empty() && options.shader_directory.empty()) {
1045 throw std::runtime_error("--midi-cc requires --shaders <directory>");
1046 }
1047 if (options.gpu_buffer_specified && options.gpu_filter_indices.empty()) {
1048 throw std::runtime_error("--gpu-buffer requires --gpu-filter <list>");
1049 }
1050 if (options.gpu_filter_before_dream && options.gpu_filter_indices.empty()) {
1051 throw std::runtime_error("--gpu-filter-before-dream requires --gpu-filter <list>");
1052 }
1053 if (options.gpu_filter_before_dream && options.maximize_fps) {
1054 throw std::runtime_error("--gpu-filter-before-dream cannot be combined with --maximize-fps");
1055 }
1056 if (options.gpu_filter_before_dream && !options.graphic_file.empty()) {
1057 throw std::runtime_error("--gpu-filter-before-dream currently supports camera and video input");
1058 }
1059 if (options.gpu_filter_before_dream && (!options.edge_model.empty() || !options.human_model.empty() || !options.onnx_configuration.empty())) {
1060 throw std::runtime_error("--gpu-filter-before-dream cannot be combined with DNN input effects");
1061 }
1062 if ((!options.shader_pass_indices.empty() || !options.shader_pass_files.empty() || !options.playlist_file.empty() || options.enable_playlist) && options.shader_directory.empty()) {
1063 throw std::runtime_error("shader passes and playlists require --shaders <directory>");
1064 }
1065 if (options.enable_playlist && options.playlist_file.empty()) {
1066 throw std::runtime_error("--enable-playlist requires --playlist <file>");
1067 }
1068 if (options.enable_random_autopilot && !options.enable_playlist) {
1069 throw std::runtime_error("--enable-random-autopilot requires --enable-playlist");
1070 }
1071 if ((options.autopilot_frames > 0 || options.autopilot_random_timeout > 0) && options.playlist_file.empty()) {
1072 throw std::runtime_error("autopilot requires --playlist <file>");
1073 }
1074 if (!options.output_file.empty() && !options.input_file.empty() && fs::absolute(options.output_file).lexically_normal() == fs::absolute(options.input_file).lexically_normal()) {
1075 throw std::runtime_error("output file must differ from the input file");
1076 }
1077 if (options.duration > 0.0 && options.output_file.empty() && !options.png_output) {
1078 throw std::runtime_error("--duration requires --output <file> or --png <directory>");
1079 }
1080 if (options.png_output && options.input_file.empty()) {
1081 throw std::runtime_error("--png requires video --input");
1082 }
1083 if (options.png_output && !options.output_file.empty()) {
1084 throw std::runtime_error("--png <directory> cannot be combined with --output <file>");
1085 }
1086 if (options.max_size_mb > 0.0 && (options.output_file.empty() || options.png_output)) {
1087 throw std::runtime_error("--max-size requires encoded video output");
1088 }
1089 if (options.constant_frame_rate && (options.input_file.empty() || options.output_file.empty() || options.png_output)) {
1090 throw std::runtime_error("--constant-frame-rate requires video input and encoded output");
1091 }
1092 if (options.fill_pts_gaps && (options.output_file.empty() || options.png_output)) {
1093 throw std::runtime_error("--fill-pts-gaps requires encoded video output");
1094 }
1095 if (options.fill_pts_gaps && options.constant_frame_rate) {
1096 throw std::runtime_error("--fill-pts-gaps and --constant-frame-rate are mutually exclusive");
1097 }
1098 if (options.copy_audio && (options.input_file.empty() || options.output_file.empty() || options.png_output || options.repeat)) {
1099 throw std::runtime_error("--copy-audio requires non-repeating video input and encoded output");
1100 }
1101 if (options.copy_audio && !options.audio_file.empty()) {
1102 throw std::runtime_error("--copy-audio and --audio-file select different audio sources");
1103 }
1104 if (!options.record_audio_file.empty() && !options.audio_file.empty()) {
1105 throw std::runtime_error("--record-audio records live input and cannot be used with --audio-file");
1106 }
1107 if (!options.record_audio_file.empty()) {
1108 const fs::path recording_path = fs::absolute(options.record_audio_file).lexically_normal();
1109 const auto conflicts_with = [&](const std::string &filename) { return !filename.empty() && recording_path == fs::absolute(filename).lexically_normal(); };
1110 if (conflicts_with(options.input_file) || conflicts_with(options.graphic_file) || conflicts_with(options.output_file)) {
1111 throw std::runtime_error("--record-audio output must differ from media input and video output");
1112 }
1113 }
1114 if (!options.graphic_file.empty() && !options.output_file.empty() && options.duration <= 0.0 && !(options.audio_trunc && !options.audio_file.empty())) {
1115 throw std::runtime_error("graphic recording requires --duration <seconds> or "
1116 "--audio-file <media> --audio-trunc");
1117 }
1118 if (options.headless && !isUtilityRequest(options)) {
1119 if (options.input_file.empty() && options.graphic_file.empty()) {
1120 throw std::runtime_error("--headless/--silent requires --input <video> or "
1121 "--graphic <image>; camera input is not supported");
1122 }
1123 if (options.output_file.empty() && !options.png_output) {
1124 throw std::runtime_error("--headless/--silent requires --output <file> or --png <directory>");
1125 }
1126 if (!options.graphic_file.empty() && options.duration <= 0.0) {
1127 throw std::runtime_error("headless graphic processing requires --duration "
1128 "<seconds>");
1129 }
1130 if (options.repeat && options.duration <= 0.0) {
1131 throw std::runtime_error("--headless/--silent with --repeat requires --duration "
1132 "<seconds>");
1133 }
1134 if (options.fullscreen || options.enable_vsync || options.enable_screenshot) {
1135 throw std::runtime_error("--headless/--silent cannot be combined with "
1136 "--fullscreen, --enable-vsync, or --enable-screenshot");
1137 }
1138 }
1139 if (options.audio_buffers > 0 && !options.enable_audio) {
1140 throw std::runtime_error("--enable-audio-buffers requires --enable-audio");
1141 }
1142 if (options.enable_audio_time && !options.enable_audio) {
1143 throw std::runtime_error("--audio-time requires an enabled audio source");
1144 }
1145 if (options.audio_warm_rate_specified && !options.enable_audio) {
1146 throw std::runtime_error("--audio-warm-rate requires an enabled audio source");
1147 }
1148 if (!options.audio_file.empty() && options.audio_input_specified) {
1149 throw std::runtime_error("--audio-file and --audio-input cannot be used together");
1150 }
1151 if (options.audio_repeat && options.audio_file.empty()) {
1152 throw std::runtime_error("--audio-repeat requires --audio-file <media>");
1153 }
1154 if (options.audio_trunc && options.audio_file.empty()) {
1155 throw std::runtime_error("--audio-trunc requires --audio-file <media>");
1156 }
1157 if (options.audio_output_specified && !options.audio_pass_through) {
1158 throw std::runtime_error("--audio-output requires --pass-through");
1159 }
1160 if (options.audio_pass_through_gain_specified && !options.audio_pass_through) {
1161 throw std::runtime_error("--pass-through-gain requires --pass-through");
1162 }
1163 if (options.audio_recording_gain_specified && (!options.enable_audio || !options.audio_file.empty() || (options.record_audio_file.empty() && (options.output_file.empty() || options.png_output || options.copy_audio || options.mute_output)))) {
1164 throw std::runtime_error("--record-gain requires live audio recording or encoded output");
1165 }
1166 if (options.maximize_fps) {
1167 if (!options.input_file.empty() || !options.graphic_file.empty()) {
1168 throw std::runtime_error("--maximize-fps is available only for camera input");
1169 }
1170 if (options.requested_fps <= 0.0) {
1171 throw std::runtime_error("--maximize-fps requires a target set with --fps");
1172 }
1173 }
1174 if (options.use_source_fps) {
1175 if (options.input_file.empty()) {
1176 throw std::runtime_error("--use-source-fps requires --input <video>");
1177 }
1178 if (options.requested_fps > 0.0) {
1179 throw std::runtime_error("--use-source-fps cannot be combined with --fps");
1180 }
1181 }
1182 if ((options.human_background || options.human_black_specified || options.human_white_specified) && options.human_model.empty()) {
1183 throw std::runtime_error("--background, --black, and --white require --human <model.onnx>");
1184 }
1185 if (!options.human_model.empty() && options.human_black_point >= options.human_white_point) {
1186 throw std::runtime_error("--black must be less than --white for human segmentation");
1187 }
1188 if (options.human_background && options.enable_3d) {
1189 throw std::runtime_error("--background is currently available only in 2D mode");
1190 }
1191 return options;
1192 }
1193
1194 void printHelp(std::ostream &output) {
1195 output << "ACMXVK v" << ACMXVK_VERSION_INFO << " - Vulkan video shader engine\n\n"
1196 << "Usage:\n"
1197 << " acmxvk -i video.mp4 -s shader-directory [options]\n"
1198 << " acmxvk -g image.png -f shader.spv [options]\n"
1199 << " acmxvk -d 0 -s shader-directory [options]\n\n"
1200 << "Resources:\n"
1201 << " -p, --path <directory> Assets root containing data/, shaders/,\n"
1202 << " playlists/, and midi-examples/\n"
1203 << " ACMXVK_PATH Default resource root when --path is absent\n"
1204 << " ACMXVK_SHADER_PATH Default SPIR-V library when shader input is absent\n"
1205 << " ACMX2_PATH is accepted as a data fallback\n\n"
1206 << "Input:\n"
1207 << " -i, --input <file> Read a video file\n"
1208 << " -g, --graphic <file> Read a still image\n"
1209 << " -d, --device <index> Camera device (default 0)\n"
1210 << " -c, --camera-res <WxH> Requested camera dimensions\n"
1211 << " --enumerate-device N List native camera modes and exit\n"
1212 << " --probe-camera N Alias for --enumerate-device\n"
1213 << " --list-camera-devices List native camera indices and names\n"
1214 << " --use-yuv Prefer YUYV camera capture over MJPG\n"
1215 << " --maximize-fps Render at --fps using the latest camera frame\n"
1216 << " --use-source-fps Play video on its reported source clock\n"
1217 << " --use-source-audio Use video audio for shader reactivity and output\n"
1218 << " Requires source FPS or headless constant-frame-rate mode\n"
1219 << " --probe-hdr <video> Print HDR/color metadata and exit\n"
1220 << " -u, --fps <rate> Camera/output FPS\n"
1221 << " Video files prefer FFmpeg/NVDEC capture\n\n"
1222 << "DNN effects (requires WITH_OPENCV_DNN=ON build):\n"
1223 << " --edge <model.onnx> Replace input with a DexiNed edge map\n"
1224 << " --human <model.onnx> Isolate a person with PP-HumanSeg\n"
1225 << " --onnx <config.yaml> Run a YAML-configured image ONNX model\n"
1226 << " --background Apply shaders only behind the person (2D)\n"
1227 << " --black <0.0-1.0> Alpha black point (default 0.35)\n"
1228 << " --white <0.0-1.0> Alpha white point (default 0.75)\n"
1229 << " --check-dnn Report compiled OpenCV DNN support\n"
1230 << " Backend is benchmarked on the first frame\n\n"
1231 << "Deep Dream (requires WITH_DEEP_DREAM=ON build):\n"
1232 << " --check-deep-dream Probe LibTorch CPU/CUDA autograd support\n"
1233 << " --dream-model <file.pt> Apply a TorchScript feature model\n"
1234 << " --dream-layer <name|N> Select a named or numbered feature layer\n"
1235 << " --dream-iterations <N> Number of ascent steps per input frame (1-100; default 1)\n"
1236 << " --dream-strength <N> Gradient step size (0-10; default 0.05)\n"
1237 << " --dream-feedback <N> Previous dreamed-frame blend (0-0.99; default 0.9)\n"
1238 << " --dream-zoom <N> Feedback zoom per source frame (0.9-1.1; default 1.01)\n"
1239 << " --dream-rotation <N> Feedback rotation degrees per frame (-5 to 5; default 0.1)\n"
1240 << " --dream-size <N> Maximum neural working dimension (default 512; 0=native)\n"
1241 << " --dream-fp16 Use FP16 model and tensors on CUDA\n"
1242 << " --dream-channel <N|all> Target one feature channel (default: all)\n"
1243 << " --dream-octaves <N> Progressive dream scales (1-8; default 1)\n"
1244 << " --dream-octave-scale <N> Scale ratio between octaves (1.1-3; default 1.4)\n"
1245 << " --dream-jitter <N> Spatial gradient jitter in pixels (0-64; default 0)\n"
1246 << " --dream-smoothing <N> Gradient smoothing radius (0-16; default 0)\n"
1247 << " --random-dream <seconds>\n"
1248 << " Randomize unspecified safe dream controls at a media-time interval\n"
1249 << " --dream-headless Offline per-frame video dreaming without temporal zoom\n"
1250 << " --deep-orig Same independent-frame mode with a preview window\n"
1251 << " --gpu-filter-before-dream\n"
1252 << " Run acidcam-gpu before Deep Dream\n"
1253 << " Deep Dream runs before the Vulkan shader chain\n\n"
1254 << "Stable Diffusion video (requires WITH_STABLE_DIFFUSION=ON):\n"
1255 << " --check-stable-diffusion\n"
1256 << " Report compiled Stable Diffusion support\n"
1257 << " --sd-model <file> Process video through a full model\n"
1258 << " --sd-prompt <text> Required image-to-image prompt\n"
1259 << " --sd-negative-prompt <text>\n"
1260 << " Optional negative prompt\n"
1261 << " --sd-lora <file> Add LoRA model (repeatable; same folder)\n"
1262 << " --sd-lora-strength <N> Preceding LoRA multiplier, -10 to 10\n"
1263 << " --sd-size <WxH> Neural size, multiples of 64 (default 576x320)\n"
1264 << " --sd-upscale-size <WxH> ESRGAN working size before final output resize\n"
1265 << " --sd-steps <N> Sampling steps, 1-150 (default 12)\n"
1266 << " --sd-strength <N> Image denoising strength, 0-1 (default 0.35)\n"
1267 << " --sd-cfg-scale <N> Text guidance, 0-50 (default 5.0)\n"
1268 << " --sd-seed <N> Fixed per-frame seed (default 1234)\n"
1269 << " --sd-sampler <name> Sampler (default euler_a)\n"
1270 << " --sd-scheduler <name> Scheduler (default discrete)\n"
1271 << " --sd-server <file> sd-server executable (default sd-server)\n"
1272 << " --sd-server-arg <arg> Extra sd-server argument (repeatable)\n"
1273 << " --sd-server-port <N> Local server port (default 1234)\n"
1274 << " --sd-after-shaders Preserve shader-chain-then-SD ordering\n"
1275 << " --sd-upscale High-quality compute upscale before shaders\n"
1276 << " --upscale-model <file> ESRGAN model loaded by sd-server\n"
1277 << " --sd-upscale-only Run ESRGAN upscale-only before shaders\n"
1278 << " --sd-quiet Suppress verbose sd-server output\n"
1279 << " Encoded output implies CFR and no-drop\n"
1280 << " Output feeds the Vulkan shader chain\n\n"
1281 << "Shaders:\n"
1282 << " --build <library.json> Compile a source shader library and exit\n"
1283 << " --builddir <directory> Output directory required by --build\n"
1284 << " --fix <directory> Continue and omit/remove failed shaders\n"
1285 << " --prune Delete GLSL sources that fail compilation\n"
1286 << " --force Confirm permanent deletion by --prune\n"
1287 << " --glslc <executable> GLSL compiler for --build (default: glslc)\n"
1288 << " --parallel <jobs> Concurrent shader jobs for --build (default: 1)\n"
1289 << " -s, --shaders <directory> SPIR-V library with library.json or index.txt\n"
1290 << " -f, --fragment <file.spv> Use one SPIR-V fragment shader\n"
1291 << " --compute <file.spv> Use one SPIR-V compute shader\n"
1292 << " -H, --shader-index <index> Initial library shader index\n"
1293 << " --shader-file <name> Initial library shader filename\n"
1294 << " --uniform <name=value> Override a library.json custom float\n\n"
1295 << "3D model:\n"
1296 << " --enable-3d Map input frames onto a 3D model\n"
1297 << " --model <file> OBJ, MXMOD, or compressed MXMOD model\n"
1298 << " Defaults to the bundled cube.obj\n\n"
1299 << " --shader-pass <indices> Mixed fragment/compute pre-pass chain\n"
1300 << " --shader-pass-files <data> ACMX2 length-prefixed shader filenames\n"
1301 << " --playlist <file> Shader or named multipass playlist\n\n"
1302 << " --cross-fade <seconds> Shader transition duration (default 0.5)\n\n"
1303 << " --enable-playlist Enable the playlist immediately\n"
1304 << " --time-speed <mult> Scale shader time (default 1.0)\n"
1305 << " --normalized Use fixed frame time outside video mode\n"
1306 << " --autopilot-frames <N> Playlist switch interval (minimum 4)\n"
1307 << " Uses decoded frames for video input\n"
1308 << " --autopilot-timeout <N> Alias for --autopilot-frames\n"
1309 << " --autopilot-random <N> Random playlist interval from 4..N\n\n"
1310 << " --enable-random-autopilot Start random playlist-node selection\n\n"
1311 << "History cache:\n"
1312 << " --texture-cache Enable Vulkan texture history\n"
1313 << " --texture-cache-array Alias using sampler2DArray history\n"
1314 << " --texture-cache-size N History layers, 1-64 (default 8)\n"
1315 << " --cache-delay N Skip N frames between cache writes\n"
1316 << " --history-test Enable history and the built-in echo demo\n\n"
1317 << "Recording:\n"
1318 << " -o, --output <file> Encode processed output with MXWrite\n"
1319 << " --duration <seconds> Stop after this much encoded video or PNG output\n"
1320 << " --max-size <MB> Stop when encoded output exceeds this size\n"
1321 << " --png <directory> Write video frames as a PNG sequence in directory\n"
1322 << " --png-level <1-9> PNG compression: 1 fastest/largest, 9 slowest/smallest (default 6)\n"
1323 << " --generate <N> Save a PNG every N processed frames\n"
1324 << " -e, --prefix <directory> Directory for Z snapshots (default .)\n"
1325 << " -b, --encode-crf <0-51> Encoder quality (default 18)\n"
1326 << " --bitrate <rate> Target VBR, e.g. 10M (disables CRF/CQ)\n"
1327 << " --video-bitrate <rate> Alias for --bitrate\n"
1328 << " --encode-preset <name> Encoder speed/quality preset\n"
1329 << " --encode-tune <name> Encoder content/latency tuning\n"
1330 << " --encode-codec <name> auto, software, nvenc, or exact encoder\n"
1331 << " --encode-params <text> Additional FFmpeg encoder options\n"
1332 << " --list-encoders List available video encoders and exit\n"
1333 << " --list-encoder-options <name>\n"
1334 << " List one encoder's options and exit\n"
1335 << " --encode-realtime Enable low-latency encoder settings\n"
1336 << " --no-drop Block when the encoder queue is full\n"
1337 << " --constant-frame-rate Encode rendered video frames sequentially\n"
1338 << " --sequential-encode Alias for --constant-frame-rate\n"
1339 << " --fill-pts-gaps Duplicate held frames for editing-compatible CFR\n"
1340 << " --display-filter Show active shader/filter details\n"
1341 << " --disable-counter Hide the shader/timer/FPS HUD at startup\n"
1342 << " --use-watermark <text> Show a text watermark in the upper-left\n"
1343 << " and hide the preview HUD by default\n"
1344 << " --use-watermark-color <r,g,b>\n"
1345 << " Watermark RGB color (default 255,0,150)\n"
1346 << " --copy-audio Copy input audio into encoded output\n"
1347 << " --mute-output Keep recorded video audio-free\n\n"
1348 << "Audio (requires AUDIO=ON build):\n"
1349 << " -w, --enable-audio Enable live audio-reactive metrics\n"
1350 << " -l, --channels <N> Capture channels (default 2)\n"
1351 << " -q, --sense <0.1-5.0> Audio sensitivity (default 1.0)\n"
1352 << " --audio-time Enable audio-reactive shader time at startup\n"
1353 << " --enable-audio-time Alias for --audio-time\n"
1354 << " --audio-warm-rate N Shader ramp per second (default 0.5; 0 off)\n"
1355 << " --audio-input <device> Input index or default\n"
1356 << " --audio-file <media> Media file or M3U/M3U8 reactivity source\n"
1357 << " -y, --pass-through Play live/file audio through an output device\n"
1358 << " --audio-output <device> Output index or default\n"
1359 << " --pass-through-gain N Monitor gain, 0.0-4.0 (default 1.0)\n"
1360 << " --record-gain N Saved/muxed mic gain, 0.0-2.0 (default 1.0)\n"
1361 << " --record-audio <wav> Record live microphone input as PCM16 WAV\n"
1362 << " --audio-repeat Restart file audio at end-of-stream\n"
1363 << " --audio-trunc Stop ACMXVK when file audio finishes\n"
1364 << " Live/file audio is muxed unless --mute-output\n"
1365 << " --enable-audio-buffers N\n"
1366 << " FFT history layers at binding 4\n"
1367 << " --list-devices List RtAudio devices and exit\n"
1368 << " --check-audio Report compiled audio support\n"
1369 << " Provides a 256-bin FFT at binding 3\n\n"
1370 << "MIDI (requires MIDI=ON build):\n"
1371 << " --midi-device <index> Open a MIDI input port (default 0)\n"
1372 << " --midi-monitor Print received MIDI messages\n"
1373 << " --midi-map <file> Load an ACMX2 .midi_cfg mapping\n"
1374 << " --midi-cc <map> Map [channel:]CC to a custom uniform\n"
1375 << " --list-midi List MIDI input ports and exit\n"
1376 << " --check-midi Report compiled MIDI support\n"
1377 << " Paired knobs repeat by distance from 64\n\n"
1378 << "CUDA and filters:\n"
1379 << " --gpu-filter <list> Comma-separated acidcam-gpu indices\n"
1380 << " --gpu-buffer <4-32> Temporal frame count (default 10)\n"
1381 << " -m, --cuda-device <index> Select NVDEC/filter device (default 0)\n"
1382 << " --list-filters List acidcam-gpu filters and exit\n"
1383 << " --list-cuda-devices List MXVK CUDA devices and exit\n"
1384 << " --check-cuda Report interop and filter support\n"
1385 << " Left/Right selects the active filter\n"
1386 << " NVDEC interop follows the MXVK build\n"
1387 << " Filters require WITH_CUDA=ON\n"
1388 << " Video/camera RGBA and rotation stay on GPU\n\n"
1389 << "Window:\n"
1390 << " -r, --resolution <WxH> Render/output resolution override\n"
1391 << " Preview fits display without changing output size\n"
1392 << " -n, --fullscreen Start fullscreen\n"
1393 << " -a, --repeat Repeat video input\n"
1394 << " --rotate <mode> clockwise, 180, or counterclockwise\n"
1395 << " --flip Flip final display/encoded output vertically\n"
1396 << " --enable-vsync Use FIFO presentation\n"
1397 << " --enable-screenshot Enable MXVK F10 screenshots\n\n"
1398 << "Headless processing:\n"
1399 << " --headless Surface-free terminal/batch rendering\n"
1400 << " --silent Alias for --headless\n"
1401 << " Requires video/image input and --output or --png\n"
1402 << " Cannot be combined with --pass-through\n"
1403 << " Image input and --repeat require --duration\n"
1404 << " Interface shared memory is disabled\n\n"
1405 << "Output:\n"
1406 << " --unbuffered Flush stdout/stderr after each write for GUI capture\n"
1407 << " --interface-shm Accept live shader selection from the ACMX interface\n\n"
1408 << "Keys: Up/Down shader or playlist node, Shift+Up/Down post-shader,\n"
1409 << " P playlist/pause, L freeze, T time, U/I step time,\n"
1410 << " Page Up/Down time speed, Q audio time, Home audio delta,\n"
1411 << " Insert/Delete audio sensitivity, End FFT sensitivity,\n"
1412 << " 3 toggle 2D/3D, V 3D view rotation, O 3D oscillation,\n"
1413 << " C 3D wave,\n"
1414 << " X reset skybox view,\n"
1415 << " E watermark, F fullscreen, F9 runtime HUD, K shader lock,\n"
1416 << " M multipass,\n"
1417 << " J random autopilot, Y sequential autopilot, N random crossfade,\n"
1418 << " [/] crossfade effect, Space bypass,\n"
1419 << " W/A/S/D 3D look, +/- 3D zoom, Shift+/- 3D scale,\n"
1420 << " 1/2 zoom sensitivity,\n"
1421 << " Z PNG, 4 TIFF (TIFF=ON), 5 WebP (WEBP=ON),\n"
1422 << " 6 raw RGBA snapshot,\n"
1423 << " mouse drag/wheel 3D look/move,\n"
1424 << " Escape quit\n";
1425 }
1426
1427 [[nodiscard]] std::string cleanEncoderField(std::string value) {
1428 std::replace_if(value.begin(), value.end(), [](char character) { return character == '\t' || character == '\r' || character == '\n'; }, ' ');
1429 return value;
1430 }
1431
1432 void printEncoders(std::ostream &output) {
1433 output << "MXWRITE_ENCODERS\t1\n";
1434 for (const EncoderInfo &encoder : available_video_encoders()) {
1435 output << "ENCODER\t" << cleanEncoderField(encoder.name) << '\t' << cleanEncoderField(encoder.long_name) << '\t' << cleanEncoderField(encoder.codec_name) << '\t' << (encoder.hardware ? "hardware" : "software") << '\t' << (encoder.experimental ? "experimental" : "stable") << '\t' << cleanEncoderField(encoder.pixel_formats) << '\n';
1436 }
1437 }
1438
1439 [[nodiscard]] bool printEncoderOptions(std::string_view encoder_name, std::ostream &output, std::ostream &error_output) {
1440 const std::string name(encoder_name);
1441 const std::vector<EncoderOptionInfo> options = video_encoder_options(name);
1442 if (options.empty() && !avcodec_find_encoder_by_name(name.c_str())) {
1443 error_output << "acmxvk: encoder not found: " << name << '\n';
1444 return false;
1445 }
1446
1447 output << "MXWRITE_ENCODER_OPTIONS\t1\t" << cleanEncoderField(name) << '\n';
1448 for (const EncoderOptionInfo &option : options) {
1449 output << "OPTION\t" << cleanEncoderField(option.name) << '\t' << cleanEncoderField(option.type) << '\t' << cleanEncoderField(option.default_value) << '\t' << cleanEncoderField(option.minimum) << '\t' << cleanEncoderField(option.maximum) << '\t' << cleanEncoderField(option.choices) << '\t' << cleanEncoderField(option.help) << '\n';
1450 }
1451 return true;
1452 }
1453
1454 [[nodiscard]] std::string trim(std::string text) {
1455 const auto first = std::find_if_not(text.begin(), text.end(), [](unsigned char value) { return std::isspace(value) != 0; });
1456 const auto last = std::find_if_not(text.rbegin(), text.rend(), [](unsigned char value) { return std::isspace(value) != 0; }).base();
1457 if (first >= last) {
1458 return {};
1459 }
1460 return std::string(first, last);
1461 }
1462} // namespace acmxvk
GLsizei GLsizei * length
#define ACMXVK_VERSION_INFO
std::int64_t parse_video_bitrate(std::string value, std::string_view option)
Definition options.cpp:22
constexpr std::int64_t MAX_FRAME_PIXELS
Definition options.cpp:20
constexpr std::size_t MAX_SHADER_ENTRIES
void validate_string(std::string_view value, StringKind kind, std::string_view context, bool allow_empty)
std::string cleanEncoderField(std::string value)
Definition options.cpp:1427
FrameRotation parseFrameRotation(std::string value)
Definition options.cpp:205
void printEncoders(std::ostream &output)
Definition options.cpp:1432
void printHelp(std::ostream &output)
Definition options.cpp:1194
std::string trim(std::string text)
Definition options.cpp:1454
std::vector< fs::path > resource_directories(const Options &options)
void applyResourceDefaults(Options &options)
Definition options.cpp:222
int parseInteger(std::string_view text, std::string_view option)
Definition options.cpp:124
bool dimensions_supported(int width, int height)
Definition options.cpp:50
void validateLocator(std::string_view value, std::string_view context, bool allow_empty=false)
Definition options.cpp:63
bool printEncoderOptions(std::string_view encoder_name, std::ostream &output, std::ostream &error_output)
Definition options.cpp:1439
FrameRotation
Definition options.hpp:17
std::vector< int > parseIntegerList(std::string_view text, std::string_view option)
Definition options.cpp:154
Options parseOptions(int argc, char **argv)
Definition options.cpp:282
bool isUtilityRequest(const Options &options)
Definition options.cpp:220
double parseNumber(std::string_view text, std::string_view option)
Definition options.cpp:139
bool hasShaderManifest(const fs::path &directory)
Definition options.cpp:52
std::string optionValue(int &index, int argc, char **argv, std::string_view option)
Definition options.cpp:54
void validateOptionStrings(const Options &options)
Definition options.cpp:71
void parseDimensions(std::string_view text, int &width, int &height, std::string_view option)
Definition options.cpp:192
std::array< std::uint8_t, 3 > parseColor(std::string_view text, std::string_view option)
Definition options.cpp:176
bool enable_audio_time
Definition options.hpp:105
int stable_diffusion_upscale_working_width
Definition options.hpp:56
std::string encode_params
Definition options.hpp:187
bool gpu_buffer_specified
Definition options.hpp:121
int audio_output_device
Definition options.hpp:43
std::string edge_model
Definition options.hpp:193
std::string snapshot_directory
Definition options.hpp:205
std::string shader_file
Definition options.hpp:176
double max_size_mb
Definition options.hpp:65
bool encode_realtime
Definition options.hpp:98
std::string png_output_directory
Definition options.hpp:183
bool dream_zoom_specified
Definition options.hpp:132
bool stable_diffusion_after_shaders
Definition options.hpp:146
std::int64_t encode_bitrate
Definition options.hpp:33
bool unbuffered_output
Definition options.hpp:160
bool midi_device_specified
Definition options.hpp:117
bool stable_diffusion_quiet
Definition options.hpp:149
std::string glslc_executable
Definition options.hpp:179
double audio_recording_gain
Definition options.hpp:69
std::vector< int > shader_pass_indices
Definition options.hpp:164
bool dream_octave_scale_specified
Definition options.hpp:138
std::string stable_diffusion_model
Definition options.hpp:198
double dream_octave_scale
Definition options.hpp:76
std::string encode_codec
Definition options.hpp:186
std::string dream_layer
Definition options.hpp:197
std::string stable_diffusion_server
Definition options.hpp:202
bool cuda_device_specified
Definition options.hpp:122
bool use_source_audio
Definition options.hpp:84
double time_speed
Definition options.hpp:64
double random_dream_interval
Definition options.hpp:77
std::string build_manifest
Definition options.hpp:177
std::string stable_diffusion_scheduler
Definition options.hpp:204
bool gpu_filter_before_dream
Definition options.hpp:144
std::string dream_model
Definition options.hpp:196
bool dream_rotation_specified
Definition options.hpp:133
int stable_diffusion_server_port
Definition options.hpp:60
double audio_pass_through_gain
Definition options.hpp:68
std::string build_directory
Definition options.hpp:178
std::vector< std::string > stable_diffusion_server_arguments
Definition options.hpp:170
double dream_rotation
Definition options.hpp:75
std::vector< std::string > custom_uniform_overrides
Definition options.hpp:166
std::vector< StableDiffusionLora > stable_diffusion_loras
Definition options.hpp:169
bool dream_feedback_specified
Definition options.hpp:131
std::string fragment_shader
Definition options.hpp:174
std::vector< std::string > shader_pass_files
Definition options.hpp:165
double duration
Definition options.hpp:62
bool random_dream_specified
Definition options.hpp:141
std::string audio_file
Definition options.hpp:190
bool audio_recording_gain_specified
Definition options.hpp:110
bool audio_input_specified
Definition options.hpp:106
std::string resource_directory
Definition options.hpp:206
bool human_background
Definition options.hpp:150
FrameRotation frame_rotation
Definition options.hpp:163
std::string output_file
Definition options.hpp:182
int texture_cache_size
Definition options.hpp:40
bool list_camera_devices
Definition options.hpp:115
std::string midi_map_file
Definition options.hpp:192
std::string model_file
Definition options.hpp:180
bool check_stable_diffusion
Definition options.hpp:128
bool enable_playlist
Definition options.hpp:90
double stable_diffusion_strength
Definition options.hpp:78
int stable_diffusion_height
Definition options.hpp:55
std::vector< int > gpu_filter_indices
Definition options.hpp:168
std::string watermark_text
Definition options.hpp:207
int stable_diffusion_width
Definition options.hpp:54
double dream_feedback
Definition options.hpp:73
std::vector< std::string > midi_cc_mappings
Definition options.hpp:167
std::string stable_diffusion_upscale_model
Definition options.hpp:199
std::string graphic_file
Definition options.hpp:172
double dream_strength
Definition options.hpp:72
int audio_input_device
Definition options.hpp:42
bool stable_diffusion_upscale
Definition options.hpp:147
bool audio_warm_rate_specified
Definition options.hpp:107
double audio_warm_rate
Definition options.hpp:67
int stable_diffusion_upscale_working_height
Definition options.hpp:57
double stable_diffusion_cfg_scale
Definition options.hpp:79
bool list_cuda_devices
Definition options.hpp:124
std::string shader_directory
Definition options.hpp:173
std::string onnx_configuration
Definition options.hpp:195
std::string encode_tune
Definition options.hpp:185
std::string record_audio_file
Definition options.hpp:191
std::string stable_diffusion_sampler
Definition options.hpp:203
std::string human_model
Definition options.hpp:194
std::string stable_diffusion_negative_prompt
Definition options.hpp:201
int gpu_frame_buffer_size
Definition options.hpp:46
int stable_diffusion_steps
Definition options.hpp:58
bool list_audio_devices
Definition options.hpp:114
std::string playlist_file
Definition options.hpp:181
bool dream_channel_specified
Definition options.hpp:136
double dream_zoom
Definition options.hpp:74
bool dream_smoothing_specified
Definition options.hpp:140
double requested_fps
Definition options.hpp:61
bool dream_jitter_specified
Definition options.hpp:139
int enumerate_camera_device
Definition options.hpp:30
bool stable_diffusion_upscale_only
Definition options.hpp:148
bool constant_frame_rate
Definition options.hpp:100
bool normalized_time
Definition options.hpp:95
std::array< std::uint8_t, 3 > watermark_color
Definition options.hpp:208
bool enable_texture_cache
Definition options.hpp:92
bool check_deep_dream
Definition options.hpp:127
std::string list_encoder_options
Definition options.hpp:188
std::string encode_preset
Definition options.hpp:184
bool audio_output_specified
Definition options.hpp:108
std::string input_file
Definition options.hpp:171
bool dream_size_specified
Definition options.hpp:134
bool list_midi_devices
Definition options.hpp:119
double cross_fade_duration
Definition options.hpp:63
bool dream_octaves_specified
Definition options.hpp:137
int stable_diffusion_seed
Definition options.hpp:59
bool dream_iterations_specified
Definition options.hpp:129
bool resolution_specified
Definition options.hpp:80
bool audio_pass_through
Definition options.hpp:111
bool enable_screenshot
Definition options.hpp:89
bool build_parallel_specified
Definition options.hpp:159
std::string probe_hdr_file
Definition options.hpp:189
bool enable_random_autopilot
Definition options.hpp:91
double human_black_point
Definition options.hpp:70
bool audio_pass_through_gain_specified
Definition options.hpp:109
double human_white_point
Definition options.hpp:71
bool use_source_fps
Definition options.hpp:83
int autopilot_random_timeout
Definition options.hpp:35
bool human_white_specified
Definition options.hpp:152
int generate_interval
Definition options.hpp:36
bool list_gpu_filters
Definition options.hpp:123
bool human_black_specified
Definition options.hpp:151
std::string compute_shader
Definition options.hpp:175
std::string stable_diffusion_prompt
Definition options.hpp:200
bool stable_diffusion_option_specified
Definition options.hpp:145
double audio_sensitivity
Definition options.hpp:66
bool dream_strength_specified
Definition options.hpp:130