]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPCommandsImpl.cpp
Don't persist media info, but generate at server startup in background thread instead...
[casparcg] / protocol / amcp / AMCPCommandsImpl.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: Nicklas P Andersson\r
20 */\r
21 \r
22 #include "../StdAfx.h"\r
23 \r
24 #if defined(_MSC_VER)\r
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings\r
26 #endif\r
27 \r
28 #include "AMCPCommandsImpl.h"\r
29 #include "AMCPProtocolStrategy.h"\r
30 \r
31 #include <common/env.h>\r
32 \r
33 #include <common/log/log.h>\r
34 #include <common/diagnostics/graph.h>\r
35 #include <common/os/windows/current_version.h>\r
36 #include <common/os/windows/system_info.h>\r
37 #include <common/utility/string.h>\r
38 #include <common/utility/utf8conv.h>\r
39 #include <common/utility/base64.h>\r
40 \r
41 #include <core/producer/frame_producer.h>\r
42 #include <core/video_format.h>\r
43 #include <core/producer/transition/transition_producer.h>\r
44 #include <core/producer/channel/channel_producer.h>\r
45 #include <core/producer/layer/layer_producer.h>\r
46 #include <core/producer/frame/frame_transform.h>\r
47 #include <core/producer/stage.h>\r
48 #include <core/producer/layer.h>\r
49 #include <core/producer/media_info/media_info.h>\r
50 #include <core/producer/media_info/media_info_repository.h>\r
51 #include <core/mixer/mixer.h>\r
52 #include <core/mixer/gpu/ogl_device.h>\r
53 #include <core/consumer/output.h>\r
54 \r
55 #include <modules/bluefish/bluefish.h>\r
56 #include <modules/decklink/decklink.h>\r
57 #include <modules/ffmpeg/ffmpeg.h>\r
58 #include <modules/flash/flash.h>\r
59 #include <modules/flash/util/swf.h>\r
60 #include <modules/flash/producer/flash_producer.h>\r
61 #include <modules/flash/producer/cg_producer.h>\r
62 #include <modules/ffmpeg/producer/util/util.h>\r
63 #include <modules/image/image.h>\r
64 #include <modules/ogl/ogl.h>\r
65 \r
66 #include <algorithm>\r
67 #include <locale>\r
68 #include <fstream>\r
69 #include <memory>\r
70 #include <cctype>\r
71 #include <io.h>\r
72 \r
73 #include <boost/date_time/posix_time/posix_time.hpp>\r
74 #include <boost/lexical_cast.hpp>\r
75 #include <boost/algorithm/string.hpp>\r
76 #include <boost/filesystem.hpp>\r
77 #include <boost/filesystem/fstream.hpp>\r
78 #include <boost/regex.hpp>\r
79 #include <boost/property_tree/xml_parser.hpp>\r
80 #include <boost/locale.hpp>\r
81 #include <boost/range/adaptor/transformed.hpp>\r
82 #include <boost/range/algorithm/copy.hpp>\r
83 #include <boost/archive/iterators/base64_from_binary.hpp>\r
84 #include <boost/archive/iterators/insert_linebreaks.hpp>\r
85 #include <boost/archive/iterators/transform_width.hpp>\r
86 \r
87 #include <tbb/concurrent_unordered_map.h>\r
88 \r
89 /* Return codes\r
90 \r
91 100 [action]                    Information om att något har hänt  \r
92 101 [action]                    Information om att något har hänt, en rad data skickas  \r
93 \r
94 202 [kommando] OK               Kommandot har utförts  \r
95 201 [kommando] OK               Kommandot har utförts, och en rad data skickas tillbaka  \r
96 200 [kommando] OK               Kommandot har utförts, och flera rader data skickas tillbaka. Avslutas med tomrad  \r
97 \r
98 400 ERROR                               Kommandot kunde inte förstås  \r
99 401 [kommando] ERROR    Ogiltig kanal  \r
100 402 [kommando] ERROR    Parameter saknas  \r
101 403 [kommando] ERROR    Ogiltig parameter  \r
102 404 [kommando] ERROR    Mediafilen hittades inte  \r
103 \r
104 500 FAILED                              Internt configurationfel  \r
105 501 [kommando] FAILED   Internt configurationfel  \r
106 502 [kommando] FAILED   Oläslig mediafil  \r
107 \r
108 600 [kommando] FAILED   funktion ej implementerad\r
109 */\r
110 \r
111 namespace caspar { namespace protocol {\r
112 \r
113 using namespace core;\r
114 \r
115 std::wstring read_file_base64(const boost::filesystem::wpath& file)\r
116 {\r
117         using namespace boost::archive::iterators;\r
118 \r
119         boost::filesystem::ifstream filestream(file, std::ios::binary);\r
120 \r
121         if (!filestream)\r
122                 return L"";\r
123 \r
124         auto length = boost::filesystem::file_size(file);\r
125         std::vector<char> bytes;\r
126         bytes.resize(length);\r
127         filestream.read(bytes.data(), length);\r
128 \r
129         return widen(to_base64(bytes.data(), length));\r
130 }\r
131 \r
132 std::wstring read_utf8_file(const boost::filesystem::wpath& file)\r
133 {\r
134         std::wstringstream result;\r
135         boost::filesystem::wifstream filestream(file);\r
136 \r
137         if (filestream) \r
138         {\r
139                 // Consume BOM first\r
140                 filestream.get();\r
141                 // read all data\r
142                 result << filestream.rdbuf();\r
143         }\r
144 \r
145         return result.str();\r
146 }\r
147 \r
148 std::wstring read_latin1_file(const boost::filesystem::wpath& file)\r
149 {\r
150         boost::locale::generator gen;\r
151         gen.locale_cache_enabled(true);\r
152         gen.categories(boost::locale::codepage_facet);\r
153 \r
154         std::stringstream result_stream;\r
155         boost::filesystem::ifstream filestream(file);\r
156         filestream.imbue(gen("en_US.ISO8859-1"));\r
157 \r
158         if (filestream)\r
159         {\r
160                 // read all data\r
161                 result_stream << filestream.rdbuf();\r
162         }\r
163 \r
164         std::string result = result_stream.str();\r
165         std::wstring widened_result;\r
166 \r
167         // The first 255 codepoints in unicode is the same as in latin1\r
168         auto from_signed_to_signed = std::function<unsigned char(char)>(\r
169                 [] (char c) { return static_cast<unsigned char>(c); }\r
170         );\r
171         boost::copy(\r
172                 result | boost::adaptors::transformed(from_signed_to_signed),\r
173                 std::back_inserter(widened_result));\r
174 \r
175         return widened_result;\r
176 }\r
177 \r
178 std::wstring read_file(const boost::filesystem::wpath& file)\r
179 {\r
180         static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};\r
181 \r
182         if (!boost::filesystem::exists(file))\r
183         {\r
184                 return L"";\r
185         }\r
186 \r
187         if (boost::filesystem::file_size(file) >= 3)\r
188         {\r
189                 boost::filesystem::ifstream bom_stream(file);\r
190 \r
191                 char header[3];\r
192                 bom_stream.read(header, 3);\r
193                 bom_stream.close();\r
194 \r
195                 if (std::memcmp(BOM, header, 3) == 0)\r
196                         return read_utf8_file(file);\r
197         }\r
198 \r
199         return read_latin1_file(file);\r
200 }\r
201 \r
202 std::wstring MediaInfo(const boost::filesystem::wpath& path, const std::shared_ptr<core::media_info_repository>& media_info_repo)\r
203 {\r
204         if(boost::filesystem::is_regular_file(path))\r
205         {\r
206                 std::wstring clipttype = TEXT(" N/A ");\r
207                 std::wstring extension = boost::to_upper_copy(path.extension());\r
208                 if(extension == TEXT(".TGA") || extension == TEXT(".COL") || extension == L".PNG" || extension == L".JPEG" || extension == L".JPG" ||\r
209                         extension == L".GIF" || extension == L".BMP")\r
210                 {\r
211                         clipttype = TEXT(" STILL ");                    \r
212                 }\r
213                 else if(extension == TEXT(".WAV") || extension == TEXT(".MP3"))\r
214                 {\r
215                         clipttype = TEXT(" AUDIO ");\r
216                 }\r
217                 else if(extension == TEXT(".SWF") || extension == TEXT(".CT") ||\r
218                                 extension == TEXT(".DV") || extension == TEXT(".MOV") || \r
219                                 extension == TEXT(".MPG") || extension == TEXT(".AVI") || \r
220                                 extension == TEXT(".MP4") || extension == TEXT(".FLV") || \r
221                                 caspar::ffmpeg::is_valid_file(path.file_string()))\r
222                 {\r
223                         clipttype = TEXT(" MOVIE ");\r
224                 }\r
225 \r
226                 if(clipttype != TEXT(" N/A "))\r
227                 {               \r
228                         auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };\r
229 \r
230                         auto relativePath = boost::filesystem::wpath(path.file_string().substr(env::media_folder().size()-1, path.file_string().size()));\r
231 \r
232                         auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));\r
233                         writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());\r
234                         auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());\r
235 \r
236                         auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));\r
237                         sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());\r
238                         auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());\r
239                                 \r
240                         auto str = relativePath.replace_extension(TEXT("")).external_file_string();\r
241                         if(str[0] == '\\' || str[0] == '/')\r
242                                 str = std::wstring(str.begin() + 1, str.end());\r
243                         auto media_info = media_info_repo->get(path.file_string());\r
244                         \r
245                         return std::wstring() \r
246                                         + L"\""         + str +\r
247                                         + L"\" "        + clipttype +\r
248                                         + L" "          + sizeStr +\r
249                                         + L" "          + writeTimeWStr +\r
250                                         + L" "          + boost::lexical_cast<std::wstring>(media_info.duration) +\r
251                                         + L" "          + boost::lexical_cast<std::wstring>(media_info.time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info.time_base.denominator())\r
252                                         + L"\r\n";      \r
253                 }       \r
254         }\r
255         return L"";\r
256 }\r
257 \r
258 std::wstring ListMedia(const std::shared_ptr<core::media_info_repository>& media_info_repo)\r
259 {               \r
260         std::wstringstream replyString;\r
261         for (boost::filesystem::wrecursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr) \r
262                 replyString << MediaInfo(itr->path(), media_info_repo);\r
263         \r
264         return boost::to_upper_copy(replyString.str());\r
265 }\r
266 \r
267 std::wstring ListTemplates() \r
268 {\r
269         std::wstringstream replyString;\r
270 \r
271         for (boost::filesystem::wrecursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)\r
272         {               \r
273                 if(boost::filesystem::is_regular_file(itr->path()) && (itr->path().extension() == L".ft" || itr->path().extension() == L".ct"))\r
274                 {\r
275                         auto relativePath = boost::filesystem::wpath(itr->path().file_string().substr(env::template_folder().size()-1, itr->path().file_string().size()));\r
276 \r
277                         auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));\r
278                         writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());\r
279                         auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());\r
280 \r
281                         auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));\r
282                         sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());\r
283 \r
284                         auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());\r
285 \r
286                         std::wstring dir = relativePath.parent_path().external_directory_string();\r
287                         std::wstring file = boost::to_upper_copy(relativePath.filename());\r
288                         relativePath = boost::filesystem::wpath(dir + L"/" + file);\r
289                                                 \r
290                         auto str = relativePath.replace_extension(TEXT("")).external_file_string();\r
291                         boost::trim_if(str, boost::is_any_of("\\/"));\r
292 \r
293                         replyString << TEXT("\"") << str\r
294                                                 << TEXT("\" ") << sizeWStr\r
295                                                 << TEXT(" ") << writeTimeWStr\r
296                                                 << TEXT("\r\n");                \r
297                 }\r
298         }\r
299         return replyString.str();\r
300 }\r
301 \r
302 namespace amcp {\r
303         \r
304 AMCPCommand::AMCPCommand() : channelIndex_(0), scheduling_(Default), layerIndex_(-1)\r
305 {}\r
306 \r
307 void AMCPCommand::SendReply()\r
308 {\r
309         if(!pClientInfo_) \r
310                 return;\r
311 \r
312         if(replyString_.empty())\r
313                 return;\r
314         pClientInfo_->Send(replyString_);\r
315 }\r
316 \r
317 void AMCPCommand::Clear() \r
318 {\r
319         pChannel_->stage()->clear();\r
320         pClientInfo_.reset();\r
321         channelIndex_ = 0;\r
322         _parameters.clear();\r
323 }\r
324 \r
325 bool DiagnosticsCommand::DoExecute()\r
326 {       \r
327         try\r
328         {\r
329                 diagnostics::show_graphs(true);\r
330 \r
331                 SetReplyString(TEXT("202 DIAG OK\r\n"));\r
332 \r
333                 return true;\r
334         }\r
335         catch(...)\r
336         {\r
337                 CASPAR_LOG_CURRENT_EXCEPTION();\r
338                 SetReplyString(TEXT("502 DIAG FAILED\r\n"));\r
339                 return false;\r
340         }\r
341 }\r
342 \r
343 bool ChannelGridCommand::DoExecute()\r
344 {\r
345         int index = 1;\r
346         auto self = GetChannels().back();\r
347         \r
348         core::parameters params;\r
349         params.push_back(L"SCREEN");\r
350         params.push_back(L"NAME");\r
351         params.push_back(L"Channel Grid Window");\r
352         auto screen = create_consumer(params);\r
353 \r
354         self->output()->add(screen);\r
355 \r
356         BOOST_FOREACH(auto channel, GetChannels())\r
357         {\r
358                 if(channel != self)\r
359                 {\r
360                         auto producer = create_channel_producer(self->mixer(), channel);                \r
361                         self->stage()->load(index, producer, false);\r
362                         self->stage()->play(index);\r
363                         index++;\r
364                 }\r
365         }\r
366 \r
367         int n = GetChannels().size()-1;\r
368         double delta = 1.0/static_cast<double>(n);\r
369         for(int x = 0; x < n; ++x)\r
370         {\r
371                 for(int y = 0; y < n; ++y)\r
372                 {\r
373                         int index = x+y*n+1;\r
374                         auto transform = [=](frame_transform transform) -> frame_transform\r
375                         {               \r
376                                 transform.fill_translation[0]   = x*delta;\r
377                                 transform.fill_translation[1]   = y*delta;\r
378                                 transform.fill_scale[0]                 = delta;\r
379                                 transform.fill_scale[1]                 = delta;\r
380                                 transform.clip_translation[0]   = x*delta;\r
381                                 transform.clip_translation[1]   = y*delta;\r
382                                 transform.clip_scale[0]                 = delta;\r
383                                 transform.clip_scale[1]                 = delta;                        \r
384                                 return transform;\r
385                         };\r
386                         self->stage()->apply_transform(index, transform);\r
387                 }\r
388         }\r
389 \r
390         return true;\r
391 }\r
392 \r
393 bool CallCommand::DoExecute()\r
394 {       \r
395         //Perform loading of the clip\r
396         try\r
397         {\r
398                 auto what = _parameters.at(0);\r
399                                 \r
400                 boost::unique_future<std::wstring> result;\r
401                 auto& params_orig = _parameters.get_original();\r
402                 if(what == L"B" || what == L"F")\r
403                 {\r
404                         std::wstring param;\r
405                         for(auto it = std::begin(params_orig)+1; it != std::end(params_orig); ++it, param += L" ")\r
406                                 param += *it;\r
407                         result = GetChannel()->stage()->call(GetLayerIndex(), what == L"F", boost::trim_copy(param));\r
408                 }\r
409                 else\r
410                 {\r
411                         std::wstring param;\r
412                         for(auto it = std::begin(params_orig); it != std::end(params_orig); ++it, param += L" ")\r
413                                 param += *it;\r
414                         result = GetChannel()->stage()->call(GetLayerIndex(), true, boost::trim_copy(param));\r
415                 }\r
416 \r
417                 if(!result.timed_wait(boost::posix_time::seconds(2)))\r
418                         BOOST_THROW_EXCEPTION(timed_out());\r
419                                 \r
420                 std::wstringstream replyString;\r
421                 if(result.get().empty())\r
422                         replyString << TEXT("202 CALL OK\r\n");\r
423                 else\r
424                         replyString << TEXT("201 CALL OK\r\n") << result.get() << L"\r\n";\r
425                 \r
426                 SetReplyString(replyString.str());\r
427 \r
428                 return true;\r
429         }\r
430         catch(...)\r
431         {\r
432                 CASPAR_LOG_CURRENT_EXCEPTION();\r
433                 SetReplyString(TEXT("502 CALL FAILED\r\n"));\r
434                 return false;\r
435         }\r
436 }\r
437 \r
438 // UGLY HACK\r
439 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms;\r
440 \r
441 core::frame_transform MixerCommand::get_current_transform()\r
442 {\r
443         return GetChannel()->stage()->get_current_transform(GetLayerIndex());\r
444 }\r
445 \r
446 bool MixerCommand::DoExecute()\r
447 {\r
448         using boost::lexical_cast;\r
449         //Perform loading of the clip\r
450         try\r
451         {       \r
452                 bool defer = _parameters.back() == L"DEFER";\r
453                 if(defer)\r
454                         _parameters.pop_back();\r
455 \r
456                 std::vector<stage::transform_tuple_t> transforms;\r
457 \r
458                 if(_parameters[0] == L"KEYER" || _parameters[0] == L"IS_KEY")\r
459                 {\r
460                         if (_parameters.size() == 1)\r
461                                 return reply_value([](const frame_transform& t) { return t.is_key ? 1 : 0; });\r
462 \r
463                         bool value = boost::lexical_cast<int>(_parameters.at(1));\r
464                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
465                         {\r
466                                 transform.is_key = value;\r
467                                 return transform;                                       \r
468                         }, 0, L"linear"));\r
469                 }\r
470                 else if(_parameters[0] == L"OPACITY")\r
471                 {\r
472                         if (_parameters.size() == 1)\r
473                                 return reply_value([](const frame_transform& t) { return t.opacity; });\r
474 \r
475                         int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
476                         std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
477 \r
478                         double value = boost::lexical_cast<double>(_parameters.at(1));\r
479                         \r
480                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
481                         {\r
482                                 transform.opacity = value;\r
483                                 return transform;                                       \r
484                         }, duration, tween));\r
485                 }\r
486                 else if(_parameters[0] == L"FILL" || _parameters[0] == L"FILL_RECT")\r
487                 {\r
488                         if (_parameters.size() == 1)\r
489                         {\r
490                                 auto transform = get_current_transform();\r
491                                 auto translation = transform.fill_translation;\r
492                                 auto scale = transform.fill_scale;\r
493                                 SetReplyString(\r
494                                                 L"201 MIXER OK\r\n" \r
495                                                 + lexical_cast<std::wstring>(translation[0]) + L" "\r
496                                                 + lexical_cast<std::wstring>(translation[1]) + L" "\r
497                                                 + lexical_cast<std::wstring>(scale[0]) + L" "\r
498                                                 + lexical_cast<std::wstring>(scale[1]) + L"\r\n");\r
499                                 return true;\r
500                         }\r
501 \r
502                         int duration = _parameters.size() > 5 ? boost::lexical_cast<int>(_parameters[5]) : 0;\r
503                         std::wstring tween = _parameters.size() > 6 ? _parameters[6] : L"linear";\r
504                         double x        = boost::lexical_cast<double>(_parameters.at(1));\r
505                         double y        = boost::lexical_cast<double>(_parameters.at(2));\r
506                         double x_s      = boost::lexical_cast<double>(_parameters.at(3));\r
507                         double y_s      = boost::lexical_cast<double>(_parameters.at(4));\r
508 \r
509                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) mutable -> frame_transform\r
510                         {\r
511                                 transform.fill_translation[0]   = x;\r
512                                 transform.fill_translation[1]   = y;\r
513                                 transform.fill_scale[0]                 = x_s;\r
514                                 transform.fill_scale[1]                 = y_s;\r
515                                 return transform;\r
516                         }, duration, tween));\r
517                 }\r
518                 else if(_parameters[0] == L"CLIP" || _parameters[0] == L"CLIP_RECT")\r
519                 {\r
520                         if (_parameters.size() == 1)\r
521                         {\r
522                                 auto transform = get_current_transform();\r
523                                 auto translation = transform.clip_translation;\r
524                                 auto scale = transform.clip_scale;\r
525                                 SetReplyString(\r
526                                                 L"201 MIXER OK\r\n" \r
527                                                 + lexical_cast<std::wstring>(translation[0]) + L" "\r
528                                                 + lexical_cast<std::wstring>(translation[1]) + L" "\r
529                                                 + lexical_cast<std::wstring>(scale[0]) + L" "\r
530                                                 + lexical_cast<std::wstring>(scale[1]) + L"\r\n");\r
531                                 return true;\r
532                         }\r
533 \r
534                         int duration = _parameters.size() > 5 ? boost::lexical_cast<int>(_parameters[5]) : 0;\r
535                         std::wstring tween = _parameters.size() > 6 ? _parameters[6] : L"linear";\r
536                         double x        = boost::lexical_cast<double>(_parameters.at(1));\r
537                         double y        = boost::lexical_cast<double>(_parameters.at(2));\r
538                         double x_s      = boost::lexical_cast<double>(_parameters.at(3));\r
539                         double y_s      = boost::lexical_cast<double>(_parameters.at(4));\r
540                         if(x_s < 0 || y_s < 0)\r
541                         {\r
542                                 SetReplyString(L"403 MIXER ERROR\r\n");\r
543                                 return false;\r
544                         }\r
545 \r
546                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
547                         {\r
548                                 transform.clip_translation[0]   = x;\r
549                                 transform.clip_translation[1]   = y;\r
550                                 transform.clip_scale[0]                 = x_s;\r
551                                 transform.clip_scale[1]                 = y_s;\r
552                                 return transform;\r
553                         }, duration, tween));\r
554                 }\r
555                 else if(_parameters[0] == L"GRID")\r
556                 {\r
557                         int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
558                         std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
559                         int n = boost::lexical_cast<int>(_parameters.at(1));\r
560                         double delta = 1.0/static_cast<double>(n);\r
561                         for(int x = 0; x < n; ++x)\r
562                         {\r
563                                 for(int y = 0; y < n; ++y)\r
564                                 {\r
565                                         int index = x+y*n+1;\r
566                                         transforms.push_back(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform\r
567                                         {               \r
568                                                 transform.fill_translation[0]   = x*delta;\r
569                                                 transform.fill_translation[1]   = y*delta;\r
570                                                 transform.fill_scale[0]                 = delta;\r
571                                                 transform.fill_scale[1]                 = delta;\r
572                                                 transform.clip_translation[0]   = x*delta;\r
573                                                 transform.clip_translation[1]   = y*delta;\r
574                                                 transform.clip_scale[0]                 = delta;\r
575                                                 transform.clip_scale[1]                 = delta;                        \r
576                                                 return transform;\r
577                                         }, duration, tween));\r
578                                 }\r
579                         }\r
580                 }\r
581                 else if(_parameters[0] == L"BLEND")\r
582                 {\r
583                         if (_parameters.size() == 1)\r
584                         {\r
585                                 auto blend_mode = GetChannel()->mixer()->get_blend_mode(GetLayerIndex());\r
586                                 SetReplyString(L"201 MIXER OK\r\n" \r
587                                         + lexical_cast<std::wstring>(get_blend_mode(blend_mode)) \r
588                                         + L"\r\n");\r
589                                 return true;\r
590                         }\r
591 \r
592                         auto blend_str = _parameters.at(1);                                                             \r
593                         int layer = GetLayerIndex();\r
594                         blend_mode::type blend = get_blend_mode(blend_str);\r
595                         GetChannel()->mixer()->set_blend_mode(GetLayerIndex(), blend);  \r
596                 }\r
597         else if(_parameters[0] == L"CHROMA")\r
598         {\r
599                         if (_parameters.size() == 1)\r
600                         {\r
601                                 auto chroma = GetChannel()->mixer()->get_chroma(GetLayerIndex());\r
602                                 SetReplyString(L"201 MIXER OK\r\n" \r
603                                         + get_chroma_mode(chroma.key)\r
604                                         + (chroma.key == chroma::none\r
605                                                 ? L""\r
606                                                 : L" "\r
607                                                 + lexical_cast<std::wstring>(chroma.threshold) + L" "\r
608                                                 + lexical_cast<std::wstring>(chroma.softness))\r
609                                         + L"\r\n");\r
610                                         // Add the rest when they are actually used and documented\r
611                                 return true;\r
612                         }\r
613 \r
614                         int layer = GetLayerIndex();\r
615             chroma chroma;\r
616             chroma.key = get_chroma_mode(_parameters[1]);\r
617 \r
618                         if (chroma.key != chroma::none)\r
619                         {\r
620                                 chroma.threshold    = boost::lexical_cast<double>(_parameters[2]);\r
621                                 chroma.softness     = boost::lexical_cast<double>(_parameters[3]);\r
622                                 chroma.spill        = _parameters.size() > 4 ? boost::lexical_cast<double>(_parameters[4]) : 0.0f;\r
623                                 chroma.blur         = _parameters.size() > 5 ? boost::lexical_cast<double>(_parameters[5]) : 0.0f;\r
624                                 chroma.show_mask    = _parameters.size() > 6 ? bool(boost::lexical_cast<int>(_parameters[6])) : false;\r
625                         }\r
626 \r
627             GetChannel()->mixer()->set_chroma(GetLayerIndex(), chroma);\r
628         }\r
629                 else if(_parameters[0] == L"MASTERVOLUME")\r
630                 {\r
631                         if (_parameters.size() == 1)\r
632                         {\r
633                                 auto volume = GetChannel()->mixer()->get_master_volume();\r
634                                 SetReplyString(L"201 MIXER OK\r\n" \r
635                                         + lexical_cast<std::wstring>(volume) + L"\r\n");\r
636                                 return true;\r
637                         }\r
638 \r
639                         float master_volume = boost::lexical_cast<float>(_parameters.at(1));\r
640                         GetChannel()->mixer()->set_master_volume(master_volume);\r
641                 }\r
642                 else if(_parameters[0] == L"BRIGHTNESS")\r
643                 {\r
644                         if (_parameters.size() == 1)\r
645                                 return reply_value([](const frame_transform& t) { return t.brightness; });\r
646 \r
647                         auto value = boost::lexical_cast<double>(_parameters.at(1));\r
648                         int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
649                         std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
650                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
651                         {\r
652                                 transform.brightness = value;\r
653                                 return transform;\r
654                         }, duration, tween));\r
655                 }\r
656                 else if(_parameters[0] == L"SATURATION")\r
657                 {\r
658                         if (_parameters.size() == 1)\r
659                                 return reply_value([](const frame_transform& t) { return t.saturation; });\r
660 \r
661                         auto value = boost::lexical_cast<double>(_parameters.at(1));\r
662                         int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
663                         std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
664                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
665                         {\r
666                                 transform.saturation = value;\r
667                                 return transform;\r
668                         }, duration, tween));   \r
669                 }\r
670                 else if(_parameters[0] == L"CONTRAST")\r
671                 {\r
672                         if (_parameters.size() == 1)\r
673                                 return reply_value([](const frame_transform& t) { return t.contrast; });\r
674 \r
675                         auto value = boost::lexical_cast<double>(_parameters.at(1));\r
676                         int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
677                         std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
678                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
679                         {\r
680                                 transform.contrast = value;\r
681                                 return transform;\r
682                         }, duration, tween));   \r
683                 }\r
684                 else if(_parameters[0] == L"LEVELS")\r
685                 {\r
686                         if (_parameters.size() == 1)\r
687                         {\r
688                                 auto levels = get_current_transform().levels;\r
689                                 SetReplyString(L"201 MIXER OK\r\n"\r
690                                         + lexical_cast<std::wstring>(levels.min_input) + L" "\r
691                                         + lexical_cast<std::wstring>(levels.max_input) + L" "\r
692                                         + lexical_cast<std::wstring>(levels.gamma) + L" "\r
693                                         + lexical_cast<std::wstring>(levels.min_output) + L" "\r
694                                         + lexical_cast<std::wstring>(levels.max_output) + L"\r\n");\r
695                                 return true;\r
696                         }\r
697 \r
698                         levels value;\r
699                         value.min_input  = boost::lexical_cast<double>(_parameters.at(1));\r
700                         value.max_input  = boost::lexical_cast<double>(_parameters.at(2));\r
701                         value.gamma              = boost::lexical_cast<double>(_parameters.at(3));\r
702                         value.min_output = boost::lexical_cast<double>(_parameters.at(4));\r
703                         value.max_output = boost::lexical_cast<double>(_parameters.at(5));\r
704                         int duration = _parameters.size() > 6 ? boost::lexical_cast<int>(_parameters[6]) : 0;\r
705                         std::wstring tween = _parameters.size() > 7 ? _parameters[7] : L"linear";\r
706 \r
707                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
708                         {\r
709                                 transform.levels = value;\r
710                                 return transform;\r
711                         }, duration, tween));\r
712                 }\r
713                 else if(_parameters[0] == L"STRAIGHT_ALPHA_OUTPUT")\r
714                 {\r
715                         if (_parameters.size() == 1)\r
716                         {\r
717                                 SetReplyString(L"201 MIXER OK\r\n"\r
718                                         + lexical_cast<std::wstring>(\r
719                                                         GetChannel()->mixer()->get_straight_alpha_output())\r
720                                         + L"\r\n");\r
721                                 return true;\r
722                         }\r
723 \r
724                         bool value = boost::lexical_cast<bool>(_parameters[1]);\r
725                         GetChannel()->mixer()->set_straight_alpha_output(value);\r
726                 }\r
727                 else if(_parameters[0] == L"VOLUME")\r
728                 {\r
729                         if (_parameters.size() == 1)\r
730                                 return reply_value([](const frame_transform& t) { return t.volume; });\r
731 \r
732                         int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
733                         std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear";\r
734                         double value = boost::lexical_cast<double>(_parameters[1]);\r
735 \r
736                         transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform\r
737                         {\r
738                                 transform.volume = value;\r
739                                 return transform;\r
740                         }, duration, tween));\r
741                 }\r
742                 else if(_parameters[0] == L"CLEAR")\r
743                 {\r
744                         int layer = GetLayerIndex(std::numeric_limits<int>::max());\r
745                         if (layer == std::numeric_limits<int>::max())\r
746                         {\r
747                                 GetChannel()->stage()->clear_transforms();\r
748                                 GetChannel()->mixer()->clear_blend_modes();\r
749                         }\r
750                         else\r
751                         {\r
752                                 GetChannel()->stage()->clear_transforms(layer);\r
753                                 GetChannel()->mixer()->clear_blend_mode(layer);\r
754                         }\r
755                 }\r
756                 else if(_parameters[0] == L"COMMIT")\r
757                 {\r
758                         transforms = std::move(deferred_transforms[GetChannelIndex()]);\r
759                 }\r
760                 else\r
761                 {\r
762                         SetReplyString(TEXT("404 MIXER ERROR\r\n"));\r
763                         return false;\r
764                 }\r
765 \r
766                 if(defer)\r
767                 {\r
768                         auto& defer_tranforms = deferred_transforms[GetChannelIndex()];\r
769                         defer_tranforms.insert(defer_tranforms.end(), transforms.begin(), transforms.end());\r
770                 }\r
771                 else\r
772                         GetChannel()->stage()->apply_transforms(transforms);\r
773         \r
774                 SetReplyString(TEXT("202 MIXER OK\r\n"));\r
775 \r
776                 return true;\r
777         }\r
778         catch(file_not_found&)\r
779         {\r
780                 CASPAR_LOG_CURRENT_EXCEPTION();\r
781                 SetReplyString(TEXT("404 MIXER ERROR\r\n"));\r
782                 return false;\r
783         }\r
784         catch(...)\r
785         {\r
786                 CASPAR_LOG_CURRENT_EXCEPTION();\r
787                 SetReplyString(TEXT("502 MIXER FAILED\r\n"));\r
788                 return false;\r
789         }\r
790 }\r
791 \r
792 bool SwapCommand::DoExecute()\r
793 {       \r
794         //Perform loading of the clip\r
795         try\r
796         {\r
797                 if(GetLayerIndex(-1) != -1)\r
798                 {\r
799                         std::vector<std::string> strs;\r
800                         boost::split(strs, _parameters[0], boost::is_any_of("-"));\r
801                         \r
802                         auto ch1 = GetChannel();\r
803                         auto ch2 = GetChannels().at(boost::lexical_cast<int>(strs.at(0))-1);\r
804 \r
805                         int l1 = GetLayerIndex();\r
806                         int l2 = boost::lexical_cast<int>(strs.at(1));\r
807 \r
808                         ch1->stage()->swap_layer(l1, l2, ch2->stage());\r
809                 }\r
810                 else\r
811                 {\r
812                         auto ch1 = GetChannel();\r
813                         auto ch2 = GetChannels().at(boost::lexical_cast<int>(_parameters[0])-1);\r
814                         ch1->stage()->swap_layers(ch2->stage());\r
815                 }\r
816                 \r
817                 SetReplyString(TEXT("202 SWAP OK\r\n"));\r
818 \r
819                 return true;\r
820         }\r
821         catch(file_not_found&)\r
822         {\r
823                 CASPAR_LOG_CURRENT_EXCEPTION();\r
824                 SetReplyString(TEXT("404 SWAP ERROR\r\n"));\r
825                 return false;\r
826         }\r
827         catch(...)\r
828         {\r
829                 CASPAR_LOG_CURRENT_EXCEPTION();\r
830                 SetReplyString(TEXT("502 SWAP FAILED\r\n"));\r
831                 return false;\r
832         }\r
833 }\r
834 \r
835 bool AddCommand::DoExecute()\r
836 {       \r
837         //Perform loading of the clip\r
838         try\r
839         {\r
840                 auto consumer = create_consumer(_parameters);\r
841                 GetChannel()->output()->add(GetLayerIndex(consumer->index()), consumer);\r
842         \r
843                 SetReplyString(TEXT("202 ADD OK\r\n"));\r
844 \r
845                 return true;\r
846         }\r
847         catch(file_not_found&)\r
848         {\r
849                 CASPAR_LOG_CURRENT_EXCEPTION();\r
850                 SetReplyString(TEXT("404 ADD ERROR\r\n"));\r
851                 return false;\r
852         }\r
853         catch(...)\r
854         {\r
855                 CASPAR_LOG_CURRENT_EXCEPTION();\r
856                 SetReplyString(TEXT("502 ADD FAILED\r\n"));\r
857                 return false;\r
858         }\r
859 }\r
860 \r
861 bool RemoveCommand::DoExecute()\r
862 {       \r
863         //Perform loading of the clip\r
864         try\r
865         {\r
866                 auto index = GetLayerIndex(std::numeric_limits<int>::min());\r
867                 if(index == std::numeric_limits<int>::min())\r
868                         index = create_consumer(_parameters)->index();\r
869 \r
870                 GetChannel()->output()->remove(index);\r
871 \r
872                 SetReplyString(TEXT("202 REMOVE OK\r\n"));\r
873 \r
874                 return true;\r
875         }\r
876         catch(file_not_found&)\r
877         {\r
878                 CASPAR_LOG_CURRENT_EXCEPTION();\r
879                 SetReplyString(TEXT("404 REMOVE ERROR\r\n"));\r
880                 return false;\r
881         }\r
882         catch(...)\r
883         {\r
884                 CASPAR_LOG_CURRENT_EXCEPTION();\r
885                 SetReplyString(TEXT("502 REMOVE FAILED\r\n"));\r
886                 return false;\r
887         }\r
888 }\r
889 \r
890 safe_ptr<core::frame_producer> RouteCommand::TryCreateProducer(AMCPCommand& command, std::wstring const& uri)\r
891 {\r
892         safe_ptr<core::frame_producer> pFP(frame_producer::empty());\r
893 \r
894         auto tokens = core::parameters::protocol_split(uri);\r
895         auto src_channel_layer_token = tokens[0] == L"route" ? tokens[1] : uri;\r
896         std::vector<std::wstring> src_channel_layer;\r
897         boost::split(src_channel_layer, src_channel_layer_token, boost::is_any_of("-"));\r
898         bool is_channel_layer_spec = src_channel_layer.size() == 2;\r
899         bool is_channel_spec = src_channel_layer.size() == 1;\r
900         int src_channel_index;\r
901 \r
902         if (is_channel_layer_spec || is_channel_spec)\r
903         {\r
904                 try\r
905                 {\r
906                         src_channel_index = boost::lexical_cast<int>(src_channel_layer[0]);\r
907                 }\r
908                 catch(const boost::bad_lexical_cast&)\r
909                 {\r
910                         is_channel_layer_spec = false;\r
911                         is_channel_spec = false;\r
912                 }\r
913         }\r
914 \r
915         int src_layer_index = -1;\r
916 \r
917         if (is_channel_layer_spec)\r
918         {\r
919                 if (!src_channel_layer[1].empty())\r
920                 {\r
921                         try\r
922                         {\r
923                                 src_layer_index = boost::lexical_cast<int>(src_channel_layer[1]);\r
924                         }\r
925                         catch(const boost::bad_lexical_cast&)\r
926                         {\r
927                                 is_channel_layer_spec = false;\r
928                         }\r
929                 }\r
930                 else\r
931                         is_channel_layer_spec = false;\r
932         }\r
933 \r
934         if (tokens[0] == L"route" || is_channel_layer_spec || is_channel_spec) // It looks like a route\r
935         {\r
936                 // Find the source channel\r
937                 auto channels = command.GetChannels();\r
938                 auto src_channel = std::find_if(\r
939                         channels.begin(), \r
940                         channels.end(), \r
941                         [src_channel_index](const safe_ptr<core::video_channel>& item) { return item->index() == src_channel_index; }\r
942                 );\r
943                 if (src_channel == channels.end())\r
944                         BOOST_THROW_EXCEPTION(null_argument() << msg_info("src channel not found"));\r
945 \r
946                 // Find the source layer (if one is given)\r
947                 if (is_channel_layer_spec)\r
948                         pFP = create_layer_producer(command.GetChannel()->mixer(), (*src_channel)->stage(), src_layer_index);\r
949                 else \r
950                         pFP = create_channel_producer(command.GetChannel()->mixer(), *src_channel);\r
951         }\r
952         return pFP;\r
953 }\r
954 \r
955 bool RouteCommand::DoExecute()\r
956 {       \r
957         try\r
958         {\r
959                 auto pFP = RouteCommand::TryCreateProducer(\r
960                                 *this, _parameters.at_original(0));\r
961 \r
962                 if (pFP != frame_producer::empty())\r
963                 {\r
964                         GetChannel()->stage()->load(GetLayerIndex(), pFP, true);\r
965                         GetChannel()->stage()->play(GetLayerIndex());\r
966 \r
967                         SetReplyString(TEXT("202 ROUTE OK\r\n"));\r
968                 \r
969                         return true;\r
970                 }\r
971                 SetReplyString(TEXT("404 ROUTE ERROR\r\n"));\r
972                 return false;\r
973         }\r
974         catch(file_not_found&)\r
975         {\r
976                 CASPAR_LOG_CURRENT_EXCEPTION();\r
977                 SetReplyString(TEXT("404 ROUTE ERROR\r\n"));\r
978                 return false;\r
979         }\r
980         catch(...)\r
981         {\r
982                 CASPAR_LOG_CURRENT_EXCEPTION();\r
983                 SetReplyString(TEXT("502 ROUTE FAILED\r\n"));\r
984                 return false;\r
985         }\r
986 }\r
987 \r
988 bool LoadCommand::DoExecute()\r
989 {       \r
990         //Perform loading of the clip\r
991         try\r
992         {\r
993                 auto uri_tokens = parameters::protocol_split(_parameters.at_original(0));\r
994                 auto pFP = frame_producer::empty();\r
995                 if (uri_tokens[0] == L"route")\r
996                 {\r
997                         pFP = RouteCommand::TryCreateProducer(*this, _parameters.at_original(0));\r
998                 }\r
999                 if (pFP == frame_producer::empty())\r
1000                 {\r
1001                         pFP = create_producer(GetChannel()->mixer(), _parameters);\r
1002                 }\r
1003                 GetChannel()->stage()->load(GetLayerIndex(), pFP, true);\r
1004         \r
1005                 SetReplyString(TEXT("202 LOAD OK\r\n"));\r
1006 \r
1007                 return true;\r
1008         }\r
1009         catch(file_not_found&)\r
1010         {\r
1011                 CASPAR_LOG_CURRENT_EXCEPTION();\r
1012                 SetReplyString(TEXT("404 LOAD ERROR\r\n"));\r
1013                 return false;\r
1014         }\r
1015         catch(...)\r
1016         {\r
1017                 CASPAR_LOG_CURRENT_EXCEPTION();\r
1018                 SetReplyString(TEXT("502 LOAD FAILED\r\n"));\r
1019                 return false;\r
1020         }\r
1021 }\r
1022 \r
1023 \r
1024 \r
1025 //std::function<std::wstring()> channel_cg_add_command::parse(const std::wstring& message, const std::vector<renderer::render_device_ptr>& channels)\r
1026 //{\r
1027 //      static boost::wregex expr(L"^CG\\s(?<video_channel>\\d+)-?(?<LAYER>\\d+)?\\sADD\\s(?<FLASH_LAYER>\\d+)\\s(?<TEMPLATE>\\S+)\\s?(?<START_LABEL>\\S\\S+)?\\s?(?<PLAY_ON_LOAD>\\d)?\\s?(?<DATA>.*)?");\r
1028 //\r
1029 //      boost::wsmatch what;\r
1030 //      if(!boost::regex_match(message, what, expr))\r
1031 //              return nullptr;\r
1032 //\r
1033 //      auto info = channel_info::parse(what, channels);\r
1034 //\r
1035 //      int flash_layer_index = boost::lexical_cast<int>(what["FLASH_LAYER"].str());\r
1036 //\r
1037 //      std::wstring templatename = what["TEMPLATE"].str();\r
1038 //      bool play_on_load = what["PLAY_ON_LOAD"].matched ? what["PLAY_ON_LOAD"].str() != L"0" : 0;\r
1039 //      std::wstring start_label = what["START_LABEL"].str();   \r
1040 //      std::wstring data = get_data(what["DATA"].str());\r
1041 //      \r
1042 //      boost::replace_all(templatename, "\"", "");\r
1043 //\r
1044 //      return [=]() -> std::wstring\r
1045 //      {       \r
1046 //              std::wstring fullFilename = flash::flash_producer::find_template(server::template_folder() + templatename);\r
1047 //              if(fullFilename.empty())\r
1048 //                      BOOST_THROW_EXCEPTION(file_not_found());\r
1049 //      \r
1050 //              std::wstring extension = boost::filesystem::wpath(fullFilename).extension();\r
1051 //              std::wstring filename = templatename;\r
1052 //              filename.append(extension);\r
1053 //\r
1054 //              flash::flash::get_default_cg_producer(info.video_channel, std::max<int>(DEFAULT_CHANNEL_LAYER+1, info.layer_index))\r
1055 //                      ->add(flash_layer_index, filename, play_on_load, start_label, data);\r
1056 //\r
1057 //              CASPAR_LOG(info) << L"Executed [amcp_channel_cg_add]";\r
1058 //              return L"";\r
1059 //      };\r
1060 \r
1061 bool LoadbgCommand::DoExecute()\r
1062 {\r
1063         transition_info transitionInfo;\r
1064         \r
1065         bool bLoop = false;\r
1066 \r
1067         // TRANSITION\r
1068 \r
1069         std::wstring message;\r
1070         for(size_t n = 0; n < _parameters.size(); ++n)\r
1071                 message += _parameters[n] + L" ";\r
1072                 \r
1073         static const boost::wregex expr(L".*(?<TRANSITION>CUT|PUSH|SLIDE|WIPE|MIX)\\s*(?<DURATION>\\d+)\\s*(?<TWEEN>(LINEAR)|(EASE[^\\s]*))?\\s*(?<DIRECTION>FROMLEFT|FROMRIGHT|LEFT|RIGHT)?.*");\r
1074         boost::wsmatch what;\r
1075         if(boost::regex_match(message, what, expr))\r
1076         {\r
1077                 auto transition = what["TRANSITION"].str();\r
1078                 transitionInfo.duration = lexical_cast_or_default<size_t>(what["DURATION"].str());\r
1079                 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";\r
1080                 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";\r
1081                 transitionInfo.tweener = get_tweener(tween);            \r
1082 \r
1083                 if(transition == TEXT("CUT"))\r
1084                         transitionInfo.type = transition::cut;\r
1085                 else if(transition == TEXT("MIX"))\r
1086                         transitionInfo.type = transition::mix;\r
1087                 else if(transition == TEXT("PUSH"))\r
1088                         transitionInfo.type = transition::push;\r
1089                 else if(transition == TEXT("SLIDE"))\r
1090                         transitionInfo.type = transition::slide;\r
1091                 else if(transition == TEXT("WIPE"))\r
1092                         transitionInfo.type = transition::wipe;\r
1093                 \r
1094                 if(direction == TEXT("FROMLEFT"))\r
1095                         transitionInfo.direction = transition_direction::from_left;\r
1096                 else if(direction == TEXT("FROMRIGHT"))\r
1097                         transitionInfo.direction = transition_direction::from_right;\r
1098                 else if(direction == TEXT("LEFT"))\r
1099                         transitionInfo.direction = transition_direction::from_right;\r
1100                 else if(direction == TEXT("RIGHT"))\r
1101                         transitionInfo.direction = transition_direction::from_left;\r
1102         }\r
1103         \r
1104         //Perform loading of the clip\r
1105         try\r
1106         {\r
1107                 auto uri_tokens = core::parameters::protocol_split(_parameters.at_original(0));\r
1108                 auto pFP = frame_producer::empty();\r
1109                 if (uri_tokens[0] == L"route")\r
1110                 {\r
1111                         pFP = RouteCommand::TryCreateProducer(*this, _parameters.at_original(0));\r
1112                 }\r
1113                 if (pFP == frame_producer::empty())\r
1114                 {\r
1115                         pFP = create_producer(GetChannel()->mixer(), _parameters);\r
1116                 }\r
1117                 if(pFP == frame_producer::empty())\r
1118                         BOOST_THROW_EXCEPTION(file_not_found() << msg_info(_parameters.size() > 0 ? narrow(_parameters[0]) : ""));\r
1119 \r
1120                 bool auto_play = std::find(_parameters.begin(), _parameters.end(), L"AUTO") != _parameters.end();\r
1121 \r
1122                 auto pFP2 = create_transition_producer(GetChannel()->get_video_format_desc().field_mode, pFP, transitionInfo);\r
1123                 GetChannel()->stage()->load(GetLayerIndex(), pFP2, false, auto_play ? transitionInfo.duration : -1); // TODO: LOOP\r
1124         \r
1125                 SetReplyString(TEXT("202 LOADBG OK\r\n"));\r
1126 \r
1127                 return true;\r
1128         }\r
1129         catch(file_not_found&)\r
1130         {               \r
1131                 CASPAR_LOG(error) << L"File not found. No match found for parameters. Check syntax:" << _parameters.get_original_string();\r
1132                 SetReplyString(TEXT("404 LOADBG ERROR\r\n"));\r
1133                 return false;\r
1134         }\r
1135         catch(...)\r
1136         {\r
1137                 CASPAR_LOG_CURRENT_EXCEPTION();\r
1138                 SetReplyString(TEXT("502 LOADBG FAILED\r\n"));\r
1139                 return false;\r
1140         }\r
1141 }\r
1142 \r
1143 bool PauseCommand::DoExecute()\r
1144 {\r
1145         try\r
1146         {\r
1147                 GetChannel()->stage()->pause(GetLayerIndex());\r
1148                 SetReplyString(TEXT("202 PAUSE OK\r\n"));\r
1149                 return true;\r
1150         }\r
1151         catch(...)\r
1152         {\r
1153                 SetReplyString(TEXT("501 PAUSE FAILED\r\n"));\r
1154         }\r
1155 \r
1156         return false;\r
1157 }\r
1158 \r
1159 bool PlayCommand::DoExecute()\r
1160 {\r
1161         try\r
1162         {\r
1163                 if(!_parameters.empty())\r
1164                 {\r
1165                         LoadbgCommand lbg;\r
1166                         lbg.SetChannels(GetChannels());\r
1167                         lbg.SetChannel(GetChannel());\r
1168                         lbg.SetChannelIndex(GetChannelIndex());\r
1169                         lbg.SetLayerIntex(GetLayerIndex());\r
1170                         lbg.SetClientInfo(GetClientInfo());\r
1171                         lbg.SetParameters(_parameters);\r
1172                         if(!lbg.Execute())\r
1173                                 throw std::exception();\r
1174                 }\r
1175 \r
1176                 GetChannel()->stage()->play(GetLayerIndex());\r
1177                 \r
1178                 SetReplyString(TEXT("202 PLAY OK\r\n"));\r
1179                 return true;\r
1180         }\r
1181         catch(...)\r
1182         {\r
1183                 SetReplyString(TEXT("501 PLAY FAILED\r\n"));\r
1184         }\r
1185 \r
1186         return false;\r
1187 }\r
1188 \r
1189 bool StopCommand::DoExecute()\r
1190 {\r
1191         try\r
1192         {\r
1193                 GetChannel()->stage()->stop(GetLayerIndex());\r
1194                 SetReplyString(TEXT("202 STOP OK\r\n"));\r
1195                 return true;\r
1196         }\r
1197         catch(...)\r
1198         {\r
1199                 SetReplyString(TEXT("501 STOP FAILED\r\n"));\r
1200         }\r
1201 \r
1202         return false;\r
1203 }\r
1204 \r
1205 bool ClearCommand::DoExecute()\r
1206 {\r
1207         int index = GetLayerIndex(std::numeric_limits<int>::min());\r
1208         if(index != std::numeric_limits<int>::min())\r
1209                 GetChannel()->stage()->clear(index);\r
1210         else\r
1211                 GetChannel()->stage()->clear();\r
1212                 \r
1213         SetReplyString(TEXT("202 CLEAR OK\r\n"));\r
1214 \r
1215         return true;\r
1216 }\r
1217 \r
1218 bool PrintCommand::DoExecute()\r
1219 {\r
1220         parameters params;\r
1221         params.push_back(L"IMAGE");\r
1222         GetChannel()->output()->add(create_consumer(params));\r
1223                 \r
1224         SetReplyString(TEXT("202 PRINT OK\r\n"));\r
1225 \r
1226         return true;\r
1227 }\r
1228 \r
1229 bool LogCommand::DoExecute()\r
1230 {\r
1231         if(_parameters.at(0) == L"LEVEL")\r
1232                 log::set_log_level(_parameters.at(1));\r
1233 \r
1234         SetReplyString(TEXT("202 LOG OK\r\n"));\r
1235 \r
1236         return true;\r
1237 }\r
1238 \r
1239 bool CGCommand::DoExecute()\r
1240 {\r
1241         try\r
1242         {\r
1243                 std::wstring command = _parameters[0];\r
1244                 if(command == TEXT("ADD"))\r
1245                         return DoExecuteAdd();\r
1246                 else if(command == TEXT("PLAY"))\r
1247                         return DoExecutePlay();\r
1248                 else if(command == TEXT("STOP"))\r
1249                         return DoExecuteStop();\r
1250                 else if(command == TEXT("NEXT"))\r
1251                         return DoExecuteNext();\r
1252                 else if(command == TEXT("REMOVE"))\r
1253                         return DoExecuteRemove();\r
1254                 else if(command == TEXT("CLEAR"))\r
1255                         return DoExecuteClear();\r
1256                 else if(command == TEXT("UPDATE"))\r
1257                         return DoExecuteUpdate();\r
1258                 else if(command == TEXT("INVOKE"))\r
1259                         return DoExecuteInvoke();\r
1260                 else if(command == TEXT("INFO"))\r
1261                         return DoExecuteInfo();\r
1262         }\r
1263         catch(...)\r
1264         {\r
1265                 CASPAR_LOG_CURRENT_EXCEPTION();\r
1266         }\r
1267 \r
1268         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1269         return false;\r
1270 }\r
1271 \r
1272 bool CGCommand::ValidateLayer(const std::wstring& layerstring) {\r
1273         int length = layerstring.length();\r
1274         for(int i = 0; i < length; ++i) {\r
1275                 if(!_istdigit(layerstring[i])) {\r
1276                         return false;\r
1277                 }\r
1278         }\r
1279 \r
1280         return true;\r
1281 }\r
1282 \r
1283 bool CGCommand::DoExecuteAdd() {\r
1284         //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]\r
1285 \r
1286         int layer = 0;                          //_parameters[1]\r
1287 //      std::wstring templateName;      //_parameters[2]\r
1288         std::wstring label;             //_parameters[3]\r
1289         bool bDoStart = false;          //_parameters[3] alt. _parameters[4]\r
1290 //      std::wstring data;                      //_parameters[4] alt. _parameters[5]\r
1291 \r
1292         if(_parameters.size() < 4) \r
1293         {\r
1294                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1295                 return false;\r
1296         }\r
1297         unsigned int dataIndex = 4;\r
1298 \r
1299         if(!ValidateLayer(_parameters[1])) \r
1300         {\r
1301                 SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1302                 return false;\r
1303         }\r
1304 \r
1305         layer = _ttoi(_parameters[1].c_str());\r
1306 \r
1307         if(_parameters[3].length() > 1) \r
1308         {       //read label\r
1309                 label = _parameters.at_original(3);\r
1310                 ++dataIndex;\r
1311 \r
1312                 if(_parameters.size() > 4 && _parameters[4].length() > 0)       //read play-on-load-flag\r
1313                         bDoStart = (_parameters[4][0]==TEXT('1')) ? true : false;\r
1314                 else \r
1315                 {\r
1316                         SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1317                         return false;\r
1318                 }\r
1319         }\r
1320         else if(_parameters[3].length() > 0) {  //read play-on-load-flag\r
1321                 bDoStart = (_parameters[3][0]==TEXT('1')) ? true : false;\r
1322         }\r
1323         else \r
1324         {\r
1325                 SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1326                 return false;\r
1327         }\r
1328 \r
1329         const TCHAR* pDataString = 0;\r
1330         std::wstringstream data;\r
1331         std::wstring dataFromFile;\r
1332         if(_parameters.size() > dataIndex) \r
1333         {       //read data\r
1334                 const std::wstring& dataString = _parameters.at_original(dataIndex);\r
1335 \r
1336                 if(dataString[0] == TEXT('<')) //the data is an XML-string\r
1337                         pDataString = dataString.c_str();\r
1338                 else \r
1339                 {\r
1340                         //The data is not an XML-string, it must be a filename\r
1341                         std::wstring filename = env::data_folder();\r
1342                         filename.append(dataString);\r
1343                         filename.append(TEXT(".ftd"));\r
1344 \r
1345                         dataFromFile = read_file(boost::filesystem::wpath(filename));\r
1346                         pDataString = dataFromFile.c_str();\r
1347                 }\r
1348         }\r
1349 \r
1350         std::wstring fullFilename = flash::find_template(env::template_folder() + _parameters[2]);\r
1351         if(!fullFilename.empty())\r
1352         {\r
1353                 std::wstring extension = boost::filesystem::wpath(fullFilename).extension();\r
1354                 std::wstring filename = _parameters[2];\r
1355                 filename.append(extension);\r
1356 \r
1357                 flash::with_default_cg_producer(\r
1358                                 [&](safe_ptr<flash::cg_producer> producer)\r
1359                                 {\r
1360                                         producer->add(layer, filename, bDoStart, label, (pDataString!=0) ? pDataString : TEXT(""));\r
1361                                 },\r
1362                                 safe_ptr<core::video_channel>(GetChannel()), false, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER));\r
1363                 SetReplyString(TEXT("202 CG OK\r\n"));\r
1364         }\r
1365         else\r
1366         {\r
1367                 CASPAR_LOG(warning) << "Could not find template " << _parameters[2];\r
1368                 SetReplyString(TEXT("404 CG ERROR\r\n"));\r
1369         }\r
1370         return true;\r
1371 }\r
1372 \r
1373 bool CGCommand::DoExecutePlay()\r
1374 {\r
1375         if(_parameters.size() > 1)\r
1376         {\r
1377                 if(!ValidateLayer(_parameters[1])) \r
1378                 {\r
1379                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1380                         return false;\r
1381                 }\r
1382                 int layer = _ttoi(_parameters[1].c_str());\r
1383                 flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), true, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->play(layer);\r
1384         }\r
1385         else\r
1386         {\r
1387                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1388                 return true;\r
1389         }\r
1390 \r
1391         SetReplyString(TEXT("202 CG OK\r\n"));\r
1392         return true;\r
1393 }\r
1394 \r
1395 bool CGCommand::DoExecuteStop() \r
1396 {\r
1397         if(_parameters.size() > 1)\r
1398         {\r
1399                 if(!ValidateLayer(_parameters[1])) \r
1400                 {\r
1401                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1402                         return false;\r
1403                 }\r
1404                 int layer = _ttoi(_parameters[1].c_str());\r
1405                 flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), true, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->stop(layer, 0);\r
1406         }\r
1407         else \r
1408         {\r
1409                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1410                 return true;\r
1411         }\r
1412 \r
1413         SetReplyString(TEXT("202 CG OK\r\n"));\r
1414         return true;\r
1415 }\r
1416 \r
1417 bool CGCommand::DoExecuteNext()\r
1418 {\r
1419         if(_parameters.size() > 1) \r
1420         {\r
1421                 if(!ValidateLayer(_parameters[1])) \r
1422                 {\r
1423                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1424                         return false;\r
1425                 }\r
1426 \r
1427                 int layer = _ttoi(_parameters[1].c_str());\r
1428                 flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), true, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->next(layer);\r
1429         }\r
1430         else \r
1431         {\r
1432                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1433                 return true;\r
1434         }\r
1435 \r
1436         SetReplyString(TEXT("202 CG OK\r\n"));\r
1437         return true;\r
1438 }\r
1439 \r
1440 bool CGCommand::DoExecuteRemove() \r
1441 {\r
1442         if(_parameters.size() > 1) \r
1443         {\r
1444                 if(!ValidateLayer(_parameters[1])) \r
1445                 {\r
1446                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1447                         return false;\r
1448                 }\r
1449 \r
1450                 int layer = _ttoi(_parameters[1].c_str());\r
1451                 flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), true, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->remove(layer);\r
1452         }\r
1453         else \r
1454         {\r
1455                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1456                 return true;\r
1457         }\r
1458 \r
1459         SetReplyString(TEXT("202 CG OK\r\n"));\r
1460         return true;\r
1461 }\r
1462 \r
1463 bool CGCommand::DoExecuteClear() \r
1464 {\r
1465         GetChannel()->stage()->clear(GetLayerIndex(flash::cg_producer::DEFAULT_LAYER));\r
1466         SetReplyString(TEXT("202 CG OK\r\n"));\r
1467         return true;\r
1468 }\r
1469 \r
1470 bool CGCommand::DoExecuteUpdate() \r
1471 {\r
1472         try\r
1473         {\r
1474                 if(!ValidateLayer(_parameters.at(1)))\r
1475                 {\r
1476                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1477                         return false;\r
1478                 }\r
1479                                                 \r
1480                 std::wstring dataString = _parameters.at_original(2);\r
1481                 if(dataString.at(0) != TEXT('<'))\r
1482                 {\r
1483                         //The data is not an XML-string, it must be a filename\r
1484                         std::wstring filename = env::data_folder();\r
1485                         filename.append(dataString);\r
1486                         filename.append(TEXT(".ftd"));\r
1487 \r
1488                         dataString = read_file(boost::filesystem::wpath(filename));\r
1489                 }               \r
1490 \r
1491                 int layer = _ttoi(_parameters.at(1).c_str());\r
1492                 flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), true, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->update(layer, dataString);\r
1493         }\r
1494         catch(...)\r
1495         {\r
1496                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1497                 return true;\r
1498         }\r
1499 \r
1500         SetReplyString(TEXT("202 CG OK\r\n"));\r
1501         return true;\r
1502 }\r
1503 \r
1504 bool CGCommand::DoExecuteInvoke() \r
1505 {\r
1506         std::wstringstream replyString;\r
1507         replyString << TEXT("201 CG OK\r\n");\r
1508 \r
1509         if(_parameters.size() > 2)\r
1510         {\r
1511                 if(!ValidateLayer(_parameters[1]))\r
1512                 {\r
1513                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1514                         return false;\r
1515                 }\r
1516                 int layer = _ttoi(_parameters[1].c_str());\r
1517                 auto result = flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), true, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->invoke(layer, _parameters.at_original(2));\r
1518                 replyString << result << TEXT("\r\n"); \r
1519         }\r
1520         else \r
1521         {\r
1522                 SetReplyString(TEXT("402 CG ERROR\r\n"));\r
1523                 return true;\r
1524         }\r
1525         \r
1526         SetReplyString(replyString.str());\r
1527         return true;\r
1528 }\r
1529 \r
1530 bool CGCommand::DoExecuteInfo() \r
1531 {\r
1532         std::wstringstream replyString;\r
1533         replyString << TEXT("201 CG OK\r\n");\r
1534 \r
1535         if(_parameters.size() > 1)\r
1536         {\r
1537                 if(!ValidateLayer(_parameters[1]))\r
1538                 {\r
1539                         SetReplyString(TEXT("403 CG ERROR\r\n"));\r
1540                         return false;\r
1541                 }\r
1542 \r
1543                 int layer = _ttoi(_parameters[1].c_str());\r
1544                 auto desc = flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), false, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->description(layer);\r
1545                 \r
1546                 replyString << desc << TEXT("\r\n"); \r
1547         }\r
1548         else \r
1549         {\r
1550                 auto info = flash::get_default_cg_producer(safe_ptr<core::video_channel>(GetChannel()), false, GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->template_host_info();\r
1551                 replyString << info << TEXT("\r\n"); \r
1552         }       \r
1553 \r
1554         SetReplyString(replyString.str());\r
1555         return true;\r
1556 }\r
1557 \r
1558 bool DataCommand::DoExecute()\r
1559 {\r
1560         std::wstring command = _parameters[0];\r
1561         if(command == TEXT("STORE"))\r
1562                 return DoExecuteStore();\r
1563         else if(command == TEXT("RETRIEVE"))\r
1564                 return DoExecuteRetrieve();\r
1565         else if(command == TEXT("REMOVE"))\r
1566                 return DoExecuteRemove();\r
1567         else if(command == TEXT("LIST"))\r
1568                 return DoExecuteList();\r
1569 \r
1570         SetReplyString(TEXT("403 DATA ERROR\r\n"));\r
1571         return false;\r
1572 }\r
1573 \r
1574 bool DataCommand::DoExecuteStore() \r
1575 {\r
1576         if(_parameters.size() < 3) \r
1577         {\r
1578                 SetReplyString(TEXT("402 DATA STORE ERROR\r\n"));\r
1579                 return false;\r
1580         }\r
1581 \r
1582         std::wstring filename = env::data_folder();\r
1583         filename.append(_parameters[1]);\r
1584         filename.append(TEXT(".ftd"));\r
1585 \r
1586         auto data_path = boost::filesystem::wpath(\r
1587                         boost::filesystem::wpath(filename).parent_path());\r
1588         \r
1589         if(!boost::filesystem::exists(data_path))\r
1590                 boost::filesystem::create_directories(data_path);\r
1591 \r
1592         std::wofstream datafile(filename.c_str());\r
1593 \r
1594         if(!datafile) \r
1595         {\r
1596                 SetReplyString(TEXT("501 DATA STORE FAILED\r\n"));\r
1597                 return false;\r
1598         }\r
1599 \r
1600         datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character\r
1601         datafile << _parameters.at_original(2) << std::flush;\r
1602         datafile.close();\r
1603 \r
1604         std::wstring replyString = TEXT("202 DATA STORE OK\r\n");\r
1605         SetReplyString(replyString);\r
1606         return true;\r
1607 }\r
1608 \r
1609 bool DataCommand::DoExecuteRetrieve() \r
1610 {\r
1611         if(_parameters.size() < 2) \r
1612         {\r
1613                 SetReplyString(TEXT("402 DATA RETRIEVE ERROR\r\n"));\r
1614                 return false;\r
1615         }\r
1616 \r
1617         std::wstring filename = env::data_folder();\r
1618         filename.append(_parameters[1]);\r
1619         filename.append(TEXT(".ftd"));\r
1620 \r
1621         std::wstring file_contents = read_file(boost::filesystem::wpath(filename));\r
1622 \r
1623         if (file_contents.empty()) \r
1624         {\r
1625                 SetReplyString(TEXT("404 DATA RETRIEVE ERROR\r\n"));\r
1626                 return false;\r
1627         }\r
1628 \r
1629         std::wstringstream reply;\r
1630         reply << TEXT("201 DATA RETRIEVE OK\r\n");\r
1631 \r
1632         std::wstringstream file_contents_stream(file_contents);\r
1633         std::wstring line;\r
1634         bool bFirstLine = true;\r
1635         \r
1636         while(std::getline(file_contents_stream, line))\r
1637         {\r
1638                 if(!bFirstLine)\r
1639                         reply << "\n";\r
1640                 else\r
1641                         bFirstLine = false;\r
1642 \r
1643                 reply << line;\r
1644         }\r
1645 \r
1646         reply << "\r\n";\r
1647         SetReplyString(reply.str());\r
1648         return true;\r
1649 }\r
1650 \r
1651 bool DataCommand::DoExecuteRemove() \r
1652\r
1653         if (_parameters.size() < 2) \r
1654         {\r
1655                 SetReplyString(TEXT("402 DATA REMOVE ERROR\r\n"));\r
1656                 return false;\r
1657         }\r
1658 \r
1659         std::wstring filename = env::data_folder();\r
1660         filename.append(_parameters[1]);\r
1661         filename.append(TEXT(".ftd"));\r
1662 \r
1663         if (!boost::filesystem::exists(filename)) \r
1664         {\r
1665                 SetReplyString(TEXT("404 DATA REMOVE ERROR\r\n"));\r
1666                 return false;\r
1667         }\r
1668 \r
1669         if (!boost::filesystem::remove(filename))\r
1670         {\r
1671                 SetReplyString(TEXT("403 DATA REMOVE ERROR\r\n"));\r
1672                 return false;\r
1673         }\r
1674 \r
1675         SetReplyString(TEXT("201 DATA REMOVE OK\r\n"));\r
1676 \r
1677         return true;\r
1678 }\r
1679 \r
1680 bool DataCommand::DoExecuteList() \r
1681 {\r
1682         std::wstringstream replyString;\r
1683         replyString << TEXT("200 DATA LIST OK\r\n");\r
1684 \r
1685         for (boost::filesystem::wrecursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)\r
1686         {                       \r
1687                 if(boost::filesystem::is_regular_file(itr->path()))\r
1688                 {\r
1689                         if(!boost::iequals(itr->path().extension(), L".ftd"))\r
1690                                 continue;\r
1691                         \r
1692                         auto relativePath = boost::filesystem::wpath(itr->path().file_string().substr(env::data_folder().size()-1, itr->path().file_string().size()));\r
1693                         \r
1694                         auto str = relativePath.replace_extension(TEXT("")).external_file_string();\r
1695                         if(str[0] == '\\' || str[0] == '/')\r
1696                                 str = std::wstring(str.begin() + 1, str.end());\r
1697 \r
1698                         replyString << str << TEXT("\r\n");     \r
1699                 }\r
1700         }\r
1701         \r
1702         replyString << TEXT("\r\n");\r
1703 \r
1704         SetReplyString(boost::to_upper_copy(replyString.str()));\r
1705         return true;\r
1706 }\r
1707 \r
1708 bool ThumbnailCommand::DoExecute()\r
1709 {\r
1710         std::wstring command = _parameters[0];\r
1711 \r
1712         if (command == TEXT("RETRIEVE"))\r
1713                 return DoExecuteRetrieve();\r
1714         else if (command == TEXT("LIST"))\r
1715                 return DoExecuteList();\r
1716         else if (command == TEXT("GENERATE"))\r
1717                 return DoExecuteGenerate();\r
1718         else if (command == TEXT("GENERATE_ALL"))\r
1719                 return DoExecuteGenerateAll();\r
1720 \r
1721         SetReplyString(TEXT("403 THUMBNAIL ERROR\r\n"));\r
1722         return false;\r
1723 }\r
1724 \r
1725 bool ThumbnailCommand::DoExecuteRetrieve() \r
1726 {\r
1727         if(_parameters.size() < 2) \r
1728         {\r
1729                 SetReplyString(TEXT("402 THUMBNAIL RETRIEVE ERROR\r\n"));\r
1730                 return false;\r
1731         }\r
1732 \r
1733         std::wstring filename = env::thumbnails_folder();\r
1734         filename.append(_parameters[1]);\r
1735         filename.append(TEXT(".png"));\r
1736 \r
1737         std::wstring file_contents = read_file_base64(boost::filesystem::wpath(filename));\r
1738 \r
1739         if (file_contents.empty())\r
1740         {\r
1741                 SetReplyString(TEXT("404 THUMBNAIL RETRIEVE ERROR\r\n"));\r
1742                 return false;\r
1743         }\r
1744 \r
1745         std::wstringstream reply;\r
1746 \r
1747         reply << L"201 THUMBNAIL RETRIEVE OK\r\n";\r
1748         reply << file_contents;\r
1749         reply << L"\r\n";\r
1750         SetReplyString(reply.str());\r
1751         return true;\r
1752 }\r
1753 \r
1754 bool ThumbnailCommand::DoExecuteList()\r
1755 {\r
1756         std::wstringstream replyString;\r
1757         replyString << TEXT("200 THUMBNAIL LIST OK\r\n");\r
1758 \r
1759         for (boost::filesystem::wrecursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)\r
1760         {                       \r
1761                 if(boost::filesystem::is_regular_file(itr->path()))\r
1762                 {\r
1763                         if(!boost::iequals(itr->path().extension(), L".png"))\r
1764                                 continue;\r
1765                         \r
1766                         auto relativePath = boost::filesystem::wpath(itr->path().file_string().substr(env::thumbnails_folder().size()-1, itr->path().file_string().size()));\r
1767                         \r
1768                         auto str = relativePath.replace_extension(L"").external_file_string();\r
1769                         if(str[0] == '\\' || str[0] == '/')\r
1770                                 str = std::wstring(str.begin() + 1, str.end());\r
1771 \r
1772                         auto mtime = boost::filesystem::last_write_time(itr->path());\r
1773                         auto mtime_readable = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(mtime));\r
1774                         auto file_size = boost::filesystem::file_size(itr->path());\r
1775 \r
1776                         replyString << L"\"" << str << L"\" " << widen(mtime_readable) << L" " << file_size << L"\r\n";\r
1777                 }\r
1778         }\r
1779         \r
1780         replyString << TEXT("\r\n");\r
1781 \r
1782         SetReplyString(boost::to_upper_copy(replyString.str()));\r
1783         return true;\r
1784 }\r
1785 \r
1786 bool ThumbnailCommand::DoExecuteGenerate()\r
1787 {\r
1788         if (_parameters.size() < 2) \r
1789         {\r
1790                 SetReplyString(L"402 THUMBNAIL GENERATE ERROR\r\n");\r
1791                 return false;\r
1792         }\r
1793 \r
1794         auto thumb_gen = GetThumbGenerator();\r
1795 \r
1796         if (thumb_gen)\r
1797         {\r
1798                 thumb_gen->generate(_parameters[1]);\r
1799                 SetReplyString(L"200 THUMBNAIL GENERATE OK\r\n");\r
1800                 return true;\r
1801         }\r
1802         else\r
1803         {\r
1804                 SetReplyString(L"501 THUMBNAIL GENERATE ERROR\r\n");\r
1805                 return false;\r
1806         }\r
1807 }\r
1808 \r
1809 bool ThumbnailCommand::DoExecuteGenerateAll()\r
1810 {\r
1811         auto thumb_gen = GetThumbGenerator();\r
1812 \r
1813         if (thumb_gen)\r
1814         {\r
1815                 thumb_gen->generate_all();\r
1816                 SetReplyString(L"200 THUMBNAIL GENERATE_ALL OK\r\n");\r
1817                 return true;\r
1818         }\r
1819         else\r
1820         {\r
1821                 SetReplyString(L"501 THUMBNAIL GENERATE_ALL ERROR\r\n");\r
1822                 return false;\r
1823         }\r
1824 }\r
1825 \r
1826 bool CinfCommand::DoExecute()\r
1827 {\r
1828         std::wstringstream replyString;\r
1829         \r
1830         try\r
1831         {\r
1832                 std::wstring info;\r
1833                 for (boost::filesystem::wrecursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)\r
1834                 {\r
1835                         auto path = itr->path();\r
1836                         auto file = path.replace_extension(L"").filename();\r
1837                         if(boost::iequals(file, _parameters.at(0)))\r
1838                                 info += MediaInfo(itr->path(), GetMediaInfoRepo()) + L"\r\n";\r
1839                 }\r
1840 \r
1841                 if(info.empty())\r
1842                 {\r
1843                         SetReplyString(TEXT("404 CINF ERROR\r\n"));\r
1844                         return false;\r
1845                 }\r
1846                 replyString << TEXT("200 CINF OK\r\n");\r
1847                 replyString << info << "\r\n";\r
1848         }\r
1849         catch(...)\r
1850         {\r
1851                 SetReplyString(TEXT("404 CINF ERROR\r\n"));\r
1852                 return false;\r
1853         }\r
1854         \r
1855         SetReplyString(replyString.str());\r
1856         return true;\r
1857 }\r
1858 \r
1859 void GenerateChannelInfo(int index, const safe_ptr<core::video_channel>& pChannel, std::wstringstream& replyString)\r
1860 {\r
1861         replyString << index+1 << TEXT(" ") << pChannel->get_video_format_desc().name << TEXT(" PLAYING") << TEXT("\r\n");\r
1862 }\r
1863 \r
1864 bool InfoCommand::DoExecute()\r
1865 {\r
1866         std::wstringstream replyString;\r
1867         \r
1868         boost::property_tree::xml_writer_settings<wchar_t> w(' ', 3);\r
1869 \r
1870         try\r
1871         {\r
1872                 if(_parameters.size() >= 1 && _parameters[0] == L"TEMPLATE")\r
1873                 {               \r
1874                         replyString << L"201 INFO TEMPLATE OK\r\n";\r
1875 \r
1876                         // Needs to be extended for any file, not just flash.\r
1877 \r
1878                         auto filename = flash::find_template(env::template_folder() + _parameters.at(1));\r
1879                                                 \r
1880                         std::wstringstream str;\r
1881                         str << widen(flash::read_template_meta_info(filename));\r
1882                         boost::property_tree::wptree info;\r
1883                         boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);\r
1884 \r
1885                         boost::property_tree::xml_parser::write_xml(replyString, info, w);\r
1886                 }\r
1887                 else if(_parameters.size() >= 1 && _parameters[0] == L"CONFIG")\r
1888                 {               \r
1889                         replyString << L"201 INFO CONFIG OK\r\n";\r
1890 \r
1891                         boost::property_tree::write_xml(replyString, caspar::env::properties(), w);\r
1892                 }\r
1893                 else if(_parameters.size() >= 1 && _parameters[0] == L"PATHS")\r
1894                 {\r
1895                         replyString << L"201 INFO PATHS OK\r\n";\r
1896 \r
1897                         boost::property_tree::wptree info;\r
1898                         info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));\r
1899                         info.add(L"paths.initial-path", boost::filesystem2::initial_path<boost::filesystem2::wpath>().directory_string() + L"\\");\r
1900 \r
1901                         boost::property_tree::write_xml(replyString, info, w);\r
1902                 }\r
1903                 else if(_parameters.size() >= 1 && _parameters[0] == L"SYSTEM")\r
1904                 {\r
1905                         replyString << L"201 INFO SYSTEM OK\r\n";\r
1906                         \r
1907                         boost::property_tree::wptree info;\r
1908                         \r
1909                         info.add(L"system.name",                                        caspar::get_system_product_name());\r
1910                         info.add(L"system.windows.name",                        caspar::get_win_product_name());\r
1911                         info.add(L"system.windows.service-pack",        caspar::get_win_sp_version());\r
1912                         info.add(L"system.cpu",                                         caspar::get_cpu_info());\r
1913         \r
1914                         BOOST_FOREACH(auto device, caspar::decklink::get_device_list())\r
1915                                 info.add(L"system.caspar.decklink.device", device);\r
1916 \r
1917                         BOOST_FOREACH(auto device, caspar::bluefish::get_device_list())\r
1918                                 info.add(L"system.caspar.bluefish.device", device);\r
1919                                 \r
1920                         info.add(L"system.caspar.flash",                                        caspar::flash::get_version());\r
1921                         info.add(L"system.caspar.template-host",                        caspar::flash::get_cg_version());\r
1922                         info.add(L"system.caspar.free-image",                           caspar::image::get_version());\r
1923                         info.add(L"system.caspar.ffmpeg.avcodec",                       caspar::ffmpeg::get_avcodec_version());\r
1924                         info.add(L"system.caspar.ffmpeg.avformat",                      caspar::ffmpeg::get_avformat_version());\r
1925                         info.add(L"system.caspar.ffmpeg.avfilter",                      caspar::ffmpeg::get_avfilter_version());\r
1926                         info.add(L"system.caspar.ffmpeg.avutil",                        caspar::ffmpeg::get_avutil_version());\r
1927                         info.add(L"system.caspar.ffmpeg.swscale",                       caspar::ffmpeg::get_swscale_version());\r
1928                                                                         \r
1929                         boost::property_tree::write_xml(replyString, info, w);\r
1930                 }\r
1931                 else if(_parameters.size() >= 1 && _parameters[0] == L"SERVER")\r
1932                 {\r
1933                         replyString << L"201 INFO SERVER OK\r\n";\r
1934                         \r
1935                         boost::property_tree::wptree info;\r
1936 \r
1937                         int index = 0;\r
1938                         BOOST_FOREACH(auto channel, channels_)\r
1939                                 info.add_child(L"channels.channel", channel->info())\r
1940                                         .add(L"index", ++index);\r
1941                         \r
1942                         boost::property_tree::write_xml(replyString, info, w);\r
1943                 }\r
1944                 else if(_parameters.size() >= 2 && _parameters[1] == L"DELAY")\r
1945                 {\r
1946                         replyString << L"201 INFO DELAY OK\r\n";\r
1947                         boost::property_tree::wptree info;\r
1948 \r
1949                         std::vector<std::wstring> split;\r
1950                         boost::split(split, _parameters[0], boost::is_any_of("-"));\r
1951                                         \r
1952                         int layer = std::numeric_limits<int>::min();\r
1953                         int channel = boost::lexical_cast<int>(split[0]) - 1;\r
1954 \r
1955                         if(split.size() > 1)\r
1956                                 layer = boost::lexical_cast<int>(split[1]);\r
1957                                 \r
1958                         if(layer == std::numeric_limits<int>::min())\r
1959                         {       \r
1960                                 info.add_child(L"channel-delay", channels_.at(channel)->delay_info());\r
1961                         }\r
1962                         else\r
1963                         {\r
1964                                 info.add_child(L"layer-delay", channels_.at(channel)->stage()->delay_info(layer).get())\r
1965                                         .add(L"index", layer);\r
1966                         }\r
1967                         boost::property_tree::xml_parser::write_xml(replyString, info, w);\r
1968                 }\r
1969                 else // channel\r
1970                 {                       \r
1971                         if(_parameters.size() >= 1)\r
1972                         {\r
1973                                 replyString << TEXT("201 INFO OK\r\n");\r
1974                                 boost::property_tree::wptree info;\r
1975 \r
1976                                 std::vector<std::wstring> split;\r
1977                                 boost::split(split, _parameters[0], boost::is_any_of("-"));\r
1978                                         \r
1979                                 int layer = std::numeric_limits<int>::min();\r
1980                                 int channel = boost::lexical_cast<int>(split[0]) - 1;\r
1981 \r
1982                                 if(split.size() > 1)\r
1983                                         layer = boost::lexical_cast<int>(split[1]);\r
1984                                 \r
1985                                 if(layer == std::numeric_limits<int>::min())\r
1986                                 {       \r
1987                                         info.add_child(L"channel", channels_.at(channel)->info())\r
1988                                                         .add(L"index", channel);\r
1989                                 }\r
1990                                 else\r
1991                                 {\r
1992                                         if(_parameters.size() >= 2)\r
1993                                         {\r
1994                                                 if(_parameters[1] == L"B")\r
1995                                                         info.add_child(L"producer", channels_.at(channel)->stage()->background(layer).get()->info());\r
1996                                                 else\r
1997                                                         info.add_child(L"producer", channels_.at(channel)->stage()->foreground(layer).get()->info());\r
1998                                         }\r
1999                                         else\r
2000                                         {\r
2001                                                 info.add_child(L"layer", channels_.at(channel)->stage()->info(layer).get())\r
2002                                                         .add(L"index", layer);\r
2003                                         }\r
2004                                 }\r
2005                                 boost::property_tree::xml_parser::write_xml(replyString, info, w);\r
2006                         }\r
2007                         else\r
2008                         {\r
2009                                 // This is needed for backwards compatibility with old clients\r
2010                                 replyString << TEXT("200 INFO OK\r\n");\r
2011                                 for(size_t n = 0; n < channels_.size(); ++n)\r
2012                                         GenerateChannelInfo(n, channels_[n], replyString);\r
2013                         }\r
2014 \r
2015                 }\r
2016         }\r
2017         catch(...)\r
2018         {\r
2019                 SetReplyString(TEXT("403 INFO ERROR\r\n"));\r
2020                 return false;\r
2021         }\r
2022 \r
2023         replyString << TEXT("\r\n");\r
2024         SetReplyString(replyString.str());\r
2025         return true;\r
2026 }\r
2027 \r
2028 bool ClsCommand::DoExecute()\r
2029 {\r
2030 /*\r
2031                 wav = audio\r
2032                 mp3 = audio\r
2033                 swf     = movie\r
2034                 dv  = movie\r
2035                 tga = still\r
2036                 col = still\r
2037         */\r
2038         std::wstringstream replyString;\r
2039         replyString << TEXT("200 CLS OK\r\n");\r
2040         replyString << ListMedia(GetMediaInfoRepo());\r
2041         replyString << TEXT("\r\n");\r
2042         SetReplyString(boost::to_upper_copy(replyString.str()));\r
2043         return true;\r
2044 }\r
2045 \r
2046 bool TlsCommand::DoExecute()\r
2047 {\r
2048         std::wstringstream replyString;\r
2049         replyString << TEXT("200 TLS OK\r\n");\r
2050 \r
2051         replyString << ListTemplates();\r
2052         replyString << TEXT("\r\n");\r
2053 \r
2054         SetReplyString(replyString.str());\r
2055         return true;\r
2056 }\r
2057 \r
2058 bool VersionCommand::DoExecute()\r
2059 {\r
2060         std::wstring replyString = TEXT("201 VERSION OK\r\n") + env::version() + TEXT("\r\n");\r
2061 \r
2062         if(_parameters.size() > 0)\r
2063         {\r
2064                 if(_parameters[0] == L"FLASH")\r
2065                         replyString = TEXT("201 VERSION OK\r\n") + flash::get_version() + TEXT("\r\n");\r
2066                 else if(_parameters[0] == L"TEMPLATEHOST")\r
2067                         replyString = TEXT("201 VERSION OK\r\n") + flash::get_cg_version() + TEXT("\r\n");\r
2068                 else if(_parameters[0] != L"SERVER")\r
2069                         replyString = TEXT("403 VERSION ERROR\r\n");\r
2070         }\r
2071 \r
2072         SetReplyString(replyString);\r
2073         return true;\r
2074 }\r
2075 \r
2076 bool ByeCommand::DoExecute()\r
2077 {\r
2078         GetClientInfo()->Disconnect();\r
2079         return true;\r
2080 }\r
2081 \r
2082 bool SetCommand::DoExecute()\r
2083 {\r
2084         std::wstring name = _parameters[0];\r
2085         std::transform(name.begin(), name.end(), name.begin(), toupper);\r
2086 \r
2087         std::wstring value = _parameters[1];\r
2088         std::transform(value.begin(), value.end(), value.begin(), toupper);\r
2089 \r
2090         if(name == TEXT("MODE"))\r
2091         {\r
2092                 auto format_desc = core::video_format_desc::get(value);\r
2093                 if(format_desc.format != core::video_format::invalid)\r
2094                 {\r
2095                         GetChannel()->set_video_format_desc(format_desc);\r
2096                         SetReplyString(TEXT("202 SET MODE OK\r\n"));\r
2097                 }\r
2098                 else\r
2099                         SetReplyString(TEXT("501 SET MODE FAILED\r\n"));\r
2100         }\r
2101         else\r
2102         {\r
2103                 this->SetReplyString(TEXT("403 SET ERROR\r\n"));\r
2104         }\r
2105 \r
2106         return true;\r
2107 }\r
2108 \r
2109 bool KillCommand::DoExecute()\r
2110 {\r
2111         GetShutdownServerNow().set_value(false); // False for not attempting to restart.\r
2112         return true;\r
2113 }\r
2114 \r
2115 bool RestartCommand::DoExecute()\r
2116 {\r
2117         GetShutdownServerNow().set_value(true); // True for attempting to restart\r
2118         return true;\r
2119 }\r
2120 \r
2121 }       //namespace amcp\r
2122 }}      //namespace caspar\r