]> git.sesse.net Git - casparcg/blobdiff - protocol/amcp/AMCPCommandsImpl.cpp
Created a consumer that provides sync to a channel based on the pace of another chann...
[casparcg] / protocol / amcp / AMCPCommandsImpl.cpp
index 5f527b4c509924696322286f6221e714d91b610b..2788325c7dda4b7ed16e0d8f500bff366d8e5556 100644 (file)
 /* Return codes
 
 102 [action]                   Information that [action] has happened
-101 [action]                   Information that [action] has happened plus one row of data  
+101 [action]                   Information that [action] has happened plus one row of data
 
 202 [command] OK               [command] has been executed
-201 [command] OK               [command] has been executed, plus one row of data  
+201 [command] OK               [command] has been executed, plus one row of data
 200 [command] OK               [command] has been executed, plus multiple lines of data. ends with an empty line
 
 400 ERROR                              the command could not be understood
 401 [command] ERROR            invalid/missing channel
 402 [command] ERROR            parameter missing
-403 [command] ERROR            invalid parameter  
+403 [command] ERROR            invalid parameter
 404 [command] ERROR            file not found
 
 500 FAILED                             internal error
@@ -134,7 +134,7 @@ std::wstring read_utf8_file(const boost::filesystem::path& file)
        std::wstringstream result;
        boost::filesystem::wifstream filestream(file);
 
-       if (filestream) 
+       if (filestream)
        {
                // Consume BOM first
                filestream.get();
@@ -234,11 +234,11 @@ std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_pt
 }
 
 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
-{      
+{
        std::wstringstream replyString;
        for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
                replyString << MediaInfo(itr->path(), media_info_repo);
-       
+
        return boost::to_upper_copy(replyString.str());
 }
 
@@ -247,7 +247,7 @@ std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg
        std::wstringstream replyString;
 
        for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
-       {               
+       {
                if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
                {
                        auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
@@ -264,7 +264,7 @@ std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg
                        auto dir = relativePath.parent_path();
                        auto file = boost::to_upper_copy(relativePath.filename().wstring());
                        relativePath = dir / file;
-                                               
+
                        auto str = relativePath.generic_wstring();
                        boost::trim_if(str, boost::is_any_of("\\/"));
 
@@ -280,13 +280,18 @@ std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg
        return replyString.str();
 }
 
+std::vector<spl::shared_ptr<core::video_channel>> get_channels(const command_context& ctx)
+{
+       return cpplinq::from(ctx.channels)
+               .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
+               .to_vector();
+}
+
 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
 {
        return core::frame_producer_dependencies(
                        channel->frame_factory(),
-                       cpplinq::from(ctx.channels)
-                                       .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
-                                       .to_vector(),
+                       get_channels(ctx),
                        channel->video_format_desc(),
                        ctx.producer_registry);
 }
@@ -536,7 +541,7 @@ void call_describer(core::help_sink& sink, const core::help_repository& repo)
 
 std::wstring call_command(command_context& ctx)
 {
-       auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
+       auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters).get();
 
        // TODO: because of std::async deferred timed waiting does not work
 
@@ -545,10 +550,10 @@ std::wstring call_command(command_context& ctx)
        CASPAR_THROW_EXCEPTION(timed_out());*/
 
        std::wstringstream replyString;
-       if (result.get().empty())
+       if (result.empty())
                replyString << L"202 CALL OK\r\n";
        else
-               replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
+               replyString << L"201 CALL OK\r\n" << result << L"\r\n";
 
        return replyString.str();
 }
@@ -598,7 +603,7 @@ std::wstring swap_command(command_context& ctx)
 void add_describer(core::help_sink& sink, const core::help_repository& repo)
 {
        sink.short_description(L"Add a consumer to a video channel.");
-       sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
+       sink.syntax(L"ADD [video_channel:int]{-[consumer_index:int]} [consumer:string] [parameters:string]");
        sink.para()
                ->text(L"Adds a consumer to the specified video channel. The string ")
                ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
@@ -606,14 +611,22 @@ void add_describer(core::help_sink& sink, const core::help_repository& repo)
                ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
                ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
                ->see(L"the CasparCG config file")->text(L".");
+       sink.para()
+               ->text(L"Specifying ")->code(L"consumer_index")
+               ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
+               ->see(L"REMOVE")->text(L" command to remove the consumer.");
        sink.para()->text(L"Examples:");
        sink.example(L">> ADD 1 DECKLINK 1");
        sink.example(L">> ADD 1 BLUEFISH 2");
        sink.example(L">> ADD 1 SCREEN");
        sink.example(L">> ADD 1 AUDIO");
        sink.example(L">> ADD 1 IMAGE filename");
+       sink.example(L">> ADD 2 SYNCTO 1");
        sink.example(L">> ADD 1 FILE filename.mov");
        sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
+       sink.example(
+               L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
+               L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
        sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
        sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
 }
@@ -628,7 +641,7 @@ std::wstring add_command(command_context& ctx)
        core::diagnostics::scoped_call_context save;
        core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
 
-       auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
+       auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx));
        ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
 
        return L"202 ADD OK\r\n";
@@ -653,7 +666,7 @@ void remove_describer(core::help_sink& sink, const core::help_repository& repo)
 std::wstring remove_command(command_context& ctx)
 {
        auto index = ctx.layer_index(std::numeric_limits<int>::min());
-       
+
        if (index == std::numeric_limits<int>::min())
        {
                replace_placeholders(
@@ -661,7 +674,7 @@ std::wstring remove_command(command_context& ctx)
                                ctx.client->address(),
                                ctx.parameters);
 
-               index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
+               index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx))->index();
        }
 
        ctx.channel.channel->output().remove(index);
@@ -682,7 +695,7 @@ void print_describer(core::help_sink& sink, const core::help_repository& repo)
 
 std::wstring print_command(command_context& ctx)
 {
-       ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
+       ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage(), get_channels(ctx)));
 
        return L"202 PRINT OK\r\n";
 }
@@ -704,6 +717,23 @@ std::wstring log_level_command(command_context& ctx)
        return L"202 LOG OK\r\n";
 }
 
+void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Enable/disable a logging category in the server.");
+       sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
+       sink.para()->text(L"Enables or disables the specified logging category.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
+       sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
+}
+
+std::wstring log_category_command(command_context& ctx)
+{
+       log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
+
+       return L"202 LOG OK\r\n";
+}
+
 void set_describer(core::help_sink& sink, const core::help_repository& repo)
 {
        sink.short_description(L"Change the value of a channel variable.");
@@ -731,7 +761,7 @@ std::wstring set_command(command_context& ctx)
                        return L"202 SET MODE OK\r\n";
                }
 
-               CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
+               CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
        }
        else if (name == L"CHANNEL_LAYOUT")
        {
@@ -743,10 +773,10 @@ std::wstring set_command(command_context& ctx)
                        return L"202 SET CHANNEL_LAYOUT OK\r\n";
                }
 
-               CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid audio channel layout"));
+               CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
        }
 
-       CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
+       CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
 }
 
 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
@@ -794,7 +824,7 @@ std::wstring data_store_command(command_context& ctx)
 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
 {
        sink.short_description(L"Retrieve a dataset.");
-       sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
+       sink.syntax(L"DATA RETRIEVE [name:string]");
        sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
        sink.para()->text(L"Examples:");
        sink.example(L">> DATA RETRIEVE my_data");
@@ -899,17 +929,6 @@ std::wstring data_remove_command(command_context& ctx)
 
 // Template Graphics Commands
 
-int get_and_validate_layer(const std::wstring& layerstring) {
-       int length = layerstring.length();
-       for (int i = 0; i < length; ++i) {
-               if (!std::isdigit(layerstring[i])) {
-                       CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
-               }
-       }
-
-       return boost::lexical_cast<int>(layerstring);
-}
-
 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
 {
        sink.short_description(L"Prepare a template for displaying.");
@@ -925,7 +944,7 @@ std::wstring cg_add_command(command_context& ctx)
 {
        //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
 
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
        std::wstring label;             //_parameters[2]
        bool bDoStart = false;          //_parameters[2] alt. _parameters[3]
        unsigned int dataIndex = 3;
@@ -994,7 +1013,7 @@ void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
 
 std::wstring cg_play_command(command_context& ctx)
 {
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
        ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
 
        return L"202 CG OK\r\n";
@@ -1005,7 +1024,7 @@ spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
        auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
 
        if (proxy == cg_proxy::empty())
-               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No CG proxy running on layer"));
+               CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
 
        return proxy;
 }
@@ -1023,7 +1042,7 @@ void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
 
 std::wstring cg_stop_command(command_context& ctx)
 {
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
        get_expected_cg_proxy(ctx)->stop(layer, 0);
 
        return L"202 CG OK\r\n";
@@ -1042,7 +1061,7 @@ void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
 
 std::wstring cg_next_command(command_context& ctx)
 {
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
        get_expected_cg_proxy(ctx)->next(layer);
 
        return L"202 CG OK\r\n";
@@ -1059,7 +1078,7 @@ void cg_remove_describer(core::help_sink& sink, const core::help_repository& rep
 
 std::wstring cg_remove_command(command_context& ctx)
 {
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
        get_expected_cg_proxy(ctx)->remove(layer);
 
        return L"202 CG OK\r\n";
@@ -1090,7 +1109,7 @@ void cg_update_describer(core::help_sink& sink, const core::help_repository& rep
 
 std::wstring cg_update_command(command_context& ctx)
 {
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
 
        std::wstring dataString = ctx.parameters.at(1);
        if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
@@ -1120,7 +1139,7 @@ std::wstring cg_invoke_command(command_context& ctx)
 {
        std::wstringstream replyString;
        replyString << L"201 CG OK\r\n";
-       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
        auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
        replyString << result << L"\r\n";
 
@@ -1147,7 +1166,7 @@ std::wstring cg_info_command(command_context& ctx)
        }
        else
        {
-               int layer = get_and_validate_layer(ctx.parameters.at(0));
+               int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
                auto desc = get_expected_cg_proxy(ctx)->description(layer);
 
                replyString << desc << L"\r\n";
@@ -1242,7 +1261,7 @@ std::wstring mixer_keyer_command(command_context& ctx)
        {
                transform.image_transform.is_key = value;
                return transform;
-       }, 0, L"linear"));
+       }, 0, tweener(L"linear")));
        transforms.apply();
 
        return L"202 MIXER OK\r\n";
@@ -1333,7 +1352,7 @@ std::wstring mixer_blend_command(command_context& ctx)
        {
                transform.image_transform.blend_mode = value;
                return transform;
-       }, 0, L"linear"));
+       }, 0, tweener(L"linear")));
        transforms.apply();
 
        return L"202 MIXER OK\r\n";
@@ -1842,7 +1861,7 @@ std::wstring mixer_mipmap_command(command_context& ctx)
        {
                transform.image_transform.use_mipmap = value;
                return transform;
-       }, 0, L"linear"));
+       }, 0, tweener(L"linear")));
        transforms.apply();
 
        return L"202 MIXER OK\r\n";
@@ -2032,7 +2051,7 @@ std::wstring channel_grid_command(command_context& ctx)
        params.push_back(L"0");
        params.push_back(L"NAME");
        params.push_back(L"Channel Grid Window");
-       auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
+       auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
 
        self.channel->output().add(screen);
 
@@ -2041,7 +2060,7 @@ std::wstring channel_grid_command(command_context& ctx)
                if (channel.channel != self.channel)
                {
                        core::diagnostics::call_context::for_thread().layer = index;
-                       auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
+                       auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
                        self.channel->stage().load(index, producer, false);
                        self.channel->stage().play(index);
                        index++;
@@ -2157,7 +2176,7 @@ std::wstring thumbnail_generate_command(command_context& ctx)
                return L"202 THUMBNAIL GENERATE OK\r\n";
        }
        else
-               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
+               CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
 }
 
 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
@@ -2175,7 +2194,7 @@ std::wstring thumbnail_generateall_command(command_context& ctx)
                return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
        }
        else
-               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
+               CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
 }
 
 // Query Commands
@@ -2287,6 +2306,14 @@ void version_describer(core::help_sink& sink, const core::help_repository& repo)
                L">> VERSION FLASH\n"
                L"<< 201 VERSION OK\n"
                L"<< 11.8.800.94");
+       sink.example(
+               L">> VERSION TEMPLATEHOST\n"
+               L"<< 201 VERSION OK\n"
+               L"<< unknown");
+       sink.example(
+               L">> VERSION CEF\n"
+               L"<< 201 VERSION OK\n"
+               L"<< 3.1750.1805");
 }
 
 std::wstring version_command(command_context& ctx)
@@ -2554,6 +2581,25 @@ std::wstring gl_info_command(command_context& ctx)
        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
@@ -2600,6 +2646,7 @@ struct simple_paragraph_builder : core::paragraph_builder
                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)); }
 };
@@ -2847,6 +2894,7 @@ void register_commands(amcp_command_repository& repo)
        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);
 
@@ -2910,6 +2958,7 @@ void register_commands(amcp_command_repository& repo)
        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);