2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Helge Norberg, helge.norberg@svt.se
22 #include "../StdAfx.h"
27 #include "frame_producer.h"
29 #include "../video_channel.h"
30 #include "../diagnostics/call_context.h"
31 #include "../module_dependencies.h"
32 #include "../frame/draw_frame.h"
33 #include "../help/help_sink.h"
34 #include "../help/help_repository.h"
36 #include <common/env.h>
37 #include <common/os/filesystem.h>
38 #include <common/future.h>
40 #include <boost/thread/mutex.hpp>
41 #include <boost/thread/lock_guard.hpp>
42 #include <boost/filesystem.hpp>
43 #include <boost/algorithm/string/predicate.hpp>
44 #include <boost/optional.hpp>
45 #include <boost/property_tree/xml_parser.hpp>
46 #include <boost/property_tree/ptree.hpp>
52 namespace caspar { namespace core {
54 const spl::shared_ptr<cg_proxy>& cg_proxy::empty()
56 class empty_proxy : public cg_proxy
58 void add(int, const std::wstring&, bool, const std::wstring&, const std::wstring&) override {}
59 void remove(int) override {}
60 void play(int) override {}
61 void stop(int, unsigned int) override {}
62 void next(int) override {}
63 void update(int, const std::wstring&) override {}
64 std::wstring invoke(int, const std::wstring&) override { return L""; }
65 std::wstring description(int) override { return L"empty cg producer"; }
66 std::wstring template_host_info() override { return L"empty cg producer"; }
69 static spl::shared_ptr<cg_proxy> instance = spl::make_shared<empty_proxy>();
73 using namespace boost::multi_index;
75 struct cg_producer_registry::impl
81 meta_info_extractor info_extractor;
82 cg_proxy_factory proxy_factory;
83 cg_producer_factory producer_factory;
84 bool reusable_producer_instance;
87 mutable boost::mutex mutex_;
88 std::map<std::wstring, record> records_by_extension_;
90 void register_cg_producer(
91 std::wstring cg_producer_name,
92 std::set<std::wstring> file_extensions,
93 meta_info_extractor info_extractor,
94 cg_proxy_factory proxy_factory,
95 cg_producer_factory producer_factory,
96 bool reusable_producer_instance)
98 boost::lock_guard<boost::mutex> lock(mutex_);
102 std::move(cg_producer_name),
103 std::move(info_extractor),
104 std::move(proxy_factory),
105 std::move(producer_factory),
106 reusable_producer_instance
109 for (auto& extension : file_extensions)
111 records_by_extension_.insert(std::make_pair(extension, rec));
115 spl::shared_ptr<frame_producer> create_producer(
116 const frame_producer_dependencies& dependencies,
117 const std::wstring& filename) const
119 auto found = find_record(filename);
122 return frame_producer::empty();
124 return found->producer_factory(dependencies, filename);
127 spl::shared_ptr<cg_proxy> get_proxy(const spl::shared_ptr<frame_producer>& producer) const
129 auto producer_name = producer->name();
131 boost::lock_guard<boost::mutex> lock(mutex_);
133 for (auto& elem : records_by_extension_)
135 if (elem.second.name == producer_name)
136 return elem.second.proxy_factory(producer);
139 return cg_proxy::empty();
142 spl::shared_ptr<cg_proxy> get_proxy(
143 const spl::shared_ptr<video_channel>& video_channel,
144 int render_layer) const
146 auto producer = spl::make_shared_ptr(video_channel->stage().foreground(render_layer).get());
148 return get_proxy(producer);
151 spl::shared_ptr<cg_proxy> get_or_create_proxy(
152 const spl::shared_ptr<video_channel>& video_channel,
153 const frame_producer_dependencies& dependencies,
155 const std::wstring& filename) const
157 using namespace boost::filesystem;
159 auto found = find_record(filename);
162 return cg_proxy::empty();
164 auto producer = spl::make_shared_ptr(video_channel->stage().foreground(render_layer).get());
165 auto current_producer_name = producer->name();
166 bool create_new = current_producer_name != found->name || !found->reusable_producer_instance;
170 diagnostics::scoped_call_context save;
171 diagnostics::call_context::for_thread().video_channel = video_channel->index();
172 diagnostics::call_context::for_thread().layer = render_layer;
174 producer = found->producer_factory(dependencies, filename);
175 video_channel->stage().load(render_layer, producer);
176 video_channel->stage().play(render_layer);
179 return found->proxy_factory(producer);
182 std::string read_meta_info(const std::wstring& filename) const
184 using namespace boost::filesystem;
186 auto basepath = path(env::template_folder()) / path(filename);
188 boost::lock_guard<boost::mutex> lock(mutex_);
190 for (auto& rec : records_by_extension_)
192 auto p = path(basepath.wstring() + rec.first);
193 auto found = find_case_insensitive(p.wstring());
196 return rec.second.info_extractor(*found);
199 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No meta info extractor for " + filename));
202 bool is_cg_extension(const std::wstring& extension) const
204 boost::lock_guard<boost::mutex> lock(mutex_);
206 return records_by_extension_.find(extension) != records_by_extension_.end();
209 std::wstring get_cg_producer_name(const std::wstring& filename) const
211 auto record = find_record(filename);
214 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" is not a cg template."));
219 boost::optional<record> find_record(const std::wstring& filename) const
221 using namespace boost::filesystem;
223 auto basepath = path(env::template_folder()) / path(filename);
225 boost::lock_guard<boost::mutex> lock(mutex_);
227 for (auto& rec : records_by_extension_)
229 auto p = path(basepath.wstring() + rec.first);
231 if (find_case_insensitive(p.wstring()))
239 cg_producer_registry::cg_producer_registry() : impl_(new impl) { }
241 void cg_producer_registry::register_cg_producer(
242 std::wstring cg_producer_name,
243 std::set<std::wstring> file_extensions,
244 meta_info_extractor info_extractor,
245 cg_proxy_factory proxy_factory,
246 cg_producer_factory producer_factory,
247 bool reusable_producer_instance)
249 impl_->register_cg_producer(
250 std::move(cg_producer_name),
251 std::move(file_extensions),
252 std::move(info_extractor),
253 std::move(proxy_factory),
254 std::move(producer_factory),
255 reusable_producer_instance);
258 spl::shared_ptr<frame_producer> cg_producer_registry::create_producer(
259 const frame_producer_dependencies& dependencies,
260 const std::wstring& filename) const
262 return impl_->create_producer(dependencies, filename);
265 spl::shared_ptr<cg_proxy> cg_producer_registry::get_proxy(
266 const spl::shared_ptr<frame_producer>& producer) const
268 return impl_->get_proxy(producer);
271 spl::shared_ptr<cg_proxy> cg_producer_registry::get_proxy(
272 const spl::shared_ptr<video_channel>& video_channel,
273 int render_layer) const
275 return impl_->get_proxy(video_channel, render_layer);
278 spl::shared_ptr<cg_proxy> cg_producer_registry::get_or_create_proxy(
279 const spl::shared_ptr<video_channel>& video_channel,
280 const frame_producer_dependencies& dependencies,
282 const std::wstring& filename) const
284 return impl_->get_or_create_proxy(video_channel, dependencies, render_layer, filename);
287 std::string cg_producer_registry::read_meta_info(const std::wstring& filename) const
289 return impl_->read_meta_info(filename);
292 bool cg_producer_registry::is_cg_extension(const std::wstring& extension) const
294 return impl_->is_cg_extension(extension);
297 std::wstring cg_producer_registry::get_cg_producer_name(const std::wstring& filename) const
299 return impl_->get_cg_producer_name(filename);
302 class cg_proxy_as_producer : public frame_producer
304 spl::shared_ptr<frame_producer> producer_;
305 spl::shared_ptr<cg_proxy> proxy_;
306 std::wstring template_name_;
307 bool producer_has_its_own_variables_defined_;
309 std::map<std::wstring, std::shared_ptr<core::variable>> variables_;
310 std::vector<std::wstring> variable_names_;
311 core::binding<std::wstring> template_data_xml_ { [=] { return generate_template_data_xml(); } };
312 std::shared_ptr<void> template_data_change_subscription_;
313 bool is_playing_ = false;
315 cg_proxy_as_producer(
316 spl::shared_ptr<frame_producer> producer,
317 spl::shared_ptr<cg_proxy> proxy,
318 const std::wstring& template_name,
319 const std::vector<std::wstring>& parameter_specification)
320 : producer_(std::move(producer))
321 , proxy_(std::move(proxy))
322 , template_name_(std::move(template_name))
323 , producer_has_its_own_variables_defined_(!producer_->get_variables().empty())
325 if (parameter_specification.size() % 2 != 0)
326 CASPAR_THROW_EXCEPTION(user_error() << msg_info("Parameter specification must be a sequence of type and parameter name pairs"));
328 if (producer_has_its_own_variables_defined_ && !parameter_specification.empty())
329 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Producer " + producer_->name() + L" does not need help with available template parameters"));
331 for (int i = 0; i < parameter_specification.size(); i += 2)
333 auto& type = parameter_specification.at(i);
334 auto& parameter_name = parameter_specification.at(i + 1);
336 if (type == L"string")
337 create_parameter<std::wstring>(parameter_name);
338 else if (type == L"number")
339 create_parameter<double>(parameter_name);
341 CASPAR_THROW_EXCEPTION(user_error() << msg_info("The type in a parameter specification must be either string or number"));
344 template_data_change_subscription_ = template_data_xml_.on_change([=]
347 proxy_->update(0, template_data_xml_.get());
353 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
355 auto& command = params.at(0);
357 if (command == L"play()")
359 proxy_->add(0, template_name_, true, L"", template_data_xml_.get());
362 else if (command == L"stop()")
364 else if (command == L"next()")
366 else if (command == L"invoke()")
367 proxy_->invoke(0, params.at(1));
369 return producer_->call(params);
371 return make_ready_future<std::wstring>(L"");
374 variable& get_variable(const std::wstring& name) override
376 if (producer_has_its_own_variables_defined_)
377 return producer_->get_variable(name);
379 auto found = variables_.find(name);
381 if (found == variables_.end())
382 CASPAR_THROW_EXCEPTION(user_error() << msg_info(name + L" not found in scene"));
384 return *found->second;
387 const std::vector<std::wstring>& get_variables() const override
389 return producer_has_its_own_variables_defined_ ? producer_->get_variables() : variable_names_;
392 draw_frame receive() override { return producer_->receive(); }
393 std::wstring print() const override { return producer_->print(); }
394 void paused(bool value) override { producer_->paused(value); }
395 std::wstring name() const override { return producer_->name(); }
396 uint32_t frame_number() const override { return producer_->frame_number(); }
397 boost::property_tree::wptree info() const override { return producer_->info(); }
398 void leading_producer(const spl::shared_ptr<frame_producer>& producer) override { return producer_->leading_producer(producer); }
399 uint32_t nb_frames() const override { return producer_->nb_frames(); }
400 draw_frame last_frame() { return producer_->last_frame(); }
401 monitor::subject& monitor_output() override { return producer_->monitor_output(); }
402 bool collides(double x, double y) const override { return producer_->collides(x, y); }
403 void on_interaction(const interaction_event::ptr& event) override { return producer_->on_interaction(event); }
404 constraints& pixel_constraints() override { return producer_->pixel_constraints(); }
406 std::wstring generate_template_data_xml() const
408 boost::property_tree::wptree document;
409 boost::property_tree::wptree template_data;
411 for (auto& parameter_name : variable_names_)
413 boost::property_tree::wptree component_data;
415 component_data.add(L"<xmlattr>.id", parameter_name);
416 component_data.add(L"data.<xmlattr>.id", L"text");
417 component_data.add(L"data.<xmlattr>.value", variables_.at(parameter_name)->to_string());
419 template_data.add_child(L"componentData", component_data);
422 document.add_child(L"templateData", template_data);
424 std::wstringstream xml;
425 static boost::property_tree::xml_writer_settings<std::wstring> NO_INDENTATION_SETTINGS(' ', 0);
426 boost::property_tree::xml_parser::write_xml(xml, document, NO_INDENTATION_SETTINGS);
428 static std::wstring PROCESSING_INSTRUCTION_TO_REMOVE = L"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
429 return xml.str().substr(PROCESSING_INSTRUCTION_TO_REMOVE.length());
433 void create_parameter(const std::wstring& name)
435 auto var = spl::make_shared<core::variable_impl<T>>();
437 template_data_xml_.depend_on(var->value());
438 variables_.insert(std::make_pair(name, var));
439 variable_names_.push_back(name);
443 void describe_cg_proxy_as_producer(core::help_sink& sink, const core::help_repository& repo)
445 sink.short_description(L"Wraps any CG producer for compatibility with scene producer.");
446 sink.syntax(L"[CG] [template:string] {[param1_type:\"string\",\"number\"] [param1_name:string] {[param2_type:\"string\",\"number\"] [param2_name:string] {...}}}");
447 sink.para()->text(L"Wraps any CG producer for compatibility with scene producer.");
448 sink.para()->text(L"It allows the user to specify what template parameters should be exposed to the parent scene. This is only required for Flash and HTML templates. PSD and Scene templates does this automatically.");
449 sink.para()->text(L"Examples only really usable from within the scene producer implementation:");
450 sink.example(L">> PLAY 1-10 [CG] folder/flas_template string f0 number f1");
451 sink.para()->text(L"...followed by the scene producer setting variables causing the equivalent of a ")->code(L"CG ADD")->text(L". Then the following calls can be made:");
452 sink.example(L">> CALL 1-10 play()");
453 sink.example(L">> CALL 1-10 next()");
454 sink.example(L">> CALL 1-10 invoke() label");
455 sink.example(L">> CALL 1-10 stop()");
458 spl::shared_ptr<frame_producer> create_cg_proxy_as_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
460 if (!boost::iequals(params.at(0), L"[CG]") || params.size() < 2)
461 return frame_producer::empty();
463 auto template_name = params.at(1);
464 auto producer = dependencies.cg_registry->create_producer(dependencies, template_name);
465 auto proxy = dependencies.cg_registry->get_proxy(producer);
466 auto params2 = params;
468 if (proxy == cg_proxy::empty())
469 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No template with name " + template_name + L" found"));
471 // Remove "[CG]" and template_name to only leave the parameter specification part
472 params2.erase(params2.begin(), params2.begin() + 2);
474 return spl::make_shared<cg_proxy_as_producer>(std::move(producer), std::move(proxy), template_name, params2);
477 void init_cg_proxy_as_producer(core::module_dependencies dependencies)
479 dependencies.producer_registry->register_producer_factory(L"CG proxy wrapper", create_cg_proxy_as_producer, describe_cg_proxy_as_producer);