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