]> git.sesse.net Git - casparcg/blob - core/producer/cg_proxy.cpp
[cg_proxy] #582 Throw file_not_found instead of generic user_error when no cg produce...
[casparcg] / core / producer / cg_proxy.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Helge Norberg, helge.norberg@svt.se
20 */
21
22 #include "../StdAfx.h"
23
24 #include "cg_proxy.h"
25 #include "variable.h"
26
27 #include "frame_producer.h"
28 #include "stage.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"
35
36 #include <common/env.h>
37 #include <common/os/filesystem.h>
38 #include <common/future.h>
39
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>
47
48 #include <future>
49 #include <map>
50 #include <sstream>
51
52 namespace caspar { namespace core {
53
54 const spl::shared_ptr<cg_proxy>& cg_proxy::empty()
55 {
56         class empty_proxy : public cg_proxy
57         {
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"; }
67         };
68
69         static spl::shared_ptr<cg_proxy> instance = spl::make_shared<empty_proxy>();
70         return instance;
71 }
72
73 using namespace boost::multi_index;
74
75 struct cg_producer_registry::impl
76 {
77 private:
78         struct record
79         {
80                 std::wstring                    name;
81                 meta_info_extractor             info_extractor;
82                 cg_proxy_factory                proxy_factory;
83                 cg_producer_factory             producer_factory;
84                 bool                                    reusable_producer_instance;
85         };
86
87         mutable boost::mutex                    mutex_;
88         std::map<std::wstring, record>  records_by_extension_;
89 public:
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)
97         {
98                 boost::lock_guard<boost::mutex> lock(mutex_);
99
100                 record rec
101                 {
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
107                 };
108
109                 for (auto& extension : file_extensions)
110                 {
111                         records_by_extension_.insert(std::make_pair(extension, rec));
112                 }
113         }
114
115         spl::shared_ptr<frame_producer> create_producer(
116                         const frame_producer_dependencies& dependencies,
117                         const std::wstring& filename) const
118         {
119                 auto found = find_record(filename);
120
121                 if (!found)
122                         return frame_producer::empty();
123
124                 return found->producer_factory(dependencies, filename);
125         }
126
127         spl::shared_ptr<cg_proxy> get_proxy(const spl::shared_ptr<frame_producer>& producer) const
128         {
129                 auto producer_name = producer->name();
130
131                 boost::lock_guard<boost::mutex> lock(mutex_);
132
133                 for (auto& elem : records_by_extension_)
134                 {
135                         if (elem.second.name == producer_name)
136                                 return elem.second.proxy_factory(producer);
137                 }
138
139                 return cg_proxy::empty();
140         }
141
142         spl::shared_ptr<cg_proxy> get_proxy(
143                         const spl::shared_ptr<video_channel>& video_channel,
144                         int render_layer) const
145         {
146                 auto producer = spl::make_shared_ptr(video_channel->stage().foreground(render_layer).get());
147
148                 return get_proxy(producer);
149         }
150
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,
154                         int render_layer,
155                         const std::wstring& filename) const
156         {
157                 using namespace boost::filesystem;
158
159                 auto found = find_record(filename);
160
161                 if (!found)
162                         return cg_proxy::empty();
163
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;
167
168                 if (create_new)
169                 {
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;
173
174                         producer = found->producer_factory(dependencies, filename);
175                         video_channel->stage().load(render_layer, producer);
176                         video_channel->stage().play(render_layer);
177                 }
178
179                 return found->proxy_factory(producer);
180         }
181
182         std::string read_meta_info(const std::wstring& filename) const
183         {
184                 using namespace boost::filesystem;
185
186                 auto basepath = path(env::template_folder()) / path(filename);
187
188                 boost::lock_guard<boost::mutex> lock(mutex_);
189
190                 for (auto& rec : records_by_extension_)
191                 {
192                         auto p = path(basepath.wstring() + rec.first);
193                         auto found = find_case_insensitive(p.wstring());
194
195                         if (found)
196                                 return rec.second.info_extractor(*found);
197                 }
198
199                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No meta info extractor for " + filename));
200         }
201
202         bool is_cg_extension(const std::wstring& extension) const
203         {
204                 boost::lock_guard<boost::mutex> lock(mutex_);
205
206                 return records_by_extension_.find(extension) != records_by_extension_.end();
207         }
208
209         std::wstring get_cg_producer_name(const std::wstring& filename) const
210         {
211                 auto record = find_record(filename);
212
213                 if (!record)
214                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" is not a cg template."));
215
216                 return record->name;
217         }
218 private:
219         boost::optional<record> find_record(const std::wstring& filename) const
220         {
221                 using namespace boost::filesystem;
222
223                 auto basepath = path(env::template_folder()) / path(filename);
224
225                 boost::lock_guard<boost::mutex> lock(mutex_);
226
227                 for (auto& rec : records_by_extension_)
228                 {
229                         auto p = path(basepath.wstring() + rec.first);
230
231                         if (find_case_insensitive(p.wstring()))
232                                 return rec.second;
233                 }
234
235                 return boost::none;
236         }
237 };
238
239 cg_producer_registry::cg_producer_registry() : impl_(new impl) { }
240
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)
248 {
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);
256 }
257
258 spl::shared_ptr<frame_producer> cg_producer_registry::create_producer(
259                 const frame_producer_dependencies& dependencies,
260                 const std::wstring& filename) const
261 {
262         return impl_->create_producer(dependencies, filename);
263 }
264
265 spl::shared_ptr<cg_proxy> cg_producer_registry::get_proxy(
266                 const spl::shared_ptr<frame_producer>& producer) const
267 {
268         return impl_->get_proxy(producer);
269 }
270
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
274 {
275         return impl_->get_proxy(video_channel, render_layer);
276 }
277
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,
281                 int render_layer,
282                 const std::wstring& filename) const
283 {
284         return impl_->get_or_create_proxy(video_channel, dependencies, render_layer, filename);
285 }
286
287 std::string cg_producer_registry::read_meta_info(const std::wstring& filename) const
288 {
289         return impl_->read_meta_info(filename);
290 }
291
292 bool cg_producer_registry::is_cg_extension(const std::wstring& extension) const
293 {
294         return impl_->is_cg_extension(extension);
295 }
296
297 std::wstring cg_producer_registry::get_cg_producer_name(const std::wstring& filename) const
298 {
299         return impl_->get_cg_producer_name(filename);
300 }
301
302 class cg_proxy_as_producer : public frame_producer
303 {
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_;
308
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;
314 public:
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())
324         {
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"));
327
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"));
330
331                 for (int i = 0; i < parameter_specification.size(); i += 2)
332                 {
333                         auto& type                              = parameter_specification.at(i);
334                         auto& parameter_name    = parameter_specification.at(i + 1);
335
336                         if (type == L"string")
337                                 create_parameter<std::wstring>(parameter_name);
338                         else if (type == L"number")
339                                 create_parameter<double>(parameter_name);
340                         else
341                                 CASPAR_THROW_EXCEPTION(user_error() << msg_info("The type in a parameter specification must be either string or number"));
342                 }
343
344                 template_data_change_subscription_ = template_data_xml_.on_change([=]
345                 {
346                         if (is_playing_)
347                                 proxy_->update(0, template_data_xml_.get());
348                 });
349         }
350
351         // frame_producer
352
353         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
354         {
355                 auto& command = params.at(0);
356
357                 if (command == L"play()")
358                 {
359                         proxy_->add(0, template_name_, true, L"", template_data_xml_.get());
360                         is_playing_ = true;
361                 }
362                 else if (command == L"stop()")
363                         proxy_->stop(0, 0);
364                 else if (command == L"next()")
365                         proxy_->next(0);
366                 else if (command == L"invoke()")
367                         proxy_->invoke(0, params.at(1));
368                 else
369                         return producer_->call(params);
370
371                 return make_ready_future<std::wstring>(L"");
372         }
373
374         variable& get_variable(const std::wstring& name) override
375         {
376                 if (producer_has_its_own_variables_defined_)
377                         return producer_->get_variable(name);
378
379                 auto found = variables_.find(name);
380
381                 if (found == variables_.end())
382                         CASPAR_THROW_EXCEPTION(user_error() << msg_info(name + L" not found in scene"));
383
384                 return *found->second;
385         }
386
387         const std::vector<std::wstring>& get_variables() const override
388         {
389                 return producer_has_its_own_variables_defined_ ? producer_->get_variables() : variable_names_;
390         }
391
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(); }
405 private:
406         std::wstring generate_template_data_xml() const
407         {
408                 boost::property_tree::wptree document;
409                 boost::property_tree::wptree template_data;
410
411                 for (auto& parameter_name : variable_names_)
412                 {
413                         boost::property_tree::wptree component_data;
414
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());
418
419                         template_data.add_child(L"componentData", component_data);
420                 }
421
422                 document.add_child(L"templateData", template_data);
423
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);
427                 // Necessary hack
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());
430         }
431
432         template<typename T>
433         void create_parameter(const std::wstring& name)
434         {
435                 auto var = spl::make_shared<core::variable_impl<T>>();
436
437                 template_data_xml_.depend_on(var->value());
438                 variables_.insert(std::make_pair(name, var));
439                 variable_names_.push_back(name);
440         }
441 };
442
443 void describe_cg_proxy_as_producer(core::help_sink& sink, const core::help_repository& repo)
444 {
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()");
456 }
457
458 spl::shared_ptr<frame_producer> create_cg_proxy_as_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
459 {
460         if (!boost::iequals(params.at(0), L"[CG]") || params.size() < 2)
461                 return frame_producer::empty();
462
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;
467
468         if (proxy == cg_proxy::empty())
469                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No template with name " + template_name + L" found"));
470
471         // Remove "[CG]" and template_name to only leave the parameter specification part
472         params2.erase(params2.begin(), params2.begin() + 2);
473
474         return spl::make_shared<cg_proxy_as_producer>(std::move(producer), std::move(proxy), template_name, params2);
475 }
476
477 void init_cg_proxy_as_producer(core::module_dependencies dependencies)
478 {
479         dependencies.producer_registry->register_producer_factory(L"CG proxy wrapper", create_cg_proxy_as_producer, describe_cg_proxy_as_producer);
480 }
481
482 }}