]> git.sesse.net Git - casparcg/blob - modules/flash/producer/flash_producer.cpp
Improved read_fps.
[casparcg] / modules / flash / producer / flash_producer.cpp
1 /*\r
2 * Copyright 2013 Sveriges Television AB http://casparcg.com/\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\r
5 *\r
6 * CasparCG is free software: you can redistribute it and/or modify\r
7 * it under the terms of the GNU General Public License as published by\r
8 * the Free Software Foundation, either version 3 of the License, or\r
9 * (at your option) any later version.\r
10 *\r
11 * CasparCG is distributed in the hope that it will be useful,\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 * GNU General Public License for more details.\r
15 *\r
16 * You should have received a copy of the GNU General Public License\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 * Author: Robert Nagy, ronag89@gmail.com\r
20 */\r
21 \r
22 #include "../stdafx.h"\r
23 \r
24 #if defined(_MSC_VER)\r
25 #pragma warning (disable : 4146)\r
26 #pragma warning (disable : 4244)\r
27 #endif\r
28 \r
29 #include "flash_producer.h"\r
30 #include "FlashAxContainer.h"\r
31 \r
32 #include "../util/swf.h"\r
33 \r
34 #include <core/video_format.h>\r
35 \r
36 #include <core/monitor/monitor.h>\r
37 #include <core/parameters/parameters.h>\r
38 #include <core/producer/frame/basic_frame.h>\r
39 #include <core/producer/frame/frame_factory.h>\r
40 #include <core/mixer/write_frame.h>\r
41 \r
42 #include <common/env.h>\r
43 #include <common/concurrency/executor.h>\r
44 #include <common/concurrency/lock.h>\r
45 #include <common/concurrency/future_util.h>\r
46 #include <common/diagnostics/graph.h>\r
47 #include <common/memory/memcpy.h>\r
48 #include <common/memory/memclr.h>\r
49 #include <common/utility/timer.h>\r
50 \r
51 #include <boost/filesystem.hpp>\r
52 #include <boost/property_tree/ptree.hpp>\r
53 #include <boost/thread.hpp>\r
54 #include <boost/timer.hpp>\r
55 #include <boost/algorithm/string.hpp>\r
56 \r
57 #include <functional>\r
58 \r
59 #include <tbb/spin_mutex.h>\r
60 \r
61 namespace caspar { namespace flash {\r
62                 \r
63 class bitmap\r
64 {\r
65 public:\r
66         bitmap(size_t width, size_t height)\r
67                 : bmp_data_(nullptr)\r
68                 , hdc_(CreateCompatibleDC(0), DeleteDC)\r
69         {       \r
70                 BITMAPINFO info;\r
71                 memset(&info, 0, sizeof(BITMAPINFO));\r
72                 info.bmiHeader.biBitCount = 32;\r
73                 info.bmiHeader.biCompression = BI_RGB;\r
74                 info.bmiHeader.biHeight = -height;\r
75                 info.bmiHeader.biPlanes = 1;\r
76                 info.bmiHeader.biSize = sizeof(BITMAPINFO);\r
77                 info.bmiHeader.biWidth = width;\r
78 \r
79                 bmp_.reset(CreateDIBSection(static_cast<HDC>(hdc_.get()), &info, DIB_RGB_COLORS, reinterpret_cast<void**>(&bmp_data_), 0, 0), DeleteObject);\r
80                 SelectObject(static_cast<HDC>(hdc_.get()), bmp_.get()); \r
81 \r
82                 if(!bmp_data_)\r
83                         BOOST_THROW_EXCEPTION(std::bad_alloc());\r
84         }\r
85 \r
86         operator HDC() {return static_cast<HDC>(hdc_.get());}\r
87 \r
88         BYTE* data() { return bmp_data_;}\r
89         const BYTE* data() const { return bmp_data_;}\r
90 \r
91 private:\r
92         BYTE* bmp_data_;        \r
93         std::shared_ptr<void> hdc_;\r
94         std::shared_ptr<void> bmp_;\r
95 };\r
96 \r
97 struct template_host\r
98 {\r
99         std::wstring  video_mode;\r
100         std::wstring  filename;\r
101         size_t            width;\r
102         size_t            height;\r
103 };\r
104 \r
105 template_host get_template_host(const core::video_format_desc& desc)\r
106 {\r
107         try\r
108         {\r
109                 std::vector<template_host> template_hosts;\r
110                 auto template_hosts_element = env::properties().get_child_optional(\r
111                                 L"configuration.template-hosts");\r
112 \r
113                 if (template_hosts_element)\r
114                         BOOST_FOREACH(auto& xml_mapping, *template_hosts_element)\r
115                         {\r
116                                 try\r
117                                 {\r
118                                         template_host template_host;\r
119                                         template_host.video_mode                = xml_mapping.second.get(L"video-mode", L"");\r
120                                         template_host.filename                  = xml_mapping.second.get(L"filename",   L"cg.fth");\r
121                                         template_host.width                             = xml_mapping.second.get(L"width",              desc.width);\r
122                                         template_host.height                    = xml_mapping.second.get(L"height",             desc.height);\r
123                                         template_hosts.push_back(template_host);\r
124                                 }\r
125                                 catch(...){}\r
126                         }\r
127 \r
128                 auto template_host_it = boost::find_if(template_hosts, [&](template_host template_host){return template_host.video_mode == desc.name;});\r
129                 if(template_host_it == template_hosts.end())\r
130                         template_host_it = boost::find_if(template_hosts, [&](template_host template_host){return template_host.video_mode == L"";});\r
131 \r
132                 if(template_host_it != template_hosts.end())\r
133                         return *template_host_it;\r
134         }\r
135         catch(...){}\r
136                 \r
137         template_host template_host;\r
138         template_host.filename = L"cg.fth";\r
139 \r
140         for(auto it = boost::filesystem2::wdirectory_iterator(env::template_folder()); it != boost::filesystem2::wdirectory_iterator(); ++it)\r
141         {\r
142                 if(boost::iequals(it->path().extension(), L"." + desc.name))\r
143                 {\r
144                         template_host.filename = it->filename();\r
145                         break;\r
146                 }\r
147         }\r
148 \r
149         template_host.width =  desc.square_width;\r
150         template_host.height = desc.square_height;\r
151         return template_host;\r
152 }\r
153 \r
154 boost::mutex& get_global_init_destruct_mutex()\r
155 {\r
156         static boost::mutex m;\r
157 \r
158         return m;\r
159 }\r
160 \r
161 class flash_renderer\r
162 {       \r
163         struct com_init\r
164         {\r
165                 HRESULT result_;\r
166 \r
167                 com_init()\r
168                         : result_(CoInitialize(nullptr))\r
169                 {\r
170                         if(FAILED(result_))\r
171                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to initialize com-context for flash-player"));\r
172                 }\r
173 \r
174                 ~com_init()\r
175                 {\r
176                         if(SUCCEEDED(result_))\r
177                                 ::CoUninitialize();\r
178                 }\r
179         } com_init_;\r
180         \r
181         const safe_ptr<diagnostics::graph>                              graph_;\r
182         const size_t                                                                    width_;\r
183         const size_t                                                                    height_;\r
184         const std::wstring                                                              filename_;\r
185         const std::shared_ptr<core::frame_factory>              frame_factory_;\r
186         \r
187         CComObject<caspar::flash::FlashAxContainer>*    ax_;\r
188         safe_ptr<core::basic_frame>                                             head_;\r
189         bitmap                                                                                  bmp_;\r
190         \r
191         boost::timer                                                                    frame_timer_;\r
192         boost::timer                                                                    tick_timer_;\r
193         \r
194 public:\r
195         flash_renderer(const safe_ptr<diagnostics::graph>& graph, const std::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, int width, int height) \r
196                 : graph_(graph)\r
197                 , width_(width)\r
198                 , height_(height)\r
199                 , filename_(filename)\r
200                 , frame_factory_(frame_factory)\r
201                 , ax_(nullptr)\r
202                 , head_(core::basic_frame::late())\r
203                 , bmp_(width, height)\r
204         {               \r
205                 graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));\r
206                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));\r
207                 graph_->set_color("param", diagnostics::color(1.0f, 0.5f, 0.0f));\r
208                 graph_->set_color("buffered", diagnostics::color(0.8f, 0.3f, 0.2f));\r
209 \r
210                 lock(get_global_init_destruct_mutex(), [this]\r
211                 {\r
212 \r
213                         CoInitialize(nullptr);\r
214 \r
215                         if(FAILED(CComObject<caspar::flash::FlashAxContainer>::CreateInstance(&ax_)))\r
216                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to create FlashAxContainer"));\r
217                 \r
218                         if(FAILED(ax_->CreateAxControl()))\r
219                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to Create FlashAxControl"));\r
220                 });\r
221 \r
222                 ax_->set_print([this]{return print();});\r
223                 \r
224                 CComPtr<IShockwaveFlash> spFlash;\r
225                 if(FAILED(ax_->QueryControl(&spFlash)))\r
226                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to Query FlashAxControl"));\r
227                                                                                                 \r
228                 if(FAILED(spFlash->put_Playing(true)) )\r
229                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to start playing Flash"));\r
230 \r
231                 if(FAILED(spFlash->put_Movie(CComBSTR(filename.c_str()))))\r
232                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to Load Template Host"));\r
233                                                                                 \r
234                 if(FAILED(spFlash->put_ScaleMode(2)))  //Exact fit. Scale without respect to the aspect ratio.\r
235                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to Set Scale Mode"));\r
236                                                 \r
237                 ax_->SetSize(width_, height_);          \r
238                 render_frame();\r
239         \r
240                 CASPAR_LOG(info) << print() << L" Initialized.";\r
241         }\r
242 \r
243         ~flash_renderer()\r
244         {               \r
245                 if(ax_)\r
246                 {\r
247                         lock(get_global_init_destruct_mutex(), [this]\r
248                         {\r
249                                 ax_->DestroyAxControl();\r
250                                 ax_->Release();\r
251                         });\r
252                 }\r
253                 graph_->set_value("tick-time", 0.0f);\r
254                 graph_->set_value("frame-time", 0.0f);\r
255                 CASPAR_LOG(info) << print() << L" Uninitialized.";\r
256         }\r
257         \r
258         std::wstring call(const std::wstring& param)\r
259         {               \r
260                 std::wstring result;\r
261 \r
262                 CASPAR_LOG(trace) << print() << " Call: " << param;\r
263 \r
264                 if(!ax_->FlashCall(param, result))\r
265                         CASPAR_LOG(warning) << print() << L" Flash call failed:" << param;//BOOST_THROW_EXCEPTION(invalid_operation() << msg_info("Flash function call failed.") << arg_name_info("param") << arg_value_info(narrow(param)));\r
266                 graph_->set_tag("param");\r
267 \r
268                 return result;\r
269         }\r
270         \r
271         safe_ptr<core::basic_frame> render_frame()\r
272         {\r
273                 float frame_time = 1.0f/ax_->GetFPS();\r
274 \r
275                 graph_->set_value("tick-time", static_cast<float>(tick_timer_.elapsed()/frame_time)*0.5f);\r
276                 tick_timer_.restart();\r
277 \r
278                 if (!ax_->IsReadyToRender())\r
279                         return head_;\r
280 \r
281                 if(ax_->IsEmpty())\r
282                         return core::basic_frame::empty();\r
283                 \r
284                 frame_timer_.restart();\r
285 \r
286                 ax_->Tick();\r
287                 if(ax_->InvalidRect())\r
288                 {\r
289                         core::pixel_format_desc desc;\r
290                         desc.pix_fmt = core::pixel_format::bgra;\r
291                         desc.planes.push_back(core::pixel_format_desc::plane(width_, height_, 4));\r
292                         auto frame = frame_factory_->create_frame(this, desc);\r
293 \r
294                         fast_memclr(bmp_.data(), width_*height_*4);\r
295                         ax_->DrawControl(bmp_);\r
296 \r
297                         if(frame->image_data().size() == static_cast<int>(width_*height_*4))\r
298                         {\r
299                                 fast_memcpy(frame->image_data().begin(), bmp_.data(), width_*height_*4);\r
300                                 frame->commit();\r
301                                 head_ = frame;\r
302                         }\r
303                 }               \r
304                                         \r
305                 MSG msg;\r
306                 while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) // DO NOT REMOVE THE MESSAGE DISPATCH LOOP. Without this some stuff doesn't work!  \r
307                 {\r
308                         if(msg.message == WM_TIMER && msg.wParam == 3 && msg.lParam == 0) // We tick this inside FlashAxContainer\r
309                                 continue;\r
310                         \r
311                         TranslateMessage(&msg);\r
312                         DispatchMessage(&msg);                  \r
313                 }\r
314                                                                                 \r
315                 graph_->set_value("frame-time", static_cast<float>(frame_timer_.elapsed()/frame_time)*0.5f);\r
316                 return head_;\r
317         }\r
318 \r
319         bool is_empty() const\r
320         {\r
321                 return ax_->IsEmpty();\r
322         }\r
323 \r
324         double fps() const\r
325         {\r
326                 return ax_->GetFPS();   \r
327         }\r
328         \r
329         std::wstring print()\r
330         {\r
331                 return L"flash-player[" + boost::filesystem::wpath(filename_).filename() \r
332                                   + L"|" + boost::lexical_cast<std::wstring>(width_)\r
333                                   + L"x" + boost::lexical_cast<std::wstring>(height_)\r
334                                   + L"]";               \r
335         }\r
336 };\r
337 \r
338 struct flash_producer : public core::frame_producer\r
339 {       \r
340         core::monitor::subject                                                                          monitor_subject_;\r
341         const std::wstring                                                                                      filename_;      \r
342         const safe_ptr<core::frame_factory>                                                     frame_factory_;\r
343         const int                                                                                                       width_;\r
344         const int                                                                                                       height_;\r
345         const int                                                                                                       buffer_size_;\r
346 \r
347         tbb::atomic<int>                                                                                        fps_;\r
348 \r
349         safe_ptr<diagnostics::graph>                                                            graph_;\r
350 \r
351         std::queue<safe_ptr<core::basic_frame>>                                         frame_buffer_;\r
352         tbb::concurrent_bounded_queue<safe_ptr<core::basic_frame>>      output_buffer_;\r
353         \r
354         safe_ptr<core::basic_frame>                                                                     last_frame_;\r
355                 \r
356         std::unique_ptr<flash_renderer>                                                         renderer_;\r
357         tbb::atomic<bool>                                                                                       has_renderer_;\r
358 \r
359         executor                                                                                                        executor_;      \r
360 public:\r
361         flash_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, size_t width, size_t height) \r
362                 : filename_(filename)           \r
363                 , frame_factory_(frame_factory)\r
364                 , last_frame_(core::basic_frame::empty())\r
365                 , width_(width > 0 ? width : frame_factory->get_video_format_desc().width)\r
366                 , height_(height > 0 ? height : frame_factory->get_video_format_desc().height)\r
367                 , buffer_size_(env::properties().get(L"configuration.flash.buffer-depth", frame_factory_->get_video_format_desc().fps > 30.0 ? 4 : 2))\r
368                 , executor_(L"flash_producer")\r
369         {       \r
370                 fps_ = 0;\r
371          \r
372                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));\r
373                 graph_->set_text(print());\r
374                 diagnostics::register_graph(graph_);\r
375                 \r
376                 has_renderer_ = false;\r
377         }\r
378 \r
379         ~flash_producer()\r
380         {\r
381                 executor_.invoke([this]\r
382                 {\r
383                         renderer_.reset();\r
384                 }, high_priority);\r
385         }\r
386 \r
387         // frame_producer\r
388                 \r
389         virtual safe_ptr<core::basic_frame> receive(int) override\r
390         {                                       \r
391                 auto frame = core::basic_frame::late();\r
392                 \r
393                 double buffered = output_buffer_.size();\r
394                 auto ratio = buffered / buffer_size_;\r
395                 graph_->set_value("buffered", ratio);\r
396 \r
397                 if(output_buffer_.try_pop(frame))\r
398                         last_frame_ = frame;\r
399                 else\r
400                         graph_->set_tag("late-frame");\r
401 \r
402                 fill_buffer();\r
403                 \r
404                 monitor_subject_ << core::monitor::message("/host/path")                % filename_\r
405                                              << core::monitor::message("/host/width")   % width_\r
406                                              << core::monitor::message("/host/height")  % height_\r
407                                              << core::monitor::message("/host/fps")             % fps_\r
408                                              << core::monitor::message("/buffer")               % output_buffer_.size() % buffer_size_;\r
409 \r
410                 return frame;\r
411         }\r
412 \r
413         virtual safe_ptr<core::basic_frame> last_frame() const override\r
414         {\r
415                 return last_frame_;\r
416         }               \r
417         \r
418         virtual boost::unique_future<std::wstring> call(const std::wstring& param) override\r
419         {       \r
420                 if (param == L"?")\r
421                         return wrap_as_future(std::wstring(has_renderer_ ? L"1" : L"0"));\r
422 \r
423                 return executor_.begin_invoke([this, param]() -> std::wstring\r
424                 {\r
425                         try\r
426                         {\r
427                                 bool initialize_renderer = !renderer_;\r
428 \r
429                                 if(initialize_renderer)\r
430                                 {\r
431                                         renderer_.reset(new flash_renderer(graph_, frame_factory_, filename_, width_, height_));\r
432 \r
433                                         has_renderer_ = true;\r
434                                 }\r
435 \r
436                                 std::wstring result = param == L"start_rendering"\r
437                                                 ? L"" : renderer_->call(param);\r
438 \r
439                                 if (initialize_renderer)\r
440                                 {\r
441                                         do_fill_buffer();\r
442                                 }\r
443 \r
444                                 return result;\r
445                                 //const auto& format_desc = frame_factory_->get_video_format_desc();\r
446                                 //if(abs(context_->fps() - format_desc.fps) > 0.01 && abs(context_->fps()/2.0 - format_desc.fps) > 0.01)\r
447                                 //      CASPAR_LOG(warning) << print() << " Invalid frame-rate: " << context_->fps() << L". Should be either " << format_desc.fps << L" or " << format_desc.fps*2.0 << L".";\r
448                         }\r
449                         catch(...)\r
450                         {\r
451                                 CASPAR_LOG_CURRENT_EXCEPTION();\r
452                                 renderer_.reset(nullptr);\r
453                                 has_renderer_ = false;\r
454                         }\r
455 \r
456                         return L"";\r
457                 }, high_priority);\r
458         }\r
459                 \r
460         virtual std::wstring print() const override\r
461         { \r
462                 return L"flash[" + boost::filesystem::wpath(filename_).filename() + L"|" + boost::lexical_cast<std::wstring>(fps_) + L"]";              \r
463         }       \r
464 \r
465         virtual boost::property_tree::wptree info() const override\r
466         {\r
467                 boost::property_tree::wptree info;\r
468                 info.add(L"type", L"flash-producer");\r
469                 return info;\r
470         }\r
471 \r
472         // flash_producer\r
473 \r
474         void fill_buffer()\r
475         {\r
476                 executor_.begin_invoke([this]\r
477                 {\r
478                         do_fill_buffer();\r
479                 });\r
480         }\r
481 \r
482         void do_fill_buffer()\r
483         {\r
484                 int nothing_rendered = 0;\r
485                 const int MAX_NOTHING_RENDERED_RETRIES = 4;\r
486 \r
487                 auto to_render = buffer_size_ - output_buffer_.size();\r
488                 int rendered = 0;\r
489 \r
490                 while (rendered < to_render)\r
491                 {\r
492                         bool was_rendered = next();\r
493 \r
494                         if (was_rendered)\r
495                         {\r
496                                 ++rendered;\r
497                         }\r
498                         else\r
499                         {\r
500                                 if (nothing_rendered++ < MAX_NOTHING_RENDERED_RETRIES)\r
501                                 {\r
502                                         // Flash player not ready with first frame, sleep to not busy-loop;\r
503                                         boost::this_thread::sleep(boost::posix_time::milliseconds(10));\r
504                                         boost::this_thread::yield();\r
505                                 }\r
506                                 else\r
507                                         return;\r
508                         }\r
509 \r
510                         executor_.yield();\r
511                 }\r
512         }\r
513         \r
514         bool next()\r
515         {       \r
516                 if(!renderer_)\r
517                         frame_buffer_.push(core::basic_frame::empty());\r
518 \r
519                 if(frame_buffer_.empty())\r
520                 {\r
521                         auto format_desc = frame_factory_->get_video_format_desc();\r
522                                         \r
523                         if(abs(renderer_->fps()/2.0 - format_desc.fps) < 2.0) // flash == 2 * format -> interlace\r
524                         {\r
525                                 auto frame1 = render_frame();\r
526 \r
527                                 if (frame1 != core::basic_frame::late())\r
528                                 {\r
529                                         auto frame2 = render_frame();\r
530                                         frame_buffer_.push(core::basic_frame::interlace(frame1, frame2, format_desc.field_mode));\r
531                                 }\r
532                         }\r
533                         else if(abs(renderer_->fps() - format_desc.fps/2.0) < 2.0) // format == 2 * flash -> duplicate\r
534                         {\r
535                                 auto frame = render_frame();\r
536 \r
537                                 if (frame != core::basic_frame::late())\r
538                                 {\r
539                                         frame_buffer_.push(frame);\r
540                                         frame_buffer_.push(frame);\r
541                                 }\r
542                         }\r
543                         else //if(abs(renderer_->fps() - format_desc_.fps) < 0.1) // format == flash -> simple\r
544                         {\r
545                                 auto frame = render_frame();\r
546 \r
547                                 if (frame != core::basic_frame::late())\r
548                                         frame_buffer_.push(frame);\r
549                         }\r
550                                                 \r
551                         fps_.fetch_and_store(static_cast<int>(renderer_->fps()*100.0));                         \r
552                         graph_->set_text(print());\r
553                         \r
554                         if(renderer_->is_empty())\r
555                         {\r
556                                 renderer_.reset();\r
557                                 has_renderer_ = false;\r
558                         }\r
559                 }\r
560 \r
561                 if (frame_buffer_.empty())\r
562                 {\r
563                         return false;\r
564                 }\r
565                 else\r
566                 {\r
567                         output_buffer_.push(std::move(frame_buffer_.front()));\r
568                         frame_buffer_.pop();\r
569                         return true;\r
570                 }\r
571         }\r
572 \r
573         safe_ptr<core::basic_frame> render_frame()\r
574         {       \r
575                 return renderer_->render_frame();\r
576         }\r
577 \r
578         core::monitor::subject& monitor_output()\r
579         {\r
580                 return monitor_subject_;\r
581         }\r
582 };\r
583 \r
584 safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
585 {\r
586         auto template_host = get_template_host(frame_factory->get_video_format_desc());\r
587         \r
588         auto filename = env::template_folder() + L"\\" + template_host.filename;\r
589         \r
590         if(!boost::filesystem::exists(filename))\r
591                 BOOST_THROW_EXCEPTION(file_not_found() << boost::errinfo_file_name(narrow(filename)));  \r
592 \r
593         return create_producer_destroy_proxy(\r
594                    create_producer_print_proxy(\r
595                         make_safe<flash_producer>(frame_factory, filename, template_host.width, template_host.height)));\r
596 }\r
597 \r
598 safe_ptr<core::frame_producer> create_swf_producer(\r
599                 const safe_ptr<core::frame_factory>& frame_factory,\r
600                 const core::parameters& params) \r
601 {\r
602         auto filename = env::media_folder() + L"\\" + params.at_original(0) + L".swf";\r
603         \r
604         if(!boost::filesystem::exists(filename))\r
605                 return core::frame_producer::empty();\r
606 \r
607         swf_t::header_t header(filename);\r
608 \r
609         auto producer = make_safe<flash_producer>(\r
610                         frame_factory, filename, header.frame_width, header.frame_height);\r
611 \r
612         producer->call(L"start_rendering").get();\r
613 \r
614         return create_producer_destroy_proxy(create_producer_print_proxy(producer));\r
615 }\r
616 \r
617 std::wstring find_template(const std::wstring& template_name)\r
618 {\r
619         if(boost::filesystem::exists(template_name + L".ft")) \r
620                 return template_name + L".ft";\r
621         \r
622         if(boost::filesystem::exists(template_name + L".ct"))\r
623                 return template_name + L".ct";\r
624         \r
625         if(boost::filesystem::exists(template_name + L".swf"))\r
626                 return template_name + L".swf";\r
627 \r
628         return L"";\r
629 }\r
630 \r
631 }}