#include <common/os/filesystem.h>
#include <common/base64.h>
#include <common/thread_info.h>
+#include <common/filesystem.h>
#include <core/producer/cg_proxy.h>
#include <core/producer/frame_producer.h>
/* 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
std::wstringstream result;
boost::filesystem::wifstream filestream(file);
- if (filestream)
+ if (filestream)
{
// Consume BOM first
filestream.get();
auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
- auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
-
auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
- auto str = relativePath.replace_extension(L"").generic_wstring();
+ auto relativePath = get_relative_without_extension(path, env::media_folder());
+ auto str = relativePath.generic_wstring();
+
if (str[0] == '\\' || str[0] == '/')
str = std::wstring(str.begin() + 1, str.end());
}
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());
}
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 = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
+ auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
auto dir = relativePath.parent_path();
auto file = boost::to_upper_copy(relativePath.filename().wstring());
relativePath = dir / file;
-
- auto str = relativePath.replace_extension(L"").generic_wstring();
+
+ auto str = relativePath.generic_wstring();
boost::trim_if(str, boost::is_any_of("\\/"));
auto template_type = cg_registry->get_cg_producer_name(str);
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);
}
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
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();
}
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. ")
->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");
}
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";
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(
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);
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";
}
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.");
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")
{
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)
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");
if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
continue;
- auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
+ auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
+ auto str = relativePath.generic_wstring();
- auto str = relativePath.replace_extension(L"").generic_wstring();
if (str[0] == L'\\' || str[0] == L'/')
str = std::wstring(str.begin() + 1, str.end());
if (!boost::filesystem::remove(filename))
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
- return L"201 DATA REMOVE OK\r\n";
+ return L"202 DATA REMOVE OK\r\n";
}
// 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.");
{
//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;
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";
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;
}
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";
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";
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";
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'{')
{
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";
}
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";
{
transform.image_transform.is_key = value;
return transform;
- }, 0, L"linear"));
+ }, 0, tweener(L"linear")));
transforms.apply();
return L"202 MIXER OK\r\n";
{
transform.image_transform.blend_mode = value;
return transform;
- }, 0, L"linear"));
+ }, 0, tweener(L"linear")));
transforms.apply();
return L"202 MIXER OK\r\n";
{
transform.image_transform.use_mipmap = value;
return transform;
- }, 0, L"linear"));
+ }, 0, tweener(L"linear")));
transforms.apply();
return L"202 MIXER OK\r\n";
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);
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++;
if (!boost::iequals(itr->path().extension().wstring(), L".png"))
continue;
- auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
+ auto relativePath = get_relative_without_extension(itr->path(), env::thumbnails_folder());
+ auto str = relativePath.generic_wstring();
- auto str = relativePath.replace_extension(L"").generic_wstring();
if (str[0] == '\\' || str[0] == '/')
str = std::wstring(str.begin() + 1, str.end());
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)
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
replyString << L"200 FLS OK\r\n";
for (auto& font : core::text::list_fonts())
- replyString << L"\"" << font.first << L"\" \"" << font.second << L"\"\r\n";
+ replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
replyString << L"\r\n";
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)
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
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)); }
};
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"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);