]> git.sesse.net Git - casparcg/blobdiff - protocol/amcp/AMCPCommandsImpl.cpp
* Merged MIXER MIPMAP support from 2.0. Implemented as a setting in frame_transform...
[casparcg] / protocol / amcp / AMCPCommandsImpl.cpp
index 014267a6bbb1b9f060a25f4de8c3b88bc14f0853..ff454f79e31ceb2143a8b51feb2821e93e022de8 100644 (file)
@@ -32,9 +32,9 @@
 
 #include <common/log.h>
 #include <common/param.h>
-#include <common/diagnostics/graph.h>
-#include <common/os/windows/current_version.h>
-#include <common/os/windows/system_info.h>
+#include <common/os/system_info.h>
+#include <common/os/filesystem.h>
+#include <common/base64.h>
 
 #include <core/producer/frame_producer.h>
 #include <core/video_format.h>
 #include <core/mixer/mixer.h>
 #include <core/consumer/output.h>
 #include <core/thumbnail_generator.h>
+#include <core/producer/media_info/media_info.h>
+#include <core/producer/media_info/media_info_repository.h>
+#include <core/diagnostics/call_context.h>
+#include <core/diagnostics/osd_graph.h>
+#include <core/system_info_provider.h>
 
-#include <modules/reroute/producer/reroute_producer.h>
-#include <modules/bluefish/bluefish.h>
-#include <modules/decklink/decklink.h>
-#include <modules/ffmpeg/ffmpeg.h>
-#include <modules/flash/flash.h>
-#include <modules/flash/util/swf.h>
-#include <modules/flash/producer/flash_producer.h>
-#include <modules/flash/producer/cg_proxy.h>
-#include <modules/ffmpeg/producer/util/util.h>
-#include <modules/image/image.h>
-#include <modules/screen/screen.h>
 #include <modules/reroute/producer/reroute_producer.h>
 
 #include <algorithm>
@@ -64,7 +58,7 @@
 #include <fstream>
 #include <memory>
 #include <cctype>
-#include <io.h>
+#include <future>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/lexical_cast.hpp>
@@ -109,7 +103,7 @@ namespace caspar { namespace protocol {
 
 using namespace core;
 
-std::wstring read_file_base64(const boost::filesystem::wpath& file)
+std::wstring read_file_base64(const boost::filesystem::path& file)
 {
        using namespace boost::archive::iterators;
 
@@ -118,40 +112,16 @@ std::wstring read_file_base64(const boost::filesystem::wpath& file)
        if (!filestream)
                return L"";
 
-       // From http://www.webbiscuit.co.uk/2012/04/02/base64-encoder-and-boost/
-
-       typedef
-               insert_linebreaks<         // insert line breaks every 76 characters
-               base64_from_binary<    // convert binary values to base64 characters
-               transform_width<   // retrieve 6 bit integers from a sequence of 8 bit bytes
-               const unsigned char *,
-               6,
-               8
-               >
-               >,
-               76
-               >
-               base64_iterator; // compose all the above operations in to a new iterator
        auto length = boost::filesystem::file_size(file);
        std::vector<char> bytes;
        bytes.resize(length);
        filestream.read(bytes.data(), length);
 
-       int padding = 0;
-
-       while (bytes.size() % 3 != 0)
-       {
-               ++padding;
-               bytes.push_back(0x00);
-       }
-
-       std::string result(base64_iterator(bytes.data()), base64_iterator(bytes.data() + length - padding));
-       result.insert(result.end(), padding, '=');
-
+       std::string result(to_base64(bytes.data(), length));
        return std::wstring(result.begin(), result.end());
 }
 
-std::wstring read_utf8_file(const boost::filesystem::wpath& file)
+std::wstring read_utf8_file(const boost::filesystem::path& file)
 {
        std::wstringstream result;
        boost::filesystem::wifstream filestream(file);
@@ -167,7 +137,7 @@ std::wstring read_utf8_file(const boost::filesystem::wpath& file)
        return result.str();
 }
 
-std::wstring read_latin1_file(const boost::filesystem::wpath& file)
+std::wstring read_latin1_file(const boost::filesystem::path& file)
 {
        boost::locale::generator gen;
        gen.locale_cache_enabled(true);
@@ -187,17 +157,15 @@ std::wstring read_latin1_file(const boost::filesystem::wpath& file)
        std::wstring widened_result;
 
        // The first 255 codepoints in unicode is the same as in latin1
-       auto from_signed_to_signed = std::function<unsigned char(char)>(
-               [] (char c) { return static_cast<unsigned char>(c); }
-       );
        boost::copy(
-               result | boost::adaptors::transformed(from_signed_to_signed),
+               result | boost::adaptors::transformed(
+                               [](char c) { return static_cast<unsigned char>(c); }),
                std::back_inserter(widened_result));
 
        return widened_result;
 }
 
-std::wstring read_file(const boost::filesystem::wpath& file)
+std::wstring read_file(const boost::filesystem::path& file)
 {
        static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
 
@@ -221,66 +189,60 @@ std::wstring read_file(const boost::filesystem::wpath& file)
        return read_latin1_file(file);
 }
 
-std::wstring MediaInfo(const boost::filesystem::path& path)
+std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
 {
-       if(boost::filesystem::is_regular_file(path))
-       {
-               std::wstring clipttype = TEXT("N/A");
-               std::wstring extension = boost::to_upper_copy(path.extension().wstring());
-               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(".WAV") || extension == TEXT(".MP3"))
-                       clipttype = TEXT("AUDIO");
-               else if(extension == TEXT("SWF") || extension == TEXT("CT") || extension == TEXT("DV") || extension == TEXT("MOV") || extension == TEXT("MPG") || extension == TEXT("AVI") || caspar::ffmpeg::is_valid_file(path.wstring()))
-                       clipttype = TEXT("MOVIE");
-
-               if(clipttype != TEXT("N/A"))
-               {               
-                       auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
+       if (!boost::filesystem::is_regular_file(path))
+               return L"";
 
-                       auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size()-1, path.wstring().size()));
+       auto media_info = media_info_repo->get(path.wstring());
 
-                       auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
-                       writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
-                       auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
+       if (!media_info)
+               return L"";
 
-                       auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
-                       sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
-                       auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
-                               
-                       auto str = relativePath.replace_extension(TEXT("")).native();
-                       while(str.size() > 0 && (str[0] == '\\' || str[0] == '/'))
-                               str = std::wstring(str.begin() + 1, str.end());
+       auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
 
-                       return std::wstring() + TEXT("\"") + str +
-                                       + TEXT("\" ") + clipttype +
-                                       + TEXT(" ") + sizeStr +
-                                       + TEXT(" ") + writeTimeWStr +
-                                       + TEXT("\r\n");         
-               }       
-       }
-       return L"";
+       auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
+
+       auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
+       writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
+       auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
+
+       auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
+       sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
+       auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
+
+       auto str = relativePath.replace_extension(L"").generic_wstring();
+       if (str[0] == '\\' || str[0] == '/')
+               str = std::wstring(str.begin() + 1, str.end());
+
+       return std::wstring()
+               + L"\"" + str +
+               + L"\" " + media_info->clip_type +
+               + L" " + sizeStr +
+               + L" " + writeTimeWStr +
+               + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
+               + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
+               + L"\r\n";
 }
 
-std::wstring ListMedia()
+std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
 {      
        std::wstringstream replyString;
-       for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)  
-               replyString << MediaInfo(itr->path());
+       for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
+               replyString << MediaInfo(itr->path(), media_info_repo);
        
        return boost::to_upper_copy(replyString.str());
 }
 
-std::wstring ListTemplates(
+std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
 {
        std::wstringstream replyString;
 
        for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
        {               
-               if(boost::filesystem::is_regular_file(itr->path()) && (itr->path().extension() == L".ft" || itr->path().extension() == L".ct"))
+               if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
                {
-                       auto relativePath = boost::filesystem::wpath(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
+                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
 
                        auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
                        writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
@@ -291,17 +253,17 @@ std::wstring ListTemplates()
 
                        auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
 
-                       std::wstring dir = relativePath.parent_path().native();
-                       std::wstring file = boost::to_upper_copy(relativePath.filename().wstring());
-                       relativePath = boost::filesystem::wpath(dir + L"/" + file);
+                       auto dir = relativePath.parent_path();
+                       auto file = boost::to_upper_copy(relativePath.filename().wstring());
+                       relativePath = dir / file;
                                                
-                       auto str = relativePath.replace_extension(TEXT("")).native();
+                       auto str = relativePath.replace_extension(L"").generic_wstring();
                        boost::trim_if(str, boost::is_any_of("\\/"));
 
-                       replyString << TEXT("\"") << str
-                                               << TEXT("\" ") << sizeWStr
-                                               << TEXT(" ") << writeTimeWStr
-                                               << TEXT("\r\n");                
+                       replyString << L"\"" << str
+                                               << L"\" " << sizeWStr
+                                               << L" " << writeTimeWStr
+                                               << L"\r\n";
                }
        }
        return replyString.str();
@@ -321,70 +283,73 @@ bool DiagnosticsCommand::DoExecute()
 {      
        try
        {
-               diagnostics::show_graphs(true);
+               core::diagnostics::osd::show_graphs(true);
 
-               SetReplyString(TEXT("202 DIAG OK\r\n"));
+               SetReplyString(L"202 DIAG OK\r\n");
 
                return true;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 DIAG FAILED\r\n"));
+               SetReplyString(L"502 DIAG FAILED\r\n");
                return false;
        }
 }
 
 bool ChannelGridCommand::DoExecute()
 {
-       CASPAR_THROW_EXCEPTION(not_implemented());
-
-       //int index = 1;
-       //auto self = channels().back();
-       //
-       //std::vector<std::wstring> 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, channels())
-       //{
-       //      if(channel != self)
-       //      {
-       //              auto producer = reroute::create_producer(self->frame_factory(), *channel);              
-       //              self->stage().load(index, producer, false);
-       //              self->stage().play(index);
-       //              index++;
-       //      }
-       //}
-
-       //int n = channels().size()-1;
-       //double delta = 1.0/static_cast<double>(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.image_transform.fill_translation[0]   = x*delta;
-       //                      transform.image_transform.fill_translation[1]   = y*delta;
-       //                      transform.image_transform.fill_scale[0]                 = delta;
-       //                      transform.image_transform.fill_scale[1]                 = delta;
-       //                      transform.image_transform.clip_translation[0]   = x*delta;
-       //                      transform.image_transform.clip_translation[1]   = y*delta;
-       //                      transform.image_transform.clip_scale[0]                 = delta;
-       //                      transform.image_transform.clip_scale[1]                 = delta;                        
-       //                      return transform;
-       //              };
-       //              self->stage().apply_transform(index, transform);
-       //      }
-       //}
-
-       //return true;
+       int index = 1;
+       auto self = channels().back().channel;
+       
+       core::diagnostics::scoped_call_context save;
+       core::diagnostics::call_context::for_thread().video_channel = channels().size();
+
+       std::vector<std::wstring> params;
+       params.push_back(L"SCREEN");
+       params.push_back(L"0");
+       params.push_back(L"NAME");
+       params.push_back(L"Channel Grid Window");
+       auto screen = create_consumer(params, &self->stage());
+
+       self->output().add(screen);
+
+       for (auto& channel : channels())
+       {
+               if(channel.channel != self)
+               {
+                       core::diagnostics::call_context::for_thread().layer = index;
+                       auto producer = reroute::create_producer(*channel.channel);
+                       self->stage().load(index, producer, false);
+                       self->stage().play(index);
+                       index++;
+               }
+       }
+
+       int n = channels().size()-1;
+       double delta = 1.0/static_cast<double>(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.image_transform.fill_translation[0]   = x*delta;
+                               transform.image_transform.fill_translation[1]   = y*delta;
+                               transform.image_transform.fill_scale[0]                 = delta;
+                               transform.image_transform.fill_scale[1]                 = delta;
+                               transform.image_transform.clip_translation[0]   = x*delta;
+                               transform.image_transform.clip_translation[1]   = y*delta;
+                               transform.image_transform.clip_scale[0]                 = delta;
+                               transform.image_transform.clip_scale[1]                 = delta;                        
+                               return transform;
+                       };
+                       self->stage().apply_transform(index, transform);
+               }
+       }
+
+       return true;
 }
 
 bool CallCommand::DoExecute()
@@ -394,14 +359,17 @@ bool CallCommand::DoExecute()
        {
                auto result = channel()->stage().call(layer_index(), parameters());
                
-               if(!result.timed_wait(boost::posix_time::seconds(2)))
-                       CASPAR_THROW_EXCEPTION(timed_out());
+               // TODO: because of std::async deferred timed waiting does not work
+
+               /*auto wait_res = result.wait_for(std::chrono::seconds(2));
+               if (wait_res == std::future_status::timeout)
+                       CASPAR_THROW_EXCEPTION(timed_out());*/
                                
                std::wstringstream replyString;
                if(result.get().empty())
-                       replyString << TEXT("202 CALL OK\r\n");
+                       replyString << L"202 CALL OK\r\n";
                else
-                       replyString << TEXT("201 CALL OK\r\n") << result.get() << L"\r\n";
+                       replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
                
                SetReplyString(replyString.str());
 
@@ -410,13 +378,18 @@ bool CallCommand::DoExecute()
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 CALL FAILED\r\n"));
+               SetReplyString(L"502 CALL FAILED\r\n");
                return false;
        }
 }
 
 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms;
 
+core::frame_transform MixerCommand::get_current_transform()
+{
+       return channel()->stage().get_current_transform(layer_index()).get();
+}
+
 bool MixerCommand::DoExecute()
 {      
        //Perform loading of the clip
@@ -430,6 +403,9 @@ bool MixerCommand::DoExecute()
 
                if(boost::iequals(parameters()[0], L"KEYER") || boost::iequals(parameters()[0], L"IS_KEY"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
+
                        bool value = boost::lexical_cast<int>(parameters().at(1));
                        transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
                        {
@@ -439,6 +415,9 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"OPACITY"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.opacity; });
+
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
 
@@ -450,8 +429,47 @@ bool MixerCommand::DoExecute()
                                return transform;                                       
                        }, duration, tween));
                }
-               else if(boost::iequals(parameters()[0], L"FILL") || boost::iequals(parameters()[0], L"FILL_RECT"))
+               else if (boost::iequals(parameters()[0], L"ANCHOR"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto transform = get_current_transform().image_transform;
+                               auto anchor = transform.anchor;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n");
+                               return true;
+                       }
+
+                       int duration = parameters().size() > 3 ? boost::lexical_cast<int>(parameters()[3]) : 0;
+                       std::wstring tween = parameters().size() > 4 ? parameters()[4] : L"linear";
+                       double x = boost::lexical_cast<double>(parameters().at(1));
+                       double y = boost::lexical_cast<double>(parameters().at(2));
+
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) mutable -> frame_transform
+                       {
+                               transform.image_transform.anchor[0] = x;
+                               transform.image_transform.anchor[1] = y;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"FILL") || boost::iequals(parameters()[0], L"FILL_RECT"))
+               {
+                       if (parameters().size() == 1)
+                       {
+                               auto transform = get_current_transform().image_transform;
+                               auto translation = transform.fill_translation;
+                               auto scale = transform.fill_scale;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(translation[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(translation[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n");
+                               return true;
+                       }
+
                        int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
                        std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
                        double x        = boost::lexical_cast<double>(parameters().at(1));
@@ -470,6 +488,20 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"CLIP") || boost::iequals(parameters()[0], L"CLIP_RECT"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto transform = get_current_transform().image_transform;
+                               auto translation = transform.clip_translation;
+                               auto scale = transform.clip_scale;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(translation[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(translation[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n");
+                               return true;
+                       }
+
                        int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
                        std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
                        double x        = boost::lexical_cast<double>(parameters().at(1));
@@ -486,7 +518,79 @@ bool MixerCommand::DoExecute()
                                return transform;
                        }, duration, tween));
                }
-               else if(boost::iequals(parameters()[0], L"GRID"))
+               else if (boost::iequals(parameters()[0], L"CROP"))
+               {
+                       if (parameters().size() == 1)
+                       {
+                               auto crop = get_current_transform().image_transform.crop;
+                               SetReplyString(
+                                       L"201 MIXER OK\r\n"
+                                       + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
+                                       + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
+                                       + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
+                                       + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n");
+                               return true;
+                       }
+
+                       int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
+                       std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
+                       double ul_x = boost::lexical_cast<double>(parameters().at(1));
+                       double ul_y = boost::lexical_cast<double>(parameters().at(2));
+                       double lr_x = boost::lexical_cast<double>(parameters().at(3));
+                       double lr_y = boost::lexical_cast<double>(parameters().at(4));
+
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.crop.ul[0] = ul_x;
+                               transform.image_transform.crop.ul[1] = ul_y;
+                               transform.image_transform.crop.lr[0] = lr_x;
+                               transform.image_transform.crop.lr[1] = lr_y;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"PERSPECTIVE"))
+               {
+                       if (parameters().size() == 1)
+                       {
+                               auto perspective = get_current_transform().image_transform.perspective;
+                               SetReplyString(
+                                               L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
+                                               + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n");
+                               return true;
+                       }
+
+                       int duration = parameters().size() > 9 ? boost::lexical_cast<int>(parameters()[9]) : 0;
+                       std::wstring tween = parameters().size() > 10 ? parameters()[10] : L"linear";
+                       double ul_x = boost::lexical_cast<double>(parameters().at(1));
+                       double ul_y = boost::lexical_cast<double>(parameters().at(2));
+                       double ur_x = boost::lexical_cast<double>(parameters().at(3));
+                       double ur_y = boost::lexical_cast<double>(parameters().at(4));
+                       double lr_x = boost::lexical_cast<double>(parameters().at(5));
+                       double lr_y = boost::lexical_cast<double>(parameters().at(6));
+                       double ll_x = boost::lexical_cast<double>(parameters().at(7));
+                       double ll_y = boost::lexical_cast<double>(parameters().at(8));
+
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.perspective.ul[0] = ul_x;
+                               transform.image_transform.perspective.ul[1] = ul_y;
+                               transform.image_transform.perspective.ur[0] = ur_x;
+                               transform.image_transform.perspective.ur[1] = ur_y;
+                               transform.image_transform.perspective.lr[0] = lr_x;
+                               transform.image_transform.perspective.lr[1] = lr_y;
+                               transform.image_transform.perspective.ll[0] = ll_x;
+                               transform.image_transform.perspective.ll[1] = ll_y;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"GRID"))
                {
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -512,19 +616,51 @@ bool MixerCommand::DoExecute()
                                }
                        }
                }
+               else if (boost::iequals(parameters()[0], L"MIPMAP"))
+               {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
+
+                       bool value = boost::lexical_cast<int>(parameters().at(1));
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.use_mipmap = value;
+                               return transform;
+                       }, 0, L"linear"));
+               }
                else if(boost::iequals(parameters()[0], L"BLEND"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto blend_mode = channel()->mixer().get_blend_mode(layer_index());
+                               SetReplyString(L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(get_blend_mode(blend_mode))
+                                               + L"\r\n");
+                               return true;
+                       }
+
                        auto blend_str = parameters().at(1);                                                            
                        int layer = layer_index();
-                       channel()->mixer().set_blend_mode(layer_index(), get_blend_mode(blend_str));    
+                       channel()->mixer().set_blend_mode(layer, get_blend_mode(blend_str));    
                }
                else if(boost::iequals(parameters()[0], L"MASTERVOLUME"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto volume = channel()->mixer().get_master_volume();
+                               SetReplyString(L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(volume)+L"\r\n");
+                               return true;
+                       }
+
                        float master_volume = boost::lexical_cast<float>(parameters().at(1));
                        channel()->mixer().set_master_volume(master_volume);
                }
                else if(boost::iequals(parameters()[0], L"BRIGHTNESS"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.brightness; });
+
                        auto value = boost::lexical_cast<double>(parameters().at(1));
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -536,6 +672,9 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"SATURATION"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.saturation; });
+
                        auto value = boost::lexical_cast<double>(parameters().at(1));
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -545,8 +684,11 @@ bool MixerCommand::DoExecute()
                                return transform;
                        }, duration, tween));   
                }
-               else if(parameters()[0] == L"CONTRAST")
+               else if (boost::iequals(parameters()[0], L"CONTRAST"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.contrast; });
+
                        auto value = boost::lexical_cast<double>(parameters().at(1));
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
@@ -556,8 +698,36 @@ bool MixerCommand::DoExecute()
                                return transform;
                        }, duration, tween));   
                }
-               else if(boost::iequals(parameters()[0], L"LEVELS"))
+               else if (boost::iequals(parameters()[0], L"ROTATION"))
+               {
+                       static const double PI = 3.141592653589793;
+
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; });
+
+                       auto value = boost::lexical_cast<double>(parameters().at(1)) * PI / 180.0;
+                       int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
+                       std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
+                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+                       {
+                               transform.image_transform.angle = value;
+                               return transform;
+                       }, duration, tween));
+               }
+               else if (boost::iequals(parameters()[0], L"LEVELS"))
                {
+                       if (parameters().size() == 1)
+                       {
+                               auto levels = get_current_transform().image_transform.levels;
+                               SetReplyString(L"201 MIXER OK\r\n"
+                                               + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
+                                               + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n");
+                               return true;
+                       }
+
                        levels value;
                        value.min_input  = boost::lexical_cast<double>(parameters().at(1));
                        value.max_input  = boost::lexical_cast<double>(parameters().at(2));
@@ -575,6 +745,9 @@ bool MixerCommand::DoExecute()
                }
                else if(boost::iequals(parameters()[0], L"VOLUME"))
                {
+                       if (parameters().size() == 1)
+                               return reply_value([](const frame_transform& t) { return t.audio_transform.volume; });
+
                        int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
                        std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
                        double value = boost::lexical_cast<double>(parameters()[1]);
@@ -606,7 +779,7 @@ bool MixerCommand::DoExecute()
                }
                else
                {
-                       SetReplyString(TEXT("404 MIXER ERROR\r\n"));
+                       SetReplyString(L"404 MIXER ERROR\r\n");
                        return false;
                }
 
@@ -618,20 +791,20 @@ bool MixerCommand::DoExecute()
                else
                        channel()->stage().apply_transforms(transforms);
        
-               SetReplyString(TEXT("202 MIXER OK\r\n"));
+               SetReplyString(L"202 MIXER OK\r\n");
 
                return true;
        }
        catch(file_not_found&)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("404 MIXER ERROR\r\n"));
+               SetReplyString(L"404 MIXER ERROR\r\n");
                return false;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 MIXER FAILED\r\n"));
+               SetReplyString(L"502 MIXER FAILED\r\n");
                return false;
        }
 }
@@ -661,20 +834,20 @@ bool SwapCommand::DoExecute()
                        ch1->stage().swap_layers(ch2.channel->stage());
                }
                
-               SetReplyString(TEXT("202 SWAP OK\r\n"));
+               SetReplyString(L"202 SWAP OK\r\n");
 
                return true;
        }
        catch(file_not_found&)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("404 SWAP ERROR\r\n"));
+               SetReplyString(L"404 SWAP ERROR\r\n");
                return false;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 SWAP FAILED\r\n"));
+               SetReplyString(L"502 SWAP FAILED\r\n");
                return false;
        }
 }
@@ -685,28 +858,31 @@ bool AddCommand::DoExecute()
        try
        {
                //create_consumer still expects all parameters to be uppercase
-               BOOST_FOREACH(std::wstring& str, parameters())
+               for (auto& str : parameters())
                {
                        boost::to_upper(str);
                }
 
-               auto consumer = create_consumer(parameters());
+               core::diagnostics::scoped_call_context save;
+               core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
+
+               auto consumer = create_consumer(parameters(), &channel()->stage());
                channel()->output().add(layer_index(consumer->index()), consumer);
        
-               SetReplyString(TEXT("202 ADD OK\r\n"));
+               SetReplyString(L"202 ADD OK\r\n");
 
                return true;
        }
        catch(file_not_found&)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("404 ADD ERROR\r\n"));
+               SetReplyString(L"404 ADD ERROR\r\n");
                return false;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 ADD FAILED\r\n"));
+               SetReplyString(L"502 ADD FAILED\r\n");
                return false;
        }
 }
@@ -720,30 +896,30 @@ bool RemoveCommand::DoExecute()
                if(index == std::numeric_limits<int>::min())
                {
                        //create_consumer still expects all parameters to be uppercase
-                       BOOST_FOREACH(std::wstring& str, parameters())
+                       for (auto& str : parameters())
                        {
                                boost::to_upper(str);
                        }
 
-                       index = create_consumer(parameters())->index();
+                       index = create_consumer(parameters(), &channel()->stage())->index();
                }
 
                channel()->output().remove(index);
 
-               SetReplyString(TEXT("202 REMOVE OK\r\n"));
+               SetReplyString(L"202 REMOVE OK\r\n");
 
                return true;
        }
        catch(file_not_found&)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("404 REMOVE ERROR\r\n"));
+               SetReplyString(L"404 REMOVE ERROR\r\n");
                return false;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 REMOVE FAILED\r\n"));
+               SetReplyString(L"502 REMOVE FAILED\r\n");
                return false;
        }
 }
@@ -753,78 +929,41 @@ bool LoadCommand::DoExecute()
        //Perform loading of the clip
        try
        {
-               auto pFP = create_producer(channel()->frame_factory(), channel()->video_format_desc(), parameters());           
+               core::diagnostics::scoped_call_context save;
+               core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
+               core::diagnostics::call_context::for_thread().layer = layer_index();
+               auto pFP = create_producer(channel()->frame_factory(), channel()->video_format_desc(), parameters());
                channel()->stage().load(layer_index(), pFP, true);
        
-               SetReplyString(TEXT("202 LOAD OK\r\n"));
+               SetReplyString(L"202 LOAD OK\r\n");
 
                return true;
        }
        catch(file_not_found&)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("404 LOAD ERROR\r\n"));
+               SetReplyString(L"404 LOAD ERROR\r\n");
                return false;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 LOAD FAILED\r\n"));
+               SetReplyString(L"502 LOAD FAILED\r\n");
                return false;
        }
 }
 
-
-
-//std::function<std::wstring()> channel_cg_add_command::parse(const std::wstring& message, const std::vector<renderer::render_device_ptr>& channels)
-//{
-//     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>.*)?");
-//
-//     boost::wsmatch what;
-//     if(!boost::regex_match(message, what, expr))
-//             return nullptr;
-//
-//     auto info = channel_info::parse(what, channels);
-//
-//     int flash_layer_index = boost::lexical_cast<int>(what["FLASH_LAYER"].str());
-//
-//     std::wstring templatename = what["TEMPLATE"].str();
-//     bool play_on_load = what["PLAY_ON_LOAD"].matched ? what["PLAY_ON_LOAD"].str() != L"0" : 0;
-//     std::wstring start_label = what["START_LABEL"].str();   
-//     std::wstring data = get_data(what["DATA"].str());
-//     
-//     boost::replace_all(templatename, "\"", "");
-//
-//     return [=]() -> std::wstring
-//     {       
-//             std::wstring fullFilename = flash::flash_producer::find_template(server::template_folder() + templatename);
-//             if(fullFilename.empty())
-//                     CASPAR_THROW_EXCEPTION(file_not_found());
-//     
-//             std::wstring extension = boost::filesystem::wpath(fullFilename).extension();
-//             std::wstring filename = templatename;
-//             filename.append(extension);
-//
-//             flash::flash::create_cg_proxy(info.video_channel, std::max<int>(DEFAULT_CHANNEL_LAYER+1, info.layer_index))
-//                     ->add(flash_layer_index, filename, play_on_load, start_label, data);
-//
-//             CASPAR_LOG(info) << L"Executed [amcp_channel_cg_add]";
-//             return L"";
-//     };
-
 bool LoadbgCommand::DoExecute()
 {
        transition_info transitionInfo;
        
-       bool bLoop = false;
-
        // TRANSITION
 
        std::wstring message;
        for(size_t n = 0; n < parameters().size(); ++n)
                message += boost::to_upper_copy(parameters()[n]) + L" ";
                
-       static const boost::wregex expr(L".*(?<TRANSITION>CUT|PUSH|SLIDE|WIPE|MIX)\\s*(?<DURATION>\\d+)\\s*(?<TWEEN>(LINEAR)|(EASE[^\\s]*))?\\s*(?<DIRECTION>FROMLEFT|FROMRIGHT|LEFT|RIGHT)?.*");
+       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)?.*)");
        boost::wsmatch what;
        if(boost::regex_match(message, what, expr))
        {
@@ -834,24 +973,24 @@ bool LoadbgCommand::DoExecute()
                auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
                transitionInfo.tweener = tween;         
 
-               if(transition == TEXT("CUT"))
+               if(transition == L"CUT")
                        transitionInfo.type = transition_type::cut;
-               else if(transition == TEXT("MIX"))
+               else if(transition == L"MIX")
                        transitionInfo.type = transition_type::mix;
-               else if(transition == TEXT("PUSH"))
+               else if(transition == L"PUSH")
                        transitionInfo.type = transition_type::push;
-               else if(transition == TEXT("SLIDE"))
+               else if(transition == L"SLIDE")
                        transitionInfo.type = transition_type::slide;
-               else if(transition == TEXT("WIPE"))
+               else if(transition == L"WIPE")
                        transitionInfo.type = transition_type::wipe;
                
-               if(direction == TEXT("FROMLEFT"))
+               if(direction == L"FROMLEFT")
                        transitionInfo.direction = transition_direction::from_left;
-               else if(direction == TEXT("FROMRIGHT"))
+               else if(direction == L"FROMRIGHT")
                        transitionInfo.direction = transition_direction::from_right;
-               else if(direction == TEXT("LEFT"))
+               else if(direction == L"LEFT")
                        transitionInfo.direction = transition_direction::from_right;
-               else if(direction == TEXT("RIGHT"))
+               else if(direction == L"RIGHT")
                        transitionInfo.direction = transition_direction::from_left;
        }
        
@@ -860,8 +999,12 @@ bool LoadbgCommand::DoExecute()
        {
                std::shared_ptr<core::frame_producer> pFP;
                
-               static boost::wregex expr(L"\\[(?<CHANNEL>\\d+)\\]", boost::regex::icase);
+               static boost::wregex expr(LR"(\[(?<CHANNEL>\d+)\])", boost::regex::icase);
                        
+               core::diagnostics::scoped_call_context save;
+               core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
+               core::diagnostics::call_context::for_thread().layer = layer_index();
+
                boost::wsmatch what;
                if(boost::regex_match(parameters().at(0), what, expr))
                {
@@ -885,20 +1028,20 @@ bool LoadbgCommand::DoExecute()
                        channel()->stage().load(layer_index(), pFP2, false); // TODO: LOOP
        
                
-               SetReplyString(TEXT("202 LOADBG OK\r\n"));
+               SetReplyString(L"202 LOADBG OK\r\n");
 
                return true;
        }
        catch(file_not_found&)
        {               
                CASPAR_LOG(error) << L"File not found. No match found for parameters. Check syntax.";
-               SetReplyString(TEXT("404 LOADBG ERROR\r\n"));
+               SetReplyString(L"404 LOADBG ERROR\r\n");
                return false;
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("502 LOADBG FAILED\r\n"));
+               SetReplyString(L"502 LOADBG FAILED\r\n");
                return false;
        }
 }
@@ -908,12 +1051,12 @@ bool PauseCommand::DoExecute()
        try
        {
                channel()->stage().pause(layer_index());
-               SetReplyString(TEXT("202 PAUSE OK\r\n"));
+               SetReplyString(L"202 PAUSE OK\r\n");
                return true;
        }
        catch(...)
        {
-               SetReplyString(TEXT("501 PAUSE FAILED\r\n"));
+               SetReplyString(L"501 PAUSE FAILED\r\n");
        }
 
        return false;
@@ -933,12 +1076,12 @@ bool PlayCommand::DoExecute()
 
                channel()->stage().play(layer_index());
                
-               SetReplyString(TEXT("202 PLAY OK\r\n"));
+               SetReplyString(L"202 PLAY OK\r\n");
                return true;
        }
        catch(...)
        {
-               SetReplyString(TEXT("501 PLAY FAILED\r\n"));
+               SetReplyString(L"501 PLAY FAILED\r\n");
        }
 
        return false;
@@ -949,12 +1092,12 @@ bool StopCommand::DoExecute()
        try
        {
                channel()->stage().stop(layer_index());
-               SetReplyString(TEXT("202 STOP OK\r\n"));
+               SetReplyString(L"202 STOP OK\r\n");
                return true;
        }
        catch(...)
        {
-               SetReplyString(TEXT("501 STOP FAILED\r\n"));
+               SetReplyString(L"501 STOP FAILED\r\n");
        }
 
        return false;
@@ -968,16 +1111,16 @@ bool ClearCommand::DoExecute()
        else
                channel()->stage().clear();
                
-       SetReplyString(TEXT("202 CLEAR OK\r\n"));
+       SetReplyString(L"202 CLEAR OK\r\n");
 
        return true;
 }
 
 bool PrintCommand::DoExecute()
 {
-       channel()->output().add(create_consumer(boost::assign::list_of(L"IMAGE")));
+       channel()->output().add(create_consumer({ L"IMAGE" }, &channel()->stage()));
                
-       SetReplyString(TEXT("202 PRINT OK\r\n"));
+       SetReplyString(L"202 PRINT OK\r\n");
 
        return true;
 }
@@ -987,7 +1130,7 @@ bool LogCommand::DoExecute()
        if(boost::iequals(parameters().at(0), L"LEVEL"))
                log::set_log_level(parameters().at(1));
 
-       SetReplyString(TEXT("202 LOG OK\r\n"));
+       SetReplyString(L"202 LOG OK\r\n");
 
        return true;
 }
@@ -997,23 +1140,23 @@ bool CGCommand::DoExecute()
        try
        {
                std::wstring command = boost::to_upper_copy(parameters()[0]);
-               if(command == TEXT("ADD"))
+               if(command == L"ADD")
                        return DoExecuteAdd();
-               else if(command == TEXT("PLAY"))
+               else if(command == L"PLAY")
                        return DoExecutePlay();
-               else if(command == TEXT("STOP"))
+               else if(command == L"STOP")
                        return DoExecuteStop();
-               else if(command == TEXT("NEXT"))
+               else if(command == L"NEXT")
                        return DoExecuteNext();
-               else if(command == TEXT("REMOVE"))
+               else if(command == L"REMOVE")
                        return DoExecuteRemove();
-               else if(command == TEXT("CLEAR"))
+               else if(command == L"CLEAR")
                        return DoExecuteClear();
-               else if(command == TEXT("UPDATE"))
+               else if(command == L"UPDATE")
                        return DoExecuteUpdate();
-               else if(command == TEXT("INVOKE"))
+               else if(command == L"INVOKE")
                        return DoExecuteInvoke();
-               else if(command == TEXT("INFO"))
+               else if(command == L"INFO")
                        return DoExecuteInfo();
        }
        catch(...)
@@ -1021,14 +1164,14 @@ bool CGCommand::DoExecute()
                CASPAR_LOG_CURRENT_EXCEPTION();
        }
 
-       SetReplyString(TEXT("403 CG ERROR\r\n"));
+       SetReplyString(L"403 CG ERROR\r\n");
        return false;
 }
 
 bool CGCommand::ValidateLayer(const std::wstring& layerstring) {
        int length = layerstring.length();
        for(int i = 0; i < length; ++i) {
-               if(!_istdigit(layerstring[i])) {
+               if(!std::isdigit(layerstring[i])) {
                        return false;
                }
        }
@@ -1047,14 +1190,14 @@ bool CGCommand::DoExecuteAdd() {
 
        if(parameters().size() < 4) 
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return false;
        }
        unsigned int dataIndex = 4;
 
        if(!ValidateLayer(parameters()[1])) 
        {
-               SetReplyString(TEXT("403 CG ERROR\r\n"));
+               SetReplyString(L"403 CG ERROR\r\n");
                return false;
        }
 
@@ -1066,58 +1209,60 @@ bool CGCommand::DoExecuteAdd() {
                ++dataIndex;
 
                if(parameters().size() > 4 && parameters()[4].length() > 0)     //read play-on-load-flag
-                       bDoStart = (parameters()[4][0]==TEXT('1')) ? true : false;
+                       bDoStart = (parameters()[4][0]==L'1') ? true : false;
                else 
                {
-                       SetReplyString(TEXT("402 CG ERROR\r\n"));
+                       SetReplyString(L"402 CG ERROR\r\n");
                        return false;
                }
        }
        else if(parameters()[3].length() > 0) { //read play-on-load-flag
-               bDoStart = (parameters()[3][0]==TEXT('1')) ? true : false;
+               bDoStart = (parameters()[3][0]==L'1') ? true : false;
        }
        else 
        {
-               SetReplyString(TEXT("403 CG ERROR\r\n"));
+               SetReplyString(L"403 CG ERROR\r\n");
                return false;
        }
 
-       const TCHAR* pDataString = 0;
-       std::wstringstream data;
+       const wchar_t* pDataString = 0;
        std::wstring dataFromFile;
        if(parameters().size() > dataIndex) 
        {       //read data
                const std::wstring& dataString = parameters()[dataIndex];
 
-               if(dataString[0] == TEXT('<')) //the data is an XML-string
+               if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
                        pDataString = dataString.c_str();
                else 
                {
                        //The data is not an XML-string, it must be a filename
                        std::wstring filename = env::data_folder();
                        filename.append(dataString);
-                       filename.append(TEXT(".ftd"));
+                       filename.append(L".ftd");
 
-                       dataFromFile = read_file(boost::filesystem::wpath(filename));
-                       pDataString = dataFromFile.c_str();
+                       auto found_file = find_case_insensitive(filename);
+
+                       if (found_file)
+                       {
+                               dataFromFile = read_file(boost::filesystem::path(*found_file));
+                               pDataString = dataFromFile.c_str();
+                       }
                }
        }
 
-       std::wstring fullFilename = flash::find_template(env::template_folder() + parameters()[2]);
-       if(!fullFilename.empty())
-       {
-               std::wstring extension = boost::filesystem::path(fullFilename).extension().wstring();
-               std::wstring filename = parameters()[2];
-               filename.append(extension);
+       auto filename = parameters()[2];
+       auto proxy = cg_registry_->get_or_create_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER), filename);
 
-               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 : TEXT(""));
-               SetReplyString(TEXT("202 CG OK\r\n"));
+       if (proxy == core::cg_proxy::empty())
+       {
+               CASPAR_LOG(warning) << "Could not find template " << parameters()[2];
+               SetReplyString(L"404 CG ERROR\r\n");
        }
        else
        {
-               CASPAR_LOG(warning) << "Could not find template " << parameters()[2];
-               SetReplyString(TEXT("404 CG ERROR\r\n"));
+               proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
        }
+
        return true;
 }
 
@@ -1127,19 +1272,19 @@ bool CGCommand::DoExecutePlay()
        {
                if(!ValidateLayer(parameters()[1])) 
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).play(layer);
+               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
        }
        else
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return true;
        }
 
-       SetReplyString(TEXT("202 CG OK\r\n"));
+       SetReplyString(L"202 CG OK\r\n");
        return true;
 }
 
@@ -1149,19 +1294,19 @@ bool CGCommand::DoExecuteStop()
        {
                if(!ValidateLayer(parameters()[1])) 
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).stop(layer, 0);
+               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
        }
        else 
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return true;
        }
 
-       SetReplyString(TEXT("202 CG OK\r\n"));
+       SetReplyString(L"202 CG OK\r\n");
        return true;
 }
 
@@ -1171,20 +1316,20 @@ bool CGCommand::DoExecuteNext()
        {
                if(!ValidateLayer(parameters()[1])) 
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
 
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).next(layer);
+               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
        }
        else 
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return true;
        }
 
-       SetReplyString(TEXT("202 CG OK\r\n"));
+       SetReplyString(L"202 CG OK\r\n");
        return true;
 }
 
@@ -1194,27 +1339,27 @@ bool CGCommand::DoExecuteRemove()
        {
                if(!ValidateLayer(parameters()[1])) 
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
 
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).remove(layer);
+               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
        }
        else 
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return true;
        }
 
-       SetReplyString(TEXT("202 CG OK\r\n"));
+       SetReplyString(L"202 CG OK\r\n");
        return true;
 }
 
 bool CGCommand::DoExecuteClear() 
 {
-       channel()->stage().clear(layer_index(flash::cg_proxy::DEFAULT_LAYER));
-       SetReplyString(TEXT("202 CG OK\r\n"));
+       channel()->stage().clear(layer_index(core::cg_proxy::DEFAULT_LAYER));
+       SetReplyString(L"202 CG OK\r\n");
        return true;
 }
 
@@ -1224,53 +1369,53 @@ bool CGCommand::DoExecuteUpdate()
        {
                if(!ValidateLayer(parameters().at(1)))
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
                                                
                std::wstring dataString = parameters().at(2);                           
-               if(dataString.at(0) != TEXT('<'))
+               if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
                {
-                       //The data is not an XML-string, it must be a filename
+                       //The data is not XML or Json, it must be a filename
                        std::wstring filename = env::data_folder();
                        filename.append(dataString);
-                       filename.append(TEXT(".ftd"));
+                       filename.append(L".ftd");
 
-                       dataString = read_file(boost::filesystem::wpath(filename));
+                       dataString = read_file(boost::filesystem::path(filename));
                }               
 
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).update(layer, dataString);
+               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->update(layer, dataString);
        }
        catch(...)
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return true;
        }
 
-       SetReplyString(TEXT("202 CG OK\r\n"));
+       SetReplyString(L"202 CG OK\r\n");
        return true;
 }
 
 bool CGCommand::DoExecuteInvoke() 
 {
        std::wstringstream replyString;
-       replyString << TEXT("201 CG OK\r\n");
+       replyString << L"201 CG OK\r\n";
 
        if(parameters().size() > 2)
        {
                if(!ValidateLayer(parameters()[1]))
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               auto result = flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).invoke(layer, parameters()[2]);
-               replyString << result << TEXT("\r\n"); 
+               auto result = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->invoke(layer, parameters()[2]);
+               replyString << result << L"\r\n";
        }
        else 
        {
-               SetReplyString(TEXT("402 CG ERROR\r\n"));
+               SetReplyString(L"402 CG ERROR\r\n");
                return true;
        }
        
@@ -1281,25 +1426,25 @@ bool CGCommand::DoExecuteInvoke()
 bool CGCommand::DoExecuteInfo() 
 {
        std::wstringstream replyString;
-       replyString << TEXT("201 CG OK\r\n");
+       replyString << L"201 CG OK\r\n";
 
        if(parameters().size() > 1)
        {
                if(!ValidateLayer(parameters()[1]))
                {
-                       SetReplyString(TEXT("403 CG ERROR\r\n"));
+                       SetReplyString(L"403 CG ERROR\r\n");
                        return false;
                }
 
                int layer = boost::lexical_cast<int>(parameters()[1]);
-               auto desc = flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).description(layer);
+               auto desc = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->description(layer);
                
-               replyString << desc << TEXT("\r\n"); 
+               replyString << desc << L"\r\n";
        }
        else 
        {
-               auto info = flash::create_cg_proxy(spl::shared_ptr<core::video_channel>(channel()), layer_index(flash::cg_proxy::DEFAULT_LAYER)).template_host_info();
-               replyString << info << TEXT("\r\n"); 
+               auto info = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->template_host_info();
+               replyString << info << L"\r\n";
        }       
 
        SetReplyString(replyString.str());
@@ -1309,16 +1454,16 @@ bool CGCommand::DoExecuteInfo()
 bool DataCommand::DoExecute()
 {
        std::wstring command = boost::to_upper_copy(parameters()[0]);
-       if(command == TEXT("STORE"))
+       if(command == L"STORE")
                return DoExecuteStore();
-       else if(command == TEXT("RETRIEVE"))
+       else if(command == L"RETRIEVE")
                return DoExecuteRetrieve();
-       else if(command == TEXT("REMOVE"))
+       else if(command == L"REMOVE")
                return DoExecuteRemove();
-       else if(command == TEXT("LIST"))
+       else if(command == L"LIST")
                return DoExecuteList();
 
-       SetReplyString(TEXT("403 DATA ERROR\r\n"));
+       SetReplyString(L"403 DATA ERROR\r\n");
        return false;
 }
 
@@ -1326,24 +1471,32 @@ bool DataCommand::DoExecuteStore()
 {
        if(parameters().size() < 3) 
        {
-               SetReplyString(TEXT("402 DATA STORE ERROR\r\n"));
+               SetReplyString(L"402 DATA STORE ERROR\r\n");
                return false;
        }
 
        std::wstring filename = env::data_folder();
        filename.append(parameters()[1]);
-       filename.append(TEXT(".ftd"));
+       filename.append(L".ftd");
+
+       auto data_path = boost::filesystem::path(filename).parent_path().wstring();
+       auto found_data_path = find_case_insensitive(data_path);
 
-       auto data_path = boost::filesystem::wpath(
-               boost::filesystem::wpath(filename).parent_path());
+       if (found_data_path)
+               data_path = *found_data_path;
 
        if(!boost::filesystem::exists(data_path))
                boost::filesystem::create_directories(data_path);
 
-       std::wofstream datafile(filename.c_str());
+       auto found_filename = find_case_insensitive(filename);
+
+       if (found_filename)
+               filename = *found_filename; // Overwrite case insensitive.
+
+       boost::filesystem::wofstream datafile(filename);
        if(!datafile) 
        {
-               SetReplyString(TEXT("501 DATA STORE FAILED\r\n"));
+               SetReplyString(L"501 DATA STORE FAILED\r\n");
                return false;
        }
 
@@ -1351,7 +1504,7 @@ bool DataCommand::DoExecuteStore()
        datafile << parameters()[2] << std::flush;
        datafile.close();
 
-       std::wstring replyString = TEXT("202 DATA STORE OK\r\n");
+       std::wstring replyString = L"202 DATA STORE OK\r\n";
        SetReplyString(replyString);
        return true;
 }
@@ -1360,28 +1513,42 @@ bool DataCommand::DoExecuteRetrieve()
 {
        if(parameters().size() < 2) 
        {
-               SetReplyString(TEXT("402 DATA RETRIEVE ERROR\r\n"));
+               SetReplyString(L"402 DATA RETRIEVE ERROR\r\n");
                return false;
        }
 
        std::wstring filename = env::data_folder();
        filename.append(parameters()[1]);
-       filename.append(TEXT(".ftd"));
+       filename.append(L".ftd");
+
+       std::wstring file_contents;
+
+       auto found_file = find_case_insensitive(filename);
 
-       std::wstring file_contents = read_file(boost::filesystem::wpath(filename));
+       if (found_file)
+               file_contents = read_file(boost::filesystem::path(*found_file));
 
        if (file_contents.empty()) 
        {
-               SetReplyString(TEXT("404 DATA RETRIEVE ERROR\r\n"));
+               SetReplyString(L"404 DATA RETRIEVE ERROR\r\n");
                return false;
        }
 
-       std::wstringstream reply(TEXT("201 DATA RETRIEVE OK\r\n"));
+       std::wstringstream reply(L"201 DATA RETRIEVE OK\r\n");
 
        std::wstringstream file_contents_stream(file_contents);
        std::wstring line;
+       
+       bool firstLine = true;
        while(std::getline(file_contents_stream, line))
-               reply << line << L"\n";
+       {
+               if(firstLine)
+                       firstLine = false;
+               else
+                       reply << "\n";
+
+               reply << line;
+       }
 
        reply << "\r\n";
        SetReplyString(reply.str());
@@ -1392,27 +1559,27 @@ bool DataCommand::DoExecuteRemove()
 { 
        if (parameters().size() < 2)
        {
-               SetReplyString(TEXT("402 DATA REMOVE ERROR\r\n"));
+               SetReplyString(L"402 DATA REMOVE ERROR\r\n");
                return false;
        }
 
        std::wstring filename = env::data_folder();
        filename.append(parameters()[1]);
-       filename.append(TEXT(".ftd"));
+       filename.append(L".ftd");
 
        if (!boost::filesystem::exists(filename))
        {
-               SetReplyString(TEXT("404 DATA REMOVE ERROR\r\n"));
+               SetReplyString(L"404 DATA REMOVE ERROR\r\n");
                return false;
        }
 
        if (!boost::filesystem::remove(filename))
        {
-               SetReplyString(TEXT("403 DATA REMOVE ERROR\r\n"));
+               SetReplyString(L"403 DATA REMOVE ERROR\r\n");
                return false;
        }
 
-       SetReplyString(TEXT("201 DATA REMOVE OK\r\n"));
+       SetReplyString(L"201 DATA REMOVE OK\r\n");
 
        return true;
 }
@@ -1420,7 +1587,7 @@ bool DataCommand::DoExecuteRemove()
 bool DataCommand::DoExecuteList() 
 {
        std::wstringstream replyString;
-       replyString << TEXT("200 DATA LIST OK\r\n");
+       replyString << L"200 DATA LIST OK\r\n";
 
        for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
        {                       
@@ -1429,17 +1596,17 @@ bool DataCommand::DoExecuteList()
                        if(!boost::iequals(itr->path().extension().wstring(), L".ftd"))
                                continue;
                        
-                       auto relativePath = boost::filesystem::wpath(itr->path().wstring().substr(env::data_folder().size()-1, itr->path().wstring().size()));
+                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size()-1, itr->path().wstring().size()));
                        
-                       auto str = relativePath.replace_extension(TEXT("")).native();
-                       if(str[0] == '\\' || str[0] == '/')
+                       auto str = relativePath.replace_extension(L"").generic_wstring();
+                       if(str[0] == L'\\' || str[0] == L'/')
                                str = std::wstring(str.begin() + 1, str.end());
 
-                       replyString << str << TEXT("\r\n");     
+                       replyString << str << L"\r\n";
                }
        }
        
-       replyString << TEXT("\r\n");
+       replyString << L"\r\n";
 
        SetReplyString(boost::to_upper_copy(replyString.str()));
        return true;
@@ -1455,23 +1622,23 @@ bool CinfCommand::DoExecute()
                for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
                {
                        auto path = itr->path();
-                       auto file = path.replace_extension(L"").filename();
-                       if(boost::iequals(file.wstring(), parameters().at(0)))
-                               info += MediaInfo(itr->path()) + L"\r\n";
+                       auto file = path.replace_extension(L"").filename().wstring();
+                       if(boost::iequals(file, parameters().at(0)))
+                               info += MediaInfo(itr->path(), system_info_repo_) + L"\r\n";
                }
 
                if(info.empty())
                {
-                       SetReplyString(TEXT("404 CINF ERROR\r\n"));
+                       SetReplyString(L"404 CINF ERROR\r\n");
                        return false;
                }
-               replyString << TEXT("200 CINF OK\r\n");
+               replyString << L"200 CINF OK\r\n";
                replyString << info << "\r\n";
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("404 CINF ERROR\r\n"));
+               SetReplyString(L"404 CINF ERROR\r\n");
                return false;
        }
        
@@ -1481,14 +1648,14 @@ bool CinfCommand::DoExecute()
 
 void GenerateChannelInfo(int index, const spl::shared_ptr<core::video_channel>& pChannel, std::wstringstream& replyString)
 {
-       replyString << index+1 << TEXT(" ") << pChannel->video_format_desc().name << TEXT(" PLAYING") << TEXT("\r\n");
+       replyString << index+1 << L" " << pChannel->video_format_desc().name << L" PLAYING\r\n";
 }
 
 bool InfoCommand::DoExecute()
 {
        std::wstringstream replyString;
        
-       boost::property_tree::xml_writer_settings<wchar_t> w(' ', 3);
+       boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
 
        try
        {
@@ -1496,12 +1663,10 @@ bool InfoCommand::DoExecute()
                {               
                        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));
+                       auto filename = parameters().at(1);
                                                
                        std::wstringstream str;
-                       str << u16(flash::read_template_meta_info(filename));
+                       str << u16(cg_registry_->read_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);
 
@@ -1519,7 +1684,7 @@ bool InfoCommand::DoExecute()
 
                        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::filesystem3::initial_path().wstring() + L"\\");
+                       info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
 
                        boost::property_tree::write_xml(replyString, info, w);
                }
@@ -1530,23 +1695,10 @@ bool InfoCommand::DoExecute()
                        boost::property_tree::wptree info;
                        
                        info.add(L"system.name",                                        caspar::system_product_name());
-                       info.add(L"system.windows.name",                        caspar::win_product_name());
-                       info.add(L"system.windows.service-pack",        caspar::win_sp_version());
+                       info.add(L"system.os.description",                      caspar::os_description());
                        info.add(L"system.cpu",                                         caspar::cpu_info());
-       
-                       BOOST_FOREACH(auto device, caspar::decklink::device_list())
-                               info.add(L"system.decklink.device", device);
 
-                       BOOST_FOREACH(auto device, caspar::bluefish::device_list())
-                               info.add(L"system.bluefish.device", device);
-                               
-                       info.add(L"system.flash",                                       caspar::flash::version());
-                       //info.add(L"system.free-image",                                caspar::image::version());
-                       info.add(L"system.ffmpeg.avcodec",                      caspar::ffmpeg::avcodec_version());
-                       info.add(L"system.ffmpeg.avformat",                     caspar::ffmpeg::avformat_version());
-                       info.add(L"system.ffmpeg.avfilter",                     caspar::ffmpeg::avfilter_version());
-                       info.add(L"system.ffmpeg.avutil",                       caspar::ffmpeg::avutil_version());
-                       info.add(L"system.ffmpeg.swscale",                      caspar::ffmpeg::swscale_version());
+                       system_info_repo_->fill_information(info);
                                                
                        boost::property_tree::write_xml(replyString, info, w);
                }
@@ -1557,7 +1709,7 @@ bool InfoCommand::DoExecute()
                        boost::property_tree::wptree info;
 
                        int index = 0;
-                       BOOST_FOREACH(auto channel, channels())
+                       for (auto& channel : channels())
                                info.add_child(L"channels.channel", channel.channel->info())
                                        .add(L"index", ++index);
                        
@@ -1567,7 +1719,7 @@ bool InfoCommand::DoExecute()
                {                       
                        if(parameters().size() >= 1)
                        {
-                               replyString << TEXT("201 INFO OK\r\n");
+                               replyString << L"201 INFO OK\r\n";
                                boost::property_tree::wptree info;
 
                                std::vector<std::wstring> split;
@@ -1604,7 +1756,7 @@ bool InfoCommand::DoExecute()
                        else
                        {
                                // This is needed for backwards compatibility with old clients
-                               replyString << TEXT("200 INFO OK\r\n");
+                               replyString << L"200 INFO OK\r\n";
                                for(size_t n = 0; n < channels().size(); ++n)
                                        GenerateChannelInfo(n, channels()[n].channel, replyString);
                        }
@@ -1614,37 +1766,29 @@ bool InfoCommand::DoExecute()
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("403 INFO ERROR\r\n"));
+               SetReplyString(L"403 INFO ERROR\r\n");
                return false;
        }
 
-       replyString << TEXT("\r\n");
+       replyString << L"\r\n";
        SetReplyString(replyString.str());
        return true;
 }
 
 bool ClsCommand::DoExecute()
 {
-/*
-               wav = audio
-               mp3 = audio
-               swf     = movie
-               dv  = movie
-               tga = still
-               col = still
-       */
        try
        {
                std::wstringstream replyString;
-               replyString << TEXT("200 CLS OK\r\n");
-               replyString << ListMedia();
-               replyString << TEXT("\r\n");
+               replyString << L"200 CLS OK\r\n";
+               replyString << ListMedia(system_info_repo_);
+               replyString << L"\r\n";
                SetReplyString(boost::to_upper_copy(replyString.str()));
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("501 CLS FAILED\r\n"));
+               SetReplyString(L"501 CLS FAILED\r\n");
                return false;
        }
 
@@ -1656,17 +1800,17 @@ bool TlsCommand::DoExecute()
        try
        {
                std::wstringstream replyString;
-               replyString << TEXT("200 TLS OK\r\n");
+               replyString << L"200 TLS OK\r\n";
 
-               replyString << ListTemplates();
-               replyString << TEXT("\r\n");
+               replyString << ListTemplates(cg_registry_);
+               replyString << L"\r\n";
 
                SetReplyString(replyString.str());
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("501 TLS FAILED\r\n"));
+               SetReplyString(L"501 TLS FAILED\r\n");
                return false;
        }
        return true;
@@ -1674,16 +1818,16 @@ bool TlsCommand::DoExecute()
 
 bool VersionCommand::DoExecute()
 {
-       std::wstring replyString = TEXT("201 VERSION OK\r\n") + env::version() + TEXT("\r\n");
+       std::wstring replyString = L"201 VERSION OK\r\n" + env::version() + L"\r\n";
 
-       if(parameters().size() > 0)
+       if (parameters().size() > 0 && !boost::iequals(parameters()[0], L"SERVER"))
        {
-               if(boost::iequals(parameters()[0], L"FLASH"))
-                       replyString = TEXT("201 VERSION OK\r\n") + flash::version() + TEXT("\r\n");
-               else if(boost::iequals(parameters()[0], L"TEMPLATEHOST"))
-                       replyString = TEXT("201 VERSION OK\r\n") + flash::cg_version() + TEXT("\r\n");
-               else if(!boost::iequals(parameters()[0], L"SERVER"))
-                       replyString = TEXT("403 VERSION ERROR\r\n");
+               auto version = system_info_repo_->get_version(parameters().at(0));
+
+               if (version.empty())
+                       replyString = L"403 VERSION ERROR\r\n";
+               else
+                       replyString = L"201 VERSION OK\r\n" + version + L"\r\n";
        }
 
        SetReplyString(replyString);
@@ -1703,26 +1847,26 @@ bool SetCommand::DoExecute()
                std::wstring name = boost::to_upper_copy(parameters()[0]);
                std::wstring value = boost::to_upper_copy(parameters()[1]);
 
-               if(name == TEXT("MODE"))
+               if(name == L"MODE")
                {
                        auto format_desc = core::video_format_desc(value);
                        if(format_desc.format != core::video_format::invalid)
                        {
                                channel()->video_format_desc(format_desc);
-                               SetReplyString(TEXT("202 SET MODE OK\r\n"));
+                               SetReplyString(L"202 SET MODE OK\r\n");
                        }
                        else
-                               SetReplyString(TEXT("501 SET MODE FAILED\r\n"));
+                               SetReplyString(L"501 SET MODE FAILED\r\n");
                }
                else
                {
-                       this->SetReplyString(TEXT("403 SET ERROR\r\n"));
+                       this->SetReplyString(L"403 SET ERROR\r\n");
                }
        }
        catch(...)
        {
                CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(TEXT("501 SET FAILED\r\n"));
+               SetReplyString(L"501 SET FAILED\r\n");
                return false;
        }
 
@@ -1859,16 +2003,16 @@ bool ThumbnailCommand::DoExecute()
 {
        std::wstring command = boost::to_upper_copy(parameters()[0]);
 
-       if (command == TEXT("RETRIEVE"))
+       if (command == L"RETRIEVE")
                return DoExecuteRetrieve();
-       else if (command == TEXT("LIST"))
+       else if (command == L"LIST")
                return DoExecuteList();
-       else if (command == TEXT("GENERATE"))
+       else if (command == L"GENERATE")
                return DoExecuteGenerate();
-       else if (command == TEXT("GENERATE_ALL"))
+       else if (command == L"GENERATE_ALL")
                return DoExecuteGenerateAll();
 
-       SetReplyString(TEXT("403 THUMBNAIL ERROR\r\n"));
+       SetReplyString(L"403 THUMBNAIL ERROR\r\n");
        return false;
 }
 
@@ -1876,19 +2020,24 @@ bool ThumbnailCommand::DoExecuteRetrieve()
 {
        if(parameters().size() < 2) 
        {
-               SetReplyString(TEXT("402 THUMBNAIL RETRIEVE ERROR\r\n"));
+               SetReplyString(L"402 THUMBNAIL RETRIEVE ERROR\r\n");
                return false;
        }
 
        std::wstring filename = env::thumbnails_folder();
        filename.append(parameters()[1]);
-       filename.append(TEXT(".png"));
+       filename.append(L".png");
+
+       std::wstring file_contents;
+
+       auto found_file = find_case_insensitive(filename);
 
-       std::wstring file_contents = read_file_base64(boost::filesystem::wpath(filename));
+       if (found_file)
+               file_contents = read_file_base64(boost::filesystem::path(*found_file));
 
        if (file_contents.empty())
        {
-               SetReplyString(TEXT("404 THUMBNAIL RETRIEVE ERROR\r\n"));
+               SetReplyString(L"404 THUMBNAIL RETRIEVE ERROR\r\n");
                return false;
        }
 
@@ -1904,7 +2053,7 @@ bool ThumbnailCommand::DoExecuteRetrieve()
 bool ThumbnailCommand::DoExecuteList()
 {
        std::wstringstream replyString;
-       replyString << TEXT("200 THUMBNAIL LIST OK\r\n");
+       replyString << L"200 THUMBNAIL LIST OK\r\n";
 
        for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
        {      
@@ -1913,9 +2062,9 @@ bool ThumbnailCommand::DoExecuteList()
                        if(!boost::iequals(itr->path().extension().wstring(), L".png"))
                                continue;
 
-                       auto relativePath = boost::filesystem::wpath(itr->path().wstring().substr(env::thumbnails_folder().size()-1, itr->path().wstring().size()));
+                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size()-1, itr->path().wstring().size()));
 
-                       auto str = relativePath.replace_extension(L"").native();
+                       auto str = relativePath.replace_extension(L"").generic_wstring();
                        if(str[0] == '\\' || str[0] == '/')
                                str = std::wstring(str.begin() + 1, str.end());
 
@@ -1927,7 +2076,7 @@ bool ThumbnailCommand::DoExecuteList()
                }
        }
 
-       replyString << TEXT("\r\n");
+       replyString << L"\r\n";
 
        SetReplyString(boost::to_upper_copy(replyString.str()));
        return true;
@@ -1971,10 +2120,17 @@ bool ThumbnailCommand::DoExecuteGenerateAll()
 
 bool KillCommand::DoExecute()
 {
-       shutdown_server_now_->set_value(false); //false for not waiting for keypress
-       SetReplyString(TEXT("202 KILL OK\r\n"));
+       shutdown_server_now_->set_value(false); //false for not attempting to restart
+       SetReplyString(L"202 KILL OK\r\n");
+       return true;
+}
+
+bool RestartCommand::DoExecute()
+{
+       shutdown_server_now_->set_value(true);  //true for attempting to restart
+       SetReplyString(L"202 RESTART OK\r\n");
        return true;
 }
 
 }      //namespace amcp
-}}     //namespace caspar
\ No newline at end of file
+}}     //namespace caspar