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