X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=protocol%2Famcp%2FAMCPCommandsImpl.cpp;h=09562ea07a968063a15d2fa8419dd6d2c1f06591;hb=b42d68c49fcd7f7f854cf4268392ebba6c6407c0;hp=e7f538f0a038b57d853a4362679013e2215443cb;hpb=904fe8a87701b092eb81e4d4e99678f38452125f;p=casparcg diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index e7f538f0a..09562ea07 100644 --- a/protocol/amcp/AMCPCommandsImpl.cpp +++ b/protocol/amcp/AMCPCommandsImpl.cpp @@ -1,21 +1,22 @@ /* -* copyright (c) 2010 Sveriges Television AB +* Copyright 2013 Sveriges Television AB http://casparcg.com/ * -* This file is part of CasparCG. +* This file is part of CasparCG (www.casparcg.com). * -* CasparCG is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. +* CasparCG is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. * -* CasparCG is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. - -* You should have received a copy of the GNU General Public License -* along with CasparCG. If not, see . +* CasparCG is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with CasparCG. If not, see . * +* Author: Nicklas P Andersson */ #include "../StdAfx.h" @@ -31,24 +32,38 @@ #include #include +#include +#include +#include +#include +#include #include #include #include +#include #include #include #include #include +#include #include +#include +#include +#include #include #include #include #include +#include +#include +#include #include #include #include +#include #include #include @@ -56,18 +71,28 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include /* Return codes -100 [action] Information om att något har hänt -101 [action] Information om att något har hänt, en rad data skickas +100 [action] Information om att n�got har h�nt +101 [action] Information om att n�got har h�nt, en rad data skickas -202 [kommando] OK Kommandot har utförts -201 [kommando] OK Kommandot har utförts, och en rad data skickas tillbaka -200 [kommando] OK Kommandot har utförts, och flera rader data skickas tillbaka. Avslutas med tomrad +202 [kommando] OK Kommandot har utf�rts +201 [kommando] OK Kommandot har utf�rts, och en rad data skickas tillbaka +200 [kommando] OK Kommandot har utf�rts, och flera rader data skickas tillbaka. Avslutas med tomrad -400 ERROR Kommandot kunde inte förstås +400 ERROR Kommandot kunde inte f�rst�s 401 [kommando] ERROR Ogiltig kanal 402 [kommando] ERROR Parameter saknas 403 [kommando] ERROR Ogiltig parameter @@ -75,7 +100,7 @@ 500 FAILED Internt configurationfel 501 [kommando] FAILED Internt configurationfel -502 [kommando] FAILED Oläslig mediafil +502 [kommando] FAILED Ol�slig mediafil 600 [kommando] FAILED funktion ej implementerad */ @@ -84,6 +109,93 @@ namespace caspar { namespace protocol { using namespace core; +std::wstring read_file_base64(const boost::filesystem::wpath& file) +{ + using namespace boost::archive::iterators; + + boost::filesystem::ifstream filestream(file, std::ios::binary); + + if (!filestream) + return L""; + + auto length = boost::filesystem::file_size(file); + std::vector bytes; + bytes.resize(length); + filestream.read(bytes.data(), length); + + return widen(to_base64(bytes.data(), length)); +} + +std::wstring read_utf8_file(const boost::filesystem::wpath& file) +{ + std::wstringstream result; + boost::filesystem::wifstream filestream(file); + + if (filestream) + { + // Consume BOM first + filestream.get(); + // read all data + result << filestream.rdbuf(); + } + + return result.str(); +} + +std::wstring read_latin1_file(const boost::filesystem::wpath& file) +{ + boost::locale::generator gen; + gen.locale_cache_enabled(true); + gen.categories(boost::locale::codepage_facet); + + std::stringstream result_stream; + boost::filesystem::ifstream filestream(file); + filestream.imbue(gen("en_US.ISO8859-1")); + + if (filestream) + { + // read all data + result_stream << filestream.rdbuf(); + } + + std::string result = result_stream.str(); + std::wstring widened_result; + + // The first 255 codepoints in unicode is the same as in latin1 + auto from_signed_to_signed = std::function( + [] (char c) { return static_cast(c); } + ); + boost::copy( + result | boost::adaptors::transformed(from_signed_to_signed), + std::back_inserter(widened_result)); + + return widened_result; +} + +std::wstring read_file(const boost::filesystem::wpath& file) +{ + static const uint8_t BOM[] = {0xef, 0xbb, 0xbf}; + + if (!boost::filesystem::exists(file)) + { + return L""; + } + + if (boost::filesystem::file_size(file) >= 3) + { + boost::filesystem::ifstream bom_stream(file); + + char header[3]; + bom_stream.read(header, 3); + bom_stream.close(); + + if (std::memcmp(BOM, header, 3) == 0) + return read_utf8_file(file); + } + + return read_latin1_file(file); +} + std::wstring MediaInfo(const boost::filesystem::wpath& path) { if(boost::filesystem::is_regular_file(path)) @@ -93,13 +205,15 @@ std::wstring MediaInfo(const boost::filesystem::wpath& path) if(extension == TEXT(".TGA") || extension == TEXT(".COL") || extension == L".PNG" || extension == L".JPEG" || extension == L".JPG" || extension == L"GIF" || extension == L"BMP") clipttype = TEXT(" STILL "); - else if(extension == TEXT(".SWF") || extension == TEXT(".DV") || extension == TEXT(".MOV") || extension == TEXT(".MPG") || - extension == TEXT(".AVI") || extension == TEXT(".FLV") || extension == TEXT(".F4V") || extension == TEXT(".MP4") || - extension == L".M2V" || extension == L".H264" || extension == L".MKV" || extension == L".WMV" || extension == L".DIVX" || - extension == L".XVID" || extension == L".OGG") - clipttype = TEXT(" MOVIE "); else if(extension == TEXT(".WAV") || extension == TEXT(".MP3")) - clipttype = TEXT(" STILL "); + clipttype = TEXT(" AUDIO "); + else if(extension == TEXT(".SWF") || extension == TEXT(".CT") || + extension == TEXT(".DV") || extension == TEXT(".MOV") || + extension == TEXT(".MPG") || extension == TEXT(".AVI") || + extension == TEXT(".MP4") || extension == TEXT(".FLV") || + extension == TEXT(".STGA") || + caspar::ffmpeg::is_valid_file(path.file_string())) + clipttype = TEXT(" MOVIE "); if(clipttype != TEXT(" N/A ")) { @@ -144,7 +258,7 @@ std::wstring ListTemplates() for (boost::filesystem::wrecursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr) { - if(boost::filesystem::is_regular_file(itr->path()) && itr->path().extension() == L".ft") + if(boost::filesystem::is_regular_file(itr->path()) && (itr->path().extension() == L".ft" || itr->path().extension() == L".ct")) { auto relativePath = boost::filesystem::wpath(itr->path().file_string().substr(env::template_folder().size()-1, itr->path().file_string().size())); @@ -162,8 +276,7 @@ std::wstring ListTemplates() relativePath = boost::filesystem::wpath(dir + L"/" + file); auto str = relativePath.replace_extension(TEXT("")).external_file_string(); - if(str[0] == '\\' || str[0] == '/') - str = std::wstring(str.begin() + 1, str.end()); + boost::trim_if(str, boost::is_any_of("\\/")); replyString << TEXT("\"") << str << TEXT("\" ") << sizeWStr @@ -215,32 +328,88 @@ bool DiagnosticsCommand::DoExecute() } } +bool ChannelGridCommand::DoExecute() +{ + int index = 1; + auto self = GetChannels().back(); + + core::parameters params; + params.push_back(L"SCREEN"); + params.push_back(L"NAME"); + params.push_back(L"Channel Grid Window"); + auto screen = create_consumer(params); + + self->output()->add(screen); + + BOOST_FOREACH(auto channel, GetChannels()) + { + if(channel != self) + { + auto producer = create_channel_producer(self->mixer(), channel); + self->stage()->load(index, producer, false); + self->stage()->play(index); + index++; + } + } + + int n = GetChannels().size()-1; + double delta = 1.0/static_cast(n); + for(int x = 0; x < n; ++x) + { + for(int y = 0; y < n; ++y) + { + int index = x+y*n+1; + auto transform = [=](frame_transform transform) -> frame_transform + { + transform.fill_translation[0] = x*delta; + transform.fill_translation[1] = y*delta; + transform.fill_scale[0] = delta; + transform.fill_scale[1] = delta; + transform.clip_translation[0] = x*delta; + transform.clip_translation[1] = y*delta; + transform.clip_scale[0] = delta; + transform.clip_scale[1] = delta; + return transform; + }; + self->stage()->apply_transform(index, transform); + } + } + + return true; +} + bool CallCommand::DoExecute() { //Perform loading of the clip try { auto what = _parameters.at(0); - - std::wstring param = _parameters2.at(1); - for(auto it = std::begin(_parameters2)+2; it != std::end(_parameters2); ++it) - param += L" " + *it; - + boost::unique_future result; - if(what == L"B") - result = GetChannel()->stage()->call(GetLayerIndex(), false, param); - else if(what == L"F") - result = GetChannel()->stage()->call(GetLayerIndex(), true, param); + auto& params_orig = _parameters.get_original(); + if(what == L"B" || what == L"F") + { + std::wstring param; + for(auto it = std::begin(params_orig)+1; it != std::end(params_orig); ++it, param += L" ") + param += *it; + result = GetChannel()->stage()->call(GetLayerIndex(), what == L"F", boost::trim_copy(param)); + } else - result = GetChannel()->stage()->call(GetLayerIndex(), true, _parameters.at(0) + L" " + param); - + { + std::wstring param; + for(auto it = std::begin(params_orig); it != std::end(params_orig); ++it, param += L" ") + param += *it; + result = GetChannel()->stage()->call(GetLayerIndex(), true, boost::trim_copy(param)); + } + if(!result.timed_wait(boost::posix_time::seconds(2))) BOOST_THROW_EXCEPTION(timed_out()); - - CASPAR_LOG(info) << "Executed call: " << _parameters[0] << TEXT(" successfully"); - + std::wstringstream replyString; - replyString << TEXT("201 CALL OK\r\n") << result.get() << L"\r\n"; + if(result.get().empty()) + replyString << TEXT("202 CALL OK\r\n"); + else + replyString << TEXT("201 CALL OK\r\n") << result.get() << L"\r\n"; SetReplyString(replyString.str()); @@ -254,88 +423,126 @@ bool CallCommand::DoExecute() } } +// UGLY HACK +tbb::concurrent_unordered_map> deferred_transforms; + +core::frame_transform MixerCommand::get_current_transform() +{ + return GetChannel()->stage()->get_current_transform(GetLayerIndex()); +} + bool MixerCommand::DoExecute() -{ +{ + using boost::lexical_cast; //Perform loading of the clip try { + bool defer = _parameters.back() == L"DEFER"; + if(defer) + _parameters.pop_back(); + + std::vector transforms; + if(_parameters[0] == L"KEYER" || _parameters[0] == L"IS_KEY") { - bool value = lexical_cast_or_default(_parameters.at(1), false); - auto transform = [=](frame_transform transform) -> frame_transform + if (_parameters.size() == 1) + return reply_value([](const frame_transform& t) { return t.is_key ? 1 : 0; }); + + bool value = boost::lexical_cast(_parameters.at(1)); + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.is_key = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform); + }, 0, L"linear")); } else if(_parameters[0] == L"OPACITY") { - int duration = _parameters.size() > 2 ? lexical_cast_or_default(_parameters[2], 0) : 0; + if (_parameters.size() == 1) + return reply_value([](const frame_transform& t) { return t.opacity; }); + + int duration = _parameters.size() > 2 ? boost::lexical_cast(_parameters[2]) : 0; std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear"; double value = boost::lexical_cast(_parameters.at(1)); - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.opacity = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"FILL" || _parameters[0] == L"FILL_RECT") { - int duration = _parameters.size() > 5 ? lexical_cast_or_default(_parameters[5], 0) : 0; + if (_parameters.size() == 1) + { + auto transform = get_current_transform(); + auto translation = transform.fill_translation; + auto scale = transform.fill_scale; + SetReplyString( + L"201 MIXER OK\r\n" + + lexical_cast(translation[0]) + L" " + + lexical_cast(translation[1]) + L" " + + lexical_cast(scale[0]) + L" " + + lexical_cast(scale[1]) + L"\r\n"); + return true; + } + + int duration = _parameters.size() > 5 ? boost::lexical_cast(_parameters[5]) : 0; std::wstring tween = _parameters.size() > 6 ? _parameters[6] : L"linear"; double x = boost::lexical_cast(_parameters.at(1)); double y = boost::lexical_cast(_parameters.at(2)); double x_s = boost::lexical_cast(_parameters.at(3)); double y_s = boost::lexical_cast(_parameters.at(4)); - auto transform = [=](frame_transform transform) mutable -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) mutable -> frame_transform { transform.fill_translation[0] = x; transform.fill_translation[1] = y; transform.fill_scale[0] = x_s; transform.fill_scale[1] = y_s; - transform.clip_translation[0] = x; - transform.clip_translation[1] = y; - transform.clip_scale[0] = x_s; - transform.clip_scale[1] = y_s; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"CLIP" || _parameters[0] == L"CLIP_RECT") { - int duration = _parameters.size() > 5 ? lexical_cast_or_default(_parameters[5], 0) : 0; + if (_parameters.size() == 1) + { + auto transform = get_current_transform(); + auto translation = transform.clip_translation; + auto scale = transform.clip_scale; + SetReplyString( + L"201 MIXER OK\r\n" + + lexical_cast(translation[0]) + L" " + + lexical_cast(translation[1]) + L" " + + lexical_cast(scale[0]) + L" " + + lexical_cast(scale[1]) + L"\r\n"); + return true; + } + + int duration = _parameters.size() > 5 ? boost::lexical_cast(_parameters[5]) : 0; std::wstring tween = _parameters.size() > 6 ? _parameters[6] : L"linear"; double x = boost::lexical_cast(_parameters.at(1)); double y = boost::lexical_cast(_parameters.at(2)); double x_s = boost::lexical_cast(_parameters.at(3)); double y_s = boost::lexical_cast(_parameters.at(4)); + if(x_s < 0 || y_s < 0) + { + SetReplyString(L"403 MIXER ERROR\r\n"); + return false; + } - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.clip_translation[0] = x; transform.clip_translation[1] = y; transform.clip_scale[0] = x_s; transform.clip_scale[1] = y_s; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"GRID") { - int duration = _parameters.size() > 2 ? lexical_cast_or_default(_parameters[2], 0) : 0; + int duration = _parameters.size() > 2 ? boost::lexical_cast(_parameters[2]) : 0; std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear"; int n = boost::lexical_cast(_parameters.at(1)); double delta = 1.0/static_cast(n); @@ -344,7 +551,7 @@ bool MixerCommand::DoExecute() for(int y = 0; y < n; ++y) { int index = x+y*n+1; - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform { transform.fill_translation[0] = x*delta; transform.fill_translation[1] = y*delta; @@ -355,107 +562,188 @@ bool MixerCommand::DoExecute() transform.clip_scale[0] = delta; transform.clip_scale[1] = delta; return transform; - }; - GetChannel()->mixer()->apply_frame_transform(index, transform, duration, tween); + }, duration, tween)); } } } else if(_parameters[0] == L"BLEND") { + if (_parameters.size() == 1) + { + auto blend_mode = GetChannel()->mixer()->get_blend_mode(GetLayerIndex()); + SetReplyString(L"201 MIXER OK\r\n" + + lexical_cast(get_blend_mode(blend_mode)) + + L"\r\n"); + return true; + } + auto blend_str = _parameters.at(1); int layer = GetLayerIndex(); - GetChannel()->mixer()->set_blend_mode(GetLayerIndex(), get_blend_mode(blend_str)); + blend_mode::type && blend = get_blend_mode(blend_str); + GetChannel()->mixer()->set_blend_mode(GetLayerIndex(), blend); + } + else if(_parameters[0] == L"CHROMA") + { + if (_parameters.size() == 1) + { + auto chroma = GetChannel()->mixer()->get_chroma(GetLayerIndex()); + SetReplyString(L"201 MIXER OK\r\n" + + get_chroma_mode(chroma.key) + + (chroma.key == chroma::none + ? L"" + : L" " + + lexical_cast(chroma.threshold) + L" " + + lexical_cast(chroma.softness)) + + L"\r\n"); + // Add the rest when they are actually used and documented + return true; + } + + int layer = GetLayerIndex(); + chroma chroma; + chroma.key = get_chroma_mode(_parameters[1]); + + if (chroma.key != chroma::none) + { + chroma.threshold = boost::lexical_cast(_parameters[2]); + chroma.softness = boost::lexical_cast(_parameters[3]); + chroma.spill = _parameters.size() > 4 ? boost::lexical_cast(_parameters[4]) : 0.0f; + chroma.blur = _parameters.size() > 5 ? boost::lexical_cast(_parameters[5]) : 0.0f; + chroma.show_mask = _parameters.size() > 6 ? bool(boost::lexical_cast(_parameters[6])) : false; + } + + GetChannel()->mixer()->set_chroma(GetLayerIndex(), chroma); + } + else if(_parameters[0] == L"MASTERVOLUME") + { + if (_parameters.size() == 1) + { + auto volume = GetChannel()->mixer()->get_master_volume(); + SetReplyString(L"201 MIXER OK\r\n" + + lexical_cast(volume) + L"\r\n"); + return true; + } + + float master_volume = boost::lexical_cast(_parameters.at(1)); + GetChannel()->mixer()->set_master_volume(master_volume); } else if(_parameters[0] == L"BRIGHTNESS") { + if (_parameters.size() == 1) + return reply_value([](const frame_transform& t) { return t.brightness; }); + auto value = boost::lexical_cast(_parameters.at(1)); - int duration = _parameters.size() > 2 ? lexical_cast_or_default(_parameters[2], 0) : 0; + int duration = _parameters.size() > 2 ? boost::lexical_cast(_parameters[2]) : 0; std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear"; - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.brightness = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"SATURATION") { + if (_parameters.size() == 1) + return reply_value([](const frame_transform& t) { return t.saturation; }); + auto value = boost::lexical_cast(_parameters.at(1)); - int duration = _parameters.size() > 2 ? lexical_cast_or_default(_parameters[2], 0) : 0; + int duration = _parameters.size() > 2 ? boost::lexical_cast(_parameters[2]) : 0; std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear"; - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.saturation = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"CONTRAST") { + if (_parameters.size() == 1) + return reply_value([](const frame_transform& t) { return t.contrast; }); + auto value = boost::lexical_cast(_parameters.at(1)); - int duration = _parameters.size() > 2 ? lexical_cast_or_default(_parameters[2], 0) : 0; + int duration = _parameters.size() > 2 ? boost::lexical_cast(_parameters[2]) : 0; std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear"; - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.contrast = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"LEVELS") { + if (_parameters.size() == 1) + { + auto levels = get_current_transform().levels; + SetReplyString(L"201 MIXER OK\r\n" + + lexical_cast(levels.min_input) + L" " + + lexical_cast(levels.max_input) + L" " + + lexical_cast(levels.gamma) + L" " + + lexical_cast(levels.min_output) + L" " + + lexical_cast(levels.max_output) + L"\r\n"); + return true; + } + levels value; value.min_input = boost::lexical_cast(_parameters.at(1)); value.max_input = boost::lexical_cast(_parameters.at(2)); value.gamma = boost::lexical_cast(_parameters.at(3)); value.min_output = boost::lexical_cast(_parameters.at(4)); value.max_output = boost::lexical_cast(_parameters.at(5)); - int duration = _parameters.size() > 6 ? lexical_cast_or_default(_parameters[6], 0) : 0; + int duration = _parameters.size() > 6 ? boost::lexical_cast(_parameters[6]) : 0; std::wstring tween = _parameters.size() > 7 ? _parameters[7] : L"linear"; - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.levels = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"VOLUME") { - int duration = _parameters.size() > 2 ? lexical_cast_or_default(_parameters[2], 0) : 0; + if (_parameters.size() == 1) + return reply_value([](const frame_transform& t) { return t.volume; }); + + int duration = _parameters.size() > 2 ? boost::lexical_cast(_parameters[2]) : 0; std::wstring tween = _parameters.size() > 3 ? _parameters[3] : L"linear"; double value = boost::lexical_cast(_parameters[1]); - auto transform = [=](frame_transform transform) -> frame_transform + transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform { transform.volume = value; return transform; - }; - - int layer = GetLayerIndex(); - GetChannel()->mixer()->apply_frame_transform(GetLayerIndex(), transform, duration, tween); + }, duration, tween)); } else if(_parameters[0] == L"CLEAR") { int layer = GetLayerIndex(std::numeric_limits::max()); - if(layer == std::numeric_limits::max()) - GetChannel()->mixer()->clear_transforms(); + if (layer == std::numeric_limits::max()) + { + GetChannel()->stage()->clear_transforms(); + GetChannel()->mixer()->clear_blend_modes(); + } else - GetChannel()->mixer()->clear_transforms(layer); + { + GetChannel()->stage()->clear_transforms(layer); + GetChannel()->mixer()->clear_blend_mode(layer); + } + } + else if(_parameters[0] == L"COMMIT") + { + transforms = std::move(deferred_transforms[GetChannelIndex()]); } else { SetReplyString(TEXT("404 MIXER ERROR\r\n")); return false; } + + if(defer) + { + auto& defer_tranforms = deferred_transforms[GetChannelIndex()]; + defer_tranforms.insert(defer_tranforms.end(), transforms.begin(), transforms.end()); + } + else + GetChannel()->stage()->apply_transforms(transforms); SetReplyString(TEXT("202 MIXER OK\r\n")); @@ -491,17 +779,15 @@ bool SwapCommand::DoExecute() int l1 = GetLayerIndex(); int l2 = boost::lexical_cast(strs.at(1)); - ch1->stage()->swap_layer(l1, l2, *ch2->stage()); + ch1->stage()->swap_layer(l1, l2, ch2->stage()); } else { auto ch1 = GetChannel(); auto ch2 = GetChannels().at(boost::lexical_cast(_parameters[0])-1); - ch1->stage()->swap(*ch2->stage()); + ch1->stage()->swap_layers(ch2->stage()); } - - CASPAR_LOG(info) << "Swapped successfully"; - + SetReplyString(TEXT("202 SWAP OK\r\n")); return true; @@ -525,10 +811,9 @@ bool AddCommand::DoExecute() //Perform loading of the clip try { - GetChannel()->output()->add(GetLayerIndex(), create_consumer(_parameters)); + auto consumer = create_consumer(_parameters); + GetChannel()->output()->add(GetLayerIndex(consumer->index()), consumer); - CASPAR_LOG(info) << "Added " << _parameters[0] << TEXT(" successfully"); - SetReplyString(TEXT("202 ADD OK\r\n")); return true; @@ -552,7 +837,11 @@ bool RemoveCommand::DoExecute() //Perform loading of the clip try { - GetChannel()->output()->remove(GetLayerIndex()); + auto index = GetLayerIndex(std::numeric_limits::min()); + if(index == std::numeric_limits::min()) + index = create_consumer(_parameters)->index(); + + GetChannel()->output()->remove(index); SetReplyString(TEXT("202 REMOVE OK\r\n")); @@ -577,12 +866,9 @@ bool LoadCommand::DoExecute() //Perform loading of the clip try { - _parameters[0] = _parameters[0]; auto pFP = create_producer(GetChannel()->mixer(), _parameters); GetChannel()->stage()->load(GetLayerIndex(), pFP, true); - CASPAR_LOG(info) << "Loaded " << _parameters[0] << TEXT(" successfully"); - SetReplyString(TEXT("202 LOAD OK\r\n")); return true; @@ -685,7 +971,6 @@ bool LoadbgCommand::DoExecute() //Perform loading of the clip try { - _parameters[0] = _parameters[0]; auto pFP = create_producer(GetChannel()->mixer(), _parameters); if(pFP == frame_producer::empty()) BOOST_THROW_EXCEPTION(file_not_found() << msg_info(_parameters.size() > 0 ? narrow(_parameters[0]) : "")); @@ -695,14 +980,13 @@ bool LoadbgCommand::DoExecute() auto pFP2 = create_transition_producer(GetChannel()->get_video_format_desc().field_mode, pFP, transitionInfo); GetChannel()->stage()->load(GetLayerIndex(), pFP2, false, auto_play ? transitionInfo.duration : -1); // TODO: LOOP - CASPAR_LOG(info) << "Loaded " << _parameters[0] << TEXT(" successfully to background"); SetReplyString(TEXT("202 LOADBG OK\r\n")); return true; } catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); + { + CASPAR_LOG(error) << L"File not found. No match found for parameters. Check syntax:" << _parameters.get_original_string(); SetReplyString(TEXT("404 LOADBG ERROR\r\n")); return false; } @@ -741,12 +1025,9 @@ bool PlayCommand::DoExecute() lbg.SetChannelIndex(GetChannelIndex()); lbg.SetLayerIntex(GetLayerIndex()); lbg.SetClientInfo(GetClientInfo()); - for(auto it = _parameters.begin(); it != _parameters.end(); ++it) - lbg.AddParameter(*it); + lbg.SetParameters(_parameters); if(!lbg.Execute()) - CASPAR_LOG(warning) << " Failed to play."; - - CASPAR_LOG(info) << "Playing " << _parameters[0]; + throw std::exception(); } GetChannel()->stage()->play(GetLayerIndex()); @@ -793,45 +1074,15 @@ bool ClearCommand::DoExecute() bool PrintCommand::DoExecute() { - GetChannel()->output()->add(99978, create_consumer(boost::assign::list_of(L"IMAGE"))); + parameters params; + params.push_back(L"IMAGE"); + GetChannel()->output()->add(create_consumer(params)); SetReplyString(TEXT("202 PRINT OK\r\n")); return true; } -bool StatusCommand::DoExecute() -{ - if (GetLayerIndex() > -1) - { - auto status = GetChannel()->stage()->get_status(GetLayerIndex()); - std::wstringstream status_text; - status_text - << L"201 STATUS OK\r\n" - << L"" - << L"\n\t" << GetLayerIndex() << L"" - << L"\n\t" << status.foreground << L"" - << L"\n\t" << status.background << L"" - << L"\n\t" << (status.is_paused ? L"paused" : L"playing") << L"" - << L"\n\t" << (status.nb_frames == std::numeric_limits::max() ? 0 : status.nb_frames) << L"" - << L"\n\t" << status.frame_number << L"" - << L"\n\t" << (status.file_nb_frames == std::numeric_limits::max() ? 0 : status.file_nb_frames) << L"" - << L"\n\t" << status.file_frame_number << L"" - << L"\n" - << L"\r\n"; - - SetReplyString(status_text.str()); - return true; - } - else - { - //NOTE: Possible to extend soo that "channel" status is returned when no layer is specified. - - SetReplyString(TEXT("403 LAYER MUST BE SPECIFIED\r\n")); - return false; - } -} - bool LogCommand::DoExecute() { if(_parameters.at(0) == L"LEVEL") @@ -844,25 +1095,32 @@ bool LogCommand::DoExecute() bool CGCommand::DoExecute() { - std::wstring command = _parameters[0]; - if(command == TEXT("ADD")) - return DoExecuteAdd(); - else if(command == TEXT("PLAY")) - return DoExecutePlay(); - else if(command == TEXT("STOP")) - return DoExecuteStop(); - else if(command == TEXT("NEXT")) - return DoExecuteNext(); - else if(command == TEXT("REMOVE")) - return DoExecuteRemove(); - else if(command == TEXT("CLEAR")) - return DoExecuteClear(); - else if(command == TEXT("UPDATE")) - return DoExecuteUpdate(); - else if(command == TEXT("INVOKE")) - return DoExecuteInvoke(); - else if(command == TEXT("INFO")) - return DoExecuteInfo(); + try + { + std::wstring command = _parameters[0]; + if(command == TEXT("ADD")) + return DoExecuteAdd(); + else if(command == TEXT("PLAY")) + return DoExecutePlay(); + else if(command == TEXT("STOP")) + return DoExecuteStop(); + else if(command == TEXT("NEXT")) + return DoExecuteNext(); + else if(command == TEXT("REMOVE")) + return DoExecuteRemove(); + else if(command == TEXT("CLEAR")) + return DoExecuteClear(); + else if(command == TEXT("UPDATE")) + return DoExecuteUpdate(); + else if(command == TEXT("INVOKE")) + return DoExecuteInvoke(); + else if(command == TEXT("INFO")) + return DoExecuteInfo(); + } + catch(...) + { + CASPAR_LOG_CURRENT_EXCEPTION(); + } SetReplyString(TEXT("403 CG ERROR\r\n")); return false; @@ -905,7 +1163,7 @@ bool CGCommand::DoExecuteAdd() { if(_parameters[3].length() > 1) { //read label - label = _parameters2[3]; + label = _parameters.at_original(3); ++dataIndex; if(_parameters.size() > 4 && _parameters[4].length() > 0) //read play-on-load-flag @@ -930,7 +1188,7 @@ bool CGCommand::DoExecuteAdd() { std::wstring dataFromFile; if(_parameters.size() > dataIndex) { //read data - const std::wstring& dataString = _parameters2[dataIndex]; + const std::wstring& dataString = _parameters.at_original(dataIndex); if(dataString[0] == TEXT('<')) //the data is an XML-string pDataString = dataString.c_str(); @@ -941,18 +1199,8 @@ bool CGCommand::DoExecuteAdd() { filename.append(dataString); filename.append(TEXT(".ftd")); - //open file - std::wifstream datafile(filename.c_str()); - if(datafile) - { - //read all data - data << datafile.rdbuf(); - datafile.close(); - - //extract data to _parameters - dataFromFile = data.str(); - pDataString = dataFromFile.c_str(); - } + dataFromFile = read_file(boost::filesystem::wpath(filename)); + pDataString = dataFromFile.c_str(); } } @@ -1081,7 +1329,7 @@ bool CGCommand::DoExecuteUpdate() return false; } - std::wstring dataString = _parameters2.at(2); + std::wstring dataString = _parameters.at_original(2); if(dataString.at(0) != TEXT('<')) { //The data is not an XML-string, it must be a filename @@ -1089,18 +1337,7 @@ bool CGCommand::DoExecuteUpdate() filename.append(dataString); filename.append(TEXT(".ftd")); - //open file - std::wifstream datafile(filename.c_str()); - if(datafile) - { - std::wstringstream data; - //read all data - data << datafile.rdbuf(); - datafile.close(); - - //extract data to _parameters - dataString = data.str(); - } + dataString = read_file(boost::filesystem::wpath(filename)); } int layer = _ttoi(_parameters.at(1).c_str()); @@ -1129,7 +1366,7 @@ bool CGCommand::DoExecuteInvoke() return false; } int layer = _ttoi(_parameters[1].c_str()); - auto result = flash::get_default_cg_producer(safe_ptr(GetChannel()), GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->invoke(layer, _parameters2[2]); + auto result = flash::get_default_cg_producer(safe_ptr(GetChannel()), GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->invoke(layer, _parameters.at_original(2)); replyString << result << TEXT("\r\n"); } else @@ -1162,7 +1399,7 @@ bool CGCommand::DoExecuteInfo() } else { - auto info = flash::get_default_cg_producer(safe_ptr(GetChannel()), GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->info(); + auto info = flash::get_default_cg_producer(safe_ptr(GetChannel()), GetLayerIndex(flash::cg_producer::DEFAULT_LAYER))->template_host_info(); replyString << info << TEXT("\r\n"); } @@ -1177,6 +1414,8 @@ bool DataCommand::DoExecute() return DoExecuteStore(); else if(command == TEXT("RETRIEVE")) return DoExecuteRetrieve(); + else if(command == TEXT("REMOVE")) + return DoExecuteRemove(); else if(command == TEXT("LIST")) return DoExecuteList(); @@ -1196,14 +1435,22 @@ bool DataCommand::DoExecuteStore() filename.append(_parameters[1]); filename.append(TEXT(".ftd")); + auto data_path = boost::filesystem::wpath( + boost::filesystem::wpath(filename).parent_path()); + + if(!boost::filesystem::exists(data_path)) + boost::filesystem::create_directories(data_path); + std::wofstream datafile(filename.c_str()); + if(!datafile) { SetReplyString(TEXT("501 DATA STORE FAILED\r\n")); return false; } - datafile << _parameters2[2]; + datafile << static_cast(65279); // UTF-8 BOM character + datafile << _parameters.at_original(2) << std::flush; datafile.close(); std::wstring replyString = TEXT("202 DATA STORE OK\r\n"); @@ -1223,32 +1470,65 @@ bool DataCommand::DoExecuteRetrieve() filename.append(_parameters[1]); filename.append(TEXT(".ftd")); - std::wifstream datafile(filename.c_str()); - if(!datafile) + std::wstring file_contents = read_file(boost::filesystem::wpath(filename)); + + if (file_contents.empty()) { SetReplyString(TEXT("404 DATA RETRIEVE ERROR\r\n")); return false; } - std::wstringstream reply(TEXT("201 DATA RETRIEVE OK\r\n")); + std::wstringstream reply; + reply << TEXT("201 DATA RETRIEVE OK\r\n"); + + std::wstringstream file_contents_stream(file_contents); std::wstring line; bool bFirstLine = true; - while(std::getline(datafile, line)) + + while(std::getline(file_contents_stream, line)) { if(!bFirstLine) - reply << "\\n"; + reply << "\n"; else bFirstLine = false; reply << line; } - datafile.close(); reply << "\r\n"; SetReplyString(reply.str()); return true; } +bool DataCommand::DoExecuteRemove() +{ + if (_parameters.size() < 2) + { + SetReplyString(TEXT("402 DATA REMOVE ERROR\r\n")); + return false; + } + + std::wstring filename = env::data_folder(); + filename.append(_parameters[1]); + filename.append(TEXT(".ftd")); + + if (!boost::filesystem::exists(filename)) + { + SetReplyString(TEXT("404 DATA REMOVE ERROR\r\n")); + return false; + } + + if (!boost::filesystem::remove(filename)) + { + SetReplyString(TEXT("403 DATA REMOVE ERROR\r\n")); + return false; + } + + SetReplyString(TEXT("201 DATA REMOVE OK\r\n")); + + return true; +} + bool DataCommand::DoExecuteList() { std::wstringstream replyString; @@ -1277,6 +1557,124 @@ bool DataCommand::DoExecuteList() return true; } +bool ThumbnailCommand::DoExecute() +{ + std::wstring command = _parameters[0]; + + if (command == TEXT("RETRIEVE")) + return DoExecuteRetrieve(); + else if (command == TEXT("LIST")) + return DoExecuteList(); + else if (command == TEXT("GENERATE")) + return DoExecuteGenerate(); + else if (command == TEXT("GENERATE_ALL")) + return DoExecuteGenerateAll(); + + SetReplyString(TEXT("403 THUMBNAIL ERROR\r\n")); + return false; +} + +bool ThumbnailCommand::DoExecuteRetrieve() +{ + if(_parameters.size() < 2) + { + SetReplyString(TEXT("402 THUMBNAIL RETRIEVE ERROR\r\n")); + return false; + } + + std::wstring filename = env::thumbnails_folder(); + filename.append(_parameters[1]); + filename.append(TEXT(".png")); + + std::wstring file_contents = read_file_base64(boost::filesystem::wpath(filename)); + + if (file_contents.empty()) + { + SetReplyString(TEXT("404 THUMBNAIL RETRIEVE ERROR\r\n")); + return false; + } + + std::wstringstream reply; + + reply << L"201 THUMBNAIL RETRIEVE OK\r\n"; + reply << file_contents; + reply << L"\r\n"; + SetReplyString(reply.str()); + return true; +} + +bool ThumbnailCommand::DoExecuteList() +{ + std::wstringstream replyString; + replyString << TEXT("200 THUMBNAIL LIST OK\r\n"); + + for (boost::filesystem::wrecursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr) + { + if(boost::filesystem::is_regular_file(itr->path())) + { + if(!boost::iequals(itr->path().extension(), L".png")) + continue; + + auto relativePath = boost::filesystem::wpath(itr->path().file_string().substr(env::thumbnails_folder().size()-1, itr->path().file_string().size())); + + auto str = relativePath.replace_extension(L"").external_file_string(); + if(str[0] == '\\' || str[0] == '/') + str = std::wstring(str.begin() + 1, str.end()); + + auto mtime = boost::filesystem::last_write_time(itr->path()); + auto mtime_readable = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(mtime)); + auto file_size = boost::filesystem::file_size(itr->path()); + + replyString << L"\"" << str << L"\" " << widen(mtime_readable) << L" " << file_size << L"\r\n"; + } + } + + replyString << TEXT("\r\n"); + + SetReplyString(boost::to_upper_copy(replyString.str())); + return true; +} + +bool ThumbnailCommand::DoExecuteGenerate() +{ + if (_parameters.size() < 2) + { + SetReplyString(L"402 THUMBNAIL GENERATE ERROR\r\n"); + return false; + } + + auto thumb_gen = GetThumbGenerator(); + + if (thumb_gen) + { + thumb_gen->generate(_parameters[1]); + SetReplyString(L"200 THUMBNAIL GENERATE OK\r\n"); + return true; + } + else + { + SetReplyString(L"501 THUMBNAIL GENERATE ERROR\r\n"); + return false; + } +} + +bool ThumbnailCommand::DoExecuteGenerateAll() +{ + auto thumb_gen = GetThumbGenerator(); + + if (thumb_gen) + { + thumb_gen->generate_all(); + SetReplyString(L"200 THUMBNAIL GENERATE_ALL OK\r\n"); + return true; + } + else + { + SetReplyString(L"501 THUMBNAIL GENERATE_ALL ERROR\r\n"); + return false; + } +} + bool CinfCommand::DoExecute() { std::wstringstream replyString; @@ -1297,7 +1695,7 @@ bool CinfCommand::DoExecute() SetReplyString(TEXT("404 CINF ERROR\r\n")); return false; } - replyString << TEXT("200 INFO OK\r\n"); + replyString << TEXT("200 CINF OK\r\n"); replyString << info << "\r\n"; } catch(...) @@ -1317,54 +1715,141 @@ void GenerateChannelInfo(int index, const safe_ptr& pChanne bool InfoCommand::DoExecute() { - if(_parameters.size() >= 1 && _parameters[0] == L"TEMPLATE") - { - try - { + std::wstringstream replyString; + + boost::property_tree::xml_writer_settings w(' ', 3); + + try + { + if(_parameters.size() >= 1 && _parameters[0] == L"TEMPLATE") + { + replyString << L"201 INFO TEMPLATE OK\r\n"; + // Needs to be extended for any file, not just flash. auto filename = flash::find_template(env::template_folder() + _parameters.at(1)); + + std::wstringstream str; + str << widen(flash::read_template_meta_info(filename)); + boost::property_tree::wptree info; + boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments); - std::wstringstream ss; - ss << L"201 INFO OK\r\n"; - ss << flash::read_template_meta_info(filename) << L"\r\n"; + boost::property_tree::xml_parser::write_xml(replyString, info, w); + } + else if(_parameters.size() >= 1 && _parameters[0] == L"CONFIG") + { + replyString << L"201 INFO CONFIG OK\r\n"; - SetReplyString(ss.str()); - return true; + boost::property_tree::write_xml(replyString, caspar::env::properties(), w); } - catch(...) + else if(_parameters.size() >= 1 && _parameters[0] == L"PATHS") { - SetReplyString(TEXT("403 INFO ERROR\r\n")); - return false; + replyString << L"201 INFO PATHS OK\r\n"; + + boost::property_tree::wptree info; + info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths")); + info.add(L"paths.initial-path", boost::filesystem2::initial_path().directory_string() + L"\\"); + + boost::property_tree::write_xml(replyString, info, w); } - } - else // channel - { - try + else if(_parameters.size() >= 1 && _parameters[0] == L"SYSTEM") { - std::wstringstream replyString; + replyString << L"201 INFO SYSTEM OK\r\n"; + + boost::property_tree::wptree info; + + info.add(L"system.name", caspar::get_system_product_name()); + info.add(L"system.windows.name", caspar::get_win_product_name()); + info.add(L"system.windows.service-pack", caspar::get_win_sp_version()); + info.add(L"system.cpu", caspar::get_cpu_info()); + + BOOST_FOREACH(auto device, caspar::decklink::get_device_list()) + info.add(L"system.caspar.decklink.device", device); + + BOOST_FOREACH(auto device, caspar::bluefish::get_device_list()) + info.add(L"system.caspar.bluefish.device", device); + + info.add(L"system.caspar.flash", caspar::flash::get_version()); + info.add(L"system.caspar.template-host", caspar::flash::get_cg_version()); + info.add(L"system.caspar.free-image", caspar::image::get_version()); + info.add(L"system.caspar.ffmpeg.avcodec", caspar::ffmpeg::get_avcodec_version()); + info.add(L"system.caspar.ffmpeg.avformat", caspar::ffmpeg::get_avformat_version()); + info.add(L"system.caspar.ffmpeg.avfilter", caspar::ffmpeg::get_avfilter_version()); + info.add(L"system.caspar.ffmpeg.avutil", caspar::ffmpeg::get_avutil_version()); + info.add(L"system.caspar.ffmpeg.swscale", caspar::ffmpeg::get_swscale_version()); + + boost::property_tree::write_xml(replyString, info, w); + } + else if(_parameters.size() >= 1 && _parameters[0] == L"SERVER") + { + replyString << L"201 INFO SERVER OK\r\n"; + + boost::property_tree::wptree info; + + int index = 0; + BOOST_FOREACH(auto channel, channels_) + info.add_child(L"channels.channel", channel->info()) + .add(L"index", ++index); + + boost::property_tree::write_xml(replyString, info, w); + } + else // channel + { if(_parameters.size() >= 1) { - int channelIndex = boost::lexical_cast(_parameters.at(0).c_str())-1; replyString << TEXT("201 INFO OK\r\n"); - GenerateChannelInfo(channelIndex, channels_.at(channelIndex), replyString); + boost::property_tree::wptree info; + + std::vector split; + boost::split(split, _parameters[0], boost::is_any_of("-")); + + int layer = std::numeric_limits::min(); + int channel = boost::lexical_cast(split[0]) - 1; + + if(split.size() > 1) + layer = boost::lexical_cast(split[1]); + + if(layer == std::numeric_limits::min()) + { + info.add_child(L"channel", channels_.at(channel)->info()) + .add(L"index", channel); + } + else + { + if(_parameters.size() >= 2) + { + if(_parameters[1] == L"B") + info.add_child(L"producer", channels_.at(channel)->stage()->background(layer).get()->info()); + else + info.add_child(L"producer", channels_.at(channel)->stage()->foreground(layer).get()->info()); + } + else + { + info.add_child(L"layer", channels_.at(channel)->stage()->info(layer).get()) + .add(L"index", layer); + } + } + boost::property_tree::xml_parser::write_xml(replyString, info, w); } else { + // This is needed for backwards compatibility with old clients replyString << TEXT("200 INFO OK\r\n"); for(size_t n = 0; n < channels_.size(); ++n) GenerateChannelInfo(n, channels_[n], replyString); - replyString << TEXT("\r\n"); } - SetReplyString(replyString.str()); - return true; - } - catch(...) - { - SetReplyString(TEXT("403 INFO ERROR\r\n")); - return false; + } } + catch(...) + { + SetReplyString(TEXT("403 INFO ERROR\r\n")); + return false; + } + + replyString << TEXT("\r\n"); + SetReplyString(replyString.str()); + return true; } bool ClsCommand::DoExecute() @@ -1399,14 +1884,14 @@ bool TlsCommand::DoExecute() bool VersionCommand::DoExecute() { - std::wstring replyString = TEXT("201 VERSION OK\r\n SERVER: ") + env::version() + TEXT("\r\n"); + std::wstring replyString = TEXT("201 VERSION OK\r\n") + env::version() + TEXT("\r\n"); if(_parameters.size() > 0) { if(_parameters[0] == L"FLASH") - replyString = TEXT("201 VERSION OK\r\n FLASH: ") + flash::get_version() + TEXT("\r\n"); + replyString = TEXT("201 VERSION OK\r\n") + flash::get_version() + TEXT("\r\n"); else if(_parameters[0] == L"TEMPLATEHOST") - replyString = TEXT("201 VERSION OK\r\n TEMPLATEHOST: ") + flash::get_cg_version() + TEXT("\r\n"); + replyString = TEXT("201 VERSION OK\r\n") + flash::get_cg_version() + TEXT("\r\n"); else if(_parameters[0] != L"SERVER") replyString = TEXT("403 VERSION ERROR\r\n"); } @@ -1448,6 +1933,17 @@ bool SetCommand::DoExecute() return true; } +bool KillCommand::DoExecute() +{ + GetShutdownServerNow().set_value(false); // False for not attempting to restart. + return true; +} + +bool RestartCommand::DoExecute() +{ + GetShutdownServerNow().set_value(true); // True for attempting to restart + return true; +} } //namespace amcp -}} //namespace caspar \ No newline at end of file +}} //namespace caspar