* Added HELP PRODUCER command to list available producers and get help for them.
* Updated generate_docs.cpp to also include a wiki page for the producers.
#include <common/except.h>
#include <boost/range/adaptor/filtered.hpp>
+#include <boost/algorithm/string.hpp>
#include <algorithm>
#include <utility>
{
auto found = impl_->items | boost::adaptors::filtered([&](const std::pair<help_item, std::set<std::wstring>>& item)
{
- return item.first.first == name && std::includes(
- item.second.begin(), item.second.end(),
- tags.begin(), tags.end());
+ return boost::iequals(item.first.first, name) && std::includes(
+ item.second.begin(), item.second.end(),
+ tags.begin(), tags.end());
});
if (found.empty())
struct frame_producer_registry::impl
{
- std::vector<producer_factory_t> producer_factories;
- std::vector<producer_factory_t> thumbnail_factories;
+ std::vector<producer_factory_t> producer_factories;
+ std::vector<producer_factory_t> thumbnail_factories;
+ spl::shared_ptr<help_repository> help_repo;
+
+ impl(spl::shared_ptr<help_repository> help_repo)
+ : help_repo(std::move(help_repo))
+ {
+ }
};
-frame_producer_registry::frame_producer_registry()
- : impl_(new impl)
+frame_producer_registry::frame_producer_registry(spl::shared_ptr<help_repository> help_repo)
+ : impl_(new impl(std::move(help_repo)))
{
}
-void frame_producer_registry::register_producer_factory(const producer_factory_t& factory)
+void frame_producer_registry::register_producer_factory(std::wstring name, const producer_factory_t& factory, const help_item_describer& describer)
{
impl_->producer_factories.push_back(factory);
+ impl_->help_repo->register_item({ L"producer" }, std::move(name), describer);
}
void frame_producer_registry::register_thumbnail_producer_factory(const producer_factory_t& factory)
#include "../monitor/monitor.h"
#include "../fwd.h"
#include "../interaction/interaction_sink.h"
+#include "../help/help_repository.h"
#include "binding.h"
#include <common/forward.h>
class frame_producer_registry : boost::noncopyable
{
public:
- frame_producer_registry();
- void register_producer_factory(const producer_factory_t& factory); // Not thread-safe.
+ frame_producer_registry(spl::shared_ptr<help_repository> help_repo);
+ void register_producer_factory(std::wstring name, const producer_factory_t& factory, const help_item_describer& describer); // Not thread-safe.
void register_thumbnail_producer_factory(const producer_factory_t& factory); // Not thread-safe.
spl::shared_ptr<core::frame_producer> create_producer(const frame_producer_dependencies&, const std::vector<std::wstring>& params) const;
spl::shared_ptr<core::frame_producer> create_producer(const frame_producer_dependencies&, const std::wstring& params) const;
#include <common/env.h>
#include <core/producer/frame_producer.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <future>
}
}
-void init(module_dependencies dependencies)
+void describe_xml_scene_producer(core::help_sink& sink, const core::help_repository& repo)
{
- dependencies.producer_registry->register_producer_factory(create_xml_scene_producer);
- dependencies.cg_registry->register_cg_producer(
- L"scene",
- { L".scene" },
- [](const std::wstring& filename) { return ""; },
- [](const spl::shared_ptr<core::frame_producer>& producer)
- {
- return spl::make_shared<core::scene::scene_cg_proxy>(producer);
- },
- [](
- const core::frame_producer_dependencies& dependencies,
- const std::wstring& filename)
- {
- return create_xml_scene_producer(dependencies, { filename });
- },
- false);
+ sink.short_description(L"A simple producer for dynamic graphics using .scene files.");
+ sink.syntax(L"[.scene_filename:string] {[param1:string] [value1:string]} {[param2:string] [value2:string]} ...");
+ sink.para()->text(L"A simple producer that looks in the ")->code(L"templates")->text(L" folder for .scene files.");
+ sink.para()->text(L"The .scene file is a simple XML document containing variables, layers and timelines.");
+ sink.example(L">> PLAY 1-10 scene_name_sign FIRSTNAME \"John\" LASTNAME \"Doe\"", L"loads and plays templates/scene_name_sign.scene and sets the variables FIRSTNAME and LASTNAME.");
+ sink.para()->text(L"The scene producer also supports setting the variables while playing via the CALL command:");
+ sink.example(L">> CALL 1-10 FIRSTNAME \"Jane\"", L"changes the variable FIRSTNAME on an already loaded scene.");
}
spl::shared_ptr<core::frame_producer> create_xml_scene_producer(
return scene;
}
+void init(module_dependencies dependencies)
+{
+ dependencies.producer_registry->register_producer_factory(L"XML Scene Producer", create_xml_scene_producer, describe_xml_scene_producer);
+ dependencies.cg_registry->register_cg_producer(
+ L"scene",
+ { L".scene" },
+ [](const std::wstring& filename) { return ""; },
+ [](const spl::shared_ptr<core::frame_producer>& producer)
+ {
+ return spl::make_shared<core::scene::scene_cg_proxy>(producer);
+ },
+ [](
+ const core::frame_producer_dependencies& dependencies,
+ const std::wstring& filename)
+ {
+ return create_xml_scene_producer(dependencies, { filename });
+ },
+ false);
+}
+
}}}
#include <core/monitor/monitor.h>
#include <core/consumer/frame_consumer.h>
#include <core/module_dependencies.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <modules/image/consumer/image_consumer.h>
};
-namespace caspar { namespace core {
- namespace text {
+namespace caspar { namespace core { namespace text {
- using namespace boost::filesystem;
+using namespace boost::filesystem;
- std::map<std::wstring, std::wstring> fonts;
+std::map<std::wstring, std::wstring> fonts;
- std::map<std::wstring, std::wstring> enumerate_fonts()
- {
- std::map<std::wstring, std::wstring> result;
+std::map<std::wstring, std::wstring> enumerate_fonts()
+{
+ std::map<std::wstring, std::wstring> result;
+
+ FT_Library lib;
+ FT_Error err = FT_Init_FreeType(&lib);
+ if(err)
+ return result;
- FT_Library lib;
- FT_Error err = FT_Init_FreeType(&lib);
+ auto fonts = directory_iterator(env::font_folder());
+ auto end = directory_iterator();
+ for(; fonts != end; ++fonts)
+ {
+ auto file = (*fonts);
+ if(is_regular_file(file.path()))
+ {
+ FT_Face face;
+ err = FT_New_Face(lib, u8(file.path().native()).c_str(), 0, &face);
if(err)
- return result;
+ continue;
- auto fonts = directory_iterator(env::font_folder());
- auto end = directory_iterator();
- for(; fonts != end; ++fonts)
+ const char* fontname = FT_Get_Postscript_Name(face); //this doesn't work for .fon fonts. Ignoring those for now
+ if(fontname != nullptr)
{
- auto file = (*fonts);
- if(is_regular_file(file.path()))
- {
- FT_Face face;
- err = FT_New_Face(lib, u8(file.path().native()).c_str(), 0, &face);
- if(err)
- continue;
-
- const char* fontname = FT_Get_Postscript_Name(face); //this doesn't work for .fon fonts. Ignoring those for now
- if(fontname != nullptr)
- {
- std::string fontname_str(fontname);
- result.insert(std::make_pair(u16(fontname_str), u16(file.path().native())));
- }
-
- FT_Done_Face(face);
- }
+ std::string fontname_str(fontname);
+ result.insert(std::make_pair(u16(fontname_str), u16(file.path().native())));
}
- FT_Done_FreeType(lib);
-
- return result;
+ FT_Done_Face(face);
}
+ }
- void init(module_dependencies dependencies)
- {
- fonts = enumerate_fonts();
- dependencies.producer_registry->register_producer_factory(create_text_producer);
- }
+ FT_Done_FreeType(lib);
- text_info& find_font_file(text_info& info)
- {
- auto& font_name = info.font;
- auto it = std::find_if(fonts.begin(), fonts.end(), font_comparer(font_name));
- info.font_file = (it != fonts.end()) ? (*it).second : L"";
- return info;
- }
- }
+ return result;
+}
+
+void describe_text_producer(help_sink&, const help_repository&);
+spl::shared_ptr<frame_producer> create_text_producer(const frame_producer_dependencies&, const std::vector<std::wstring>&);
+
+void init(module_dependencies dependencies)
+{
+ fonts = enumerate_fonts();
+ dependencies.producer_registry->register_producer_factory(L"Text Producer", create_text_producer, describe_text_producer);
+}
+
+text_info& find_font_file(text_info& info)
+{
+ auto& font_name = info.font;
+ auto it = std::find_if(fonts.begin(), fonts.end(), font_comparer(font_name));
+ info.font_file = (it != fonts.end()) ? (*it).second : L"";
+ return info;
+}
+
+} // namespace text
struct text_producer::impl
{
return spl::make_shared<text_producer>(frame_factory, x, y, str, text_info, parent_width, parent_height, standalone);
}
+namespace text {
+
+void describe_text_producer(help_sink& sink, const help_repository& repo)
+{
+ sink.short_description(L"A producer for rendering dynamic text.");
+ sink.syntax(L"[TEXT] [text:string] {[x:int] [y:int]} {FONT [font:string]|verdana} {SIZE [size:float]|30.0} {COLOR [color:string]|#ffffffff} {STANDALONE [standalone:0,1]|0}");
+ sink.para()
+ ->text(L"Renders dynamic text using fonts found under the ")->code(L"fonts")->text(L" folder. ")
+ ->text(L"Parameters:");
+ sink.definitions()
+ ->item(L"text", L"The text to display. Can be changed later via CALL as well.")
+ ->item(L"x", L"The x position of the text.")
+ ->item(L"y", L"The y position of the text.")
+ ->item(L"font", L"The name of the font (not the actual filename, but the font name).")
+ ->item(L"size", L"The point size.")
+ ->item(L"color", L"The color as an ARGB hex value.")
+ ->item(L"standalone", L"Whether to normalize coordinates or not.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 [TEXT] \"John Doe\" 0 0 FONT ArialMT SIZE 30 COLOR #1b698d STANDALONE 1");
+ sink.example(L">> CALL 1-10 \"Jane Doe\"", L"for modifying the text while playing.");
+}
spl::shared_ptr<frame_producer> create_text_producer(const frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
standalone);
}
-}}
+}}}
spl::unique_ptr<impl> impl_;
};
-spl::shared_ptr<frame_producer> create_text_producer(const frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
-
-
}}
{
dependencies.consumer_registry->register_consumer_factory(create_consumer);
dependencies.consumer_registry->register_preconfigured_consumer_factory(L"decklink", create_preconfigured_consumer);
- dependencies.producer_registry->register_producer_factory(create_producer);
+ dependencies.producer_registry->register_producer_factory(L"Decklink Producer", create_producer, describe_producer);
dependencies.system_info_provider_repo->register_system_info_provider([](boost::property_tree::wptree& info)
{
info.add(L"system.decklink.version", version());
#include <core/producer/frame_producer.h>
#include <core/monitor/monitor.h>
#include <core/mixer/audio/audio_mixer.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <tbb/concurrent_queue.h>
}
};
+void describe_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Allows video sources to be input from BlackMagic Design cards.");
+ sink.syntax(L"DECKLINK [device:int],DEVICE [device:int] {FILTER [filter:string]} {LENGTH [length:int]} {FORMAT [format:string]}");
+ sink.para()->text(L"Allows video sources to be input from BlackMagic Design cards. Parameters:");
+ sink.definitions()
+ ->item(L"device", L"The decklink device to stream the input from. See the Blackmagic control panel for the order of devices in your system.")
+ ->item(L"filter", L"If specified, sets an FFmpeg video filter to use.")
+ ->item(L"length", L"Optionally specify a limit on how many frames to produce.")
+ ->item(L"format", L"Specifies what video format to expect on the incoming SDI/HDMI signal. If not specified the video format of the channel is assumed.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2", L"Play using decklink device 2 expecting the video signal to have the same video format as the channel.");
+ sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2 FORMAT PAL FILTER yadif=1:-1", L"Play using decklink device 2 expecting the video signal to be in PAL and deinterlace it.");
+ sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2 LENGTH 1000", L"Play using decklink device 2 but only produce 1000 frames.");
+}
+
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
if(params.empty() || !boost::iequals(params.at(0), "decklink"))
#include <vector>
namespace caspar { namespace decklink {
-
+
+void describe_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
}}
dependencies.consumer_registry->register_consumer_factory(create_streaming_consumer);
dependencies.consumer_registry->register_preconfigured_consumer_factory(L"file", create_preconfigured_consumer);
dependencies.consumer_registry->register_preconfigured_consumer_factory(L"stream", create_preconfigured_streaming_consumer);
- dependencies.producer_registry->register_producer_factory(create_producer);
+ dependencies.producer_registry->register_producer_factory(L"FFmpeg Producer", create_producer, describe_producer);
dependencies.media_info_repo->register_extractor(
[](const std::wstring& file, const std::wstring& extension, core::media_info& info) -> bool
#include <common/diagnostics/graph.h>
#include <common/future.h>
#include <common/timer.h>
+#include <common/assert.h>
#include <core/video_format.h>
#include <core/producer/frame_producer.h>
#include <core/frame/draw_frame.h>
#include <core/frame/frame_transform.h>
#include <core/monitor/monitor.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <boost/algorithm/string.hpp>
-#include <common/assert.h>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/regex.hpp>
}
};
+void describe_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"A producer for playing media files supported by FFmpeg.");
+ sink.syntax(L"[clip:string] {[loop:LOOP]} {START,SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]}");
+ sink.para()
+ ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ")
+ ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ")
+ ->text(L"H.264, FLV, WMV and several audio codecs as well as uncompressed audio.");
+ sink.definitions()
+ ->item(L"clip", L"The file without the file extension to play. It should reside under the media folder.")
+ ->item(L"loop", L"Will cause the media file to loop between start and start + length")
+ ->item(L"start", L"Optionally sets the start frame. 0 by default. If loop is specified this will be the frame where it starts over again.")
+ ->item(L"length", L"Optionally sets the length of the clip. If not specified the clip will be played to the end. If loop is specified the file will jump to start position once this number of frames has been played.")
+ ->item(L"filter", L"If specified, will be used as an FFmpeg video filter.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 folder/clip", L"to play all frames in a clip and stop at the last frame.");
+ sink.example(L">> PLAY 1-10 folder/clip LOOP", L"to loop a clip between the first frame and the last frame.");
+ sink.example(L">> PLAY 1-10 folder/clip LOOP START 10", L"to loop a clip between frame 10 and the last frame.");
+ sink.example(L">> PLAY 1-10 folder/clip LOOP START 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60.");
+ sink.example(L">> PLAY 1-10 folder/clip START 10 LENGTH 50", L"to play frames 10-60 in a clip and stop.");
+ sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video.");
+ sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via CALL:");
+ sink.example(L">> CALL 1-10 LOOP 1");
+ sink.example(L">> CALL 1-10 START 10");
+ sink.example(L">> CALL 1-10 LENGTH 50");
+}
+
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
auto filename = probe_stem(env::media_folder() + L"/" + params.at(0));
namespace caspar { namespace ffmpeg {
+void describe_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
}}
\ No newline at end of file
#include <core/system_info_provider.h>
#include <core/frame/frame_factory.h>
#include <core/video_format.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/noncopyable.hpp>
}
};
+void describe_ct_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Plays compressed flash templates (.ct files).");
+ sink.syntax(L"[ct_file:string]");
+ sink.para()->text(L"Plays compressed flash templates (.ct files). The file should reside under the media folder.");
+ sink.para()->text(L"A ct file is a zip file containing a flash template (.ft), an XML file with template data and media files.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 folder/ct_file");
+}
+
spl::shared_ptr<core::frame_producer> create_ct_producer(
const core::frame_producer_dependencies& dependencies,
const std::vector<std::wstring>& params)
void init(core::module_dependencies dependencies)
{
- dependencies.producer_registry->register_producer_factory(create_ct_producer);
- dependencies.producer_registry->register_producer_factory(create_swf_producer);
+ dependencies.producer_registry->register_producer_factory(L"Flash Producer (.ct)", create_ct_producer, describe_ct_producer);
+ dependencies.producer_registry->register_producer_factory(L"Flash Producer (.swf)", create_swf_producer, describe_swf_producer);
dependencies.media_info_repo->register_extractor([](const std::wstring& file, const std::wstring& extension, core::media_info& info)
{
if (extension != L".CT" && extension != L".SWF")
#include <core/frame/pixel_format.h>
#include <core/producer/frame_producer.h>
#include <core/monitor/monitor.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <common/env.h>
#include <common/executor.h>
return create_destroy_proxy(spl::make_shared<flash_producer>(dependencies.frame_factory, dependencies.format_desc, filename, template_host.width, template_host.height));
}
+void describe_swf_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Plays flash files (.swf files).");
+ sink.syntax(L"[swf_file:string]");
+ sink.para()->text(L"Plays flash files (.swf files). The file should reside under the media folder.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 folder/swf_file");
+}
+
spl::shared_ptr<core::frame_producer> create_swf_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
auto filename = env::media_folder() + L"\\" + params.at(0) + L".swf";
namespace caspar { namespace flash {
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
+void describe_swf_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_swf_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
std::wstring find_template(const std::wstring& templateName);
void init(core::module_dependencies dependencies)
{
- dependencies.producer_registry->register_producer_factory(html::create_producer);
+ dependencies.producer_registry->register_producer_factory(L"HTML Producer", html::create_producer, html::describe_producer);
CefMainArgs main_args;
g_cef_executor.reset(new executor(L"cef"));
#include <core/frame/frame.h>
#include <core/frame/pixel_format.h>
#include <core/frame/geometry.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <common/assert.h>
#include <common/env.h>
#pragma comment (lib, "libcef.lib")
#pragma comment (lib, "libcef_dll_wrapper.lib")
-namespace caspar {
- namespace html {
+namespace caspar { namespace html {
- class html_client
- : public CefClient
- , public CefRenderHandler
- , public CefLifeSpanHandler
- , public CefLoadHandler
+class html_client
+ : public CefClient
+ , public CefRenderHandler
+ , public CefLifeSpanHandler
+ , public CefLoadHandler
+{
+ std::wstring url_;
+ spl::shared_ptr<diagnostics::graph> graph_;
+ boost::timer tick_timer_;
+ boost::timer frame_timer_;
+ boost::timer paint_timer_;
+
+ spl::shared_ptr<core::frame_factory> frame_factory_;
+ core::video_format_desc format_desc_;
+ tbb::concurrent_queue<std::wstring> javascript_before_load_;
+ tbb::atomic<bool> loaded_;
+ tbb::atomic<bool> removed_;
+ tbb::atomic<bool> animation_frame_requested_;
+ std::queue<core::draw_frame> frames_;
+ mutable boost::mutex frames_mutex_;
+
+ core::draw_frame last_frame_;
+ core::draw_frame last_progressive_frame_;
+ mutable boost::mutex last_frame_mutex_;
+
+ CefRefPtr<CefBrowser> browser_;
+
+ executor executor_;
+
+public:
+
+ html_client(
+ spl::shared_ptr<core::frame_factory> frame_factory,
+ const core::video_format_desc& format_desc,
+ const std::wstring& url)
+ : url_(url)
+ , frame_factory_(std::move(frame_factory))
+ , format_desc_(format_desc)
+ , last_frame_(core::draw_frame::empty())
+ , last_progressive_frame_(core::draw_frame::empty())
+ , executor_(L"html_producer")
+ {
+ graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
+ graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
+ graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
+ graph_->set_text(print());
+ diagnostics::register_graph(graph_);
+
+ loaded_ = false;
+ removed_ = false;
+ animation_frame_requested_ = false;
+ executor_.begin_invoke([&]{ update(); });
+ }
+
+ core::draw_frame receive()
+ {
+ auto frame = last_frame();
+ executor_.begin_invoke([&]{ update(); });
+ return frame;
+ }
+
+ core::draw_frame last_frame() const
+ {
+ return lock(last_frame_mutex_, [&]
{
- std::wstring url_;
- spl::shared_ptr<diagnostics::graph> graph_;
- boost::timer tick_timer_;
- boost::timer frame_timer_;
- boost::timer paint_timer_;
-
- spl::shared_ptr<core::frame_factory> frame_factory_;
- core::video_format_desc format_desc_;
- tbb::concurrent_queue<std::wstring> javascript_before_load_;
- tbb::atomic<bool> loaded_;
- tbb::atomic<bool> removed_;
- tbb::atomic<bool> animation_frame_requested_;
- std::queue<core::draw_frame> frames_;
- mutable boost::mutex frames_mutex_;
-
- core::draw_frame last_frame_;
- core::draw_frame last_progressive_frame_;
- mutable boost::mutex last_frame_mutex_;
-
- CefRefPtr<CefBrowser> browser_;
-
- executor executor_;
-
- public:
-
- html_client(
- spl::shared_ptr<core::frame_factory> frame_factory,
- const core::video_format_desc& format_desc,
- const std::wstring& url)
- : url_(url)
- , frame_factory_(std::move(frame_factory))
- , format_desc_(format_desc)
- , last_frame_(core::draw_frame::empty())
- , last_progressive_frame_(core::draw_frame::empty())
- , executor_(L"html_producer")
- {
- graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
- graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
- graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
- graph_->set_text(print());
- diagnostics::register_graph(graph_);
-
- loaded_ = false;
- removed_ = false;
- animation_frame_requested_ = false;
- executor_.begin_invoke([&]{ update(); });
- }
+ return last_frame_;
+ });
+ }
- core::draw_frame receive()
- {
- auto frame = last_frame();
- executor_.begin_invoke([&]{ update(); });
- return frame;
- }
+ void execute_javascript(const std::wstring& javascript)
+ {
+ if (!loaded_)
+ {
+ javascript_before_load_.push(javascript);
+ }
+ else
+ {
+ execute_queued_javascript();
+ do_execute_javascript(javascript);
+ }
+ }
- core::draw_frame last_frame() const
- {
- return lock(last_frame_mutex_, [&]
- {
- return last_frame_;
- });
- }
+ CefRefPtr<CefBrowserHost> get_browser_host()
+ {
+ return browser_->GetHost();
+ }
- void execute_javascript(const std::wstring& javascript)
- {
- if (!loaded_)
- {
- javascript_before_load_.push(javascript);
- }
- else
- {
- execute_queued_javascript();
- do_execute_javascript(javascript);
- }
- }
+ void close()
+ {
+ if (!animation_frame_requested_)
+ CASPAR_LOG(warning) << print()
+ << " window.requestAnimationFrame() never called. "
+ << "Animations might have been laggy";
- CefRefPtr<CefBrowserHost> get_browser_host()
+ html::invoke([=]
+ {
+ if (browser_ != nullptr)
{
- return browser_->GetHost();
+ browser_->GetHost()->CloseBrowser(true);
}
+ });
+ }
- void close()
- {
- if (!animation_frame_requested_)
- CASPAR_LOG(warning) << print()
- << " window.requestAnimationFrame() never called. "
- << "Animations might have been laggy";
+ void remove()
+ {
+ close();
+ removed_ = true;
+ }
- html::invoke([=]
- {
- if (browser_ != nullptr)
- {
- browser_->GetHost()->CloseBrowser(true);
- }
- });
- }
+ bool is_removed() const
+ {
+ return removed_;
+ }
- void remove()
- {
- close();
- removed_ = true;
- }
+private:
- bool is_removed() const
- {
- return removed_;
- }
+ bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
+ {
+ CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
- private:
+ rect = CefRect(0, 0, format_desc_.square_width, format_desc_.square_height);
+ return true;
+ }
- bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
- {
- CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+ void OnPaint(
+ CefRefPtr<CefBrowser> browser,
+ PaintElementType type,
+ const RectList &dirtyRects,
+ const void *buffer,
+ int width,
+ int height)
+ {
+ graph_->set_value("browser-tick-time", paint_timer_.elapsed()
+ * format_desc_.fps
+ * format_desc_.field_count
+ * 0.5);
+ paint_timer_.restart();
+ CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+
+ boost::timer copy_timer;
+ core::pixel_format_desc pixel_desc;
+ pixel_desc.format = core::pixel_format::bgra;
+ pixel_desc.planes.push_back(
+ core::pixel_format_desc::plane(width, height, 4));
+ auto frame = frame_factory_->create_frame(this, pixel_desc);
+ A_memcpy(frame.image_data().begin(), buffer, width * height * 4);
+
+ lock(frames_mutex_, [&]
+ {
+ frames_.push(core::draw_frame(std::move(frame)));
- rect = CefRect(0, 0, format_desc_.square_width, format_desc_.square_height);
- return true;
- }
+ size_t max_in_queue = format_desc_.field_count;
- void OnPaint(
- CefRefPtr<CefBrowser> browser,
- PaintElementType type,
- const RectList &dirtyRects,
- const void *buffer,
- int width,
- int height)
+ while (frames_.size() > max_in_queue)
{
- graph_->set_value("browser-tick-time", paint_timer_.elapsed()
- * format_desc_.fps
- * format_desc_.field_count
- * 0.5);
- paint_timer_.restart();
- CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
-
- boost::timer copy_timer;
- core::pixel_format_desc pixel_desc;
- pixel_desc.format = core::pixel_format::bgra;
- pixel_desc.planes.push_back(
- core::pixel_format_desc::plane(width, height, 4));
- auto frame = frame_factory_->create_frame(this, pixel_desc);
- A_memcpy(frame.image_data().begin(), buffer, width * height * 4);
-
- lock(frames_mutex_, [&]
- {
- frames_.push(core::draw_frame(std::move(frame)));
+ frames_.pop();
+ graph_->set_tag("dropped-frame");
+ }
+ });
+ graph_->set_value("copy-time", copy_timer.elapsed()
+ * format_desc_.fps
+ * format_desc_.field_count
+ * 0.5);
+ }
- size_t max_in_queue = format_desc_.field_count;
+ void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
+ {
+ CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
- while (frames_.size() > max_in_queue)
- {
- frames_.pop();
- graph_->set_tag("dropped-frame");
- }
- });
- graph_->set_value("copy-time", copy_timer.elapsed()
- * format_desc_.fps
- * format_desc_.field_count
- * 0.5);
- }
+ browser_ = browser;
+ }
- void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
- {
- CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+ void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
+ {
+ CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
- browser_ = browser;
- }
+ browser_ = nullptr;
+ }
- void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
- {
- CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+ bool DoClose(CefRefPtr<CefBrowser> browser) override
+ {
+ CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
- browser_ = nullptr;
- }
+ return false;
+ }
- bool DoClose(CefRefPtr<CefBrowser> browser) override
- {
- CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+ CefRefPtr<CefRenderHandler> GetRenderHandler() override
+ {
+ return this;
+ }
- return false;
- }
+ CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
+ {
+ return this;
+ }
- CefRefPtr<CefRenderHandler> GetRenderHandler() override
- {
- return this;
- }
+ CefRefPtr<CefLoadHandler> GetLoadHandler() override {
+ return this;
+ }
- CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
- {
- return this;
- }
+ void OnLoadEnd(
+ CefRefPtr<CefBrowser> browser,
+ CefRefPtr<CefFrame> frame,
+ int httpStatusCode) override
+ {
+ loaded_ = true;
+ execute_queued_javascript();
+ }
- CefRefPtr<CefLoadHandler> GetLoadHandler() override {
- return this;
- }
+ bool OnProcessMessageReceived(
+ CefRefPtr<CefBrowser> browser,
+ CefProcessId source_process,
+ CefRefPtr<CefProcessMessage> message) override
+ {
+ auto name = message->GetName().ToString();
- void OnLoadEnd(
- CefRefPtr<CefBrowser> browser,
- CefRefPtr<CefFrame> frame,
- int httpStatusCode) override
- {
- loaded_ = true;
- execute_queued_javascript();
- }
+ if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
+ {
+ CASPAR_LOG(trace)
+ << print() << L" Requested animation frame";
+ animation_frame_requested_ = true;
- bool OnProcessMessageReceived(
- CefRefPtr<CefBrowser> browser,
- CefProcessId source_process,
- CefRefPtr<CefProcessMessage> message) override
- {
- auto name = message->GetName().ToString();
+ return true;
+ }
+ else if (name == REMOVE_MESSAGE_NAME)
+ {
+ remove();
- if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
- {
- CASPAR_LOG(trace)
- << print() << L" Requested animation frame";
- animation_frame_requested_ = true;
+ return true;
+ }
+ else if (name == LOG_MESSAGE_NAME)
+ {
+ auto args = message->GetArgumentList();
+ auto severity =
+ static_cast<boost::log::trivial::severity_level>(args->GetInt(0));
+ auto msg = args->GetString(1).ToWString();
+
+ BOOST_LOG_STREAM_WITH_PARAMS(
+ log::logger::get(),
+ (boost::log::keywords::severity = severity))
+ << print() << L" [renderer_process] " << msg;
+ }
- return true;
- }
- else if (name == REMOVE_MESSAGE_NAME)
- {
- remove();
+ return false;
+ }
- return true;
- }
- else if (name == LOG_MESSAGE_NAME)
- {
- auto args = message->GetArgumentList();
- auto severity =
- static_cast<boost::log::trivial::severity_level>(args->GetInt(0));
- auto msg = args->GetString(1).ToWString();
-
- BOOST_LOG_STREAM_WITH_PARAMS(
- log::logger::get(),
- (boost::log::keywords::severity = severity))
- << print() << L" [renderer_process] " << msg;
- }
-
- return false;
- }
+ void invoke_requested_animation_frames()
+ {
+ if (browser_)
+ browser_->SendProcessMessage(
+ CefProcessId::PID_RENDERER,
+ CefProcessMessage::Create(TICK_MESSAGE_NAME));
+ graph_->set_value("tick-time", tick_timer_.elapsed()
+ * format_desc_.fps
+ * format_desc_.field_count
+ * 0.5);
+ tick_timer_.restart();
+ }
- void invoke_requested_animation_frames()
+ bool try_pop(core::draw_frame& result)
+ {
+ return lock(frames_mutex_, [&]() -> bool
+ {
+ if (!frames_.empty())
{
- if (browser_)
- browser_->SendProcessMessage(
- CefProcessId::PID_RENDERER,
- CefProcessMessage::Create(TICK_MESSAGE_NAME));
- graph_->set_value("tick-time", tick_timer_.elapsed()
- * format_desc_.fps
- * format_desc_.field_count
- * 0.5);
- tick_timer_.restart();
+ result = std::move(frames_.front());
+ frames_.pop();
+
+ return true;
}
- bool try_pop(core::draw_frame& result)
- {
- return lock(frames_mutex_, [&]() -> bool
- {
- if (!frames_.empty())
- {
- result = std::move(frames_.front());
- frames_.pop();
+ return false;
+ });
+ }
- return true;
- }
+ core::draw_frame pop()
+ {
+ core::draw_frame frame;
- return false;
- });
- }
+ if (!try_pop(frame))
+ {
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " No frame in buffer"));
+ }
- core::draw_frame pop()
- {
- core::draw_frame frame;
+ return frame;
+ }
- if (!try_pop(frame))
- {
- BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " No frame in buffer"));
- }
+ void update()
+ {
+ invoke_requested_animation_frames();
- return frame;
- }
+ prec_timer timer;
+ timer.tick(0.0);
- void update()
+ auto num_frames = lock(frames_mutex_, [&]
+ {
+ return frames_.size();
+ });
+
+ if (num_frames >= format_desc_.field_count)
+ {
+ if (format_desc_.field_mode != core::field_mode::progressive)
{
+ auto frame1 = pop();
+
+ executor_.yield(caspar::task_priority::lowest_priority);
+ timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
invoke_requested_animation_frames();
- prec_timer timer;
- timer.tick(0.0);
+ auto frame2 = pop();
- auto num_frames = lock(frames_mutex_, [&]
+ lock(last_frame_mutex_, [&]
{
- return frames_.size();
+ last_progressive_frame_ = frame2;
+ last_frame_ = core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode);
});
-
- if (num_frames >= format_desc_.field_count)
- {
- if (format_desc_.field_mode != core::field_mode::progressive)
- {
- auto frame1 = pop();
-
- executor_.yield(caspar::task_priority::lowest_priority);
- timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
- invoke_requested_animation_frames();
-
- auto frame2 = pop();
-
- lock(last_frame_mutex_, [&]
- {
- last_progressive_frame_ = frame2;
- last_frame_ = core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode);
- });
- }
- else
- {
- auto frame = pop();
-
- lock(last_frame_mutex_, [&]
- {
- last_frame_ = frame;
- });
- }
- }
- else if (num_frames == 1) // Interlaced but only one frame
- { // available. Probably the last frame
- // of some animation sequence.
- auto frame = pop();
-
- lock(last_frame_mutex_, [&]
- {
- last_progressive_frame_ = frame;
- last_frame_ = frame;
- });
-
- timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
- invoke_requested_animation_frames();
- }
- else
- {
- graph_->set_tag("late-frame");
-
- if (format_desc_.field_mode != core::field_mode::progressive)
- {
- lock(last_frame_mutex_, [&]
- {
- last_frame_ = last_progressive_frame_;
- });
-
- timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
- invoke_requested_animation_frames();
- }
- }
}
-
- void do_execute_javascript(const std::wstring& javascript)
+ else
{
- html::begin_invoke([=]
+ auto frame = pop();
+
+ lock(last_frame_mutex_, [&]
{
- if (browser_ != nullptr)
- browser_->GetMainFrame()->ExecuteJavaScript(u8(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
+ last_frame_ = frame;
});
}
+ }
+ else if (num_frames == 1) // Interlaced but only one frame
+ { // available. Probably the last frame
+ // of some animation sequence.
+ auto frame = pop();
- void execute_queued_javascript()
+ lock(last_frame_mutex_, [&]
{
- std::wstring javascript;
+ last_progressive_frame_ = frame;
+ last_frame_ = frame;
+ });
- while (javascript_before_load_.try_pop(javascript))
- do_execute_javascript(javascript);
- }
+ timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
+ invoke_requested_animation_frames();
+ }
+ else
+ {
+ graph_->set_tag("late-frame");
- std::wstring print() const
+ if (format_desc_.field_mode != core::field_mode::progressive)
{
- return L"html[" + url_ + L"]";
- }
+ lock(last_frame_mutex_, [&]
+ {
+ last_frame_ = last_progressive_frame_;
+ });
- IMPLEMENT_REFCOUNTING(html_client);
- };
+ timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
+ invoke_requested_animation_frames();
+ }
+ }
+ }
- class html_producer
- : public core::frame_producer_base
+ void do_execute_javascript(const std::wstring& javascript)
+ {
+ html::begin_invoke([=]
{
- core::monitor::subject monitor_subject_;
- const std::wstring url_;
- core::constraints constraints_;
-
- CefRefPtr<html_client> client_;
-
- public:
- html_producer(
- const spl::shared_ptr<core::frame_factory>& frame_factory,
- const core::video_format_desc& format_desc,
- const std::wstring& url)
- : url_(url)
- {
- constraints_.width.set(format_desc.square_width);
- constraints_.height.set(format_desc.square_height);
+ if (browser_ != nullptr)
+ browser_->GetMainFrame()->ExecuteJavaScript(u8(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
+ });
+ }
- html::invoke([&]
- {
- client_ = new html_client(frame_factory, format_desc, url_);
+ void execute_queued_javascript()
+ {
+ std::wstring javascript;
- CefWindowInfo window_info;
+ while (javascript_before_load_.try_pop(javascript))
+ do_execute_javascript(javascript);
+ }
- window_info.SetTransparentPainting(true);
- window_info.SetAsOffScreen(nullptr);
- //window_info.SetAsWindowless(nullptr, true);
-
- CefBrowserSettings browser_settings;
- CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
- });
- }
+ std::wstring print() const
+ {
+ return L"html[" + url_ + L"]";
+ }
- ~html_producer()
- {
- if (client_)
- client_->close();
- }
+ IMPLEMENT_REFCOUNTING(html_client);
+};
+
+class html_producer
+ : public core::frame_producer_base
+{
+ core::monitor::subject monitor_subject_;
+ const std::wstring url_;
+ core::constraints constraints_;
+
+ CefRefPtr<html_client> client_;
+
+public:
+ html_producer(
+ const spl::shared_ptr<core::frame_factory>& frame_factory,
+ const core::video_format_desc& format_desc,
+ const std::wstring& url)
+ : url_(url)
+ {
+ constraints_.width.set(format_desc.square_width);
+ constraints_.height.set(format_desc.square_height);
+
+ html::invoke([&]
+ {
+ client_ = new html_client(frame_factory, format_desc, url_);
- // frame_producer
+ CefWindowInfo window_info;
- std::wstring name() const override
- {
- return L"html";
- }
+ window_info.SetTransparentPainting(true);
+ window_info.SetAsOffScreen(nullptr);
+ //window_info.SetAsWindowless(nullptr, true);
+
+ CefBrowserSettings browser_settings;
+ CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
+ });
+ }
- void on_interaction(const core::interaction_event::ptr& event) override
- {
- if (core::is<core::mouse_move_event>(event))
- {
- auto move = core::as<core::mouse_move_event>(event);
- int x = static_cast<int>(move->x * constraints_.width.get());
- int y = static_cast<int>(move->y * constraints_.height.get());
-
- CefMouseEvent e;
- e.x = x;
- e.y = y;
- client_->get_browser_host()->SendMouseMoveEvent(e, false);
- }
- else if (core::is<core::mouse_button_event>(event))
- {
- auto button = core::as<core::mouse_button_event>(event);
- int x = static_cast<int>(button->x * constraints_.width.get());
- int y = static_cast<int>(button->y * constraints_.height.get());
-
- CefMouseEvent e;
- e.x = x;
- e.y = y;
- client_->get_browser_host()->SendMouseClickEvent(
- e,
- static_cast<CefBrowserHost::MouseButtonType>(button->button),
- !button->pressed,
- 1);
- }
- else if (core::is<core::mouse_wheel_event>(event))
- {
- auto wheel = core::as<core::mouse_wheel_event>(event);
- int x = static_cast<int>(wheel->x * constraints_.width.get());
- int y = static_cast<int>(wheel->y * constraints_.height.get());
-
- CefMouseEvent e;
- e.x = x;
- e.y = y;
- static const int WHEEL_TICKS_AMPLIFICATION = 40;
- client_->get_browser_host()->SendMouseWheelEvent(
- e,
- 0, // delta_x
- wheel->ticks_delta * WHEEL_TICKS_AMPLIFICATION); // delta_y
- }
- }
+ ~html_producer()
+ {
+ if (client_)
+ client_->close();
+ }
- bool collides(double x, double y) const override
- {
- return true;
- }
+ // frame_producer
- core::draw_frame receive_impl() override
- {
- if (client_)
- {
- if (client_->is_removed())
- {
- client_ = nullptr;
- return core::draw_frame::empty();
- }
+ std::wstring name() const override
+ {
+ return L"html";
+ }
+
+ void on_interaction(const core::interaction_event::ptr& event) override
+ {
+ if (core::is<core::mouse_move_event>(event))
+ {
+ auto move = core::as<core::mouse_move_event>(event);
+ int x = static_cast<int>(move->x * constraints_.width.get());
+ int y = static_cast<int>(move->y * constraints_.height.get());
+
+ CefMouseEvent e;
+ e.x = x;
+ e.y = y;
+ client_->get_browser_host()->SendMouseMoveEvent(e, false);
+ }
+ else if (core::is<core::mouse_button_event>(event))
+ {
+ auto button = core::as<core::mouse_button_event>(event);
+ int x = static_cast<int>(button->x * constraints_.width.get());
+ int y = static_cast<int>(button->y * constraints_.height.get());
+
+ CefMouseEvent e;
+ e.x = x;
+ e.y = y;
+ client_->get_browser_host()->SendMouseClickEvent(
+ e,
+ static_cast<CefBrowserHost::MouseButtonType>(button->button),
+ !button->pressed,
+ 1);
+ }
+ else if (core::is<core::mouse_wheel_event>(event))
+ {
+ auto wheel = core::as<core::mouse_wheel_event>(event);
+ int x = static_cast<int>(wheel->x * constraints_.width.get());
+ int y = static_cast<int>(wheel->y * constraints_.height.get());
+
+ CefMouseEvent e;
+ e.x = x;
+ e.y = y;
+ static const int WHEEL_TICKS_AMPLIFICATION = 40;
+ client_->get_browser_host()->SendMouseWheelEvent(
+ e,
+ 0, // delta_x
+ wheel->ticks_delta * WHEEL_TICKS_AMPLIFICATION); // delta_y
+ }
+ }
- return client_->receive();
- }
+ bool collides(double x, double y) const override
+ {
+ return true;
+ }
+ core::draw_frame receive_impl() override
+ {
+ if (client_)
+ {
+ if (client_->is_removed())
+ {
+ client_ = nullptr;
return core::draw_frame::empty();
}
- std::future<std::wstring> call(const std::vector<std::wstring>& params) override
- {
- if (!client_)
- return make_ready_future(std::wstring(L""));
+ return client_->receive();
+ }
- auto javascript = params.at(0);
+ return core::draw_frame::empty();
+ }
- client_->execute_javascript(javascript);
+ std::future<std::wstring> call(const std::vector<std::wstring>& params) override
+ {
+ if (!client_)
+ return make_ready_future(std::wstring(L""));
- return make_ready_future(std::wstring(L""));
- }
+ auto javascript = params.at(0);
- std::wstring print() const override
- {
- return L"html[" + url_ + L"]";
- }
+ client_->execute_javascript(javascript);
- boost::property_tree::wptree info() const override
- {
- boost::property_tree::wptree info;
- info.add(L"type", L"html-producer");
- return info;
- }
+ return make_ready_future(std::wstring(L""));
+ }
- core::constraints& pixel_constraints() override
- {
- return constraints_;
- }
+ std::wstring print() const override
+ {
+ return L"html[" + url_ + L"]";
+ }
- core::monitor::subject& monitor_output()
- {
- return monitor_subject_;
- }
- };
+ boost::property_tree::wptree info() const override
+ {
+ boost::property_tree::wptree info;
+ info.add(L"type", L"html-producer");
+ return info;
+ }
- spl::shared_ptr<core::frame_producer> create_producer(
- const core::frame_producer_dependencies& dependencies,
- const std::vector<std::wstring>& params)
- {
- const auto filename = env::template_folder() + params.at(0) + L".html";
- const auto found_filename = find_case_insensitive(filename);
+ core::constraints& pixel_constraints() override
+ {
+ return constraints_;
+ }
+
+ core::monitor::subject& monitor_output()
+ {
+ return monitor_subject_;
+ }
+};
+
+void describe_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Renders a web page in real time.");
+ sink.syntax(L"{[html_filename:string]},{[HTML] [url:string]}");
+ sink.para()->text(L"Embeds an actual web browser and renders the content in realtime.");
+ sink.para()
+ ->text(L"HTML content can either be stored locally under the ")->code(L"templates")
+ ->text(L" folder or fetched directly via an URL. If a .html file is found with the name ")
+ ->code(L"html_filename")->text(L" under the ")->code(L"templates")->text(L" folder it will be rendered. If the ")
+ ->code(L"[HTML] url")->text(L" syntax is used instead, the URL will be loaded.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 [HTML] http://www.casparcg.com");
+ sink.example(L">> PLAY 1-10 folder/html_file");
+}
+
+spl::shared_ptr<core::frame_producer> create_producer(
+ const core::frame_producer_dependencies& dependencies,
+ const std::vector<std::wstring>& params)
+{
+ const auto filename = env::template_folder() + params.at(0) + L".html";
+ const auto found_filename = find_case_insensitive(filename);
- if (!found_filename && !boost::iequals(params.at(0), L"[HTML]"))
- return core::frame_producer::empty();
+ if (!found_filename && !boost::iequals(params.at(0), L"[HTML]"))
+ return core::frame_producer::empty();
- const auto url = found_filename
- ? L"file://" + *found_filename
- : params.at(1);
+ const auto url = found_filename
+ ? L"file://" + *found_filename
+ : params.at(1);
- if (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
- return core::frame_producer::empty();
+ if (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
+ return core::frame_producer::empty();
- return core::create_destroy_proxy(spl::make_shared<html_producer>(
- dependencies.frame_factory,
- dependencies.format_desc,
- url));
- }
- }
+ return core::create_destroy_proxy(spl::make_shared<html_producer>(
+ dependencies.frame_factory,
+ dependencies.format_desc,
+ url));
}
+
+}}
namespace caspar { namespace html {
+void describe_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_producer(
const core::frame_producer_dependencies& dependencies,
const std::vector<std::wstring>& params);
void init(core::module_dependencies dependencies)
{
FreeImage_Initialise();
- dependencies.producer_registry->register_producer_factory(create_scroll_producer);
- dependencies.producer_registry->register_producer_factory(create_producer);
+ dependencies.producer_registry->register_producer_factory(L"Image Scroll Producer", create_scroll_producer, describe_scroll_producer);
+ dependencies.producer_registry->register_producer_factory(L"Image Producer", create_producer, describe_producer);
dependencies.producer_registry->register_thumbnail_producer_factory(create_thumbnail_producer);
dependencies.consumer_registry->register_consumer_factory(create_consumer);
dependencies.media_info_repo->register_extractor([](const std::wstring& file, const std::wstring& extension, core::media_info& info)
#include <core/frame/frame_factory.h>
#include <core/frame/pixel_format.h>
#include <core/monitor/monitor.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
#include <common/env.h>
#include <common/log.h>
}
};
+void describe_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Loads a still image.");
+ sink.syntax(L"{[image_file:string]},{[PNG_BASE64] [encoded:string]}");
+ sink.para()->text(L"Loads a still image, either from disk or via a base64 encoded image submitted via AMCP.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 image_file", L"Plays an image from the media folder.");
+ sink.example(L">> PLAY 1-10 [PNG_BASE64] data...", L"Plays a PNG image transferred as a base64 encoded string.");
+}
+
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
static const auto extensions = {
{
return caspar::image::create_producer(dependencies, params);
}
+
}}
namespace caspar { namespace image {
+void describe_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
#include <core/frame/frame_transform.h>
#include <core/frame/pixel_format.h>
#include <core/monitor/monitor.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
#include <common/env.h>
#include <common/log.h>
}
};
+void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Scrolls an image either horizontally or vertically.");
+ sink.syntax(L"[image_file:string] SPEED [speed:float] {BLUR [blur_px:int]} {[premultiply:PREMULTIPLY]} {[progressive:PROGRESSIVE]}");
+ sink.para()
+ ->text(L"Scrolls an image either horizontally or vertically. ")
+ ->text(L"It is the image dimensions that decide if it will be a vertical scroll or a horizontal scroll. ")
+ ->text(L"A horizontal scroll will be selected if the image height is exactly the same as the video format height. ")
+ ->text(L"A vertical scroll will be selected if the image width is exactly the same as the video format width.");
+ sink.definitions()
+ ->item(L"image_file", L"The image without extension. The file has to have either the same width or the same height as the video format.")
+ ->item(L"speed", L"A positive or negative float defining how many pixels to move the image each frame.")
+ ->item(L"blur_px", L"If specified, will do a directional blur in the scrolling direction by the given number of pixels.")
+ ->item(L"premultiply", L"If the image is in straight alpha, use this option to make it display correctly in CasparCG.")
+ ->item(L"progressive", L"When an interlaced video format is used, by default the image is moved every field. This can be overridden by specifying this option, causing the image to only move on full frames.");
+ sink.para()->text(L"If ")->code(L"SPEED [speed]")->text(L" is ommitted, the ordinary ")->see(L"Image Producer")->text(L" will be used instead.");
+ sink.example(L">> PLAY 1-10 cred_1280 SPEED 8 BLUR 2", L"Given that cred_1280 is a as wide as the video mode, this will create a rolling end credits with a little bit of blur and a speed of 8 pixels per frame.");
+}
+
spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
static const auto extensions = {
namespace caspar { namespace image {
+void describe_scroll_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_scroll_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
}}
\ No newline at end of file
#include <core/producer/scene/hotswap_producer.h>
#include <core/producer/media_info/media_info.h>
#include <core/frame/draw_frame.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <common/env.h>
#include <common/memory.h>
void init(core::module_dependencies dependencies)
{
- dependencies.producer_registry->register_producer_factory(create_psd_scene_producer);
- dependencies.media_info_repo->register_extractor(
- [](const std::wstring& file, const std::wstring& upper_case_extension, core::media_info& info)
- {
- if (upper_case_extension == L".PSD")
- {
- info.clip_type = L"STILL";
-
- return true;
- }
-
- return false;
- });
dependencies.cg_registry->register_cg_producer(
L"scene",
{ L".psd" },
#include <core/producer/frame_producer.h>
#include <core/video_channel.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
#include <boost/algorithm/string.hpp>
#include <boost/range/algorithm/find_if.hpp>
namespace caspar { namespace reroute {
-
+
+void describe_producer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Reroutes a complete channel or a layer to another layer.");
+ sink.syntax(L"route://[source_channel:int]{-[source_layer:int]}");
+ sink.para()->text(L"Reroutes the composited video of a channel or the untransformed video of a layer .");
+ sink.para()
+ ->text(L"If ")->code(L"source_layer")->text(L" is specified, only the video of the source layer is rerouted. ")
+ ->text(L"If on the other hand only ")->code(L"source_channel")->text(L" is specified, the video of the complete channel is rerouted.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 route://1-11", L"Play the contents of layer 1-11 on layer 1-10 as well.");
+ sink.example(L">> PLAY 1-10 route://2", L"Play the composited contents of channel 2 on layer 1-10 as well.");
+ sink.example(
+ L">> MIXER 1-10 FILL 0.02 0.01 0.9 0.9\n"
+ L">> PLAY 1-10 route://1\n"
+ L">> PLAY 1-9 AMB LOOP", L"Play the composited contents of channel 1 on layer 1-10. Since the source and destination channel is the same, an \"infinity\" effect is created.");
+ sink.para()->text(L"Always expect a few frames delay on the routed-to layer.");
+}
+
spl::shared_ptr<core::frame_producer> create_producer(
const core::frame_producer_dependencies& dependencies,
const std::vector<std::wstring>& params)
#include <string>
namespace caspar { namespace reroute {
-
+
+void describe_producer(core::help_sink& sink, const core::help_repository& repository);
spl::shared_ptr<core::frame_producer> create_producer(
const core::frame_producer_dependencies& dependencies,
const std::vector<std::wstring>& params);
void init(core::module_dependencies dependencies)
{
- dependencies.producer_registry->register_producer_factory(reroute::create_producer);
+ dependencies.producer_registry->register_producer_factory(L"Reroute Producer", reroute::create_producer, reroute::describe_producer);
}
}}
return L"202 DIAG OK\r\n";
}
-void help_describer(core::help_sink& sink, const core::help_repository& repository)
-{
- sink.short_description(L"Show online help.");
- 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.");
-}
+static const int WIDTH = 80;
-std::wstring help_command(command_context& ctx)
+struct max_width_sink : public core::help_sink
{
- static const int WIDTH = 80;
- struct max_width_sink : public core::help_sink
- {
- std::size_t max_width = 0;
+ 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
+ void begin_item(const std::wstring& name) override
{
- std::size_t width;
- std::wstringstream& out;
+ max_width = std::max(name.length(), max_width);
+ };
+};
- short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
+struct short_description_sink : public core::help_sink
+{
+ std::size_t width;
+ std::wstringstream& out;
- void begin_item(const std::wstring& name) override
- {
- out << std::left << std::setw(width + 1) << name;
- };
+ short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
- void short_description(const std::wstring& short_description) override
- {
- out << short_description << L"\r\n";
- };
+ void begin_item(const std::wstring& name) override
+ {
+ out << std::left << std::setw(width + 1) << name;
};
- struct simple_paragraph_builder : core::paragraph_builder
+ void short_description(const std::wstring& short_description) override
{
- 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> 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)); }
+ out << short_description << L"\r\n";
};
+};
+
+struct simple_paragraph_builder : core::paragraph_builder
+{
+ std::wostringstream out;
+ std::wstringstream& commit_to;
- struct simple_definition_list_builder : core::definition_list_builder
+ 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
{
- std::wstringstream& out;
+ 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> 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)); }
+};
- simple_definition_list_builder(std::wstringstream& out) : out(out) { }
- ~simple_definition_list_builder()
- {
- out << L"\n";
- }
+struct simple_definition_list_builder : core::definition_list_builder
+{
+ std::wstringstream& out;
- 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();
- }
- };
+ simple_definition_list_builder(std::wstringstream& out) : out(out) { }
+ ~simple_definition_list_builder()
+ {
+ out << L"\n";
+ }
- struct long_description_sink : public core::help_sink
+ spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
{
- std::wstringstream& out;
+ out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
+ out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
+ return shared_from_this();
+ }
+};
- long_description_sink(std::wstringstream& out) : out(out) { }
+struct long_description_sink : public core::help_sink
+{
+ std::wstringstream& out;
- void syntax(const std::wstring& syntax) override
- {
- out << L"Syntax:\n";
- out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
- };
+ long_description_sink(std::wstringstream& out) : out(out) { }
- spl::shared_ptr<core::paragraph_builder> para() override
- {
- return spl::make_shared<simple_paragraph_builder>(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::definition_list_builder> definitions() override
- {
- return spl::make_shared<simple_definition_list_builder>(out);
- }
+ spl::shared_ptr<core::paragraph_builder> para() override
+ {
+ return spl::make_shared<simple_paragraph_builder>(out);
+ }
- void example(const std::wstring& code, const std::wstring& caption = L"") override
- {
- out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
+ spl::shared_ptr<core::definition_list_builder> definitions() override
+ {
+ return spl::make_shared<simple_definition_list_builder>(out);
+ }
- if (!caption.empty())
- out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
+ void example(const std::wstring& code, const std::wstring& caption) override
+ {
+ out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
- out << L"\n";
- }
- private:
- void begin_item(const std::wstring& name) override
- {
- out << name << L"\n\n";
- };
+ 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)
- {
- result << L"200 HELP OK\r\n";
- max_width_sink width;
- ctx.help_repo->help({ L"AMCP" }, width);
- short_description_sink sink(width.max_width, result);
- sink.width = width.max_width;
- ctx.help_repo->help({ L"AMCP" }, sink);
- result << L"\r\n";
- }
+ return create_help_list(L"HELP", ctx, { L"AMCP" });
else
- {
- result << L"201 HELP OK\r\n";
- auto command = boost::to_upper_copy(
- ctx.parameters.size() == 2
- ? ctx.parameters.at(0) + L" " + ctx.parameters.at(1)
- : ctx.parameters.at(0));
- long_description_sink sink(result);
- ctx.help_repo->help({ L"AMCP" }, command, sink);
- result << L"\r\n";
- }
+ return create_help_details(L"HELP", ctx, { L"AMCP" });
+}
- return result.str();
+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 bye_describer(core::help_sink& sink, const core::help_repository& repo)
CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
}
-/*bool LockCommand::DoExecute()
-{
- try
- {
- auto it = parameters().begin();
-
- std::shared_ptr<caspar::IO::lock_container> lock;
- try
- {
- int channel_index = boost::lexical_cast<int>(*it) - 1;
- lock = channels().at(channel_index).lock;
- }
- catch(const boost::bad_lexical_cast&) {}
- catch(...)
- {
- SetReplyString(L"401 LOCK ERROR\r\n");
- return false;
- }
-
- if(lock)
- ++it;
-
- if(it == parameters().end()) //too few parameters
- {
- SetReplyString(L"402 LOCK ERROR\r\n");
- return false;
- }
-
- std::wstring command = boost::to_upper_copy(*it);
- if(command == L"ACQUIRE")
- {
- ++it;
- if(it == parameters().end()) //too few parameters
- {
- SetReplyString(L"402 LOCK ACQUIRE ERROR\r\n");
- return false;
- }
- std::wstring lock_phrase = (*it);
-
- //TODO: read options
-
- if(lock)
- {
- //just lock one channel
- if(!lock->try_lock(lock_phrase, client()))
- {
- SetReplyString(L"503 LOCK ACQUIRE FAILED\r\n");
- return false;
- }
- }
- else
- {
- //TODO: lock all channels
- CASPAR_THROW_EXCEPTION(not_implemented());
- }
- SetReplyString(L"202 LOCK ACQUIRE OK\r\n");
-
- }
- else if(command == L"RELEASE")
- {
- if(lock)
- {
- lock->release_lock(client());
- }
- else
- {
- //TODO: release all channels
- CASPAR_THROW_EXCEPTION(not_implemented());
- }
- SetReplyString(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())
- {
- ++it;
- if(it == parameters().end())
- {
- SetReplyString(L"402 LOCK CLEAR ERROR\r\n");
- return false;
- }
- client_override_phrase = (*it);
- }
-
- if(lock)
- {
- //just clear one channel
- if(client_override_phrase != override_phrase)
- {
- SetReplyString(L"503 LOCK CLEAR FAILED\r\n");
- return false;
- }
-
- lock->clear_locks();
- }
- else
- {
- //TODO: clear all channels
- CASPAR_THROW_EXCEPTION(not_implemented());
- }
-
- SetReplyString(L"202 LOCK CLEAR OK\r\n");
- }
- else
- {
- SetReplyString(L"403 LOCK ERROR\r\n");
- return false;
- }
- }
- catch(not_implemented&)
- {
- SetReplyString(L"600 LOCK FAILED\r\n");
- return false;
- }
- catch(...)
- {
- CASPAR_LOG_CURRENT_EXCEPTION();
- SetReplyString(L"501 LOCK FAILED\r\n");
- return false;
- }
-
- return true;
-}*/
-
void register_commands(amcp_command_repository& repo)
{
repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
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);
}
} //namespace amcp
#include <core/help/help_repository.h>
#include <core/help/help_sink.h>
#include <core/help/util.h>
+#include <core/producer/text/text_producer.h>
#include <protocol/amcp/amcp_command_repository.h>
#include <protocol/amcp/AMCPCommandsImpl.h>
~mediawiki_definition_list_builder()
{
- out_ << L"\n" << std::endl;
+ out_ << std::endl;
}
spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
file.flush();
}
-int main(int argc, char** argv)
+void generate_producers_help(const core::help_repository& help_repo)
{
- //env::configure(L"casparcg.config");
- //log::set_log_level(L"info");
+ boost::filesystem::wofstream file(L"producers_help.wiki");
+ mediawiki_help_sink sink(file);
+ sink.start_section(L"Producers (Input Modules)");
+ help_repo.help({ L"producer" }, sink);
+
+ file.flush();
+}
+
+int main(int argc, char** argv)
+{
+ env::configure(L"casparcg.config");
spl::shared_ptr<core::system_info_provider_repository> system_info_provider_repo;
spl::shared_ptr<core::cg_producer_registry> cg_registry;
auto media_info_repo = core::create_in_memory_media_info_repository();
spl::shared_ptr<core::help_repository> help_repo;
- spl::shared_ptr<core::frame_producer_registry> producer_registry;
+ auto producer_registry = spl::make_shared<core::frame_producer_registry>(help_repo);
spl::shared_ptr<core::frame_consumer_registry> consumer_registry;
std::promise<bool> shutdown_server_now;
protocol::amcp::amcp_command_repository repo(
core::module_dependencies dependencies(system_info_provider_repo, cg_registry, media_info_repo, producer_registry, consumer_registry);
initialize_modules(dependencies);
+ core::text::init(dependencies);
generate_amcp_commands_help(*help_repo);
+ generate_producers_help(*help_repo);
uninitialize_modules();
: accelerator_(env::properties().get(L"configuration.accelerator", L"auto"))
, osc_client_(io_service_manager_.service())
, media_info_repo_(create_in_memory_media_info_repository())
+ , producer_registry_(spl::make_shared<core::frame_producer_registry>(help_repo_))
, shutdown_server_now_(shutdown_server_now)
{
running_ = false;