+std::wstring info_delay_command(command_context& ctx)
+{
+ boost::property_tree::wptree info;
+ auto layer = ctx.layer_index(std::numeric_limits<int>::min());
+
+ if (layer == std::numeric_limits<int>::min())
+ info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
+ else
+ info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
+ .add(L"index", layer);
+
+ return create_info_xml_reply(info, L"DELAY");
+}
+
+void diag_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Open the diagnostics window.");
+ sink.syntax(L"DIAG");
+ sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
+}
+
+std::wstring diag_command(command_context& ctx)
+{
+ core::diagnostics::osd::show_graphs(true);
+
+ return L"202 DIAG OK\r\n";
+}
+
+void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
+ sink.syntax(L"GL INFO");
+ sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
+}
+
+std::wstring gl_info_command(command_context& ctx)
+{
+ auto device = ctx.ogl_device;
+
+ if (!device)
+ CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
+
+ std::wstringstream result;
+ result << L"201 GL INFO OK\r\n";
+
+ boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
+ auto info = device->info();
+ boost::property_tree::write_xml(result, info, w);
+ result << L"\r\n";
+
+ return result.str();
+}
+
+void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Release pooled OpenGL resources.");
+ sink.syntax(L"GL GC");
+ sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
+}
+
+std::wstring gl_gc_command(command_context& ctx)
+{
+ auto device = ctx.ogl_device;
+
+ if (!device)
+ CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
+
+ device->gc().wait();
+
+ return L"202 GL GC OK\r\n";
+}
+
+static const int WIDTH = 80;
+
+struct max_width_sink : public core::help_sink
+{
+ std::size_t max_width = 0;
+
+ void begin_item(const std::wstring& name) override
+ {
+ max_width = std::max(name.length(), max_width);
+ };
+};
+
+struct short_description_sink : public core::help_sink
+{
+ std::size_t width;
+ std::wstringstream& out;
+
+ short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
+
+ void begin_item(const std::wstring& name) override
+ {
+ out << std::left << std::setw(width + 1) << name;
+ };
+
+ void short_description(const std::wstring& short_description) override
+ {
+ out << short_description << L"\r\n";
+ };
+};
+
+struct simple_paragraph_builder : core::paragraph_builder
+{
+ std::wostringstream out;
+ std::wstringstream& commit_to;
+
+ simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
+ ~simple_paragraph_builder()
+ {
+ commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
+ }
+ spl::shared_ptr<paragraph_builder> text(std::wstring text) override
+ {
+ out << std::move(text);
+ return shared_from_this();
+ }
+ spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
+ spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
+ spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
+ spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
+};
+
+struct simple_definition_list_builder : core::definition_list_builder
+{
+ std::wstringstream& out;
+
+ simple_definition_list_builder(std::wstringstream& out) : out(out) { }
+ ~simple_definition_list_builder()
+ {
+ out << L"\n";
+ }
+
+ spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
+ {
+ out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
+ out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
+ return shared_from_this();
+ }
+};
+
+struct long_description_sink : public core::help_sink
+{
+ std::wstringstream& out;
+
+ long_description_sink(std::wstringstream& out) : out(out) { }
+
+ void syntax(const std::wstring& syntax) override
+ {
+ out << L"Syntax:\n";
+ out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
+ };
+
+ spl::shared_ptr<core::paragraph_builder> para() override
+ {
+ return spl::make_shared<simple_paragraph_builder>(out);
+ }
+
+ spl::shared_ptr<core::definition_list_builder> definitions() override
+ {
+ return spl::make_shared<simple_definition_list_builder>(out);
+ }
+
+ void example(const std::wstring& code, const std::wstring& caption) override
+ {
+ out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
+
+ if (!caption.empty())
+ out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
+
+ out << L"\n";
+ }
+private:
+ void begin_item(const std::wstring& name) override
+ {
+ out << name << L"\n\n";
+ };
+};
+
+std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
+{
+ std::wstringstream result;
+ result << L"200 " << help_command << L" OK\r\n";
+ max_width_sink width;
+ ctx.help_repo->help(tags, width);
+ short_description_sink sink(width.max_width, result);
+ sink.width = width.max_width;
+ ctx.help_repo->help(tags, sink);
+ result << L"\r\n";
+ return result.str();
+}
+
+std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
+{
+ std::wstringstream result;
+ result << L"201 " << help_command << L" OK\r\n";
+ auto joined = boost::join(ctx.parameters, L" ");
+ long_description_sink sink(result);
+ ctx.help_repo->help(tags, joined, sink);
+ result << L"\r\n";
+ return result.str();
+}
+
+void help_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Show online help for AMCP commands.");
+ sink.syntax(LR"(HELP {[command:string]})");
+ sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
+ sink.example(L">> HELP", L"Shows a list of commands.");
+ sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
+}
+
+std::wstring help_command(command_context& ctx)
+{
+ if (ctx.parameters.size() == 0)
+ return create_help_list(L"HELP", ctx, { L"AMCP" });
+ else
+ return create_help_details(L"HELP", ctx, { L"AMCP" });
+}
+
+void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Show online help for producers.");
+ sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
+ sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
+ sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
+ sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
+}
+
+std::wstring help_producer_command(command_context& ctx)
+{
+ if (ctx.parameters.size() == 0)
+ return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
+ else
+ return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
+}
+
+void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Show online help for consumers.");
+ sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
+ sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
+ sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
+ sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
+}
+
+std::wstring help_consumer_command(command_context& ctx)
+{
+ if (ctx.parameters.size() == 0)
+ return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
+ else
+ return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
+}
+
+void bye_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Disconnect the session.");
+ sink.syntax(L"BYE");
+ sink.para()
+ ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
+ ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
+}
+
+std::wstring bye_command(command_context& ctx)
+{
+ ctx.client->disconnect();
+ return L"";
+}
+
+void kill_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Shutdown the server.");
+ sink.syntax(L"KILL");
+ sink.para()->text(L"Shuts the server down.");
+}
+
+std::wstring kill_command(command_context& ctx)
+{
+ ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
+ return L"202 KILL OK\r\n";
+}
+
+void restart_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Shutdown the server with restart exit code.");
+ sink.syntax(L"RESTART");
+ sink.para()
+ ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
+ ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
+}
+
+std::wstring restart_command(command_context& ctx)
+{
+ ctx.shutdown_server_now.set_value(true); //true for attempting to restart
+ return L"202 RESTART OK\r\n";
+}
+
+void lock_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Lock or unlock access to a channel.");
+ sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
+ sink.para()->text(L"Allows for exclusive access to a channel.");
+ sink.para()->text(L"Examples:");
+ sink.example(L"LOCK 1 ACQUIRE secret");
+ sink.example(L"LOCK 1 RELEASE");
+ sink.example(L"LOCK 1 CLEAR");
+}
+
+std::wstring lock_command(command_context& ctx)
+{
+ int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
+ auto lock = ctx.channels.at(channel_index).lock;
+ auto command = boost::to_upper_copy(ctx.parameters.at(1));
+
+ if (command == L"ACQUIRE")
+ {
+ std::wstring lock_phrase = ctx.parameters.at(2);
+
+ //TODO: read options
+
+ //just lock one channel
+ if (!lock->try_lock(lock_phrase, ctx.client))
+ return L"503 LOCK ACQUIRE FAILED\r\n";
+
+ return L"202 LOCK ACQUIRE OK\r\n";
+ }
+ else if (command == L"RELEASE")
+ {
+ lock->release_lock(ctx.client);
+ return L"202 LOCK RELEASE OK\r\n";
+ }
+ else if (command == L"CLEAR")
+ {
+ std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
+ std::wstring client_override_phrase;
+
+ if (!override_phrase.empty())
+ client_override_phrase = ctx.parameters.at(2);
+
+ //just clear one channel
+ if (client_override_phrase != override_phrase)
+ return L"503 LOCK CLEAR FAILED\r\n";
+
+ lock->clear_locks();
+
+ return L"202 LOCK CLEAR OK\r\n";
+ }
+
+ CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
+}
+
+void register_commands(amcp_command_repository& repo)
+{
+ repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
+ repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
+ repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
+ repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
+ repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
+ repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
+ repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
+ repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
+ repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
+ repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
+ repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
+ repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
+ repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
+ repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
+ repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
+ repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
+
+ repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
+ repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
+ repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
+ repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
+
+ repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
+ repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
+ repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
+ repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
+ repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
+ repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
+ repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
+ repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
+ repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
+
+ repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
+ repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
+ repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
+
+ repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
+ repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
+ repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
+ repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
+
+ repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
+ repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
+ repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
+ repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
+ repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
+ repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
+ repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
+ repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
+ repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
+ repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
+ repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
+ repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
+ repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
+ repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
+ repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
+ repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
+ repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
+ repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
+ repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
+ repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
+ repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
+ repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
+ repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
+ repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
+}