]> git.sesse.net Git - casparcg/blob - modules/html/producer/html_producer.cpp
Manually merged pull request #222
[casparcg] / modules / html / producer / html_producer.cpp
1 /*
2 * Copyright 2013 Sveriges Television AB http://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: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "html_producer.h"
23
24 #include <core/video_format.h>
25
26 #include <core/monitor/monitor.h>
27 #include <core/parameters/parameters.h>
28 #include <core/producer/frame/basic_frame.h>
29 #include <core/producer/frame/frame_factory.h>
30 #include <core/producer/frame_producer.h>
31 #include <core/mixer/write_frame.h>
32
33 #include <common/env.h>
34 #include <common/concurrency/executor.h>
35 #include <common/concurrency/lock.h>
36 #include <common/diagnostics/graph.h>
37 #include <common/utility/timer.h>
38
39 #include <berkelium/Berkelium.hpp>
40 #include <berkelium/Context.hpp>
41 #include <berkelium/Window.hpp>
42 #include <berkelium/WindowDelegate.hpp>
43 #include <berkelium/Rect.hpp>
44
45 #include <boost/algorithm/string/predicate.hpp>
46 #include <boost/algorithm/string/trim.hpp>
47 #include <boost/filesystem.hpp>
48 #include <boost/format.hpp>
49
50 #include <tbb/atomic.h>
51 #include <tbb/parallel_for.h>
52
53 #include "html.h"
54
55 namespace caspar { namespace html {
56                 
57 class html_producer 
58         : public core::frame_producer
59         , public Berkelium::WindowDelegate
60 {       
61         core::monitor::subject                          monitor_subject_;
62         const std::wstring                                      url_;   
63         safe_ptr<diagnostics::graph>            graph_;
64         
65         const safe_ptr<core::frame_factory>     frame_factory_;
66
67         safe_ptr<core::basic_frame>                     last_frame_;
68         mutable boost::mutex                            last_frame_mutex_;
69         
70         std::vector<unsigned char>                      frame_;
71         mutable boost::mutex                            frame_mutex_;
72
73         tbb::atomic<bool>                                       invalidated_;
74
75         std::unique_ptr<Berkelium::Window>      window_;
76
77         high_prec_timer                                         timer_;
78
79         executor                                                        executor_;
80         
81 public:
82         html_producer(
83                 const safe_ptr<core::frame_factory>& frame_factory, 
84                 const std::wstring& url) 
85                 : url_(url)             
86                 , frame_factory_(frame_factory)
87                 , last_frame_(core::basic_frame::empty())
88                 , frame_(frame_factory->get_video_format_desc().width * frame_factory->get_video_format_desc().height * 4, 0)
89                 , executor_(L"html_producer")
90         {               
91                 invalidated_ = true;
92
93                 graph_->set_text(print());
94                 diagnostics::register_graph(graph_);
95
96                 html::invoke([&]
97                 {
98                         {
99                                 std::unique_ptr<Berkelium::Context> context(Berkelium::Context::create());
100                                 window_.reset(Berkelium::Window::create(context.get()));
101                         }
102
103                         window_->resize(
104                                 frame_factory->get_video_format_desc().width,
105                                 frame_factory->get_video_format_desc().height);
106                         window_->setDelegate(this);
107                         window_->setTransparent(true);
108                         
109                         const auto narrow_url = narrow(url);
110                         
111                         if(!window_->navigateTo(
112                                 Berkelium::URLString::point_to(
113                                         narrow_url.data(), 
114                                         narrow_url.length())))
115                         {
116                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to navigate."));
117                         }
118                 });
119
120                 tick();
121         }
122
123         ~html_producer()
124         {
125                 html::invoke([=]
126                 {
127                         window_.reset();
128                 });
129         }
130         
131         // frame_producer
132                 
133         safe_ptr<core::basic_frame> receive(int) override
134         {                               
135                 return last_frame();
136         }
137
138         safe_ptr<core::basic_frame> last_frame() const override
139         {               
140                 return lock(last_frame_mutex_, [&]
141                 {       
142                         return last_frame_;
143                 });
144         }               
145         
146         boost::unique_future<std::wstring> call(const std::wstring& param) override
147         {       
148                 static const boost::wregex play_exp(L"PLAY\\s*(\\d+)?", boost::regex::icase);
149                 static const boost::wregex stop_exp(L"STOP\\s*(\\d+)?", boost::regex::icase);
150                 static const boost::wregex next_exp(L"NEXT\\s*(\\d+)?", boost::regex::icase);
151                 static const boost::wregex remove_exp(L"REMOVE\\s*(\\d+)?", boost::regex::icase);
152                 static const boost::wregex update_exp(L"UPDATE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
153                 static const boost::wregex invoke_exp(L"INVOKE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
154
155                 auto command = [=]
156                 {
157                         auto javascript = param;
158
159                         boost::wsmatch what;
160
161                         if (boost::regex_match(param, what, play_exp))
162                         {
163                                 javascript = (boost::wformat(L"play()")).str();
164                         }
165                         else if (boost::regex_match(param, what, stop_exp))
166                         {
167                                 javascript = (boost::wformat(L"stop()")).str();
168                         }
169                         else if (boost::regex_match(param, what, next_exp))
170                         {
171                                 javascript = (boost::wformat(L"next()")).str();
172                         }
173                         else if (boost::regex_match(param, what, remove_exp))
174                         {
175                                 javascript = (boost::wformat(L"remove()")).str();
176                         }
177                         else if (boost::regex_match(param, what, update_exp))
178                         {
179                                 javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
180                         }
181                         else if (boost::regex_match(param, what, invoke_exp))
182                         {
183                                 javascript = (boost::wformat(L"invoke(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
184                         }
185                                                                                                 
186                         window_->executeJavascript(Berkelium::WideString::point_to(javascript.data(), javascript.length()));
187                 };
188                 
189                 boost::packaged_task<std::wstring> task([=]() -> std::wstring
190                 {
191                         html::invoke(command);
192
193                         return L"";
194                 });
195
196                 task();
197
198                 return task.get_future();
199         }
200                 
201         std::wstring print() const override
202         { 
203                 return L"html[" + url_ + L"]";          
204         }       
205
206         boost::property_tree::wptree info() const override
207         {
208                 boost::property_tree::wptree info;
209                 info.add(L"type", L"html-producer");
210                 return info;
211         }
212         
213         core::monitor::subject& monitor_output()
214         {
215                 return monitor_subject_;
216         }
217         
218         // Berkelium::WindowDelegate
219         
220         void onPaint(
221                 Berkelium::Window* wini, 
222                 const unsigned char* bitmap_in, 
223                 const Berkelium::Rect& bitmap_rect, 
224                 size_t num_copy_rects, 
225                 const Berkelium::Rect* copy_rects,      
226                 int dx, 
227                 int dy, 
228                 const Berkelium::Rect& scroll_rect) override
229         {               
230                 lock(frame_mutex_, [&]
231                 {       
232                         invalidated_ = true;
233
234                         tbb::parallel_for<int>(0, num_copy_rects, 1, [&](int i)
235                         {                                                                       
236                                 tbb::parallel_for<int>(0, copy_rects[i].height(), 1, [&](int y)
237                                 {
238                                         memcpy(
239                                                 frame_.data() + ((y + copy_rects[i].top()) * frame_factory_->get_video_format_desc().width + copy_rects[i].left()) * 4,
240                                                 bitmap_in + ((y + copy_rects[i].top() - bitmap_rect.top()) * bitmap_rect.width() + copy_rects[i].left() - bitmap_rect.left()) * 4,
241                                                 copy_rects[i].width() * 4);
242                                 });
243                         });             
244                 });
245         }
246
247     void onExternalHost(
248         Berkelium::Window *win,
249         Berkelium::WideString message,
250         Berkelium::URLString origin,
251         Berkelium::URLString target) override
252         {
253         }
254
255         // html_producer
256                 
257         safe_ptr<core::basic_frame> draw(
258                 safe_ptr<core::write_frame> frame, 
259                 core::field_mode::type field_mode)
260         {
261                 const auto& pixel_desc = frame->get_pixel_format_desc();
262
263                 CASPAR_ASSERT(pixel_desc.pix_fmt == core::pixel_format::bgra);
264                                 
265                 const auto& height = pixel_desc.planes[0].height;
266                 const auto& linesize = pixel_desc.planes[0].linesize;
267
268                 lock(frame_mutex_, [&]
269                 {                               
270                         tbb::parallel_for<int>(
271                                 field_mode == core::field_mode::upper ? 0 : 1, 
272                                 height,
273                                 field_mode == core::field_mode::progressive ? 1 : 2,
274                                 [&](int y)
275                         {
276                                 memcpy(
277                                         frame->image_data().begin() + y * linesize, 
278                                         frame_.data() + y * linesize,
279                                         linesize);
280                         });
281                 });
282                                                         
283                 return frame;
284         }       
285
286         void tick()
287         {
288                 if(invalidated_.fetch_and_store(false))
289                 {
290                         core::pixel_format_desc pixel_desc;
291                         pixel_desc.pix_fmt = core::pixel_format::bgra;
292                         pixel_desc.planes.push_back(
293                                 core::pixel_format_desc::plane(
294                                         frame_factory_->get_video_format_desc().width, 
295                                         frame_factory_->get_video_format_desc().height,
296                                         4));
297
298                         auto frame = frame_factory_->create_frame(this, pixel_desc);
299
300                         const auto& format_desc = frame_factory_->get_video_format_desc();
301
302                         if (format_desc.field_mode != core::field_mode::progressive)
303                         {
304                                 draw(frame, format_desc.field_mode);
305                                 
306                                 executor_.yield();
307                                 timer_.tick(1.0 / (format_desc.fps * format_desc.field_count));
308                 
309                                 draw(frame, static_cast<core::field_mode::type>(format_desc.field_mode ^ core::field_mode::progressive));
310                         }
311                         else
312                         {                               
313                                 draw(frame, format_desc.field_mode);                    
314                         }       
315                 
316                         frame->commit();
317
318                         lock(last_frame_mutex_, [&]
319                         {       
320                                 last_frame_ = frame;
321                         });
322                         
323                         executor_.yield();
324
325                         timer_.tick(1.0 / (format_desc.fps * format_desc.field_count));
326                 }
327
328                 executor_.begin_invoke([this]{ tick(); });
329         }
330 };
331
332 safe_ptr<core::frame_producer> create_producer(
333         const safe_ptr<core::frame_factory>& frame_factory,
334         const core::parameters& params)
335 {
336         const auto filename = env::template_folder() + L"\\" + params.at_original(0) + L".html";
337         
338         const auto url = boost::filesystem::exists(filename) 
339                 ? filename 
340                 : params.at_original(0);
341                 
342         if(!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
343                 return core::frame_producer::empty();
344
345         return core::create_producer_destroy_proxy(
346                 core::create_producer_print_proxy(
347                         make_safe<html_producer>(
348                                 frame_factory,
349                                 url)));
350 }
351
352 }}