]> git.sesse.net Git - casparcg/commitdiff
* Merged RESUME command.
authorHelge Norberg <helge.norberg@svt.se>
Tue, 30 Jun 2015 12:53:52 +0000 (14:53 +0200)
committerHelge Norberg <helge.norberg@svt.se>
Tue, 30 Jun 2015 12:53:52 +0000 (14:53 +0200)
* Refactored AMCP commands to simply be implemented as free functions instead of a class for each command. Also added support for considering sub commands as a command on their own, to allow for example MixerCommand to be split up into MIXER FILL and MIXER OPACITY etc.
* Also simplified error handling in AMCP commands by embracing the use of exceptions more (letting AMCPCommandQueue.cpp create the error responses based on the type of exception).
* Created an online help system which could also be leveraged for generating parts of the wiki (AMCP command syntax and other artifacts that are tightly coupled to server code). This makes it easier to be forced to write documentation when adding new AMCP commands etc. The actual documentation for each AMCP command is documented close to the implementation of each command, making it easier to catch inconsistencies in the documentation.
* Added possibility for the SWAP command to swap mixer transformations in addition to the layers themselves.
* Fixed bug in INFO TEMPLATE command.

28 files changed:
CMakeLists.txt
core/CMakeLists.txt
core/fwd.h
core/help/help_repository.cpp [new file with mode: 0644]
core/help/help_repository.h [new file with mode: 0644]
core/help/help_sink.h [new file with mode: 0644]
core/producer/layer.cpp
core/producer/layer.h
core/producer/stage.cpp
core/producer/stage.h
core/system_info_provider.cpp
modules/flash/flash.cpp
protocol/CMakeLists.txt
protocol/amcp/AMCPCommand.h
protocol/amcp/AMCPCommandQueue.cpp
protocol/amcp/AMCPCommandQueue.h
protocol/amcp/AMCPCommandsImpl.cpp
protocol/amcp/AMCPCommandsImpl.h
protocol/amcp/AMCPProtocolStrategy.cpp
protocol/amcp/AMCPProtocolStrategy.h
protocol/amcp/amcp_command_repository.cpp [new file with mode: 0644]
protocol/amcp/amcp_command_repository.h [new file with mode: 0644]
protocol/amcp/amcp_shared.h
protocol/util/lock_container.h
protocol/util/strategy_adapters.cpp
shell/main.cpp
shell/server.cpp
shell/server.h

index 9bf3d1cb370677cce0d6c550e6d97fa972ba2b17..d17ab5e1d1e81995537101314c6c3abf80a9ac76 100644 (file)
@@ -73,9 +73,10 @@ add_definitions( -D_UNICODE )
 add_definitions( -DGLEW_NO_GLU )
 
 if (MSVC)
-       set(CMAKE_CXX_FLAGS                     "${CMAKE_CXX_FLAGS}                     /EHa /Zi /W4 /WX /MP /fp:fast /FIcommon/compiler/vs/disable_silly_warnings.h")
-       set(CMAKE_CXX_FLAGS_DEBUG       "${CMAKE_CXX_FLAGS_DEBUG}       /D TBB_USE_ASSERT=1 /D TBB_USE_DEBUG /bigobj")
-       set(CMAKE_CXX_FLAGS_RELEASE     "${CMAKE_CXX_FLAGS_RELEASE}     /Oi /Ot /Gy")
+       set(CMAKE_CXX_FLAGS                                     "${CMAKE_CXX_FLAGS}                                     /EHa /Zi /W4 /WX /MP /fp:fast /FIcommon/compiler/vs/disable_silly_warnings.h")
+       set(CMAKE_CXX_FLAGS_DEBUG                       "${CMAKE_CXX_FLAGS_DEBUG}                       /D TBB_USE_ASSERT=1 /D TBB_USE_DEBUG /bigobj")
+       set(CMAKE_CXX_FLAGS_RELEASE                     "${CMAKE_CXX_FLAGS_RELEASE}                     /Oi /Ot /Gy /bigobj")
+       set(CMAKE_CXX_FLAGS_RELWITHDEBINFO      "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}      /Oi /Ot /Gy /bigobj /Ob2")
 elseif (CMAKE_COMPILER_IS_GNUCXX)
        set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g")
        add_compile_options( -std=c++11 )
index 8f8a12a0c98957c4affce269fc5b16b617c6f957..0738bc1719982c5f4eb4bc3258a8d1d1247fbd9e 100644 (file)
@@ -15,6 +15,8 @@ set(SOURCES
                frame/frame_transform.cpp
                frame/geometry.cpp
 
+               help/help_repository.cpp
+
                mixer/audio/audio_mixer.cpp
                mixer/image/blend_modes.cpp
                mixer/mixer.cpp
@@ -71,6 +73,9 @@ set(HEADERS
                frame/geometry.h
                frame/pixel_format.h
 
+               help/help_repository.h
+               help/help_sink.h
+
                interaction/interaction_aggregator.h
                interaction/interaction_event.h
                interaction/interaction_sink.h
@@ -136,6 +141,7 @@ source_group(sources ./*)
 source_group(sources\\consumer consumer/*)
 source_group(sources\\diagnostics diagnostics/*)
 source_group(sources\\frame frame/*)
+source_group(sources\\help help/*)
 source_group(sources\\interaction interaction/*)
 source_group(sources\\mixer mixer/*)
 source_group(sources\\producer\\draw producer/draw/*)
index cea52bb83dbebcb50666680c80dbf148276e9628..6442a3dcb7bd0dc7c01ca8a7e6a4e133a4fafcb5 100644 (file)
@@ -46,3 +46,5 @@ FORWARD2(caspar, core, class cg_producer_registry);
 FORWARD2(caspar, core, struct frame_transform);
 FORWARD2(caspar, core, struct write_frame_consumer);
 FORWARD2(caspar, core, struct frame_producer_dependencies);
+FORWARD2(caspar, core, class help_sink);
+FORWARD2(caspar, core, class help_repository);
diff --git a/core/help/help_repository.cpp b/core/help/help_repository.cpp
new file mode 100644 (file)
index 0000000..d66f8cb
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* 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 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 <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#include "help_repository.h"
+#include "help_sink.h"
+
+#include <common/except.h>
+
+#include <boost/range/adaptor/filtered.hpp>
+
+#include <algorithm>
+#include <utility>
+
+namespace caspar { namespace core {
+
+typedef std::pair<std::wstring, help_item_describer> help_item;
+
+struct help_repository::impl
+{
+       std::vector<std::pair<help_item, std::set<std::wstring>>> items;
+
+       static void help(const help_repository& self, help_item item, help_sink& sink)
+       {
+               sink.begin_item(item.first);
+               item.second(sink, self);
+               sink.end_item();
+       }
+};
+
+
+help_repository::help_repository()
+       : impl_(new impl)
+{
+}
+
+void help_repository::register_item(std::set<std::wstring> tags, std::wstring name, help_item_describer describer)
+{
+       impl_->items.push_back(std::make_pair(help_item(name, describer), tags));
+}
+
+void help_repository::help(std::set<std::wstring> tags, help_sink& sink) const
+{
+       for (auto& item : impl_->items)
+       {
+               if (std::includes(item.second.begin(), item.second.end(), tags.begin(), tags.end()))
+                       impl::help(*this, item.first, sink);
+       }
+}
+
+void help_repository::help(std::set<std::wstring> tags, std::wstring name, help_sink& sink) const
+{
+       auto found = impl_->items | boost::adaptors::filtered([&](const std::pair<help_item, std::set<std::wstring>>& item)
+       {
+               return item.first.first == name && std::includes(
+                       item.second.begin(), item.second.end(),
+                       tags.begin(), tags.end());
+       });
+
+       if (found.empty())
+               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find help item " + name));
+
+       for (auto& item : found)
+       {
+               if (std::includes(item.second.begin(), item.second.end(), tags.begin(), tags.end()))
+                       impl::help(*this, item.first, sink);
+       }
+}
+
+}}
diff --git a/core/help/help_repository.h b/core/help/help_repository.h
new file mode 100644 (file)
index 0000000..edffb64
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* 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 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 <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include "../fwd.h"
+
+#include <common/memory.h>
+
+#include <set>
+#include <vector>
+#include <functional>
+#include <utility>
+
+namespace caspar { namespace core {
+
+typedef std::function<void(core::help_sink&, const core::help_repository&)> help_item_describer;
+
+class help_repository
+{
+public:
+       help_repository();
+       void register_item(std::set<std::wstring> tags, std::wstring name, help_item_describer describer); // Not thread safe
+       void help(std::set<std::wstring> tags, help_sink& sink) const;
+       void help(std::set<std::wstring> tags, std::wstring name, help_sink& sink) const;
+private:
+       struct impl;
+       spl::shared_ptr<impl> impl_;
+};
+
+}}
diff --git a/core/help/help_sink.h b/core/help/help_sink.h
new file mode 100644 (file)
index 0000000..5a85421
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* 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 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 <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include <string>
+
+#include <common/memory.h>
+
+namespace caspar { namespace core {
+
+class paragraph_builder : public spl::enable_shared_from_this<paragraph_builder>
+{
+public:
+       virtual ~paragraph_builder() { }
+       virtual spl::shared_ptr<paragraph_builder> text(std::wstring text) { return shared_from_this(); };
+       virtual spl::shared_ptr<paragraph_builder> code(std::wstring text) { return shared_from_this(); };
+       virtual spl::shared_ptr<paragraph_builder> see(std::wstring item) { return shared_from_this(); };
+       virtual spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name = L"") { return shared_from_this(); };
+};
+
+class definition_list_builder : public spl::enable_shared_from_this<definition_list_builder>
+{
+public:
+       virtual ~definition_list_builder() { }
+       virtual spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) { return shared_from_this(); };
+};
+
+class help_sink
+{
+public:
+       virtual ~help_sink() { }
+
+       virtual void short_description(const std::wstring& short_description) { };
+       virtual void syntax(const std::wstring& syntax) { };
+       virtual spl::shared_ptr<paragraph_builder> para() { return spl::make_shared<paragraph_builder>(); }
+       virtual spl::shared_ptr<definition_list_builder> definitions() { return spl::make_shared<definition_list_builder>(); }
+       virtual void example(const std::wstring& code, const std::wstring& caption = L"") { }
+private:
+       virtual void begin_item(const std::wstring& name) { };
+       virtual void end_item() { };
+
+       friend help_repository;
+};
+
+}}
index 1e7d31a63657bef92809f0730328f28acdbbacd0..24131efb69a40ac41cfdd3d5968eb459247b32f9 100644 (file)
@@ -65,7 +65,13 @@ public:
                foreground_->paused(true);
                is_paused_ = true;
        }
-       
+
+       void resume()
+       {
+               foreground_->paused(false);
+               is_paused_ = false;
+       }
+
        void load(spl::shared_ptr<frame_producer> producer, bool preview, const boost::optional<int32_t>& auto_play_delta)
        {               
 //             background_->unsubscribe(background_event_subject_);
@@ -187,6 +193,7 @@ void layer::swap(layer& other)
 void layer::load(spl::shared_ptr<frame_producer> frame_producer, bool preview, const boost::optional<int32_t>& auto_play_delta){return impl_->load(std::move(frame_producer), preview, auto_play_delta);}      
 void layer::play(){impl_->play();}
 void layer::pause(){impl_->pause();}
+void layer::resume(){impl_->resume();}
 void layer::stop(){impl_->stop();}
 draw_frame layer::receive(const video_format_desc& format_desc) {return impl_->receive(format_desc);}
 spl::shared_ptr<frame_producer> layer::foreground() const { return impl_->foreground_;}
index 3e9a517842e85820181abd6f135f8f42fa676d8f..f5874131d663d597dce3a20dd891c0ab7104d417 100644 (file)
@@ -57,12 +57,13 @@ public:
 
        void swap(layer& other);  
                
-       void load(spl::shared_ptr<frame_producer> producer, bool preview, const boost::optional<int32_t>& auto_play_delta = nullptr); 
-       void play(); 
-       void pause(); 
-       void stop(); 
+       void load(spl::shared_ptr<frame_producer> producer, bool preview, const boost::optional<int32_t>& auto_play_delta = nullptr);
+       void play();
+       void pause();
+       void resume();
+       void stop();
        
-       draw_frame receive(const video_format_desc& format_desc); 
+       draw_frame receive(const video_format_desc& format_desc);
        
        // monitor::observable
 
index 992554ef4f0e153f986c54e7c65f00f1c0419def..98630b53b20f74765477e0314ff903ba32ab0908 100644 (file)
@@ -222,6 +222,14 @@ public:
                }, task_priority::high_priority);
        }
 
+       std::future<void> resume(int index)
+       {
+               return executor_.begin_invoke([=]
+               {
+                       get_layer(index).resume();
+               }, task_priority::high_priority);
+       }
+
        std::future<void> play(int index)
        {               
                return executor_.begin_invoke([=]
@@ -254,7 +262,7 @@ public:
                }, task_priority::high_priority);
        }       
                
-       std::future<void> swap_layers(stage& other)
+       std::future<void> swap_layers(stage& other, bool swap_transforms)
        {
                auto other_impl = other.impl_;
 
@@ -281,7 +289,10 @@ public:
                        
                        for (auto& layer : other_layers)
                                layer.monitor_output().attach_parent(monitor_subject_);
-               };              
+
+                       if (swap_transforms)
+                               std::swap(tweens_, other_impl->tweens_);
+               };
 
                return executor_.begin_invoke([=]
                {
@@ -289,20 +300,23 @@ public:
                }, task_priority::high_priority);
        }
 
-       std::future<void> swap_layer(int index, int other_index)
+       std::future<void> swap_layer(int index, int other_index, bool swap_transforms)
        {
                return executor_.begin_invoke([=]
                {
                        std::swap(get_layer(index), get_layer(other_index));
+
+                       if (swap_transforms)
+                               std::swap(tweens_[index], tweens_[other_index]);
                }, task_priority::high_priority);
        }
 
-       std::future<void> swap_layer(int index, int other_index, stage& other)
+       std::future<void> swap_layer(int index, int other_index, stage& other, bool swap_transforms)
        {
                auto other_impl = other.impl_;
 
                if(other_impl.get() == this)
-                       return swap_layer(index, other_index);
+                       return swap_layer(index, other_index, swap_transforms);
                else
                {
                        auto func = [=]
@@ -317,6 +331,13 @@ public:
 
                                my_layer.monitor_output().attach_parent(monitor_subject_);
                                other_layer.monitor_output().attach_parent(other_impl->monitor_subject_);
+
+                               if (swap_transforms)
+                               {
+                                       auto& my_tween          = tweens_[index];
+                                       auto& other_tween       = other_impl->tweens_[other_index];
+                                       std::swap(my_tween, other_tween);
+                               }
                        };              
 
                        return executor_.begin_invoke([=]
@@ -429,13 +450,14 @@ std::future<void> stage::clear_transforms(){ return impl_->clear_transforms(); }
 std::future<frame_transform> stage::get_current_transform(int index){ return impl_->get_current_transform(index); }
 std::future<void> stage::load(int index, const spl::shared_ptr<frame_producer>& producer, bool preview, const boost::optional<int32_t>& auto_play_delta){ return impl_->load(index, producer, preview, auto_play_delta); }
 std::future<void> stage::pause(int index){ return impl_->pause(index); }
+std::future<void> stage::resume(int index){ return impl_->resume(index); }
 std::future<void> stage::play(int index){ return impl_->play(index); }
 std::future<void> stage::stop(int index){ return impl_->stop(index); }
 std::future<void> stage::clear(int index){ return impl_->clear(index); }
 std::future<void> stage::clear(){ return impl_->clear(); }
-std::future<void> stage::swap_layers(stage& other){ return impl_->swap_layers(other); }
-std::future<void> stage::swap_layer(int index, int other_index){ return impl_->swap_layer(index, other_index); }
-std::future<void> stage::swap_layer(int index, int other_index, stage& other){ return impl_->swap_layer(index, other_index, other); }
+std::future<void> stage::swap_layers(stage& other, bool swap_transforms){ return impl_->swap_layers(other, swap_transforms); }
+std::future<void> stage::swap_layer(int index, int other_index, bool swap_transforms){ return impl_->swap_layer(index, other_index, swap_transforms); }
+std::future<void> stage::swap_layer(int index, int other_index, stage& other, bool swap_transforms){ return impl_->swap_layer(index, other_index, other, swap_transforms); }
 void stage::add_layer_consumer(void* token, int layer, const spl::shared_ptr<write_frame_consumer>& layer_consumer){ impl_->add_layer_consumer(token, layer, layer_consumer); }
 void stage::remove_layer_consumer(void* token, int layer){ impl_->remove_layer_consumer(token, layer); }std::future<std::shared_ptr<frame_producer>> stage::foreground(int index) { return impl_->foreground(index); }
 std::future<std::shared_ptr<frame_producer>> stage::background(int index) { return impl_->background(index); }
index 0a2ded3990383a416a019277916ecb2a8461b51b..7ef500d194be5de926ed4f6c6b2929c04f0316d5 100644 (file)
@@ -70,14 +70,15 @@ public:
        std::future<frame_transform>    get_current_transform(int index);
        std::future<void>                               load(int index, const spl::shared_ptr<frame_producer>& producer, bool preview = false, const boost::optional<int32_t>& auto_play_delta = nullptr);
        std::future<void>                               pause(int index);
+       std::future<void>                               resume(int index);
        std::future<void>                               play(int index);
        std::future<void>                               stop(int index);
        std::future<std::wstring>               call(int index, const std::vector<std::wstring>& params);
        std::future<void>                               clear(int index);
        std::future<void>                               clear();
-       std::future<void>                               swap_layers(stage& other);
-       std::future<void>                               swap_layer(int index, int other_index);
-       std::future<void>                               swap_layer(int index, int other_index, stage& other);
+       std::future<void>                               swap_layers(stage& other, bool swap_transforms);
+       std::future<void>                               swap_layer(int index, int other_index, bool swap_transforms);
+       std::future<void>                               swap_layer(int index, int other_index, stage& other, bool swap_transforms);
 
        void                                                    add_layer_consumer(void* token, int layer, const spl::shared_ptr<write_frame_consumer>& layer_consumer);
        void                                                    remove_layer_consumer(void* token, int layer);
index c26758a5726aff73d7928eed3daee6cfd9a5fd17..100886a4315c83b77e730452fb2d525b4876337f 100644 (file)
@@ -68,7 +68,7 @@ struct system_info_provider_repository::impl
                auto found = version_providers_.find(boost::algorithm::to_lower_copy(version_name));
 
                if (found == version_providers_.end())
-                       return L"";
+                       CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No version provider with name " + version_name));
 
                return found->second();
        }
index 9c6558829fe629ee3f1ed028d24dc98cc4a0ec19..b2c6c891d490e5679866c7da3703612f4c8fb765 100644 (file)
@@ -197,7 +197,7 @@ void init(core::module_dependencies dependencies)
                        { L".ft", L".ct" },
                        [](const std::wstring& filename)
                        {
-                               return read_template_meta_info(get_absolute(env::template_folder(), filename) + L".ft");
+                               return read_template_meta_info(filename);
                        },
                        [](const spl::shared_ptr<core::frame_producer>& producer)
                        {
index 11642bd2c61bf18bd5109b5c5ca6d73a07b271d0..e9a3b156e912813fba8227eb89b87c4e9300d1c0 100644 (file)
@@ -5,6 +5,7 @@ set(SOURCES
                amcp/AMCPCommandQueue.cpp
                amcp/AMCPCommandsImpl.cpp
                amcp/AMCPProtocolStrategy.cpp
+               amcp/amcp_command_repository.cpp
 
                asio/io_service_manager.cpp
 
@@ -33,6 +34,7 @@ set(HEADERS
                amcp/AMCPCommandQueue.h
                amcp/AMCPCommandsImpl.h
                amcp/AMCPProtocolStrategy.h
+               amcp/amcp_command_repository.h
                amcp/amcp_shared.h
 
                asio/io_service_manager.h
index ebc399578fa6fc3786d160ae413aae5a6bc36cbc..d7cbf607521f13e7e6655b256ce1443252583790 100644 (file)
 
 namespace caspar { namespace protocol {
 namespace amcp {
-       
-       class AMCPCommand
-       {
-               AMCPCommand& operator=(const AMCPCommand&);
-       protected:
-               AMCPCommand(const AMCPCommand& rhs) : client_(rhs.client_), parameters_(rhs.parameters_)
-               {}
-
-       public:
-               typedef std::shared_ptr<AMCPCommand> ptr_type;
-
-               explicit AMCPCommand(IO::ClientInfoPtr client) : client_(client) {}
-               virtual ~AMCPCommand() {}
-
-               virtual bool Execute() = 0;
-               virtual int minimum_parameters() = 0;
-
-               void SendReply();
-
-               std::vector<std::wstring>& parameters() { return parameters_; }
-
-               IO::ClientInfoPtr client() { return client_; }
 
-               virtual std::wstring print() const = 0;
+       struct command_context
+       {
+               IO::ClientInfoPtr                                                                               client;
+               channel_context                                                                                 channel;
+               int                                                                                                             channel_index;
+               int                                                                                                             layer_id;
+               std::vector<channel_context>                                                    channels;
+               spl::shared_ptr<core::help_repository>                                  help_repo;
+               spl::shared_ptr<core::media_info_repository>                    media_info_repo;
+               spl::shared_ptr<core::cg_producer_registry>                             cg_registry;
+               spl::shared_ptr<core::system_info_provider_repository>  system_info_repo;
+               std::shared_ptr<core::thumbnail_generator>                              thumb_gen;
+               std::promise<bool>&                                                                             shutdown_server_now;
+               std::vector<std::wstring>                                                               parameters;
+
+               int layer_index(int default = 0) const { return layer_id == -1 ? default: layer_id; }
+
+               command_context(
+                               IO::ClientInfoPtr client,
+                               channel_context channel,
+                               int channel_index,
+                               int layer_id,
+                               std::vector<channel_context> channels,
+                               spl::shared_ptr<core::help_repository> help_repo,
+                               spl::shared_ptr<core::media_info_repository> media_info_repo,
+                               spl::shared_ptr<core::cg_producer_registry> cg_registry,
+                               spl::shared_ptr<core::system_info_provider_repository> system_info_repo,
+                               std::shared_ptr<core::thumbnail_generator> thumb_gen,
+                               std::promise<bool>& shutdown_server_now)
+                       : client(std::move(client))
+                       , channel(channel)
+                       , channel_index(channel_index)
+                       , layer_id(layer_id)
+                       , channels(std::move(channels))
+                       , help_repo(std::move(help_repo))
+                       , media_info_repo(std::move(media_info_repo))
+                       , cg_registry(std::move(cg_registry))
+                       , system_info_repo(std::move(system_info_repo))
+                       , thumb_gen(std::move(thumb_gen))
+                       , shutdown_server_now(shutdown_server_now)
+               {
+               }
+       };
 
-               void SetReplyString(const std::wstring& str){replyString_ = str;}
+       typedef std::function<std::wstring(command_context& args)> amcp_command_func;
 
+       class AMCPCommand
+       {
        private:
-               std::vector<std::wstring> parameters_;
-               IO::ClientInfoPtr client_;
-               std::wstring replyString_;
-       };
+               command_context         ctx_;
+               amcp_command_func       command_;
+               int                                     min_num_params_;
+               std::wstring            name_;
+               std::wstring            replyString_;
+       public:
+               AMCPCommand(const command_context& ctx, const amcp_command_func& command, int min_num_params, const std::wstring& name)
+                       : ctx_(ctx)
+                       , command_(command)
+                       , min_num_params_(min_num_params)
+                       , name_(name)
+               {
+               }
 
-       template<int TMinParameters>
-       class AMCPCommandBase : public AMCPCommand
-       {
-       protected:
-               explicit AMCPCommandBase(IO::ClientInfoPtr client) : AMCPCommand(client) {}
-               AMCPCommandBase(const AMCPCommandBase& rhs) : AMCPCommand(rhs) {}
-               template<int T>
-               AMCPCommandBase(const AMCPCommandBase<T>& rhs) : AMCPCommand(rhs) {}
+               typedef std::shared_ptr<AMCPCommand> ptr_type;
 
-               ~AMCPCommandBase(){}
-       public:
-               virtual bool Execute()
+               bool Execute()
                {
-                       return (parameters().size() < TMinParameters) ? false : DoExecute();
+                       SetReplyString(command_(ctx_));
+                       return true;
                }
-               virtual int minimum_parameters(){return TMinParameters;}
-       private:
-               virtual bool DoExecute() = 0;
-       };      
 
-       class AMCPChannelCommand
-       {
-       protected:
-               AMCPChannelCommand(const channel_context& ctx, unsigned int channel_index, int layer_index) : ctx_(ctx), channel_index_(channel_index), layer_index_(layer_index)
-               {}
-               AMCPChannelCommand(const AMCPChannelCommand& rhs) : ctx_(rhs.ctx_), channel_index_(rhs.channel_index_), layer_index_(rhs.layer_index_)
-               {}
+               int minimum_parameters() const
+               {
+                       return min_num_params_;
+               }
 
-               spl::shared_ptr<core::video_channel>& channel() { return ctx_.channel; }
-               spl::shared_ptr<IO::lock_container>& lock_container() { return ctx_.lock; }
+               void SendReply()
+               {
+                       if (replyString_.empty())
+                               return;
 
-               unsigned int channel_index(){return channel_index_;}
-               int layer_index(int default_ = 0) const{return layer_index_ != -1 ? layer_index_ : default_; }
+                       ctx_.client->send(std::move(replyString_));
+               }
 
-       private:
-               unsigned int channel_index_;
-               int layer_index_;
-               channel_context ctx_;
-       };
+               std::vector<std::wstring>& parameters() { return ctx_.parameters; }
 
-       class AMCPChannelsAwareCommand
-       {
-       protected:
-               AMCPChannelsAwareCommand(const std::vector<channel_context>& channels) : channels_(channels) {}
-               AMCPChannelsAwareCommand(const AMCPChannelsAwareCommand& rhs) : channels_(rhs.channels_) {}
+               IO::ClientInfoPtr client() { return ctx_.client; }
 
-               const std::vector<channel_context>& channels() { return channels_; }
-               core::frame_producer_dependencies get_dependencies(const spl::shared_ptr<core::video_channel>& channel)
+               std::wstring print() const
                {
-                       return core::frame_producer_dependencies(
-                                       channel->frame_factory(),
-                                       cpplinq::from(channels())
-                                                       .select([](channel_context c) { return c.channel; })
-                                                       .to_vector(),
-                                       channel->video_format_desc());
+                       return name_;
                }
 
-       private:
-               const std::vector<channel_context>& channels_;
-       };
-
-       template<int TMinParameters>
-       class AMCPChannelCommandBase : public AMCPChannelCommand, public AMCPCommandBase<TMinParameters>
-       {
-       public:
-               AMCPChannelCommandBase(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommand(channel, channel_index, layer_index), AMCPCommandBase<TMinParameters>(client)
-               {}
-       protected:
-               AMCPChannelCommandBase(const AMCPChannelCommandBase& rhs) : AMCPChannelCommand(rhs), AMCPCommandBase<TMinParameters>(rhs)
-               {}
-               template<int T>
-               AMCPChannelCommandBase(const AMCPChannelCommandBase<T>& rhs) : AMCPChannelCommand(rhs), AMCPCommandBase<TMinParameters>(rhs)
-               {}
+               void SetReplyString(const std::wstring& str)
+               {
+                       replyString_ = str;
+               }
        };
 }}}
index ac496d1aa2db298533d42a8fa54e12e4446273ae..8b986f2b2bfd1191d57357d4e93ee70b3dd127fa 100644 (file)
@@ -60,19 +60,30 @@ void AMCPCommandQueue::AddCommand(AMCPCommand::ptr_type pCurrentCommand)
        {
                try
                {
+                       caspar::timer timer;
+
                        try
                        {
-                               caspar::timer timer;
                                if(pCurrentCommand->Execute()) 
                                        CASPAR_LOG(debug) << "Executed command: " << pCurrentCommand->print() << " " << timer.elapsed();
                                else 
                                        CASPAR_LOG(warning) << "Failed to execute command: " << pCurrentCommand->print() << " " << timer.elapsed();
                        }
-                       catch(...)
+                       catch (file_not_found&)
+                       {
+                               CASPAR_LOG(error) << L"File not found. No match found for parameters. Check syntax.";
+                               pCurrentCommand->SetReplyString(L"404 " + pCurrentCommand->print() + L" FAILED\r\n");
+                       }
+                       catch (std::out_of_range&)
+                       {
+                               CASPAR_LOG(error) << L"Missing parameter. Check syntax.";
+                               pCurrentCommand->SetReplyString(L"402 " + pCurrentCommand->print() + L" FAILED\r\n");
+                       }
+                       catch (...)
                        {
                                CASPAR_LOG_CURRENT_EXCEPTION();
-                               CASPAR_LOG(error) << "Failed to execute command:" << pCurrentCommand->print();
-                               pCurrentCommand->SetReplyString(L"500 FAILED\r\n");
+                               CASPAR_LOG(warning) << "Failed to execute command:" << pCurrentCommand->print() << " " << timer.elapsed();
+                               pCurrentCommand->SetReplyString(L"501 " + pCurrentCommand->print() + L" FAILED\r\n");
                        }
                                
                        pCurrentCommand->SendReply();
index 0e42dd5df5c8e5567ea0b565352229e11ad7efbc..f1fb2c63c522f089d54992ae8e6fed41c508b472 100644 (file)
@@ -24,6 +24,7 @@
 #include "AMCPCommand.h"
 
 #include <common/executor.h>
+#include <common/memory.h>
 
 #include <tbb/mutex.h>
 
@@ -34,7 +35,7 @@ class AMCPCommandQueue
        AMCPCommandQueue(const AMCPCommandQueue&);
        AMCPCommandQueue& operator=(const AMCPCommandQueue&);
 public:
-       typedef std::shared_ptr<AMCPCommandQueue> ptr_type;
+       typedef spl::shared_ptr<AMCPCommandQueue> ptr_type;
 
        AMCPCommandQueue();
        ~AMCPCommandQueue();
index f778fd3a48fa9a7ec2fc86a94ff47d7756681735..8a07dfd3d26802673a49811bc375295f23c576c8 100644 (file)
@@ -26,7 +26,8 @@
 #endif
 
 #include "AMCPCommandsImpl.h"
-#include "AMCPProtocolStrategy.h"
+
+#include "amcp_command_repository.h"
 
 #include <common/env.h>
 
 #include <common/os/filesystem.h>
 #include <common/base64.h>
 
+#include <core/producer/cg_proxy.h>
 #include <core/producer/frame_producer.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
 #include <core/video_format.h>
 #include <core/producer/transition/transition_producer.h>
 #include <core/frame/frame_transform.h>
 600 [command] FAILED   [command] not implemented
 */
 
-namespace caspar { namespace protocol {
+namespace caspar { namespace protocol { namespace amcp {
 
 using namespace core;
 
@@ -267,963 +271,663 @@ std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg
        return replyString.str();
 }
 
-namespace amcp {
-       
-void AMCPCommand::SendReply()
+core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
 {
-       if(replyString_.empty())
-               return;
+       return core::frame_producer_dependencies(
+                       channel->frame_factory(),
+                       cpplinq::from(ctx.channels)
+                                       .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
+                                       .to_vector(),
+                       channel->video_format_desc());
+}
+
+// Basic Commands
 
-       client_->send(std::move(replyString_));
+void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+       sink.short_description(L"Load a media file or resource in the background.");
+       sink.syntax(LR"(LOADBG [channel:int]{-[layer:int]} [clip:string] {[loop:LOOP]} {[transition:CUT,MIX,PUSH,WIPE,SLIDE] [duration:int] {[tween:string]|linear} {[direction:LEFT,RIGHT]|RIGHT}|CUT 0} {SEEK [frame:int]} {LENGTH [frames:int]} {FILTER [filter:string]} {[auto:AUTO]})");
+       sink.para()
+               ->text(L"Loads a producer in the background and prepares it for playout. ")
+               ->text(L"If no layer is specified the default layer index will be used.");
+       sink.para()
+               ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
+               ->text(L"If a successfully match is found, the producer will be loaded into the background.");
+       sink.para()
+               ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
+               ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
+       sink.para()
+               ->code(L"loop")->text(L" will cause the clip to loop.");
+       sink.para()
+               ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
+       sink.para()
+               ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
+       sink.para()
+               ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
+               ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
+       sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
+       sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
+       sink.example(L">> LOADBG 1-0 MY_FILE");
+       sink.example(
+                       L">> PLAY 1-1 MY_FILE\n"
+                       L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
+                       L"To automatically fade out a layer after a video file has been played to the end");
+       sink.para()
+               ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
+       sink.para()
+               ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
+               ->code(L"filter")->text(L" command.");
 }
 
-bool DiagnosticsCommand::DoExecute()
-{      
-       try
-       {
-               core::diagnostics::osd::show_graphs(true);
+std::wstring loadbg_command(command_context& ctx)
+{
+       transition_info transitionInfo;
 
-               SetReplyString(L"202 DIAG OK\r\n");
+       // TRANSITION
 
-               return true;
-       }
-       catch(...)
+       std::wstring message;
+       for (size_t n = 0; n < ctx.parameters.size(); ++n)
+               message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
+
+       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))
        {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 DIAG FAILED\r\n");
-               return false;
+               auto transition = what["TRANSITION"].str();
+               transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
+               auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
+               auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
+               transitionInfo.tweener = tween;
+
+               if (transition == L"CUT")
+                       transitionInfo.type = transition_type::cut;
+               else if (transition == L"MIX")
+                       transitionInfo.type = transition_type::mix;
+               else if (transition == L"PUSH")
+                       transitionInfo.type = transition_type::push;
+               else if (transition == L"SLIDE")
+                       transitionInfo.type = transition_type::slide;
+               else if (transition == L"WIPE")
+                       transitionInfo.type = transition_type::wipe;
+
+               if (direction == L"FROMLEFT")
+                       transitionInfo.direction = transition_direction::from_left;
+               else if (direction == L"FROMRIGHT")
+                       transitionInfo.direction = transition_direction::from_right;
+               else if (direction == L"LEFT")
+                       transitionInfo.direction = transition_direction::from_right;
+               else if (direction == L"RIGHT")
+                       transitionInfo.direction = transition_direction::from_left;
        }
-}
 
-bool ChannelGridCommand::DoExecute()
-{
-       int index = 1;
-       auto self = channels().back().channel;
-       
+       //Perform loading of the clip
        core::diagnostics::scoped_call_context save;
-       core::diagnostics::call_context::for_thread().video_channel = channels().size();
+       core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
+       core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
 
-       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());
+       auto channel = ctx.channel.channel;
+       auto pFP = create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
 
-       self->output().add(screen);
+       if (pFP == frame_producer::empty())
+               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
 
-       for (auto& channel : channels())
-       {
-               if(channel.channel != self)
-               {
-                       core::diagnostics::call_context::for_thread().layer = index;
-                       auto producer = create_producer(get_dependencies(channel.channel), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
-                       self->stage().load(index, producer, false);
-                       self->stage().play(index);
-                       index++;
-               }
-       }
+       bool auto_play = contains_param(L"AUTO", ctx.parameters);
 
-       MixerCommand mixer(client(), channels().back(), self->index(), -1);
-       auto num_channels = channels().size() - 1;
-       int square_side_length = std::ceil(std::sqrt(num_channels));
-       mixer.parameters().push_back(L"GRID");
-       mixer.parameters().push_back(boost::lexical_cast<std::wstring>(square_side_length));
-       mixer.Execute();
+       auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
+       if (auto_play)
+               channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
+       else
+               channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
 
-       return true;
+       return L"202 LOADBG OK\r\n";
 }
 
-bool CallCommand::DoExecute()
-{      
-       //Perform loading of the clip
-       try
-       {
-               auto result = channel()->stage().call(layer_index(), parameters());
-               
-               // TODO: because of std::async deferred timed waiting does not work
+void load_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Load a media file or resource to the foreground.");
+       sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
+       sink.para()
+               ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
+               ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> LOAD 1 MY_FILE");
+       sink.example(L">> LOAD 1-1 MY_FILE");
+       sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
+}
 
-               /*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 << L"202 CALL OK\r\n";
-               else
-                       replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
-               
-               SetReplyString(replyString.str());
+std::wstring load_command(command_context& ctx)
+{
+       core::diagnostics::scoped_call_context save;
+       core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
+       core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
+       auto pFP = create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
+       ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
 
-               return true;
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 CALL FAILED\r\n");
-               return false;
-       }
+       return L"202 LOAD OK\r\n";
 }
 
-tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms;
-
-core::frame_transform MixerCommand::get_current_transform()
+void play_describer(core::help_sink& sink, const core::help_repository& repository)
 {
-       return channel()->stage().get_current_transform(layer_index()).get();
+       sink.short_description(L"Play a media file or resource.");
+       sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
+       sink.para()
+               ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
+               ->text(L") is prepared, it will be executed.");
+       sink.para()
+               ->text(L"If additional parameters (see ")->see(L"LOADBG")
+               ->text(L") are provided then the provided clip will first be loaded to the background.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
+       sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
+       sink.example(L">> PLAY 1-0 MY_FILE");
+       sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
 }
 
-bool MixerCommand::DoExecute()
-{      
-       //Perform loading of the clip
-       try
-       {       
-               bool defer = boost::iequals(parameters().back(), L"DEFER");
-               if(defer)
-                       parameters().pop_back();
+std::wstring play_command(command_context& ctx)
+{
+       if (!ctx.parameters.empty())
+               loadbg_command(ctx);
 
-               std::vector<stage::transform_tuple_t> transforms;
+       ctx.channel.channel->stage().play(ctx.layer_index());
 
-               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; });
+       return L"202 PLAY OK\r\n";
+}
 
-                       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.is_key = value;
-                               return transform;                                       
-                       }, 0, L"linear"));
-               }
-               else if(boost::iequals(parameters()[0], L"OPACITY"))
-               {
-                       if (parameters().size() == 1)
-                               return reply_value([](const frame_transform& t) { return t.image_transform.opacity; });
+void pause_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Pause playback of a layer.");
+       sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
+       sink.para()
+               ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
+               ->text(L" command can be used to resume playback again.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> PAUSE 1");
+       sink.example(L">> PAUSE 1-1");
+}
 
-                       int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
-                       std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
+std::wstring pause_command(command_context& ctx)
+{
+       ctx.channel.channel->stage().pause(ctx.layer_index());
+       return L"202 PAUSE OK\r\n";
+}
 
-                       double value = boost::lexical_cast<double>(parameters().at(1));
-                       
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.opacity = value;
-                               return transform;                                       
-                       }, duration, tween));
-               }
-               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;
-                       }
+void resume_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Resume playback of a layer.");
+       sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
+       sink.para()
+               ->text(L"Resumes playback of a foreground clip previously paused with the ")
+               ->see(L"PAUSE")->text(L" command.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> RESUME 1");
+       sink.example(L">> RESUME 1-1");
+}
 
-                       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));
+std::wstring resume_command(command_context& ctx)
+{
+       ctx.channel.channel->stage().resume(ctx.layer_index());
+       return L"202 RESUME OK\r\n";
+}
 
-                       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;
-                       }
+void stop_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Remove the foreground clip of a layer.");
+       sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
+       sink.para()
+               ->text(L"Removes the foreground clip of the specified layer.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> STOP 1");
+       sink.example(L">> STOP 1-1");
+}
 
-                       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));
-                       double y        = boost::lexical_cast<double>(parameters().at(2));
-                       double x_s      = boost::lexical_cast<double>(parameters().at(3));
-                       double y_s      = boost::lexical_cast<double>(parameters().at(4));
+std::wstring stop_command(command_context& ctx)
+{
+       ctx.channel.channel->stage().stop(ctx.layer_index());
+       return L"202 STOP OK\r\n";
+}
 
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) mutable -> frame_transform
-                       {
-                               transform.image_transform.fill_translation[0]   = x;
-                               transform.image_transform.fill_translation[1]   = y;
-                               transform.image_transform.fill_scale[0]                 = x_s;
-                               transform.image_transform.fill_scale[1]                 = y_s;
-                               return transform;
-                       }, duration, tween));
-               }
-               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;
-                       }
+void clear_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Remove all clips of a layer or an entire channel.");
+       sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
+       sink.para()
+               ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
+               ->text(L"If no layer is specified then all layers in the specified ")
+               ->code(L"video_channel")->text(L" are cleared.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
+       sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
+}
 
-                       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));
-                       double y        = boost::lexical_cast<double>(parameters().at(2));
-                       double x_s      = boost::lexical_cast<double>(parameters().at(3));
-                       double y_s      = boost::lexical_cast<double>(parameters().at(4));
+std::wstring clear_command(command_context& ctx)
+{
+       int index = ctx.layer_index(std::numeric_limits<int>::min());
+       if (index != std::numeric_limits<int>::min())
+               ctx.channel.channel->stage().clear(index);
+       else
+               ctx.channel.channel->stage().clear();
 
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.clip_translation[0]   = x;
-                               transform.image_transform.clip_translation[1]   = y;
-                               transform.image_transform.clip_scale[0]                 = x_s;
-                               transform.image_transform.clip_scale[1]                 = y_s;
-                               return transform;
-                       }, duration, tween));
-               }
-               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;
-                       }
+       return L"202 CLEAR OK\r\n";
+}
 
-                       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));
+void call_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Call a method on a producer.");
+       sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
+       sink.para()
+               ->text(L"Calls method on the specified producer with the provided ")
+               ->code(L"param")->text(L" string.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> CALL 1 LOOP");
+       sink.example(L">> CALL 1-2 SEEK 25");
+}
 
-                       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;
-                       }
+std::wstring call_command(command_context& ctx)
+{
+       auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
 
-                       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";
-                       int n = boost::lexical_cast<int>(parameters().at(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;
-                                       transforms.push_back(stage::transform_tuple_t(index, [=](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;
-                                       }, duration, tween));
-                               }
-                       }
-               }
-               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; });
+       // TODO: because of std::async deferred timed waiting does not work
 
-                       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)
-                               return reply_value([](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
+       /*auto wait_res = result.wait_for(std::chrono::seconds(2));
+       if (wait_res == std::future_status::timeout)
+       CASPAR_THROW_EXCEPTION(timed_out());*/
 
-                       auto value = get_blend_mode(parameters().at(1));
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.blend_mode = value;
-                               return transform;
-                       }, 0, L"linear"));
-               }
-               else if (boost::iequals(parameters()[0], L"CHROMA"))
-               {
-                       if (parameters().size() == 1)
-                       {
-                               auto chroma = get_current_transform().image_transform.chroma;
-                               SetReplyString(
-                                       L"201 MIXER OK\r\n"
-                                       + core::get_chroma_mode(chroma.key) + L" "
-                                       + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
-                                       + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
-                                       + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n");
-                               return true;
-                       }
+       std::wstringstream replyString;
+       if (result.get().empty())
+               replyString << L"202 CALL OK\r\n";
+       else
+               replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
 
-                       int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters().at(5)) : 0;
-                       std::wstring tween = parameters().size() > 6 ? parameters().at(6) : L"linear";
+       return replyString.str();
+}
 
-                       core::chroma chroma;
-                       chroma.key                      = get_chroma_mode(parameters().at(1));
-                       chroma.threshold        = boost::lexical_cast<double>(parameters().at(2));
-                       chroma.softness         = boost::lexical_cast<double>(parameters().at(3));
-                       chroma.spill            = parameters().size() > 4 ? boost::lexical_cast<double>(parameters().at(4)) : 0.0;
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.chroma = chroma;
-                               return transform;
-                       }, duration, tween));
-               }
-               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;
-                       }
+void swap_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Swap layers between channels.");
+       sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
+       sink.para()
+               ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
+               ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
+       sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> SWAP 1 2");
+       sink.example(L">> SWAP 1-1 2-3");
+       sink.example(L">> SWAP 1-1 1-2");
+       sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
+}
 
-                       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; });
+std::wstring swap_command(command_context& ctx)
+{
+       bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
 
-                       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";
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.brightness = value;
-                               return transform;
-                       }, duration, tween));
-               }
-               else if(boost::iequals(parameters()[0], L"SATURATION"))
-               {
-                       if (parameters().size() == 1)
-                               return reply_value([](const frame_transform& t) { return t.image_transform.saturation; });
+       if (ctx.layer_index(-1) != -1)
+       {
+               std::vector<std::string> strs;
+               boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
 
-                       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";
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.saturation = value;
-                               return transform;
-                       }, duration, tween));   
-               }
-               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 ch1 = ctx.channel.channel;
+               auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
 
-                       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";
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.contrast = value;
-                               return transform;
-                       }, duration, tween));   
-               }
-               else if (boost::iequals(parameters()[0], L"ROTATION"))
-               {
-                       static const double PI = 3.141592653589793;
+               int l1 = ctx.layer_index();
+               int l2 = boost::lexical_cast<int>(strs.at(1));
 
-                       if (parameters().size() == 1)
-                               return reply_value([](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; });
+               ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
+       }
+       else
+       {
+               auto ch1 = ctx.channel.channel;
+               auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
+               ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
+       }
 
-                       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;
-                       }
+       return L"202 SWAP OK\r\n";
+}
 
-                       levels value;
-                       value.min_input  = boost::lexical_cast<double>(parameters().at(1));
-                       value.max_input  = boost::lexical_cast<double>(parameters().at(2));
-                       value.gamma              = boost::lexical_cast<double>(parameters().at(3));
-                       value.min_output = boost::lexical_cast<double>(parameters().at(4));
-                       value.max_output = boost::lexical_cast<double>(parameters().at(5));
-                       int duration = parameters().size() > 6 ? boost::lexical_cast<int>(parameters()[6]) : 0;
-                       std::wstring tween = parameters().size() > 7 ? parameters()[7] : L"linear";
+void add_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Add a consumer to a video channel.");
+       sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
+       sink.para()
+               ->text(L"Adds a consumer to the specified video channel. The string ")
+               ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
+               ->text(L"If a successful match is found a consumer will be created and added to the ")
+               ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
+               ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
+               ->see(L"the CasparCG config file")->text(L".");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> ADD 1 DECKLINK 1");
+       sink.example(L">> ADD 1 BLUEFISH 2");
+       sink.example(L">> ADD 1 SCREEN");
+       sink.example(L">> ADD 1 AUDIO");
+       sink.example(L">> ADD 1 IMAGE filename");
+       sink.example(L">> ADD 1 FILE filename.mov");
+       sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
+       sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
+       sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
+}
 
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.image_transform.levels = value;
-                               return transform;
-                       }, duration, tween));
-               }
-               else if(boost::iequals(parameters()[0], L"VOLUME"))
-               {
-                       if (parameters().size() == 1)
-                               return reply_value([](const frame_transform& t) { return t.audio_transform.volume; });
+std::wstring add_command(command_context& ctx)
+{
+       replace_placeholders(
+                       L"<CLIENT_IP_ADDRESS>",
+                       ctx.client->address(),
+                       ctx.parameters);
 
-                       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]);
+       core::diagnostics::scoped_call_context save;
+       core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
 
-                       transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
-                       {
-                               transform.audio_transform.volume = value;
-                               return transform;
-                       }, duration, tween));
-               }
-               else if(boost::iequals(parameters()[0], L"CLEAR"))
-               {
-                       int layer = layer_index(std::numeric_limits<int>::max());
+       auto consumer = create_consumer(ctx.parameters, &ctx.channel.channel->stage());
+       ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
 
-                       if (layer == std::numeric_limits<int>::max())
-                       {
-                               channel()->stage().clear_transforms();
-                       }
-                       else
-                       {
-                               channel()->stage().clear_transforms(layer);
-                       }
-               }
-               else if(boost::iequals(parameters()[0], L"COMMIT"))
-               {
-                       transforms = std::move(deferred_transforms[channel_index()]);
-               }
-               else
-               {
-                       SetReplyString(L"404 MIXER ERROR\r\n");
-                       return false;
-               }
+       return L"202 ADD OK\r\n";
+}
 
-               if(defer)
-               {
-                       auto& defer_tranforms = deferred_transforms[channel_index()];
-                       defer_tranforms.insert(defer_tranforms.end(), transforms.begin(), transforms.end());
-               }
-               else
-                       channel()->stage().apply_transforms(transforms);
-       
-               SetReplyString(L"202 MIXER OK\r\n");
+void remove_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Remove a consumer from a video channel.");
+       sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
+       sink.para()
+               ->text(L"Removes an existing consumer from ")->code(L"video_channel")
+               ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
+               ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> REMOVE 1 DECKLINK 1");
+       sink.example(L">> REMOVE 1 BLUEFISH 2");
+       sink.example(L">> REMOVE 1 SCREEN");
+       sink.example(L">> REMOVE 1 AUDIO");
+       sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
+}
 
-               return true;
-       }
-       catch(file_not_found&)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"404 MIXER ERROR\r\n");
-               return false;
-       }
-       catch(...)
+std::wstring remove_command(command_context& ctx)
+{
+       auto index = ctx.layer_index(std::numeric_limits<int>::min());
+       
+       if (index == std::numeric_limits<int>::min())
        {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 MIXER FAILED\r\n");
-               return false;
+               replace_placeholders(
+                               L"<CLIENT_IP_ADDRESS>",
+                               ctx.client->address(),
+                               ctx.parameters);
+
+               index = create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
        }
-}
 
-bool SwapCommand::DoExecute()
-{      
-       //Perform loading of the clip
-       try
-       {
-               if(layer_index(-1) != -1)
-               {
-                       std::vector<std::string> strs;
-                       boost::split(strs, parameters()[0], boost::is_any_of("-"));
-                       
-                       auto ch1 = channel();
-                       auto ch2 = channels().at(boost::lexical_cast<int>(strs.at(0))-1);
+       ctx.channel.channel->output().remove(index);
 
-                       int l1 = layer_index();
-                       int l2 = boost::lexical_cast<int>(strs.at(1));
+       return L"202 REMOVE OK\r\n";
+}
 
-                       ch1->stage().swap_layer(l1, l2, ch2.channel->stage());
-               }
-               else
-               {
-                       auto ch1 = channel();
-                       auto ch2 = channels().at(boost::lexical_cast<int>(parameters()[0])-1);
-                       ch1->stage().swap_layers(ch2.channel->stage());
-               }
-               
-               SetReplyString(L"202 SWAP OK\r\n");
+void print_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Take a snapshot of a channel.");
+       sink.syntax(L"PRINT [video_channel:int]");
+       sink.para()
+               ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
+               ->code(L"media")->text(L" folder.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
+}
 
-               return true;
-       }
-       catch(file_not_found&)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"404 SWAP ERROR\r\n");
-               return false;
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 SWAP FAILED\r\n");
-               return false;
-       }
+std::wstring print_command(command_context& ctx)
+{
+       ctx.channel.channel->output().add(create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
+
+       return L"202 PRINT OK\r\n";
 }
 
-bool AddCommand::DoExecute()
-{      
-       //Perform loading of the clip
-       try
-       {
-               replace_placeholders(
-                               L"<CLIENT_IP_ADDRESS>",
-                               this->client()->address(),
-                               parameters());
+void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the log level of the server.");
+       sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
+       sink.para()->text(L"Changes the log level of the server.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> LOG LEVEL trace");
+       sink.example(L">> LOG LEVEL info");
+}
 
-               core::diagnostics::scoped_call_context save;
-               core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
+std::wstring log_level_command(command_context& ctx)
+{
+       log::set_log_level(ctx.parameters.at(0));
 
-               auto consumer = create_consumer(parameters(), &channel()->stage());
-               channel()->output().add(layer_index(consumer->index()), consumer);
-       
-               SetReplyString(L"202 ADD OK\r\n");
+       return L"202 LOG OK\r\n";
+}
 
-               return true;
-       }
-       catch(file_not_found&)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"404 ADD ERROR\r\n");
-               return false;
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 ADD FAILED\r\n");
-               return false;
-       }
+void set_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the value of a channel variable.");
+       sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
+       sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
+       sink.definitions()
+               ->item(L"MODE", L"Changes the video format of the channel.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL");
 }
 
-bool RemoveCommand::DoExecute()
-{      
-       //Perform loading of the clip
-       try
+std::wstring set_command(command_context& ctx)
+{
+       std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
+       std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
+
+       if (name == L"MODE")
        {
-               auto index = layer_index(std::numeric_limits<int>::min());
-               if(index == std::numeric_limits<int>::min())
+               auto format_desc = core::video_format_desc(value);
+               if (format_desc.format != core::video_format::invalid)
                {
-                       replace_placeholders(
-                                       L"<CLIENT_IP_ADDRESS>",
-                                       this->client()->address(),
-                                       parameters());
-
-                       index = create_consumer(parameters(), &channel()->stage())->index();
+                       ctx.channel.channel->video_format_desc(format_desc);
+                       return L"202 SET MODE OK\r\n";
                }
 
-               channel()->output().remove(index);
-
-               SetReplyString(L"202 REMOVE OK\r\n");
-
-               return true;
-       }
-       catch(file_not_found&)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"404 REMOVE ERROR\r\n");
-               return false;
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 REMOVE FAILED\r\n");
-               return false;
+               CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
        }
-}
 
-bool LoadCommand::DoExecute()
-{      
-       //Perform loading of the clip
-       try
-       {
-               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(get_dependencies(channel()), parameters());
-               channel()->stage().load(layer_index(), pFP, true);
-       
-               SetReplyString(L"202 LOAD OK\r\n");
+       CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
+}
 
-               return true;
-       }
-       catch(file_not_found&)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"404 LOAD ERROR\r\n");
-               return false;
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 LOAD FAILED\r\n");
-               return false;
-       }
+void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Store a dataset.");
+       sink.syntax(L"DATA STORE [name:string] [data:string]");
+       sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
+       sink.para()->text(L"Directories will be created if they do not exist.");
+       sink.para()->text(L"Examples:");
+       sink.example(LR"(>> DATA STORE my_data "Some useful data")");
+       sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
 }
 
-bool LoadbgCommand::DoExecute()
+std::wstring data_store_command(command_context& ctx)
 {
-       transition_info transitionInfo;
-       
-       // TRANSITION
+       std::wstring filename = env::data_folder();
+       filename.append(ctx.parameters[0]);
+       filename.append(L".ftd");
 
-       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(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))
-       {
-               auto transition = what["TRANSITION"].str();
-               transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
-               auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
-               auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
-               transitionInfo.tweener = tween;         
+       auto data_path = boost::filesystem::path(filename).parent_path().wstring();
+       auto found_data_path = find_case_insensitive(data_path);
 
-               if(transition == L"CUT")
-                       transitionInfo.type = transition_type::cut;
-               else if(transition == L"MIX")
-                       transitionInfo.type = transition_type::mix;
-               else if(transition == L"PUSH")
-                       transitionInfo.type = transition_type::push;
-               else if(transition == L"SLIDE")
-                       transitionInfo.type = transition_type::slide;
-               else if(transition == L"WIPE")
-                       transitionInfo.type = transition_type::wipe;
-               
-               if(direction == L"FROMLEFT")
-                       transitionInfo.direction = transition_direction::from_left;
-               else if(direction == L"FROMRIGHT")
-                       transitionInfo.direction = transition_direction::from_right;
-               else if(direction == L"LEFT")
-                       transitionInfo.direction = transition_direction::from_right;
-               else if(direction == L"RIGHT")
-                       transitionInfo.direction = transition_direction::from_left;
-       }
-       
-       //Perform loading of the clip
-       try
-       {
-               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();
+       if (found_data_path)
+               data_path = *found_data_path;
 
-               auto pFP = create_producer(get_dependencies(channel()), parameters());
-               
-               if(pFP == frame_producer::empty())
-                       CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(parameters().size() > 0 ? parameters()[0] : L""));
+       if (!boost::filesystem::exists(data_path))
+               boost::filesystem::create_directories(data_path);
 
-               bool auto_play = contains_param(L"AUTO", parameters());
+       auto found_filename = find_case_insensitive(filename);
 
-               auto pFP2 = create_transition_producer(channel()->video_format_desc().field_mode, pFP, transitionInfo);
-               if(auto_play)
-                       channel()->stage().load(layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
-               else
-                       channel()->stage().load(layer_index(), pFP2, false); // TODO: LOOP
-       
-               
-               SetReplyString(L"202 LOADBG OK\r\n");
+       if (found_filename)
+               filename = *found_filename; // Overwrite case insensitive.
 
-               return true;
-       }
-       catch(file_not_found&)
-       {               
-               CASPAR_LOG(error) << L"File not found. No match found for parameters. Check syntax.";
-               SetReplyString(L"404 LOADBG ERROR\r\n");
-               return false;
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"502 LOADBG FAILED\r\n");
-               return false;
-       }
+       boost::filesystem::wofstream datafile(filename);
+       if (!datafile)
+               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
+
+       datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
+       datafile << ctx.parameters[1] << std::flush;
+       datafile.close();
+
+       return L"202 DATA STORE OK\r\n";
 }
 
-bool PauseCommand::DoExecute()
+void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       try
-       {
-               channel()->stage().pause(layer_index());
-               SetReplyString(L"202 PAUSE OK\r\n");
-               return true;
-       }
-       catch(...)
-       {
-               SetReplyString(L"501 PAUSE FAILED\r\n");
-       }
-
-       return false;
+       sink.short_description(L"Retrieve a dataset.");
+       sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
+       sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> DATA RETRIEVE my_data");
+       sink.example(L">> DATA RETRIEVE Folder1/my_data");
 }
 
-bool PlayCommand::DoExecute()
+std::wstring data_retrieve_command(command_context& ctx)
 {
-       try
-       {
-               if(!parameters().empty())
-               {
-                       LoadbgCommand lbg(*this);
+       std::wstring filename = env::data_folder();
+       filename.append(ctx.parameters[0]);
+       filename.append(L".ftd");
 
-                       if(!lbg.Execute())
-                               throw std::exception();
-               }
+       std::wstring file_contents;
 
-               channel()->stage().play(layer_index());
-               
-               SetReplyString(L"202 PLAY OK\r\n");
-               return true;
-       }
-       catch(...)
-       {
-               SetReplyString(L"501 PLAY FAILED\r\n");
-       }
+       auto found_file = find_case_insensitive(filename);
 
-       return false;
-}
+       if (found_file)
+               file_contents = read_file(boost::filesystem::path(*found_file));
 
-bool StopCommand::DoExecute()
-{
-       try
-       {
-               channel()->stage().stop(layer_index());
-               SetReplyString(L"202 STOP OK\r\n");
-               return true;
-       }
-       catch(...)
+       if (file_contents.empty())
+               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
+
+       std::wstringstream reply;
+       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))
        {
-               SetReplyString(L"501 STOP FAILED\r\n");
+               if (firstLine)
+                       firstLine = false;
+               else
+                       reply << "\n";
+
+               reply << line;
        }
 
-       return false;
+       reply << "\r\n";
+       return reply.str();
 }
 
-bool ClearCommand::DoExecute()
+void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       int index = layer_index(std::numeric_limits<int>::min());
-       if(index != std::numeric_limits<int>::min())
-               channel()->stage().clear(index);
-       else
-               channel()->stage().clear();
-               
-       SetReplyString(L"202 CLEAR OK\r\n");
-
-       return true;
+       sink.short_description(L"List stored datasets.");
+       sink.syntax(L"DATA LIST");
+       sink.para()->text(L"Returns a list of all stored datasets.");
 }
 
-bool PrintCommand::DoExecute()
+std::wstring data_list_command(command_context& ctx)
 {
-       channel()->output().add(create_consumer({ L"IMAGE" }, &channel()->stage()));
-               
-       SetReplyString(L"202 PRINT OK\r\n");
+       std::wstringstream replyString;
+       replyString << L"200 DATA LIST OK\r\n";
 
-       return true;
-}
+       for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
+       {
+               if (boost::filesystem::is_regular_file(itr->path()))
+               {
+                       if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
+                               continue;
 
-bool LogCommand::DoExecute()
-{
-       if(boost::iequals(parameters().at(0), L"LEVEL"))
-               log::set_log_level(parameters().at(1));
+                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
 
-       SetReplyString(L"202 LOG OK\r\n");
+                       auto str = relativePath.replace_extension(L"").generic_wstring();
+                       if (str[0] == L'\\' || str[0] == L'/')
+                               str = std::wstring(str.begin() + 1, str.end());
 
-       return true;
+                       replyString << str << L"\r\n";
+               }
+       }
+
+       replyString << L"\r\n";
+
+       return boost::to_upper_copy(replyString.str());
 }
 
-bool CGCommand::DoExecute()
+void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       try
-       {
-               std::wstring command = boost::to_upper_copy(parameters()[0]);
-               if(command == L"ADD")
-                       return DoExecuteAdd();
-               else if(command == L"PLAY")
-                       return DoExecutePlay();
-               else if(command == L"STOP")
-                       return DoExecuteStop();
-               else if(command == L"NEXT")
-                       return DoExecuteNext();
-               else if(command == L"REMOVE")
-                       return DoExecuteRemove();
-               else if(command == L"CLEAR")
-                       return DoExecuteClear();
-               else if(command == L"UPDATE")
-                       return DoExecuteUpdate();
-               else if(command == L"INVOKE")
-                       return DoExecuteInvoke();
-               else if(command == L"INFO")
-                       return DoExecuteInfo();
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-       }
+       sink.short_description(L"Remove a stored dataset.");
+       sink.syntax(L"DATA REMOVE [name:string]");
+       sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> DATA REMOVE my_data");
+       sink.example(L">> DATA REMOVE Folder1/my_data");
+}
+
+std::wstring data_remove_command(command_context& ctx)
+{
+       std::wstring filename = env::data_folder();
+       filename.append(ctx.parameters[0]);
+       filename.append(L".ftd");
+
+       if (!boost::filesystem::exists(filename))
+               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
+
+       if (!boost::filesystem::remove(filename))
+               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
 
-       SetReplyString(L"403 CG ERROR\r\n");
-       return false;
+       return L"201 DATA REMOVE OK\r\n";
 }
 
-bool CGCommand::ValidateLayer(const std::wstring& layerstring) {
+// Template Graphics Commands
+
+int get_and_validate_layer(const std::wstring& layerstring) {
        int length = layerstring.length();
-       for(int i = 0; i < length; ++i) {
-               if(!std::isdigit(layerstring[i])) {
-                       return false;
+       for (int i = 0; i < length; ++i) {
+               if (!std::isdigit(layerstring[i])) {
+                       CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
                }
        }
 
-       return true;
+       return boost::lexical_cast<int>(layerstring);
 }
 
-bool CGCommand::DoExecuteAdd() {
-       //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
-
-       int layer = 0;                          //_parameters[1]
-//     std::wstring templateName;      //_parameters[2]
-       std::wstring label;             //_parameters[3]
-       bool bDoStart = false;          //_parameters[3] alt. _parameters[4]
-//     std::wstring data;                      //_parameters[4] alt. _parameters[5]
-
-       if(parameters().size() < 4) 
-       {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return false;
-       }
-       unsigned int dataIndex = 4;
+void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Prepare a template for displaying.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
+       sink.para()
+               ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
+               ->text(L" (unless you supply the play-on-load flag, 1 for true). Data is either inline XML or a reference to a saved dataset.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"CG 1 ADD 10 svtnews/info 1");
+}
 
-       if(!ValidateLayer(parameters()[1])) 
-       {
-               SetReplyString(L"403 CG ERROR\r\n");
-               return false;
-       }
+std::wstring cg_add_command(command_context& ctx)
+{
+       //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
 
-       layer = boost::lexical_cast<int>(parameters()[1]);
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       std::wstring label;             //_parameters[2]
+       bool bDoStart = false;          //_parameters[2] alt. _parameters[3]
+       unsigned int dataIndex = 3;
 
-       if(parameters()[3].length() > 1) 
+       if (ctx.parameters.at(2).length() > 1)
        {       //read label
-               label = parameters()[3];
+               label = ctx.parameters.at(2);
                ++dataIndex;
 
-               if(parameters().size() > 4 && parameters()[4].length() > 0)     //read play-on-load-flag
-                       bDoStart = (parameters()[4][0]==L'1') ? true : false;
-               else 
-               {
-                       SetReplyString(L"402 CG ERROR\r\n");
-                       return false;
-               }
+               if (ctx.parameters.at(3).length() > 0)  //read play-on-load-flag
+                       bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
        }
-       else if(parameters()[3].length() > 0) { //read play-on-load-flag
-               bDoStart = (parameters()[3][0]==L'1') ? true : false;
-       }
-       else 
-       {
-               SetReplyString(L"403 CG ERROR\r\n");
-               return false;
+       else
+       {       //read play-on-load-flag
+               bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
        }
 
        const wchar_t* pDataString = 0;
        std::wstring dataFromFile;
-       if(parameters().size() > dataIndex) 
+       if (ctx.parameters.size() > dataIndex)
        {       //read data
-               const std::wstring& dataString = parameters()[dataIndex];
+               const std::wstring& dataString = ctx.parameters.at(dataIndex);
 
                if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
                        pDataString = dataString.c_str();
-               else 
+               else
                {
                        //The data is not an XML-string, it must be a filename
                        std::wstring filename = env::data_folder();
@@ -1240,630 +944,1694 @@ bool CGCommand::DoExecuteAdd() {
                }
        }
 
-       auto filename = parameters()[2];
-       auto proxy = cg_registry_->get_or_create_proxy(channel(), get_dependencies(channel()), layer_index(core::cg_proxy::DEFAULT_LAYER), filename);
+       auto filename = ctx.parameters.at(1);
+       auto proxy = ctx.cg_registry->get_or_create_proxy(
+               spl::make_shared_ptr(ctx.channel.channel),
+               get_producer_dependencies(ctx.channel.channel, ctx),
+               ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
+               filename);
 
        if (proxy == core::cg_proxy::empty())
-       {
-               CASPAR_LOG(warning) << "Could not find template " << parameters()[2];
-               SetReplyString(L"404 CG ERROR\r\n");
-       }
+               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
        else
-       {
                proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
-       }
 
-       return true;
+       return L"202 CG OK\r\n";
 }
 
-bool CGCommand::DoExecutePlay()
+void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       if(parameters().size() > 1)
-       {
-               if(!ValidateLayer(parameters()[1])) 
-               {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
-               }
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
-       }
-       else
-       {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return true;
-       }
-
-       SetReplyString(L"202 CG OK\r\n");
-       return true;
+       sink.short_description(L"Play and display a template.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
+       sink.para()->text(L"Plays and displays the template in the specified layer.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"CG 1 PLAY 0");
 }
 
-bool CGCommand::DoExecuteStop() 
+std::wstring cg_play_command(command_context& ctx)
 {
-       if(parameters().size() > 1)
-       {
-               if(!ValidateLayer(parameters()[1])) 
-               {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
-               }
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
-       }
-       else 
-       {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return true;
-       }
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
 
-       SetReplyString(L"202 CG OK\r\n");
-       return true;
+       return L"202 CG OK\r\n";
 }
 
-bool CGCommand::DoExecuteNext()
+void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       if(parameters().size() > 1) 
-       {
-               if(!ValidateLayer(parameters()[1])) 
-               {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
-               }
+       sink.short_description(L"Stop and remove a template.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
+       sink.para()
+               ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
+               ->text(L" in that the template gets a chance to animate out when it is stopped.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"CG 1 STOP 0");
+}
 
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
-       }
-       else 
-       {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return true;
-       }
+std::wstring cg_stop_command(command_context& ctx)
+{
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
 
-       SetReplyString(L"202 CG OK\r\n");
-       return true;
+       return L"202 CG OK\r\n";
 }
 
-bool CGCommand::DoExecuteRemove() 
+void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       if(parameters().size() > 1) 
+       sink.short_description(LR"(Trigger a "continue" in a template.)");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
+       sink.para()
+               ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
+               ->text(L"This is used to control animations that has multiple discreet steps.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"CG 1 NEXT 0");
+}
+
+std::wstring cg_next_command(command_context& ctx)
+{
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
+
+       return L"202 CG OK\r\n";
+}
+
+void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Remove a template.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
+       sink.para()->text(L"Removes the template from the specified layer.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"CG 1 REMOVE 0");
+}
+
+std::wstring cg_remove_command(command_context& ctx)
+{
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
+
+       return L"202 CG OK\r\n";
+}
+
+void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Remove all templates on a video layer.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
+       sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"CG 1 CLEAR");
+}
+
+std::wstring cg_clear_command(command_context& ctx)
+{
+       ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
+
+       return L"202 CG OK\r\n";
+}
+
+void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Update a template with new data.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
+       sink.para()->text(L"Sends new data to the template on specified layer. Data is either inline XML or a reference to a saved dataset.");
+}
+
+std::wstring cg_update_command(command_context& ctx)
+{
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+
+       std::wstring dataString = ctx.parameters.at(1);
+       if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
        {
-               if(!ValidateLayer(parameters()[1])) 
-               {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
-               }
+               //The data is not XML or Json, it must be a filename
+               std::wstring filename = env::data_folder();
+               filename.append(dataString);
+               filename.append(L".ftd");
 
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
+               dataString = read_file(boost::filesystem::path(filename));
        }
-       else 
+
+       ctx.cg_registry->get_proxy(
+               spl::make_shared_ptr(ctx.channel.channel),
+               ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
+               ->update(layer, dataString);
+
+       return L"202 CG OK\r\n";
+}
+
+void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Invoke a method/label on a template.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
+       sink.para()->text(L"Invokes the given method on the template on the specified layer.");
+       sink.para()->text(L"Can be used to jump the playhead to a specific label.");
+}
+
+std::wstring cg_invoke_command(command_context& ctx)
+{
+       std::wstringstream replyString;
+       replyString << L"201 CG OK\r\n";
+       int layer = get_and_validate_layer(ctx.parameters.at(0));
+       auto result = ctx.cg_registry->get_proxy(
+               spl::make_shared_ptr(ctx.channel.channel),
+               ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
+               ->invoke(layer, ctx.parameters.at(1));
+       replyString << result << L"\r\n";
+
+       return replyString.str();
+}
+
+void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get information about a running template or the template host.");
+       sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
+       sink.para()->text(L"Retrieves information about the template on the specified layer.");
+       sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
+}
+
+std::wstring cg_info_command(command_context& ctx)
+{
+       std::wstringstream replyString;
+       replyString << L"201 CG OK\r\n";
+
+       if (ctx.parameters.empty())
        {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return true;
+               auto info = ctx.cg_registry->get_proxy(
+                       spl::make_shared_ptr(ctx.channel.channel),
+                       ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
+                       ->template_host_info();
+               replyString << info << L"\r\n";
        }
+       else
+       {
+               int layer = get_and_validate_layer(ctx.parameters.at(0));
+               auto desc = ctx.cg_registry->get_proxy(
+                       spl::make_shared_ptr(ctx.channel.channel),
+                       ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
+                       ->description(layer);
 
-       SetReplyString(L"202 CG OK\r\n");
-       return true;
+               replyString << desc << L"\r\n";
+       }
+
+       return replyString.str();
 }
 
-bool CGCommand::DoExecuteClear() 
+// Mixer Commands
+
+core::frame_transform get_current_transform(command_context& ctx)
 {
-       channel()->stage().clear(layer_index(core::cg_proxy::DEFAULT_LAYER));
-       SetReplyString(L"202 CG OK\r\n");
-       return true;
+       return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
 }
 
-bool CGCommand::DoExecuteUpdate() 
+template<typename Func>
+std::wstring reply_value(command_context& ctx, const Func& extractor)
 {
-       try
+       auto value = extractor(get_current_transform(ctx));
+
+       return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
+}
+
+class transforms_applier
+{
+       static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
+
+       std::vector<stage::transform_tuple_t>   transforms_;
+       command_context&                                                ctx_;
+       bool                                                                    defer_;
+public:
+       transforms_applier(command_context& ctx)
+               : ctx_(ctx)
        {
-               if(!ValidateLayer(parameters().at(1)))
+               defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
+
+               if (defer_)
+                       ctx.parameters.pop_back();
+       }
+
+       void add(stage::transform_tuple_t&& transform)
+       {
+               transforms_.push_back(std::move(transform));
+       }
+
+       void commit_deferred()
+       {
+               ctx_.channel.channel->stage().apply_transforms(
+                               std::move(deferred_transforms_[ctx_.channel_index]));
+       }
+
+       void apply()
+       {
+               if (defer_)
                {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
+                       auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
+                       defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
                }
-                                               
-               std::wstring dataString = parameters().at(2);                           
-               if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
-               {
-                       //The data is not XML or Json, it must be a filename
-                       std::wstring filename = env::data_folder();
-                       filename.append(dataString);
-                       filename.append(L".ftd");
+               else
+                       ctx_.channel.channel->stage().apply_transforms(transforms_);
+       }
+};
+tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
+
+void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Let a layer act as alpha for the one obove.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
+       sink.para()
+               ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
+               ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
+               ->text(L", and hides the RGB channels of layer ")->code(L"n")
+               ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
+               ->text(L"instead it will be used as the key for the layer above.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 KEYER 1");
+       sink.example(
+               L">> MIXER 1-0 KEYER\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 1", L"to retrieve the current state");
+}
+
+std::wstring mixer_keyer_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+               return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
+
+       transforms_applier transforms(ctx);
+       bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               transform.image_transform.is_key = value;
+               return transform;
+       }, 0, L"linear"));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
 
-                       dataString = read_file(boost::filesystem::path(filename));
-               }               
+void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Enable chroma keying on a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
+       sink.example(L">> MIXER 1-1 CHROMA none");
+       sink.example(
+               L">> MIXER 1-1 BLEND\n"
+               L"<< 201 MIXER OK\n"
+               L"<< SCREEN", L"for getting the current blend mode");
+}
 
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->update(layer, dataString);
+std::wstring mixer_chroma_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto chroma = get_current_transform(ctx).image_transform.chroma;
+               return L"201 MIXER OK\r\n"
+                       + core::get_chroma_mode(chroma.key) + L" "
+                       + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
+                       + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
+                       + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
        }
-       catch(...)
+
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
+       std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
+
+       core::chroma chroma;
+       chroma.key = get_chroma_mode(ctx.parameters.at(0));
+
+       if (chroma.key != core::chroma::type::none)
        {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return true;
+               chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
+               chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
+               chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
        }
 
-       SetReplyString(L"202 CG OK\r\n");
-       return true;
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               transform.image_transform.chroma = chroma;
+               return transform;
+       }, duration, tween));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
 }
 
-bool CGCommand::DoExecuteInvoke() 
+void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       std::wstringstream replyString;
-       replyString << L"201 CG OK\r\n";
+       sink.short_description(L"Set the blend mode for a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
+       sink.para()
+               ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
+               ->text(L"If no argument is given the current blend mode is returned.");
+       sink.para()
+               ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
+               ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
+               ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
+               ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-1 BLEND OVERLAY");
+       sink.example(
+               L">> MIXER 1-1 BLEND\n"
+               L"<< 201 MIXER OK\n"
+               L"<< SCREEN", L"for getting the current blend mode");
+       sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
+}
+
+std::wstring mixer_blend_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+               return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
+
+       transforms_applier transforms(ctx);
+       auto value = get_blend_mode(ctx.parameters.at(0));
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               transform.image_transform.blend_mode = value;
+               return transform;
+       }, 0, L"linear"));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+template<typename Getter, typename Setter>
+std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
+{
+       if (ctx.parameters.empty())
+               return reply_value(ctx, getter);
+
+       transforms_applier transforms(ctx);
+       double value = boost::lexical_cast<double>(ctx.parameters.at(0));
+       int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
+       std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
+
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               setter(transform, value);
+               return transform;
+       }, duration, tween));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the opacity of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
+       sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
+       sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 OPACITY\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.5", L"to retrieve the current opacity");
+}
+
+std::wstring mixer_opacity_command(command_context& ctx)
+{
+       return single_double_animatable_mixer_command(
+                       ctx,
+                       [](const frame_transform& t) { return t.image_transform.opacity; },
+                       [](frame_transform& t, double value) { t.image_transform.opacity = value; });
+}
+
+void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the brightness of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
+       sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
+       sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 BRIGHTNESS\n"
+               L"<< 201 MIXER OK\n"
+               L"0.5", L"to retrieve the current brightness");
+}
+
+std::wstring mixer_brightness_command(command_context& ctx)
+{
+       return single_double_animatable_mixer_command(
+                       ctx,
+                       [](const frame_transform& t) { return t.image_transform.brightness; },
+                       [](frame_transform& t, double value) { t.image_transform.brightness = value; });
+}
+
+void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the saturation of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
+       sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
+       sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 SATURATION\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.5", L"to retrieve the current saturation");
+}
+
+std::wstring mixer_saturation_command(command_context& ctx)
+{
+       return single_double_animatable_mixer_command(
+                       ctx,
+                       [](const frame_transform& t) { return t.image_transform.saturation; },
+                       [](frame_transform& t, double value) { t.image_transform.saturation = value; });
+}
+
+void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the contrast of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
+       sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
+       sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 CONTRAST\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.5", L"to retrieve the current contrast");
+}
+
+std::wstring mixer_contrast_command(command_context& ctx)
+{
+       return single_double_animatable_mixer_command(
+                       ctx,
+                       [](const frame_transform& t) { return t.image_transform.contrast; },
+                       [](frame_transform& t, double value) { t.image_transform.contrast = value; });
+}
+
+void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Adjust the video levels of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} LEVELS {[min-input:float] [max-input:float] [gamma:float] [min-output:float] [max-output:float]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
+       sink.definitions()
+               ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
+               ->item(L"gamma", L"Adjusts the gamma of the image.")
+               ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
+               sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-10 LEVELS 0.0627 0.922 1 0 1 25 easeinsine", L"for stretching 16-235 video to 0-255 video");
+       sink.example(L">> MIXER 1-10 LEVELS 0 1 1 0.0627 0.922 25 easeinsine", L"for compressing 0-255 video to 16-235 video");
+       sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
+       sink.example(
+               L">> MIXER 1-10 LEVELS\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
+}
+
+std::wstring mixer_levels_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto levels = get_current_transform(ctx).image_transform.levels;
+               return 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";
+       }
+
+       transforms_applier transforms(ctx);
+       levels value;
+       value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
+       value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
+       value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
+       value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
+       value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
+       int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
+       std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
+
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               transform.image_transform.levels = value;
+               return transform;
+       }, duration, tween));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the fill position and scale of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
+               ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
+               ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
+               ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
+       sink.para()
+               ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
+               ->text(L"You set the left edge to full right => 1 and the width to 0. So this give you the start-coordinates of 1 0 0 1.");
+       sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
+       sink.para()
+               ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
+               ->text(L"if you want to do a smaller window. If, for instance you want to have a window of half the size of your screen, ")
+               ->text(L"you set with and height to 0.5. If you want to center it you set left and top edge to 0.25 so you will get the arguments 0.25 0.25 0.5 0.5");
+       sink.definitions()
+               ->item(L"x", L"The new x position, 0 = left edge of monitor, 0.5 = middle of monitor, 1.0 = right edge of monitor. Higher and lower values allowed.")
+               ->item(L"y", L"The new y position, 0 = top edge of monitor, 0.5 = middle of monitor, 1.0 = bottom edge of monitor. Higher and lower values allowed.")
+               ->item(L"x-scale", L"The new x scale, 1 = 1x the screen width, 0.5 = half the screen width. Higher and lower values allowed. Negative values flips the layer.")
+               ->item(L"y-scale", L"The new y scale, 1 = 1x the screen height, 0.5 = half the screen height. Higher and lower values allowed. Negative values flips the layer.");
+       sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 FILL\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
+}
+
+std::wstring mixer_fill_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto transform = get_current_transform(ctx).image_transform;
+               auto translation = transform.fill_translation;
+               auto scale = transform.fill_scale;
+               return 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";
+       }
+
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
+       std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
+       double x = boost::lexical_cast<double>(ctx.parameters.at(0));
+       double y = boost::lexical_cast<double>(ctx.parameters.at(1));
+       double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
+       double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
+
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
+       {
+               transform.image_transform.fill_translation[0] = x;
+               transform.image_transform.fill_translation[1] = y;
+               transform.image_transform.fill_scale[0] = x_s;
+               transform.image_transform.fill_scale[1] = y_s;
+               return transform;
+       }, duration, tween));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the clipping viewport of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
+               ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
+               ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
+       sink.definitions()
+               ->item(L"x", L"The new x position, 0 = left edge of monitor, 0.5 = middle of monitor, 1.0 = right edge of monitor. Higher and lower values allowed.")
+               ->item(L"y", L"The new y position, 0 = top edge of monitor, 0.5 = middle of monitor, 1.0 = bottom edge of monitor. Higher and lower values allowed.")
+               ->item(L"width", L"The new width, 1 = 1x the screen width, 0.5 = half the screen width. Higher and lower values allowed. Negative values flips the layer.")
+               ->item(L"height", L"The new height, 1 = 1x the screen height, 0.5 = half the screen height. Higher and lower values allowed. Negative values flips the layer.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 CLIP\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
+}
 
-       if(parameters().size() > 2)
+std::wstring mixer_clip_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto transform = get_current_transform(ctx).image_transform;
+               auto translation = transform.clip_translation;
+               auto scale = transform.clip_scale;
+
+               return 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";
+       }
+
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
+       std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
+       double x = boost::lexical_cast<double>(ctx.parameters.at(0));
+       double y = boost::lexical_cast<double>(ctx.parameters.at(1));
+       double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
+       double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
+
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               transform.image_transform.clip_translation[0] = x;
+               transform.image_transform.clip_translation[1] = y;
+               transform.image_transform.clip_scale[0] = x_s;
+               transform.image_transform.clip_scale[1] = y_s;
+               return transform;
+       }, duration, tween));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the anchor point of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
+       sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
+       sink.para()
+               ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
+               ->text(L" will be done from.");
+       sink.definitions()
+               ->item(L"x", L"The x anchor point, 0 = left edge of layer, 0.5 = middle of layer, 1.0 = right edge of layer. Higher and lower values allowed.")
+               ->item(L"y", L"The y anchor point, 0 = top edge of layer, 0.5 = middle of layer, 1.0 = bottom edge of layer. Higher and lower values allowed.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
+       sink.example(
+               L">> MIXER 1-10 ANCHOR\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.5 0.6", L"gets the anchor point");
+}
+
+std::wstring mixer_anchor_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto transform = get_current_transform(ctx).image_transform;
+               auto anchor = transform.anchor;
+               return L"201 MIXER OK\r\n"
+                       + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
+                       + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
+       }
+
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
+       std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
+       double x = boost::lexical_cast<double>(ctx.parameters.at(0));
+       double y = boost::lexical_cast<double>(ctx.parameters.at(1));
+
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
+       {
+               transform.image_transform.anchor[0] = x;
+               transform.image_transform.anchor[1] = y;
+               return transform;
+       }, duration, tween));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Crop a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CROP {[left-edge:float] [top-edge:float] [right-edge:float] [bottom-edge:float]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Defines how a layer should be cropped before making other transforms via ")
+               ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
+               ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
+       sink.definitions()
+               ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
+               ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
+               ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
+               ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 CROP 0.25 0.25 0.75 0.75 25 easeinsine", L"leaving a 25% crop around the edges");
+       sink.example(
+               L">> MIXER 1-0 CROP\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
+}
+
+std::wstring mixer_crop_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto crop = get_current_transform(ctx).image_transform.crop;
+               return 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";
+       }
+
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
+       std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
+       double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
+       double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
+       double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
+       double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
+
+       transforms.add(stage::transform_tuple_t(ctx.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));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Rotate a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
+               ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-0 ROTATION\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 45", L"to retrieve the current angle");
+}
+
+std::wstring mixer_rotation_command(command_context& ctx)
+{
+       static const double PI = 3.141592653589793;
+
+       return single_double_animatable_mixer_command(
+                       ctx,
+                       [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
+                       [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
+}
+
+void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Adjust the perspective transform of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} PERSPECTIVE {[top-left-x:float] [top-left-y:float] [top-right-x:float] [top-right-y:float] [bottom-right-x:float] [bottom-right-y:float] [bottom-left-x:float] [bottom-left-y:float]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
+       sink.definitions()
+               ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
+               ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
+               ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
+               ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
+       sink.example(
+               L">> MIXER 1-10 PERSPECTIVE\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
+}
+
+std::wstring mixer_perspective_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+       {
+               auto perspective = get_current_transform(ctx).image_transform.perspective;
+               return
+                       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";
+       }
+
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
+       std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
+       double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
+       double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
+       double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
+       double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
+       double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
+       double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
+       double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
+       double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
+
+       transforms.add(stage::transform_tuple_t(ctx.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));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Enable or disable mipmapping for a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
+       sink.para()
+               ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
+               ->text(L"If no argument is given the current state is returned.");
+       sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turing mipmapping on");
+       sink.example(
+               L">> MIXER 1-10 MIPMAP\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 1", L"for getting the current state");
+}
+
+std::wstring mixer_mipmap_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
+               return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
+
+       transforms_applier transforms(ctx);
+       bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
+       transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
+       {
+               transform.image_transform.use_mipmap = value;
+               return transform;
+       }, 0, L"linear"));
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the volume of a layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
+       sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
+       sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
+       sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
+       sink.example(
+               L">> MIXER 1-0 VOLUME\n"
+               L"<< 201 MIXER OK\n"
+               L"<< 0.8", L"to retrieve the current volume");
+}
+
+std::wstring mixer_volume_command(command_context& ctx)
+{
+       return single_double_animatable_mixer_command(
+               ctx,
+               [](const frame_transform& t) { return t.audio_transform.volume; },
+               [](frame_transform& t, double value) { t.audio_transform.volume = value; });
+}
+
+void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Change the volume of an entire channel.");
+       sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
+       sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1 MASTERVOLUME 0");
+       sink.example(L">> MIXER 1 MASTERVOLUME 1");
+       sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
+}
+
+std::wstring mixer_mastervolume_command(command_context& ctx)
+{
+       if (ctx.parameters.empty())
        {
-               if(!ValidateLayer(parameters()[1]))
+               auto volume = ctx.channel.channel->mixer().get_master_volume();
+               return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
+       }
+
+       float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
+       ctx.channel.channel->mixer().set_master_volume(master_volume);
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Create a grid of video layers.");
+       sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
+       sink.para()
+               ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
+               ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1 GRID 2");
+}
+
+std::wstring mixer_grid_command(command_context& ctx)
+{
+       transforms_applier transforms(ctx);
+       int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
+       std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
+       int n = boost::lexical_cast<int>(ctx.parameters.at(0));
+       double delta = 1.0 / static_cast<double>(n);
+       for (int x = 0; x < n; ++x)
+       {
+               for (int y = 0; y < n; ++y)
                {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
+                       int index = x + y*n + 1;
+                       transforms.add(stage::transform_tuple_t(index, [=](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;
+                       }, duration, tween));
                }
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               auto result = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->invoke(layer, parameters()[2]);
-               replyString << result << L"\r\n";
        }
-       else 
+       transforms.apply();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Commit all deferred mixer transforms.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} COMMIT");
+       sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
+       sink.para()->text(L"Examples:");
+       sink.example(
+               L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
+               L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
+               L">> MIXER 1 COMMIT");
+}
+
+std::wstring mixer_commit_command(command_context& ctx)
+{
+       transforms_applier transforms(ctx);
+       transforms.commit_deferred();
+
+       return L"202 MIXER OK\r\n";
+}
+
+void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Clear all transformations on a channel or layer.");
+       sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
+       sink.para()->text(L"Clears all transformations on a channel or layer.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
+       sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
+}
+
+std::wstring mixer_clear_command(command_context& ctx)
+{
+       int layer = ctx.layer_id;
+
+       if (layer == -1)
+               ctx.channel.channel->stage().clear_transforms();
+       else
+               ctx.channel.channel->stage().clear_transforms(layer);
+
+       return L"202 MIXER OK\r\n";
+}
+
+void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
+       sink.syntax(L"CHANNEL_GRID");
+       sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
+       sink.para()
+               ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
+               ->code(L"casparcg.config")->text(L" for this to work correctly.");
+}
+
+std::wstring channel_grid_command(command_context& ctx)
+{
+       int index = 1;
+       auto self = ctx.channels.back();
+
+       core::diagnostics::scoped_call_context save;
+       core::diagnostics::call_context::for_thread().video_channel = ctx.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.channel->stage());
+
+       self.channel->output().add(screen);
+
+       for (auto& channel : ctx.channels)
        {
-               SetReplyString(L"402 CG ERROR\r\n");
-               return true;
+               if (channel.channel != self.channel)
+               {
+                       core::diagnostics::call_context::for_thread().layer = index;
+                       auto producer = create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
+                       self.channel->stage().load(index, producer, false);
+                       self.channel->stage().play(index);
+                       index++;
+               }
        }
-       
-       SetReplyString(replyString.str());
-       return true;
+
+       auto num_channels = ctx.channels.size() - 1;
+       int square_side_length = std::ceil(std::sqrt(num_channels));
+
+       ctx.channel_index = self.channel->index();
+       ctx.channel = self;
+       ctx.parameters.clear();
+       ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
+       mixer_grid_command(ctx);
+
+       return L"202 CHANNEL_GRID OK\r\n";
+}
+
+// Thumbnail Commands
+
+void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"List all thumbnails.");
+       sink.syntax(L"THUMBNAIL LIST");
+       sink.para()->text(L"Lists all thumbnails.");
+       sink.para()->text(L"Examples:");
+       sink.example(
+               L">> THUMBNAIL LIST\n"
+               L"<< 200 THUMBNAIL LIST OK\n"
+               L"<< \"AMB\" 20130301T124409 1149\n"
+               L"<< \"foo/bar\" 20130523T234001 244");
 }
 
-bool CGCommand::DoExecuteInfo() 
+std::wstring thumbnail_list_command(command_context& ctx)
 {
        std::wstringstream replyString;
-       replyString << L"201 CG OK\r\n";
+       replyString << L"200 THUMBNAIL LIST OK\r\n";
 
-       if(parameters().size() > 1)
+       for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
        {
-               if(!ValidateLayer(parameters()[1]))
+               if (boost::filesystem::is_regular_file(itr->path()))
                {
-                       SetReplyString(L"403 CG ERROR\r\n");
-                       return false;
-               }
+                       if (!boost::iequals(itr->path().extension().wstring(), L".png"))
+                               continue;
 
-               int layer = boost::lexical_cast<int>(parameters()[1]);
-               auto desc = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->description(layer);
-               
-               replyString << desc << L"\r\n";
+                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
+
+                       auto str = relativePath.replace_extension(L"").generic_wstring();
+                       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_wstring(boost::posix_time::from_time_t(mtime));
+                       auto file_size = boost::filesystem::file_size(itr->path());
+
+                       replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
+               }
        }
-       else 
+
+       replyString << L"\r\n";
+
+       return boost::to_upper_copy(replyString.str());
+}
+
+void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Retrieve a thumbnail.");
+       sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
+       sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
+       sink.para()->text(L"Examples:");
+       sink.example(
+               L">> THUMBNAIL RETRIEVE foo/bar\n"
+               L"<< 201 THUMBNAIL RETRIEVE OK\n"
+               L"<< ...base64 data...");
+}
+
+std::wstring thumbnail_retrieve_command(command_context& ctx)
+{
+       std::wstring filename = env::thumbnails_folder();
+       filename.append(ctx.parameters.at(0));
+       filename.append(L".png");
+
+       std::wstring file_contents;
+
+       auto found_file = find_case_insensitive(filename);
+
+       if (found_file)
+               file_contents = read_file_base64(boost::filesystem::path(*found_file));
+
+       if (file_contents.empty())
+               CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
+
+       std::wstringstream reply;
+
+       reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
+       reply << file_contents;
+       reply << L"\r\n";
+       return reply.str();
+}
+
+void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Regenerate a thumbnail.");
+       sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
+       sink.para()->text(L"Regenerates a thumbnail.");
+}
+
+std::wstring thumbnail_generate_command(command_context& ctx)
+{
+       if (ctx.thumb_gen)
        {
-               auto info = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->template_host_info();
-               replyString << info << L"\r\n";
-       }       
+               ctx.thumb_gen->generate(ctx.parameters.at(0));
+               return L"202 THUMBNAIL GENERATE OK\r\n";
+       }
+       else
+               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
+}
 
-       SetReplyString(replyString.str());
-       return true;
+void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Regenerate all thumbnails.");
+       sink.syntax(L"THUMBNAIL GENERATE_ALL");
+       sink.para()->text(L"Regenerates all thumbnails.");
 }
 
-bool DataCommand::DoExecute()
+std::wstring thumbnail_generateall_command(command_context& ctx)
 {
-       std::wstring command = boost::to_upper_copy(parameters()[0]);
-       if(command == L"STORE")
-               return DoExecuteStore();
-       else if(command == L"RETRIEVE")
-               return DoExecuteRetrieve();
-       else if(command == L"REMOVE")
-               return DoExecuteRemove();
-       else if(command == L"LIST")
-               return DoExecuteList();
+       if (ctx.thumb_gen)
+       {
+               ctx.thumb_gen->generate_all();
+               return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
+       }
+       else
+               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
+}
+
+// Query Commands
 
-       SetReplyString(L"403 DATA ERROR\r\n");
-       return false;
+void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get information about a media file.");
+       sink.syntax(L"CINF [filename:string]");
+       sink.para()->text(L"Returns information about a media file.");
 }
 
-bool DataCommand::DoExecuteStore() 
+std::wstring cinf_command(command_context& ctx)
 {
-       if(parameters().size() < 3) 
+       std::wstring info;
+       for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
        {
-               SetReplyString(L"402 DATA STORE ERROR\r\n");
-               return false;
+               auto path = itr->path();
+               auto file = path.replace_extension(L"").filename().wstring();
+               if (boost::iequals(file, ctx.parameters.at(0)))
+                       info += MediaInfo(itr->path(), ctx.media_info_repo) + L"\r\n";
        }
 
-       std::wstring filename = env::data_folder();
-       filename.append(parameters()[1]);
-       filename.append(L".ftd");
+       if (info.empty())
+               CASPAR_THROW_EXCEPTION(file_not_found());
 
-       auto data_path = boost::filesystem::path(filename).parent_path().wstring();
-       auto found_data_path = find_case_insensitive(data_path);
+       std::wstringstream replyString;
+       replyString << L"200 CINF OK\r\n";
+       replyString << info << "\r\n";
 
-       if (found_data_path)
-               data_path = *found_data_path;
+       return replyString.str();
+}
 
-       if(!boost::filesystem::exists(data_path))
-               boost::filesystem::create_directories(data_path);
+void cls_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"List all media files.");
+       sink.syntax(L"CLS");
+       sink.para()
+               ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
+               ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
+}
 
-       auto found_filename = find_case_insensitive(filename);
+std::wstring cls_command(command_context& ctx)
+{
+       std::wstringstream replyString;
+       replyString << L"200 CLS OK\r\n";
+       replyString << ListMedia(ctx.media_info_repo);
+       replyString << L"\r\n";
+       return boost::to_upper_copy(replyString.str());
+}
 
-       if (found_filename)
-               filename = *found_filename; // Overwrite case insensitive.
+void tls_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"List all templates.");
+       sink.syntax(L"TLS");
+       sink.para()
+               ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
+               ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
+}
 
-       boost::filesystem::wofstream datafile(filename);
-       if(!datafile) 
-       {
-               SetReplyString(L"501 DATA STORE FAILED\r\n");
-               return false;
-       }
+std::wstring tls_command(command_context& ctx)
+{
+       std::wstringstream replyString;
+       replyString << L"200 TLS OK\r\n";
 
-       datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
-       datafile << parameters()[2] << std::flush;
-       datafile.close();
+       replyString << ListTemplates(ctx.cg_registry);
+       replyString << L"\r\n";
 
-       std::wstring replyString = L"202 DATA STORE OK\r\n";
-       SetReplyString(replyString);
-       return true;
+       return replyString.str();
 }
 
-bool DataCommand::DoExecuteRetrieve() 
+void version_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       if(parameters().size() < 2) 
+       sink.short_description(L"Get version information.");
+       sink.syntax(L"VERSION {[component:string]}");
+       sink.para()->text(L"Returns the version of specified component.");
+       sink.para()->text(L"Examples:");
+       sink.example(
+               L">> VERSION\n"
+               L"<< 201 VERSION OK\n"
+               L"<< 2.1.0.f207a33 STABLE");
+       sink.example(
+               L">> VERSION SERVER\n"
+               L"<< 201 VERSION OK\n"
+               L"<< 2.1.0.f207a33 STABLE");
+       sink.example(
+               L">> VERSION FLASH\n"
+               L"<< 201 VERSION OK\n"
+               L"<< 11.8.800.94");
+}
+
+std::wstring version_command(command_context& ctx)
+{
+       if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
        {
-               SetReplyString(L"402 DATA RETRIEVE ERROR\r\n");
-               return false;
+               auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
+
+               return L"201 VERSION OK\r\n" + version + L"\r\n";
        }
 
-       std::wstring filename = env::data_folder();
-       filename.append(parameters()[1]);
-       filename.append(L".ftd");
+       return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
+}
 
-       std::wstring file_contents;
+void info_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get a list of the available channels.");
+       sink.syntax(L"INFO");
+       sink.para()->text(L"Retrieves a list of the available channels.");
+       sink.example(
+               L">> INFO\n"
+               L"<< 200 INFO OK\n"
+               L"<< 1 720p5000 PLAYING\n"
+               L"<< 2 PAL PLAYING");
+}
 
-       auto found_file = find_case_insensitive(filename);
+std::wstring info_command(command_context& ctx)
+{
+       std::wstringstream replyString;
+       // This is needed for backwards compatibility with old clients
+       replyString << L"200 INFO OK\r\n";
+       for (size_t n = 0; n < ctx.channels.size(); ++n)
+               replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
+       replyString << L"\r\n";
+       return replyString.str();
+}
 
-       if (found_file)
-               file_contents = read_file(boost::filesystem::path(*found_file));
+std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
+{
+       std::wstringstream replyString;
+
+       if (command.empty())
+               replyString << L"201 INFO OK\r\n";
+       else
+               replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
+
+       boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
+       boost::property_tree::xml_parser::write_xml(replyString, info, w);
+       replyString << L"\r\n";
+       return replyString.str();
+}
 
-       if (file_contents.empty()) 
+void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get information about a channel or a layer.");
+       sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
+       sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
+       sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
+}
+
+std::wstring info_channel_command(command_context& ctx)
+{
+       boost::property_tree::wptree info;
+       int layer = ctx.layer_index(std::numeric_limits<int>::min());
+
+       if (layer == std::numeric_limits<int>::min())
        {
-               SetReplyString(L"404 DATA RETRIEVE ERROR\r\n");
-               return false;
+               info.add_child(L"channel", ctx.channel.channel->info())
+                       .add(L"index", ctx.channel_index);
+       }
+       else
+       {
+               if (ctx.parameters.size() >= 1)
+               {
+                       if (boost::iequals(ctx.parameters.at(0), L"B"))
+                               info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
+                       else
+                               info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
+               }
+               else
+               {
+                       info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
+               }
        }
 
-       std::wstringstream reply(L"201 DATA RETRIEVE OK\r\n");
+       return create_info_xml_reply(info);
+}
+
+void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get information about a template.");
+       sink.syntax(L"INFO TEMPLATE [template:string]");
+       sink.para()->text(L"Gets information about the specified template.");
+}
+
+std::wstring info_template_command(command_context& ctx)
+{
+       auto filename = ctx.parameters.at(0);
+
+       std::wstringstream str;
+       str << u16(ctx.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);
+
+       return create_info_xml_reply(info, L"TEMPLATE");
+}
+
+void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get the contents of the configuration used.");
+       sink.syntax(L"INFO CONFIG");
+       sink.para()->text(L"Gets the contents of the configuration used.");
+}
+
+std::wstring info_config_command(command_context& ctx)
+{
+       return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
+}
 
-       std::wstringstream file_contents_stream(file_contents);
-       std::wstring line;
-       
-       bool firstLine = true;
-       while(std::getline(file_contents_stream, line))
-       {
-               if(firstLine)
-                       firstLine = false;
-               else
-                       reply << "\n";
+void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get information about the paths used.");
+       sink.syntax(L"INFO PATHS");
+       sink.para()->text(L"Gets information about the paths used.");
+}
 
-               reply << line;
-       }
+std::wstring info_paths_command(command_context& ctx)
+{
+       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::filesystem::initial_path().wstring() + L"/");
 
-       reply << "\r\n";
-       SetReplyString(reply.str());
-       return true;
+       return create_info_xml_reply(info, L"PATHS");
 }
 
-bool DataCommand::DoExecuteRemove()
-{ 
-       if (parameters().size() < 2)
-       {
-               SetReplyString(L"402 DATA REMOVE ERROR\r\n");
-               return false;
-       }
+void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get system information.");
+       sink.syntax(L"INFO SYSTEM");
+       sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
+}
 
-       std::wstring filename = env::data_folder();
-       filename.append(parameters()[1]);
-       filename.append(L".ftd");
+std::wstring info_system_command(command_context& ctx)
+{
+       boost::property_tree::wptree info;
 
-       if (!boost::filesystem::exists(filename))
-       {
-               SetReplyString(L"404 DATA REMOVE ERROR\r\n");
-               return false;
-       }
+       info.add(L"system.name", caspar::system_product_name());
+       info.add(L"system.os.description", caspar::os_description());
+       info.add(L"system.cpu", caspar::cpu_info());
 
-       if (!boost::filesystem::remove(filename))
-       {
-               SetReplyString(L"403 DATA REMOVE ERROR\r\n");
-               return false;
-       }
+       ctx.system_info_repo->fill_information(info);
 
-       SetReplyString(L"201 DATA REMOVE OK\r\n");
+       return create_info_xml_reply(info, L"SYSTEM");
+}
 
-       return true;
+void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Get detailed information about all channels.");
+       sink.syntax(L"INFO SERVER");
+       sink.para()->text(L"Gets detailed information about all channels.");
 }
 
-bool DataCommand::DoExecuteList() 
+std::wstring info_server_command(command_context& ctx)
 {
-       std::wstringstream replyString;
-       replyString << L"200 DATA LIST OK\r\n";
+       boost::property_tree::wptree info;
 
-       for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
-       {                       
-               if(boost::filesystem::is_regular_file(itr->path()))
-               {
-                       if(!boost::iequals(itr->path().extension().wstring(), L".ftd"))
-                               continue;
-                       
-                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size()-1, itr->path().wstring().size()));
-                       
-                       auto str = relativePath.replace_extension(L"").generic_wstring();
-                       if(str[0] == L'\\' || str[0] == L'/')
-                               str = std::wstring(str.begin() + 1, str.end());
+       int index = 0;
+       for (auto& channel : ctx.channels)
+               info.add_child(L"channels.channel", channel.channel->info())
+                               .add(L"index", ++index);
 
-                       replyString << str << L"\r\n";
-               }
-       }
-       
-       replyString << L"\r\n";
+       return create_info_xml_reply(info, L"SERVER");
+}
 
-       SetReplyString(boost::to_upper_copy(replyString.str()));
-       return true;
+void diag_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Open the diagnostics window.");
+       sink.syntax(L"DIAG");
+       sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
 }
 
-bool CinfCommand::DoExecute()
+std::wstring diag_command(command_context& ctx)
 {
-       std::wstringstream replyString;
-       
-       try
-       {
-               std::wstring info;
-               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().wstring();
-                       if(boost::iequals(file, parameters().at(0)))
-                               info += MediaInfo(itr->path(), system_info_repo_) + L"\r\n";
-               }
+       core::diagnostics::osd::show_graphs(true);
 
-               if(info.empty())
-               {
-                       SetReplyString(L"404 CINF ERROR\r\n");
-                       return false;
-               }
-               replyString << L"200 CINF OK\r\n";
-               replyString << info << "\r\n";
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"404 CINF ERROR\r\n");
-               return false;
-       }
-       
-       SetReplyString(replyString.str());
-       return true;
+       return L"202 DIAG OK\r\n";
 }
 
-void GenerateChannelInfo(int index, const spl::shared_ptr<core::video_channel>& pChannel, std::wstringstream& replyString)
+void help_describer(core::help_sink& sink, const core::help_repository& repository)
 {
-       replyString << index+1 << L" " << pChannel->video_format_desc().name << L" PLAYING\r\n";
+       sink.short_description(L"Show online help.");
+       sink.syntax(LR"(HELP {[command:string]})");
+       sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
+       sink.example(L">> HELP", L"Shows a list of commands.");
+       sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
 }
 
-bool InfoCommand::DoExecute()
+std::wstring help_command(command_context& ctx)
 {
-       std::wstringstream replyString;
-       
-       boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
+       struct max_width_sink : public core::help_sink
+       {
+               std::size_t max_width = 0;
 
-       try
+               void begin_item(const std::wstring& name) override
+               {
+                       max_width = std::max(name.length(), max_width);
+               };
+       };
+
+       struct short_description_sink : public core::help_sink
        {
-               if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"TEMPLATE"))
-               {               
-                       replyString << L"201 INFO TEMPLATE OK\r\n";
+               std::size_t width;
+               std::wstringstream& out;
 
-                       auto filename = parameters().at(1);
-                                               
-                       std::wstringstream str;
-                       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);
+               short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
 
-                       boost::property_tree::xml_parser::write_xml(replyString, info, w);
-               }
-               else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"CONFIG"))
-               {               
-                       replyString << L"201 INFO CONFIG OK\r\n";
+               void begin_item(const std::wstring& name) override
+               {
+                       out << std::left << std::setw(width + 1) << name;
+               };
 
-                       boost::property_tree::write_xml(replyString, caspar::env::properties(), w);
-               }
-               else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"PATHS"))
+               void short_description(const std::wstring& short_description) override
                {
-                       replyString << L"201 INFO PATHS OK\r\n";
+                       out << short_description << L"\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::filesystem::initial_path().wstring() + L"/");
+       struct simple_paragraph_builder : core::paragraph_builder
+       {
+               std::wstringstream& out;
 
-                       boost::property_tree::write_xml(replyString, info, w);
+               simple_paragraph_builder(std::wstringstream& out) : out(out) { }
+               ~simple_paragraph_builder()
+               {
+                       out << L"\n\n";
                }
-               else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"SYSTEM"))
+               spl::shared_ptr<paragraph_builder> text(std::wstring text) override
                {
-                       replyString << L"201 INFO SYSTEM OK\r\n";
-                       
-                       boost::property_tree::wptree info;
-                       
-                       info.add(L"system.name",                                        caspar::system_product_name());
-                       info.add(L"system.os.description",                      caspar::os_description());
-                       info.add(L"system.cpu",                                         caspar::cpu_info());
-
-                       system_info_repo_->fill_information(info);
-                                               
-                       boost::property_tree::write_xml(replyString, info, w);
+                       out << std::move(text);
+                       return shared_from_this();
                }
-               else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"SERVER"))
+               spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
+               spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
+               spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name)  override { return text(std::move(url)); }
+       };
+
+       struct simple_definition_list_builder : core::definition_list_builder
+       {
+               std::wstringstream& out;
+
+               simple_definition_list_builder(std::wstringstream& out) : out(out) { }
+               spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
                {
-                       replyString << L"201 INFO SERVER OK\r\n";
-                       
-                       boost::property_tree::wptree info;
-
-                       int index = 0;
-                       for (auto& channel : channels())
-                               info.add_child(L"channels.channel", channel.channel->info())
-                                       .add(L"index", ++index);
-                       
-                       boost::property_tree::write_xml(replyString, info, w);
+                       out << L"  " << std::move(term) << L"\n";
+                       out << L"    " << std::move(description) << L"\n\n";
+                       return shared_from_this();
                }
-               else // channel
-               {                       
-                       if(parameters().size() >= 1)
-                       {
-                               replyString << L"201 INFO OK\r\n";
-                               boost::property_tree::wptree info;
+       };
 
-                               std::vector<std::wstring> split;
-                               boost::split(split, parameters()[0], boost::is_any_of("-"));
-                                       
-                               int layer = std::numeric_limits<int>::min();
-                               int channel = boost::lexical_cast<int>(split[0]) - 1;
+       struct long_description_sink : public core::help_sink
+       {
+               std::wstringstream& out;
 
-                               if(split.size() > 1)
-                                       layer = boost::lexical_cast<int>(split[1]);
-                               
-                               if(layer == std::numeric_limits<int>::min())
-                               {       
-                                       info.add_child(L"channel", channels().at(channel).channel->info())
-                                                       .add(L"index", channel);
-                               }
-                               else
-                               {
-                                       if(parameters().size() >= 2)
-                                       {
-                                               if(boost::iequals(parameters()[1], L"B"))
-                                                       info.add_child(L"producer", channels().at(channel).channel->stage().background(layer).get()->info());
-                                               else
-                                                       info.add_child(L"producer", channels().at(channel).channel->stage().foreground(layer).get()->info());
-                                       }
-                                       else
-                                       {
-                                               info.add_child(L"layer", channels().at(channel).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 << L"200 INFO OK\r\n";
-                               for(size_t n = 0; n < channels().size(); ++n)
-                                       GenerateChannelInfo(n, channels()[n].channel, replyString);
-                       }
+               long_description_sink(std::wstringstream& out) : out(out) { }
+
+               void syntax(const std::wstring& syntax) override
+               {
+                       out << L"Syntax\n";
+                       out << L"  " << syntax << L"\n\n";
+               };
 
+               spl::shared_ptr<core::paragraph_builder> para() override
+               {
+                       return spl::make_shared<simple_paragraph_builder>(out);
                }
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"403 INFO ERROR\r\n");
-               return false;
-       }
 
-       replyString << L"\r\n";
-       SetReplyString(replyString.str());
-       return true;
-}
+               spl::shared_ptr<core::definition_list_builder> definitions() override
+               {
+                       return spl::make_shared<simple_definition_list_builder>(out);
+               }
 
-bool ClsCommand::DoExecute()
-{
-       try
+               void example(const std::wstring& code, const std::wstring& caption = L"") override
+               {
+                       out << L"  " << code << L"\n";
+                       if (!caption.empty())
+                               out << L"  ..." << caption << L"\n";
+                       out << L"\n";
+               }
+       private:
+               void begin_item(const std::wstring& name) override
+               {
+                       out << name << L"\n\n";
+               };
+       };
+
+       std::wstringstream result;
+
+       if (ctx.parameters.size() == 0)
        {
-               std::wstringstream replyString;
-               replyString << L"200 CLS OK\r\n";
-               replyString << ListMedia(system_info_repo_);
-               replyString << L"\r\n";
-               SetReplyString(boost::to_upper_copy(replyString.str()));
+               result << L"200 HELP OK\r\n";
+               max_width_sink width;
+               ctx.help_repo->help({ L"AMCP" }, width);
+               short_description_sink sink(width.max_width, result);
+               sink.width = width.max_width;
+               ctx.help_repo->help({ L"AMCP" }, sink);
+               result << L"\r\n";
        }
-       catch(...)
+       else
        {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"501 CLS FAILED\r\n");
-               return false;
+               result << L"201 HELP OK\r\n";
+               auto command = boost::to_upper_copy(
+                       ctx.parameters.size() == 2
+                                       ? ctx.parameters.at(0) + L" " + ctx.parameters.at(1)
+                                       : ctx.parameters.at(0));
+               long_description_sink sink(result);
+               ctx.help_repo->help({ L"AMCP" }, command, sink);
+               result << L"\r\n";
        }
 
-       return true;
+       return result.str();
 }
 
-bool TlsCommand::DoExecute()
+void bye_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       try
-       {
-               std::wstringstream replyString;
-               replyString << L"200 TLS OK\r\n";
-
-               replyString << ListTemplates(cg_registry_);
-               replyString << L"\r\n";
+       sink.short_description(L"Disconnect the session.");
+       sink.syntax(L"BYE");
+       sink.para()
+               ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
+               ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
+}
 
-               SetReplyString(replyString.str());
-       }
-       catch(...)
-       {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"501 TLS FAILED\r\n");
-               return false;
-       }
-       return true;
+std::wstring bye_command(command_context& ctx)
+{
+       ctx.client->disconnect();
+       return L"";
 }
 
-bool VersionCommand::DoExecute()
+void kill_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       std::wstring replyString = L"201 VERSION OK\r\n" + env::version() + L"\r\n";
+       sink.short_description(L"Shutdown the server.");
+       sink.syntax(L"KILL");
+       sink.para()->text(L"Shuts the server down.");
+}
 
-       if (parameters().size() > 0 && !boost::iequals(parameters()[0], L"SERVER"))
-       {
-               auto version = system_info_repo_->get_version(parameters().at(0));
+std::wstring kill_command(command_context& ctx)
+{
+       ctx.shutdown_server_now.set_value(false);       //false for not attempting to restart
+       return L"202 KILL OK\r\n";
+}
 
-               if (version.empty())
-                       replyString = L"403 VERSION ERROR\r\n";
-               else
-                       replyString = L"201 VERSION OK\r\n" + version + L"\r\n";
-       }
+void restart_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"Shutdown the server with restart exit code.");
+       sink.syntax(L"RESTART");
+       sink.para()
+               ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
+               ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
+}
 
-       SetReplyString(replyString);
-       return true;
+std::wstring restart_command(command_context& ctx)
+{
+       ctx.shutdown_server_now.set_value(true);        //true for attempting to restart
+       return L"202 RESTART OK\r\n";
 }
 
-bool ByeCommand::DoExecute()
+void lock_describer(core::help_sink& sink, const core::help_repository& repo)
 {
-       client()->disconnect();
-       return true;
+       sink.short_description(L"Lock or unlock access to a channel.");
+       sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
+       sink.para()->text(L"Allows for exclusive access to a channel.");
+       sink.para()->text(L"Examples:");
+       sink.example(L"LOCK 1 ACQUIRE secret");
+       sink.example(L"LOCK 1 RELEASE");
+       sink.example(L"LOCK 1 CLEAR");
 }
 
-bool SetCommand::DoExecute()
+std::wstring lock_command(command_context& ctx)
 {
-       try
+       int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
+       auto lock = ctx.channels.at(channel_index).lock;
+       auto command = boost::to_upper_copy(ctx.parameters.at(1));
+
+       if (command == L"ACQUIRE")
        {
-               std::wstring name = boost::to_upper_copy(parameters()[0]);
-               std::wstring value = boost::to_upper_copy(parameters()[1]);
+               std::wstring lock_phrase = ctx.parameters.at(2);
 
-               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(L"202 SET MODE OK\r\n");
-                       }
-                       else
-                               SetReplyString(L"501 SET MODE FAILED\r\n");
-               }
-               else
-               {
-                       this->SetReplyString(L"403 SET ERROR\r\n");
-               }
+               //TODO: read options
+
+               //just lock one channel
+               if (!lock->try_lock(lock_phrase, ctx.client))
+                       return L"503 LOCK ACQUIRE FAILED\r\n";
+
+               return L"202 LOCK ACQUIRE OK\r\n";
        }
-       catch(...)
+       else if (command == L"RELEASE")
        {
-               CASPAR_LOG_CURRENT_EXCEPTION();
-               SetReplyString(L"501 SET FAILED\r\n");
-               return false;
+               lock->release_lock(ctx.client);
+               return L"202 LOCK RELEASE OK\r\n";
        }
+       else if (command == L"CLEAR")
+       {
+               std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
+               std::wstring client_override_phrase;
 
-       return true;
+               if (!override_phrase.empty())
+                       client_override_phrase = ctx.parameters.at(2);
+
+               //just clear one channel
+               if (client_override_phrase != override_phrase)
+                       return L"503 LOCK CLEAR FAILED\r\n";
+
+               lock->clear_locks();
+
+               return L"202 LOCK CLEAR OK\r\n";
+       }
+
+       CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
 }
 
-bool LockCommand::DoExecute()
+/*bool LockCommand::DoExecute()
 {
        try
        {
@@ -1987,139 +2755,84 @@ bool LockCommand::DoExecute()
        }
 
        return true;
-}
-
-bool ThumbnailCommand::DoExecute()
-{
-       std::wstring command = boost::to_upper_copy(parameters()[0]);
-
-       if (command == L"RETRIEVE")
-               return DoExecuteRetrieve();
-       else if (command == L"LIST")
-               return DoExecuteList();
-       else if (command == L"GENERATE")
-               return DoExecuteGenerate();
-       else if (command == L"GENERATE_ALL")
-               return DoExecuteGenerateAll();
-
-       SetReplyString(L"403 THUMBNAIL ERROR\r\n");
-       return false;
-}
-
-bool ThumbnailCommand::DoExecuteRetrieve() 
-{
-       if(parameters().size() < 2) 
-       {
-               SetReplyString(L"402 THUMBNAIL RETRIEVE ERROR\r\n");
-               return false;
-       }
-
-       std::wstring filename = env::thumbnails_folder();
-       filename.append(parameters()[1]);
-       filename.append(L".png");
-
-       std::wstring file_contents;
-
-       auto found_file = find_case_insensitive(filename);
-
-       if (found_file)
-               file_contents = read_file_base64(boost::filesystem::path(*found_file));
-
-       if (file_contents.empty())
-       {
-               SetReplyString(L"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 << L"200 THUMBNAIL LIST OK\r\n";
-
-       for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
-       {      
-               if(boost::filesystem::is_regular_file(itr->path()))
-               {
-                       if(!boost::iequals(itr->path().extension().wstring(), L".png"))
-                               continue;
-
-                       auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size()-1, itr->path().wstring().size()));
-
-                       auto str = relativePath.replace_extension(L"").generic_wstring();
-                       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_wstring(boost::posix_time::from_time_t(mtime));
-                       auto file_size = boost::filesystem::file_size(itr->path());
-
-                       replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
-               }
-       }
-
-       replyString << L"\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;
-       }
-
-       if (thumb_gen_)
-       {
-               thumb_gen_->generate(parameters()[1]);
-               SetReplyString(L"202 THUMBNAIL GENERATE OK\r\n");
-               return true;
-       }
-       else
-       {
-               SetReplyString(L"500 THUMBNAIL GENERATE ERROR\r\n");
-               return false;
-       }
-}
-
-bool ThumbnailCommand::DoExecuteGenerateAll()
-{
-       if (thumb_gen_)
-       {
-               thumb_gen_->generate_all();
-               SetReplyString(L"202 THUMBNAIL GENERATE_ALL OK\r\n");
-               return true;
-       }
-       else
-       {
-               SetReplyString(L"500 THUMBNAIL GENERATE_ALL ERROR\r\n");
-               return false;
-       }
-}
-
-bool KillCommand::DoExecute()
-{
-       shutdown_server_now_->set_value(false); //false for not attempting to restart
-       SetReplyString(L"202 KILL OK\r\n");
-       return true;
-}
+}*/
 
-bool RestartCommand::DoExecute()
+void register_commands(amcp_command_repository& repo)
 {
-       shutdown_server_now_->set_value(true);  //true for attempting to restart
-       SetReplyString(L"202 RESTART OK\r\n");
-       return true;
+       repo.register_channel_command(  L"Basic Commands",              L"LOADBG",                                      loadbg_describer,                                       loadbg_command,                                 1);
+       repo.register_channel_command(  L"Basic Commands",              L"LOAD",                                        load_describer,                                         load_command,                                   1);
+       repo.register_channel_command(  L"Basic Commands",              L"PLAY",                                        play_describer,                                         play_command,                                   0);
+       repo.register_channel_command(  L"Basic Commands",              L"PAUSE",                                       pause_describer,                                        pause_command,                                  0);
+       repo.register_channel_command(  L"Basic Commands",              L"RESUME",                                      resume_describer,                                       resume_command,                                 0);
+       repo.register_channel_command(  L"Basic Commands",              L"STOP",                                        stop_describer,                                         stop_command,                                   0);
+       repo.register_channel_command(  L"Basic Commands",              L"CLEAR",                                       clear_describer,                                        clear_command,                                  0);
+       repo.register_channel_command(  L"Basic Commands",              L"CALL",                                        call_describer,                                         call_command,                                   1);
+       repo.register_channel_command(  L"Basic Commands",              L"SWAP",                                        swap_describer,                                         swap_command,                                   1);
+       repo.register_channel_command(  L"Basic Commands",              L"ADD",                                         add_describer,                                          add_command,                                    1);
+       repo.register_channel_command(  L"Basic Commands",              L"REMOVE",                                      remove_describer,                                       remove_command,                                 0);
+       repo.register_channel_command(  L"Basic Commands",              L"PRINT",                                       print_describer,                                        print_command,                                  0);
+       repo.register_command(                  L"Basic Commands",              L"LOG LEVEL",                           log_level_describer,                            log_level_command,                              1);
+       repo.register_channel_command(  L"Basic Commands",              L"SET",                                         set_describer,                                          set_command,                                    2);
+       repo.register_command(                  L"Basic Commands",              L"LOCK",                                        lock_describer,                                         lock_command,                                   2);
+
+       repo.register_command(                  L"Data Commands",               L"DATA STORE",                          data_store_describer,                           data_store_command,                             2);
+       repo.register_command(                  L"Data Commands",               L"DATA RETRIEVE",                       data_retrieve_describer,                        data_retrieve_command,                  1);
+       repo.register_command(                  L"Data Commands",               L"DATA LIST",                           data_list_describer,                            data_list_command,                              0);
+       repo.register_command(                  L"Data Commands",               L"DATA REMOVE",                         data_remove_describer,                          data_remove_command,                    1);
+
+       repo.register_channel_command(  L"Template Commands",   L"CG ADD",                                      cg_add_describer,                                       cg_add_command,                                 3);
+       repo.register_channel_command(  L"Template Commands",   L"CG PLAY",                                     cg_play_describer,                                      cg_play_command,                                1);
+       repo.register_channel_command(  L"Template Commands",   L"CG STOP",                                     cg_stop_describer,                                      cg_stop_command,                                1);
+       repo.register_channel_command(  L"Template Commands",   L"CG NEXT",                                     cg_next_describer,                                      cg_next_command,                                1);
+       repo.register_channel_command(  L"Template Commands",   L"CG REMOVE",                           cg_remove_describer,                            cg_remove_command,                              1);
+       repo.register_channel_command(  L"Template Commands",   L"CG CLEAR",                            cg_clear_describer,                                     cg_clear_command,                               0);
+       repo.register_channel_command(  L"Template Commands",   L"CG UPDATE",                           cg_update_describer,                            cg_update_command,                              2);
+       repo.register_channel_command(  L"Template Commands",   L"CG INVOKE",                           cg_invoke_describer,                            cg_invoke_command,                              2);
+       repo.register_channel_command(  L"Template Commands",   L"CG INFO",                                     cg_info_describer,                                      cg_info_command,                                0);
+
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER KEYER",                         mixer_keyer_describer,                          mixer_keyer_command,                    0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER CHROMA",                        mixer_chroma_describer,                         mixer_chroma_command,                   0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER BLEND",                         mixer_blend_describer,                          mixer_blend_command,                    0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER OPACITY",                       mixer_opacity_describer,                        mixer_opacity_command,                  0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER BRIGHTNESS",            mixer_brightness_describer,                     mixer_brightness_command,               0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER SATURATION",            mixer_saturation_describer,                     mixer_saturation_command,               0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER CONTRAST",                      mixer_contrast_describer,                       mixer_contrast_command,                 0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER LEVELS",                        mixer_levels_describer,                         mixer_levels_command,                   0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER FILL",                          mixer_fill_describer,                           mixer_fill_command,                             0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLIP",                          mixer_clip_describer,                           mixer_clip_command,                             0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER ANCHOR",                        mixer_anchor_describer,                         mixer_anchor_command,                   0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER CROP",                          mixer_crop_describer,                           mixer_crop_command,                             0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER ROTATION",                      mixer_rotation_describer,                       mixer_rotation_command,                 0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER PERSPECTIVE",           mixer_perspective_describer,            mixer_perspective_command,              0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER MIPMAP",                        mixer_mipmap_describer,                         mixer_mipmap_command,                   0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER VOLUME",                        mixer_volume_describer,                         mixer_volume_command,                   0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER MASTERVOLUME",          mixer_mastervolume_describer,           mixer_mastervolume_command,             0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER GRID",                          mixer_grid_describer,                           mixer_grid_command,                             1);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER COMMIT",                        mixer_commit_describer,                         mixer_commit_command,                   0);
+       repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLEAR",                         mixer_clear_describer,                          mixer_clear_command,                    0);
+       repo.register_command(                  L"Mixer Commands",              L"CHANNEL_GRID",                        channel_grid_describer,                         channel_grid_command,                   0);
+
+       repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL LIST",                      thumbnail_list_describer,                       thumbnail_list_command,                 0);
+       repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL RETRIEVE",          thumbnail_retrieve_describer,           thumbnail_retrieve_command,             1);
+       repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE",          thumbnail_generate_describer,           thumbnail_generate_command,             1);
+       repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE_ALL",      thumbnail_generateall_describer,        thumbnail_generateall_command,  0);
+
+       repo.register_command(                  L"Query Commands",              L"CINF",                                        cinf_describer,                                         cinf_command,                                   1);
+       repo.register_command(                  L"Query Commands",              L"CLS",                                         cls_describer,                                          cls_command,                                    0);
+       repo.register_command(                  L"Query Commands",              L"TLS",                                         tls_describer,                                          tls_command,                                    0);
+       repo.register_command(                  L"Query Commands",              L"VERSION",                                     version_describer,                                      version_command,                                0);
+       repo.register_command(                  L"Query Commands",              L"INFO",                                        info_describer,                                         info_command,                                   0);
+       repo.register_channel_command(  L"Query Commands",              L"INFO",                                        info_channel_describer,                         info_channel_command,                   0);
+       repo.register_command(                  L"Query Commands",              L"INFO TEMPLATE",                       info_template_describer,                        info_template_command,                  1);
+       repo.register_command(                  L"Query Commands",              L"INFO CONFIG",                         info_config_describer,                          info_config_command,                    0);
+       repo.register_command(                  L"Query Commands",              L"INFO PATHS",                          info_paths_describer,                           info_paths_command,                             0);
+       repo.register_command(                  L"Query Commands",              L"INFO SYSTEM",                         info_system_describer,                          info_system_command,                    0);
+       repo.register_command(                  L"Query Commands",              L"INFO SERVER",                         info_server_describer,                          info_server_command,                    0);
+       repo.register_command(                  L"Query Commands",              L"DIAG",                                        diag_describer,                                         diag_command,                                   0);
+       repo.register_command(                  L"Query Commands",              L"BYE",                                         bye_describer,                                          bye_command,                                    0);
+       repo.register_command(                  L"Query Commands",              L"KILL",                                        kill_describer,                                         kill_command,                                   0);
+       repo.register_command(                  L"Query Commands",              L"RESTART",                                     restart_describer,                                      restart_command,                                0);
+       repo.register_command(                  L"Query Commands",              L"HELP",                                        help_describer,                                         help_command,                                   0);
 }
 
 }      //namespace amcp
index a131eb01280c8a6830a3f6f09fabad30cfea62ae..626dbb9a153a8c784f9535c47229d6742814c831 100644 (file)
 
 #include "AMCPCommand.h"
 
-#include <core/thumbnail_generator.h>
-#include <core/producer/media_info/media_info_repository.h>
-#include <core/producer/cg_proxy.h>
-#include <core/system_info_provider.h>
-
-#include <future>
-
 namespace caspar { namespace protocol { namespace amcp {
-       
-class ChannelGridCommand : public AMCPCommandBase<0>, AMCPChannelsAwareCommand
-{
-public:
-       ChannelGridCommand(IO::ClientInfoPtr client, const std::vector<channel_context>& channels) : AMCPCommandBase(client), AMCPChannelsAwareCommand(channels) {}
-       std::wstring print() const { return L"ChannelGridCommand";}
-       bool DoExecute();
-};
-
-class DiagnosticsCommand : public AMCPCommandBase<0>
-{
-public:
-       explicit DiagnosticsCommand(IO::ClientInfoPtr client) : AMCPCommandBase(client) {}
-       std::wstring print() const { return L"DiagnosticsCommand";}
-       bool DoExecute();
-};
-
-class CallCommand : public AMCPChannelCommandBase<1>
-{
-public:
-       CallCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"CallCommand";}
-       bool DoExecute();
-};
-
-class MixerCommand : public AMCPChannelCommandBase<1>
-{
-public:
-       MixerCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-private:
-       std::wstring print() const { return L"MixerCommand";}
-       core::frame_transform get_current_transform();
-       template<typename Func>
-       bool reply_value(const Func& extractor)
-       {
-               auto value = extractor(get_current_transform());
-
-               SetReplyString(L"201 MIXER OK\r\n"
-                       + boost::lexical_cast<std::wstring>(value) + L"\r\n");
-
-               return true;
-       }
-       bool DoExecute();
-};
-       
-class AddCommand : public AMCPChannelCommandBase<1>
-{
-public:
-       AddCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"AddCommand";}
-       bool DoExecute();
-};
-
-class RemoveCommand : public AMCPChannelCommandBase<0>
-{
-public:
-       RemoveCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"RemoveCommand";}
-       bool DoExecute();
-};
-
-class SwapCommand : public AMCPChannelCommandBase<1>, AMCPChannelsAwareCommand
-{
-public:
-       SwapCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index, const std::vector<channel_context>& channels) : AMCPChannelCommandBase(client, channel, channel_index, layer_index), AMCPChannelsAwareCommand(channels)
-       {}
-
-private:
-       std::wstring print() const { return L"SwapCommand";}
-       bool DoExecute();
-};
-
-class LoadCommand : public AMCPChannelCommandBase<1>, AMCPChannelsAwareCommand
-{
-public:
-       LoadCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index, const std::vector<channel_context>& channels) : AMCPChannelCommandBase(client, channel, channel_index, layer_index), AMCPChannelsAwareCommand(channels)
-       {}
-
-private:
-       std::wstring print() const { return L"LoadCommand";}
-       bool DoExecute();
-};
-
-
-class PlayCommand: public AMCPChannelCommandBase<0>, public AMCPChannelsAwareCommand
-{
-public:
-       PlayCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index, const std::vector<channel_context>& channels) : AMCPChannelCommandBase(client, channel, channel_index, layer_index), AMCPChannelsAwareCommand(channels)
-       {}
-
-private:
-       std::wstring print() const { return L"PlayCommand";}
-       bool DoExecute();
-};
-
-class LoadbgCommand : public AMCPChannelCommandBase<1>, public AMCPChannelsAwareCommand
-{
-public:
-       LoadbgCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index, const std::vector<channel_context>& channels) : AMCPChannelCommandBase(client, channel, channel_index, layer_index), AMCPChannelsAwareCommand(channels)
-       {}
-       LoadbgCommand(const PlayCommand& rhs) : AMCPChannelCommandBase<1>(rhs), AMCPChannelsAwareCommand(rhs) {}
-
-private:
-       std::wstring print() const { return L"LoadbgCommand";}
-       bool DoExecute();
-};
-
-class PauseCommand: public AMCPChannelCommandBase<0>
-{
-public:
-       PauseCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"PauseCommand";}
-       bool DoExecute();
-};
-
-class StopCommand : public AMCPChannelCommandBase<0>
-{
-public:
-       StopCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"StopCommand";}
-       bool DoExecute();
-};
-
-class ClearCommand : public AMCPChannelCommandBase<0>
-{
-public:
-       ClearCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"ClearCommand";}
-       bool DoExecute();
-};
-
-class PrintCommand : public AMCPChannelCommandBase<0>
-{
-public:
-       PrintCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-private:
-       std::wstring print() const { return L"PrintCommand";}
-       bool DoExecute();
-};
-
-class LogCommand : public AMCPCommandBase<2>
-{
-public:
-       explicit LogCommand(IO::ClientInfoPtr client) : AMCPCommandBase(client) {}
-       std::wstring print() const { return L"LogCommand";}
-       bool DoExecute();
-};
-
-class CGCommand : public AMCPChannelCommandBase<1>, public AMCPChannelsAwareCommand
-{
-public:
-       CGCommand(
-                       IO::ClientInfoPtr client,
-                       const channel_context& channel,
-                       unsigned int channel_index,
-                       int layer_index,
-                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
-                       const std::vector<channel_context>& channels)
-               : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-               , AMCPChannelsAwareCommand(channels)
-               , cg_registry_(cg_registry)
-       {
-       }
-
-private:
-       std::wstring print() const { return L"CGCommand";}
-       bool DoExecute();
-       bool ValidateLayer(const std::wstring& layerstring);
-
-       bool DoExecuteAdd();
-       bool DoExecutePlay();
-       bool DoExecuteStop();
-       bool DoExecuteNext();
-       bool DoExecuteRemove();
-       bool DoExecuteClear();
-       bool DoExecuteUpdate();
-       bool DoExecuteInvoke();
-       bool DoExecuteInfo();
-private:
-       spl::shared_ptr<core::cg_producer_registry> cg_registry_;
-};
-
-class DataCommand : public AMCPCommandBase<1>
-{
-public:
-       explicit DataCommand(IO::ClientInfoPtr client) : AMCPCommandBase(client) {}
-
-       std::wstring print() const { return L"DataCommand";}
-       bool DoExecute();
-       bool DoExecuteStore();
-       bool DoExecuteRetrieve();
-       bool DoExecuteRemove();
-       bool DoExecuteList();
-};
-
-class ClsCommand : public AMCPCommandBase<0>
-{
-public:
-       explicit ClsCommand(IO::ClientInfoPtr client, const spl::shared_ptr<core::media_info_repository>& system_info_repo)
-               : AMCPCommandBase(client)
-               , system_info_repo_(system_info_repo)
-       {}
-       std::wstring print() const { return L"ClsCommand";}
-       bool DoExecute();
-private:
-       spl::shared_ptr<core::media_info_repository> system_info_repo_;
-};
-
-class TlsCommand : public AMCPCommandBase<0>
-{
-public:
-       explicit TlsCommand(
-                       IO::ClientInfoPtr client,
-                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
-               : AMCPCommandBase(client)
-               , cg_registry_(cg_registry)
-       {}
-       std::wstring print() const { return L"TlsCommand";}
-       bool DoExecute();
-private:
-       spl::shared_ptr<core::cg_producer_registry> cg_registry_;
-};
-
-class CinfCommand : public AMCPCommandBase<1>
-{
-public:
-       explicit CinfCommand(IO::ClientInfoPtr client, const spl::shared_ptr<core::media_info_repository>& system_info_repo)
-               : AMCPCommandBase(client)
-               , system_info_repo_(system_info_repo)
-       {}
-       std::wstring print() const { return L"CinfCommand";}
-       bool DoExecute();
-private:
-       spl::shared_ptr<core::media_info_repository> system_info_repo_;
-};
-
-class InfoCommand : public AMCPCommandBase<0>, AMCPChannelsAwareCommand
-{
-public:
-       InfoCommand(
-                       IO::ClientInfoPtr client,
-                       const std::vector<channel_context>& channels,
-                       const spl::shared_ptr<core::system_info_provider_repository>& system_info_repo,
-                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
-               : AMCPChannelsAwareCommand(channels)
-               , AMCPCommandBase(client)
-               , system_info_repo_(system_info_repo)
-               , cg_registry_(cg_registry)
-       {}
-       std::wstring print() const { return L"InfoCommand";}
-       bool DoExecute();
-private:
-       spl::shared_ptr<core::system_info_provider_repository> system_info_repo_;
-       spl::shared_ptr<core::cg_producer_registry> cg_registry_;
-};
-
-class VersionCommand : public AMCPCommandBase<0>
-{
-public:
-       explicit VersionCommand(IO::ClientInfoPtr client, const spl::shared_ptr<core::system_info_provider_repository>& system_info_repo)
-               : AMCPCommandBase(client)
-               , system_info_repo_(system_info_repo)
-       {}
-       std::wstring print() const { return L"VersionCommand";}
-       bool DoExecute();
-private:
-       spl::shared_ptr<core::system_info_provider_repository> system_info_repo_;
-};
-
-class ByeCommand : public AMCPCommandBase<0>
-{
-public:
-       explicit ByeCommand(IO::ClientInfoPtr client) : AMCPCommandBase(client) {}
-       std::wstring print() const { return L"ByeCommand";}
-       bool DoExecute();
-};
-
-class SetCommand : public AMCPChannelCommandBase<2>
-{
-public:
-       SetCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
-       {}
-
-       std::wstring print() const { return L"SetCommand";}
-       bool DoExecute();
-};
-
-class LockCommand : public AMCPCommandBase<1>, AMCPChannelsAwareCommand
-{
-public:
-       LockCommand(IO::ClientInfoPtr client, const std::vector<channel_context>& channels) : AMCPChannelsAwareCommand(channels), AMCPCommandBase(client) {}
-       std::wstring print() const { return L"LockCommand";}
-       bool DoExecute();
-};
-
-class ThumbnailCommand : public AMCPCommandBase<1>
-{
-public:
-       ThumbnailCommand(IO::ClientInfoPtr client, std::shared_ptr<core::thumbnail_generator> thumb_gen) : AMCPCommandBase(client), thumb_gen_(thumb_gen) 
-       {}
-       std::wstring print() const { return L"ThumbnailCommand";}
-       bool DoExecute();
-
-private:
-       bool DoExecuteRetrieve();
-       bool DoExecuteList();
-       bool DoExecuteGenerate();
-       bool DoExecuteGenerateAll();
-
-       std::shared_ptr<core::thumbnail_generator> thumb_gen_;
-};
-
-class KillCommand : public AMCPCommandBase<0> 
-{
-public:
-       KillCommand(IO::ClientInfoPtr client, std::promise<bool>& shutdown_server_now) : AMCPCommandBase(client), shutdown_server_now_(&shutdown_server_now) {}
-       std::wstring print() const { return L"KillCommand";}
-       bool DoExecute();
-
-private:
-       std::promise<bool>* shutdown_server_now_;
-};
-
-class RestartCommand : public AMCPCommandBase<0> 
-{
-public:
-       RestartCommand(IO::ClientInfoPtr client, std::promise<bool>& shutdown_server_now) : AMCPCommandBase(client), shutdown_server_now_(&shutdown_server_now) {}
-       std::wstring print() const { return L"RestartCommand";}
-       bool DoExecute();
 
-private:
-       std::promise<bool>* shutdown_server_now_;
-};
+void register_commands(class amcp_command_repository& repo);
 
-}      //namespace amcp
-}}     //namespace caspar
+}}}
index 8faaf245b5c98bf2ac1df63e126118dc0f92277b..5db0c6e09b06b807c3d993286f67d79d229d6df7 100644 (file)
 #include "../StdAfx.h"
 
 #include "AMCPProtocolStrategy.h"
-#include "AMCPCommandsImpl.h"
 #include "amcp_shared.h"
 #include "AMCPCommand.h"
 #include "AMCPCommandQueue.h"
+#include "amcp_command_repository.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -34,6 +34,9 @@
 #include <cctype>
 #include <future>
 
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
+
 #include <boost/algorithm/string/trim.hpp>
 #include <boost/algorithm/string/split.hpp>
 #include <boost/algorithm/string/replace.hpp>
@@ -47,52 +50,33 @@ namespace caspar { namespace protocol { namespace amcp {
 
 using IO::ClientInfoPtr;
 
+template <typename Out, typename In>
+bool try_lexical_cast(const In& input, Out& result)
+{
+       Out saved = result;
+       bool success = boost::conversion::detail::try_lexical_convert(input, result);
+
+       if (!success)
+               result = saved; // Needed because of how try_lexical_convert is implemented.
+
+       return success;
+}
+
 struct AMCPProtocolStrategy::impl
 {
 private:
-       std::vector<channel_context>                                                    channels_;
-       std::vector<AMCPCommandQueue::ptr_type>                                 commandQueues_;
-       std::shared_ptr<core::thumbnail_generator>                              thumb_gen_;
-       spl::shared_ptr<core::media_info_repository>                    media_info_repo_;
-       spl::shared_ptr<core::system_info_provider_repository>  system_info_provider_repo_;
-       spl::shared_ptr<core::cg_producer_registry>                             cg_registry_;
-       std::promise<bool>&                                                                             shutdown_server_now_;
+       std::vector<AMCPCommandQueue::ptr_type>         commandQueues_;
+       spl::shared_ptr<amcp_command_repository>        repo_;
 
 public:
-       impl(
-                       const std::vector<spl::shared_ptr<core::video_channel>>& channels,
-                       const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
-                       const spl::shared_ptr<core::media_info_repository>& media_info_repo,
-                       const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
-                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
-                       std::promise<bool>& shutdown_server_now)
-               : thumb_gen_(thumb_gen)
-               , media_info_repo_(media_info_repo)
-               , system_info_provider_repo_(system_info_provider_repo)
-               , cg_registry_(cg_registry)
-               , shutdown_server_now_(shutdown_server_now)
+       impl(const spl::shared_ptr<amcp_command_repository>& repo)
+               : repo_(repo)
        {
-               commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
-
-               int index = 0;
-               for (const auto& channel : channels)
-               {
-                       std::wstring lifecycle_key = L"lock" + boost::lexical_cast<std::wstring>(index);
-                       channels_.push_back(channel_context(channel, lifecycle_key));
-                       auto queue(std::make_shared<AMCPCommandQueue>());
-                       commandQueues_.push_back(queue);
-                       ++index;
-               }
+               commandQueues_.resize(repo_->channels().size() + 1);
        }
 
        ~impl() {}
 
-       enum class parser_state {
-               New = 0,
-               GetSwitch,
-               GetCommand,
-               GetParameters
-       };
        enum class error_state {
                no_error = 0,
                command_error,
@@ -104,12 +88,10 @@ public:
 
        struct command_interpreter_result
        {
-               command_interpreter_result() : error(error_state::no_error) {}
-
                std::shared_ptr<caspar::IO::lock_container>     lock;
                std::wstring                                                            command_name;
                AMCPCommand::ptr_type                                           command;
-               error_state                                                                     error;
+               error_state                                                                     error                   = error_state::no_error;
                AMCPCommandQueue::ptr_type                                      queue;
        };
 
@@ -131,7 +113,6 @@ public:
                if (result.error != error_state::no_error)
                {
                        std::wstringstream answer;
-                       boost::to_upper(result.command_name);
 
                        switch(result.error)
                        {
@@ -156,123 +137,93 @@ public:
        }
 
 private:
-       friend class AMCPCommand;
-
        bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
        {
                try
                {
-                       std::vector<std::wstring> tokens;
-                       parser_state state = parser_state::New;
+                       std::list<std::wstring> tokens;
+                       tokenize(message, tokens);
 
-                       tokenize(message, &tokens);
+                       // Discard GetSwitch
+                       if (!tokens.empty() && tokens.front().at(0) == L'/')
+                               tokens.pop_front();
 
-                       //parse the message one token at the time
-                       auto end = tokens.end();
-                       auto it = tokens.begin();
-                       while (it != end && result.error == error_state::no_error)
+                       // Fail if no more tokens.
+                       if (tokens.empty())
                        {
-                               switch(state)
+                               result.error = error_state::command_error;
+                               return false;
+                       }
+
+                       // Consume command name
+                       result.command_name = boost::to_upper_copy(tokens.front());
+                       tokens.pop_front();
+
+                       // Determine whether the next parameter is a channel spec or not
+                       int channel_index = -1;
+                       int layer_index = -1;
+                       std::wstring channel_spec;
+
+                       if (!tokens.empty())
+                       {
+                               channel_spec = tokens.front();
+                               std::wstring channelid_str = boost::trim_copy(channel_spec);
+                               std::vector<std::wstring> split;
+                               boost::split(split, channelid_str, boost::is_any_of("-"));
+
+                               // Use non_throwing lexical cast to not hit exception break point all the time.
+                               if (try_lexical_cast(split[0], channel_index))
                                {
-                               case parser_state::New:
-                                       if((*it)[0] == L'/')
-                                               state = parser_state::GetSwitch;
-                                       else
-                                               state = parser_state::GetCommand;
-                                       break;
+                                       --channel_index;
 
-                               case parser_state::GetSwitch:
-                                       //command_switch = (*it);       //we dont care for the switch anymore
-                                       state = parser_state::GetCommand;
-                                       ++it;
-                                       break;
+                                       if (split.size() > 1)
+                                               try_lexical_cast(split[1], layer_index);
 
-                               case parser_state::GetCommand:
-                                       {
-                                               result.command_name = (*it);
-                                               result.command = create_command(result.command_name, client);
-                                               if(result.command)      //the command doesn't need a channel
-                                               {
-                                                       result.queue = commandQueues_[0];
-                                                       state = parser_state::GetParameters;
-                                               }
-                                               else
-                                               {
-                                                       //get channel index from next token
-                                                       int channel_index = -1;
-                                                       int layer_index = -1;
-
-                                                       ++it;
-                                                       if(it == end)
-                                                       {
-                                                               if(create_channel_command(result.command_name, client, channels_.at(0), 0, 0))  //check if there is a command like this
-                                                                       result.error = error_state::channel_error;
-                                                               else
-                                                                       result.error = error_state::command_error;
-
-                                                               break;
-                                                       }
-
-                                                       {       //parse channel/layer token
-                                                               try
-                                                               {
-                                                                       std::wstring channelid_str = boost::trim_copy(*it);
-                                                                       std::vector<std::wstring> split;
-                                                                       boost::split(split, channelid_str, boost::is_any_of("-"));
-
-                                                                       channel_index = boost::lexical_cast<int>(split[0]) - 1;
-                                                                       if(split.size() > 1)
-                                                                               layer_index = boost::lexical_cast<int>(split[1]);
-                                                               }
-                                                               catch(...)
-                                                               {
-                                                                       result.error = error_state::channel_error;
-                                                                       break;
-                                                               }
-                                                       }
-                                               
-                                                       if(channel_index >= 0 && channel_index < channels_.size())
-                                                       {
-                                                               result.command = create_channel_command(result.command_name, client, channels_.at(channel_index), channel_index, layer_index);
-                                                               if(result.command)
-                                                               {
-                                                                       result.lock = channels_.at(channel_index).lock;
-                                                                       result.queue = commandQueues_[channel_index + 1];
-                                                               }
-                                                               else
-                                                               {
-                                                                       result.error = error_state::command_error;
-                                                                       break;
-                                                               }
-                                                       }
-                                                       else
-                                                       {
-                                                               result.error = error_state::channel_error;
-                                                               break;
-                                                       }
-                                               }
-
-                                               state = parser_state::GetParameters;
-                                               ++it;
-                                       }
-                                       break;
+                                       // Consume channel-spec
+                                       tokens.pop_front();
+                               }
+                       }
 
-                               case parser_state::GetParameters:
-                                       {
-                                               int parameterCount=0;
-                                               while(it != end)
-                                               {
-                                                       result.command->parameters().push_back((*it));
-                                                       ++it;
-                                                       ++parameterCount;
-                                               }
-                                       }
-                                       break;
+                       bool is_channel_command = channel_index != -1;
+
+                       // Create command instance
+                       if (is_channel_command)
+                       {
+                               result.command = repo_->create_channel_command(result.command_name, client, channel_index, layer_index, tokens);
+
+                               if (result.command)
+                               {
+                                       result.lock = repo_->channels().at(channel_index).lock;
+                                       result.queue = commandQueues_.at(channel_index + 1);
+                               }
+                               else // Might be a non channel command, although the first argument is numeric
+                               {
+                                       // Restore backed up channel spec string.
+                                       tokens.push_front(channel_spec);
+                                       result.command = repo_->create_command(result.command_name, client, tokens);
+
+                                       if (result.command)
+                                               result.queue = commandQueues_.at(0);
                                }
                        }
+                       else
+                       {
+                               result.command = repo_->create_command(result.command_name, client, tokens);
 
-                       if(result.command && result.error == error_state::no_error && result.command->parameters().size() < result.command->minimum_parameters()) {
-                               result.error = error_state::parameters_error;
+                               if (result.command)
+                                       result.queue = commandQueues_.at(0);
+                       }
+
+                       if (!result.command)
+                               result.error = error_state::command_error;
+                       else
+                       {
+                               std::vector<std::wstring> parameters(tokens.begin(), tokens.end());
+
+                               result.command->parameters() = std::move(parameters);
+
+                               if (result.command->parameters().size() < result.command->minimum_parameters())
+                                       result.error = error_state::parameters_error;
                        }
                }
                catch(...)
@@ -284,7 +235,8 @@ private:
                return result.error == error_state::no_error;
        }
 
-       std::size_t tokenize(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
+       template<typename C>
+       std::size_t tokenize(const std::wstring& message, C& pTokenVector)
        {
                //split on whitespace but keep strings within quotationmarks
                //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
@@ -325,9 +277,9 @@ private:
 
                        if(message[charIndex]==L' ' && inQuote==false)
                        {
-                               if(currentToken.size()>0)
+                               if(!currentToken.empty())
                                {
-                                       pTokenVector->push_back(currentToken);
+                                       pTokenVector.push_back(currentToken);
                                        currentToken.clear();
                                }
                                continue;
@@ -337,9 +289,9 @@ private:
                        {
                                inQuote = !inQuote;
 
-                               if(currentToken.size()>0 || !inQuote)
+                               if(!currentToken.empty() || !inQuote)
                                {
-                                       pTokenVector->push_back(currentToken);
+                                       pTokenVector.push_back(currentToken);
                                        currentToken.clear();
                                }
                                continue;
@@ -348,74 +300,18 @@ private:
                        currentToken += message[charIndex];
                }
 
-               if(currentToken.size()>0)
+               if(!currentToken.empty())
                {
-                       pTokenVector->push_back(currentToken);
+                       pTokenVector.push_back(currentToken);
                        currentToken.clear();
                }
 
-               return pTokenVector->size();
-       }
-
-       AMCPCommand::ptr_type create_command(const std::wstring& str, ClientInfoPtr client)
-       {
-               std::wstring s = boost::to_upper_copy(str);
-               if (     s == L"DIAG")                  return std::make_shared<DiagnosticsCommand>(client);
-               else if (s == L"CHANNEL_GRID")  return std::make_shared<ChannelGridCommand>(client, channels_);
-               else if (s == L"DATA")                  return std::make_shared<DataCommand>(client);
-               else if (s == L"CINF")                  return std::make_shared<CinfCommand>(client, media_info_repo_);
-               else if (s == L"INFO")                  return std::make_shared<InfoCommand>(client, channels_, system_info_provider_repo_, cg_registry_);
-               else if (s == L"CLS")                   return std::make_shared<ClsCommand>(client, media_info_repo_);
-               else if (s == L"TLS")                   return std::make_shared<TlsCommand>(client, cg_registry_);
-               else if (s == L"VERSION")               return std::make_shared<VersionCommand>(client, system_info_provider_repo_);
-               else if (s == L"BYE")                   return std::make_shared<ByeCommand>(client);
-               else if (s == L"LOCK")                  return std::make_shared<LockCommand>(client, channels_);
-               else if (s == L"LOG")                   return std::make_shared<LogCommand>(client);
-               else if (s == L"THUMBNAIL")             return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
-               else if (s == L"KILL")                  return std::make_shared<KillCommand>(client, shutdown_server_now_);
-               else if (s == L"RESTART")               return std::make_shared<RestartCommand>(client, shutdown_server_now_);
-
-               return nullptr;
-       }
-
-       AMCPCommand::ptr_type create_channel_command(const std::wstring& str, ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index)
-       {
-               std::wstring s = boost::to_upper_copy(str);
-       
-               if (     s == L"MIXER")         return std::make_shared<MixerCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"CALL")          return std::make_shared<CallCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"SWAP")          return std::make_shared<SwapCommand>(client, channel, channel_index, layer_index, channels_);
-               else if (s == L"LOAD")          return std::make_shared<LoadCommand>(client, channel, channel_index, layer_index, channels_);
-               else if (s == L"LOADBG")        return std::make_shared<LoadbgCommand>(client, channel, channel_index, layer_index, channels_);
-               else if (s == L"ADD")           return std::make_shared<AddCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"REMOVE")        return std::make_shared<RemoveCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"PAUSE")         return std::make_shared<PauseCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"PLAY")          return std::make_shared<PlayCommand>(client, channel, channel_index, layer_index, channels_);
-               else if (s == L"STOP")          return std::make_shared<StopCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"CLEAR")         return std::make_shared<ClearCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"PRINT")         return std::make_shared<PrintCommand>(client, channel, channel_index, layer_index);
-               else if (s == L"CG")            return std::make_shared<CGCommand>(client, channel, channel_index, layer_index, cg_registry_, channels_);
-               else if (s == L"SET")           return std::make_shared<SetCommand>(client, channel, channel_index, layer_index);
-
-               return nullptr;
+               return pTokenVector.size();
        }
 };
 
-
-AMCPProtocolStrategy::AMCPProtocolStrategy(
-               const std::vector<spl::shared_ptr<core::video_channel>>& channels,
-               const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
-               const spl::shared_ptr<core::media_info_repository>& media_info_repo,
-               const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
-               const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
-               std::promise<bool>& shutdown_server_now)
-       : impl_(spl::make_unique<impl>(
-                       channels,
-                       thumb_gen,
-                       media_info_repo,
-                       system_info_provider_repo,
-                       cg_registry,
-                       shutdown_server_now))
+AMCPProtocolStrategy::AMCPProtocolStrategy(const spl::shared_ptr<amcp_command_repository>& repo)
+       : impl_(spl::make_unique<impl>(repo))
 {
 }
 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
index 967fd9311223cd84ee32c41ff395a9b553e4a589..3e4a991b27d1e21b9d22fddc4667c3fa9c6f69d5 100644 (file)
@@ -41,13 +41,7 @@ namespace caspar { namespace protocol { namespace amcp {
 class AMCPProtocolStrategy : public IO::IProtocolStrategy, boost::noncopyable
 {
 public:
-       AMCPProtocolStrategy(
-                       const std::vector<spl::shared_ptr<core::video_channel>>& channels,
-                       const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
-                       const spl::shared_ptr<core::media_info_repository>& media_info_repo,
-                       const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
-                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
-                       std::promise<bool>& shutdown_server_now);
+       AMCPProtocolStrategy(const spl::shared_ptr<class amcp_command_repository>& repo);
 
        virtual ~AMCPProtocolStrategy();
 
diff --git a/protocol/amcp/amcp_command_repository.cpp b/protocol/amcp/amcp_command_repository.cpp
new file mode 100644 (file)
index 0000000..d993c73
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* 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 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 <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#include "../StdAfx.h"
+
+#include "amcp_command_repository.h"
+
+#include <map>
+
+namespace caspar { namespace protocol { namespace amcp {
+
+AMCPCommand::ptr_type find_command(
+               const std::map<std::wstring, std::pair<amcp_command_func, int>>& commands,
+               const std::wstring& str,
+               const command_context& ctx,
+               std::list<std::wstring>& tokens)
+{
+       std::wstring subcommand;
+
+       if (!tokens.empty())
+               subcommand = boost::to_upper_copy(tokens.front());
+
+       // Start with subcommand syntax like MIXER CLEAR etc
+       if (!subcommand.empty())
+       {
+               auto s = str + L" " + subcommand;
+               auto subcmd = commands.find(s);
+
+               if (subcmd != commands.end())
+               {
+                       tokens.pop_front();
+                       return std::make_shared<AMCPCommand>(ctx, subcmd->second.first, subcmd->second.second, s);
+               }
+       }
+
+       // Resort to ordinary command
+       auto s = str;
+       auto command = commands.find(s);
+
+       if (command != commands.end())
+               return std::make_shared<AMCPCommand>(ctx, command->second.first, command->second.second, s);
+
+       return nullptr;
+}
+
+struct amcp_command_repository::impl
+{
+       std::vector<channel_context>                                                            channels;
+       std::shared_ptr<core::thumbnail_generator>                                      thumb_gen;
+       spl::shared_ptr<core::media_info_repository>                            media_info_repo;
+       spl::shared_ptr<core::system_info_provider_repository>          system_info_provider_repo;
+       spl::shared_ptr<core::cg_producer_registry>                                     cg_registry;
+       spl::shared_ptr<core::help_repository>                                          help_repo;
+       std::promise<bool>&                                                                                     shutdown_server_now;
+
+       std::map<std::wstring, std::pair<amcp_command_func, int>>       commands;
+       std::map<std::wstring, std::pair<amcp_command_func, int>>       channel_commands;
+
+       impl(
+                       const std::vector<spl::shared_ptr<core::video_channel>>& channels,
+                       const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
+                       const spl::shared_ptr<core::media_info_repository>& media_info_repo,
+                       const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
+                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
+                       const spl::shared_ptr<core::help_repository>& help_repo,
+                       std::promise<bool>& shutdown_server_now)
+               : thumb_gen(thumb_gen)
+               , media_info_repo(media_info_repo)
+               , system_info_provider_repo(system_info_provider_repo)
+               , cg_registry(cg_registry)
+               , help_repo(help_repo)
+               , shutdown_server_now(shutdown_server_now)
+       {
+               int index = 0;
+               for (const auto& channel : channels)
+               {
+                       std::wstring lifecycle_key = L"lock" + boost::lexical_cast<std::wstring>(index);
+                       this->channels.push_back(channel_context(channel, lifecycle_key));
+                       ++index;
+               }
+       }
+};
+
+amcp_command_repository::amcp_command_repository(
+               const std::vector<spl::shared_ptr<core::video_channel>>& channels,
+               const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
+               const spl::shared_ptr<core::media_info_repository>& media_info_repo,
+               const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
+               const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
+               const spl::shared_ptr<core::help_repository>& help_repo,
+               std::promise<bool>& shutdown_server_now)
+       : impl_(new impl(channels, thumb_gen, media_info_repo, system_info_provider_repo, cg_registry, help_repo, shutdown_server_now))
+{
+}
+
+AMCPCommand::ptr_type amcp_command_repository::create_command(const std::wstring& s, IO::ClientInfoPtr client, std::list<std::wstring>& tokens)
+{
+       auto& self = *impl_;
+
+       command_context ctx(
+                       client,
+                       channel_context(),
+                       -1,
+                       -1,
+                       self.channels,
+                       self.help_repo,
+                       self.media_info_repo,
+                       self.cg_registry,
+                       self.system_info_provider_repo,
+                       self.thumb_gen,
+                       self.shutdown_server_now);
+
+       auto command = find_command(self.commands, s, ctx, tokens);
+
+       if (command)
+               return command;
+
+       return nullptr;
+}
+
+const std::vector<channel_context>& amcp_command_repository::channels() const
+{
+       return impl_->channels;
+}
+
+AMCPCommand::ptr_type amcp_command_repository::create_channel_command(
+               const std::wstring& s,
+               IO::ClientInfoPtr client,
+               unsigned int channel_index,
+               int layer_index,
+               std::list<std::wstring>& tokens)
+{
+       auto& self = *impl_;
+
+       auto channel = self.channels.at(channel_index);
+
+       command_context ctx(
+                       std::move(client),
+                       channel,
+                       channel_index,
+                       layer_index,
+                       self.channels,
+                       self.help_repo,
+                       self.media_info_repo,
+                       self.cg_registry,
+                       self.system_info_provider_repo,
+                       self.thumb_gen,
+                       self.shutdown_server_now);
+
+       auto command = find_command(self.channel_commands, s, ctx, tokens);
+
+       if (command)
+               return command;
+
+       return nullptr;
+}
+
+void amcp_command_repository::register_command(
+               std::wstring category,
+               std::wstring name,
+               core::help_item_describer describer,
+               amcp_command_func command,
+               int min_num_params)
+{
+       auto& self = *impl_;
+       self.help_repo->register_item({ L"AMCP", category }, name, describer);
+       self.commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params)));
+}
+
+void amcp_command_repository::register_channel_command(
+               std::wstring category,
+               std::wstring name,
+               core::help_item_describer describer,
+               amcp_command_func command,
+               int min_num_params)
+{
+       auto& self = *impl_;
+       self.help_repo->register_item({ L"AMCP", category }, name, describer);
+       self.channel_commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params)));
+}
+
+}}}
diff --git a/protocol/amcp/amcp_command_repository.h b/protocol/amcp/amcp_command_repository.h
new file mode 100644 (file)
index 0000000..daa2f7b
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* 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 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 <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include "AMCPCommand.h"
+#include "../util/ClientInfo.h"
+
+#include <common/memory.h>
+
+#include <core/fwd.h>
+#include <core/help/help_repository.h>
+
+#include <future>
+
+namespace caspar { namespace protocol { namespace amcp {
+
+class amcp_command_repository : boost::noncopyable
+{
+public:
+       amcp_command_repository(
+                       const std::vector<spl::shared_ptr<core::video_channel>>& channels,
+                       const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
+                       const spl::shared_ptr<core::media_info_repository>& media_info_repo,
+                       const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
+                       const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
+                       const spl::shared_ptr<core::help_repository>& help_repo,
+                       std::promise<bool>& shutdown_server_now);
+
+       AMCPCommand::ptr_type create_command(const std::wstring& s, IO::ClientInfoPtr client, std::list<std::wstring>& tokens);
+       AMCPCommand::ptr_type create_channel_command(
+                       const std::wstring& s,
+                       IO::ClientInfoPtr client,
+                       unsigned int channel_index,
+                       int layer_index,
+                       std::list<std::wstring>& tokens);
+
+       const std::vector<channel_context>& channels() const;
+
+       void register_command(std::wstring category, std::wstring name, core::help_item_describer describer, amcp_command_func command, int min_num_params);
+       void register_channel_command(std::wstring category, std::wstring name, core::help_item_describer describer, amcp_command_func command, int min_num_params);
+private:
+       struct impl;
+       spl::shared_ptr<impl> impl_;
+};
+
+}}}
index 8e64d4db9b7f54de0b41ba74d4021767e3c87477..7a1161fd20264854ee9042c516a9e10d50c795ea 100644 (file)
@@ -8,11 +8,11 @@ namespace caspar { namespace protocol { namespace amcp {
 
 class channel_context
 {
-       channel_context();
 public:
-       explicit channel_context(const spl::shared_ptr<core::video_channel>& c, const std::wstring& lifecycle_key) : channel(c), lock(spl::make_shared<caspar::IO::lock_container>(lifecycle_key)) {}
-       spl::shared_ptr<core::video_channel>            channel;
-       caspar::IO::lock_container::ptr_type            lock;
+       explicit channel_context() {}
+       explicit channel_context(const std::shared_ptr<core::video_channel>& c, const std::wstring& lifecycle_key) : channel(c), lock(std::make_shared<caspar::IO::lock_container>(lifecycle_key)) {}
+       std::shared_ptr<core::video_channel>            channel;
+       std::shared_ptr<caspar::IO::lock_container>     lock;
 };
 
 }}}
\ No newline at end of file
index 43d35a1eee0577ae221d72145c88648bbeb92c5d..1c5e787ce03fcc304bdedcd17806a763583f1a21 100644 (file)
@@ -11,8 +11,6 @@ namespace caspar { namespace IO {
        class lock_container : public boost::noncopyable
        {
        public:
-               typedef spl::shared_ptr<lock_container> ptr_type;
-
                lock_container(const std::wstring& lifecycle_key);
                ~lock_container();
 
index 8f422dfbb3cddeb484efb039d377e0e1c525ce1a..c3d5714ab7c3a41824df9512b4147dae46b88463 100644 (file)
@@ -24,6 +24,7 @@
 #include "strategy_adapters.h"\r
 \r
 #include <boost/locale.hpp>\r
+#include <boost/algorithm/string/replace.hpp>\r
 \r
 namespace caspar { namespace IO {\r
 \r
@@ -67,9 +68,18 @@ public:
 \r
        void send(std::basic_string<wchar_t>&& data) override\r
        {\r
-               auto str = boost::locale::conv::from_utf<wchar_t>(std::move(data), codepage_);\r
+               auto str = boost::locale::conv::from_utf<wchar_t>(data, codepage_);\r
 \r
                client_->send(std::move(str));\r
+\r
+               if (data.length() < 512)\r
+               {\r
+                       boost::replace_all(data, L"\n", L"\\n");\r
+                       boost::replace_all(data, L"\r", L"\\r");\r
+                       CASPAR_LOG(info) << L"Sent message to " << client_->print() << L":" << data;\r
+               }\r
+               else\r
+                       CASPAR_LOG(info) << L"Sent more than 512 bytes to " << client_->print();\r
        }\r
 \r
        void disconnect() override\r
index f7d6e9f9c6e9b6bab5fbcb666b58648d62b6de9b..0fa28cdcbdf42e3d3083b9493172db71f170e8c4 100644 (file)
@@ -111,23 +111,8 @@ void print_system_info(const spl::shared_ptr<core::system_info_provider_reposito
                print_child(L"", elem.first, elem.second);
 }
 
-void do_run(server& caspar_server, std::promise<bool>& shutdown_server_now)
+void do_run(std::weak_ptr<caspar::IO::protocol_strategy<wchar_t>> amcp, std::promise<bool>& shutdown_server_now)
 {
-       // Create a dummy client which prints amcp responses to console.
-       auto console_client = spl::make_shared<IO::ConsoleClientInfo>();
-       
-       // Create a amcp parser for console commands.
-       auto amcp = spl::make_shared<caspar::IO::delimiter_based_chunking_strategy_factory<wchar_t>>(
-                       L"\r\n",
-                       spl::make_shared<caspar::IO::legacy_strategy_adapter_factory>(
-                                       spl::make_shared<protocol::amcp::AMCPProtocolStrategy>(
-                                                       caspar_server.channels(),
-                                                       caspar_server.get_thumbnail_generator(),
-                                                       caspar_server.get_media_info_repo(),
-                                                       caspar_server.get_system_info_provider_repo(),
-                                                       caspar_server.get_cg_registry(),
-                                                       shutdown_server_now)))->create(console_client);
-
        std::wstring wcmd;
        while(true)
        {
@@ -215,7 +200,11 @@ void do_run(server& caspar_server, std::promise<bool>& shutdown_server_now)
                }
 
                wcmd += L"\r\n";
-               amcp->parse(wcmd);
+               auto strong = amcp.lock();
+               if (strong)
+                       strong->parse(wcmd);
+               else
+                       break;
        }
 };
 
@@ -239,19 +228,19 @@ bool run()
        
        caspar_server.start();
 
-       //auto console_obs = reactive::make_observer([](const monitor::event& e)
-       //{
-       //      std::stringstream str;
-       //      str << e;
-       //      CASPAR_LOG(trace) << str.str().c_str();
-       //});
-
-       //caspar_server.subscribe(console_obs);
+       // Create a dummy client which prints amcp responses to console.
+       auto console_client = spl::make_shared<IO::ConsoleClientInfo>();
 
+       // Create a amcp parser for console commands.
+       auto amcp = spl::make_shared<caspar::IO::delimiter_based_chunking_strategy_factory<wchar_t>>(
+                       L"\r\n",
+                       spl::make_shared<caspar::IO::legacy_strategy_adapter_factory>(
+                                       spl::make_shared<protocol::amcp::AMCPProtocolStrategy>(
+                                                       caspar_server.get_amcp_command_repository())))->create(console_client);
 
        // Use separate thread for the blocking console input, will be terminated 
        // anyway when the main thread terminates.
-       boost::thread stdin_thread(std::bind(do_run, std::ref(caspar_server), std::ref(shutdown_server_now)));  //compiler didn't like lambda here...
+       boost::thread stdin_thread(std::bind(do_run, amcp, std::ref(shutdown_server_now)));     //compiler didn't like lambda here...
        stdin_thread.detach();
        return shutdown_server.get();
 }
@@ -315,6 +304,7 @@ int main(int argc, char** argv)
                return_code = run() ? 5 : 0;
                
                boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
+
                CASPAR_LOG(info) << "Successfully shutdown CasparCG Server.";
        }
        catch(boost::property_tree::file_parser_error&)
index bd4feeea39f58513f23eb8384ac172eed4cb61b8..79bd335483d1e4eb27650874e76ebf08b8c317ef 100644 (file)
 #include <core/diagnostics/call_context.h>
 #include <core/diagnostics/osd_graph.h>
 #include <core/system_info_provider.h>
+#include <core/help/help_repository.h>
 
 #include <modules/image/consumer/image_consumer.h>
 
 #include <protocol/asio/io_service_manager.h>
 #include <protocol/amcp/AMCPProtocolStrategy.h>
+#include <protocol/amcp/amcp_command_repository.h>
+#include <protocol/amcp/AMCPCommandsImpl.h>
 #include <protocol/cii/CIIProtocolStrategy.h>
 #include <protocol/clk/CLKProtocolStrategy.h>
 #include <protocol/util/AsyncEventServer.h>
@@ -81,6 +84,8 @@ struct server::impl : boost::noncopyable
        spl::shared_ptr<monitor::subject>                                       monitor_subject_;
        spl::shared_ptr<monitor::subject>                                       diag_subject_                                   = core::diagnostics::get_or_create_subject();
        accelerator::accelerator                                                        accelerator_;
+       spl::shared_ptr<help_repository>                                        help_repo_;
+       std::shared_ptr<amcp::amcp_command_repository>          amcp_command_repo_;
        std::vector<spl::shared_ptr<IO::AsyncEventServer>>      async_servers_;
        std::shared_ptr<IO::AsyncEventServer>                           primary_amcp_server_;
        osc::client                                                                                     osc_client_;
@@ -261,7 +266,17 @@ struct server::impl : boost::noncopyable
        }
                
        void setup_controllers(const boost::property_tree::wptree& pt)
-       {               
+       {
+               amcp_command_repo_ = spl::make_shared<amcp::amcp_command_repository>(
+                               channels_,
+                               thumbnail_generator_,
+                               media_info_repo_,
+                               system_info_provider_repo_,
+                               cg_registry_,
+                               help_repo_,
+                               shutdown_server_now_);
+               amcp::register_commands(*amcp_command_repo_);
+
                using boost::property_tree::wptree;
                for (auto& xml_controller : pt.get_child(L"configuration.controllers"))
                {
@@ -294,13 +309,7 @@ struct server::impl : boost::noncopyable
                using namespace IO;
 
                if(boost::iequals(name, L"AMCP"))
-                       return wrap_legacy_protocol("\r\n", spl::make_shared<amcp::AMCPProtocolStrategy>(
-                                       channels_,
-                                       thumbnail_generator_,
-                                       media_info_repo_,
-                                       system_info_provider_repo_,
-                                       cg_registry_,
-                                       shutdown_server_now_));
+                       return wrap_legacy_protocol("\r\n", spl::make_shared<amcp::AMCPProtocolStrategy>(spl::make_shared_ptr(amcp_command_repo_)));
                else if(boost::iequals(name, L"CII"))
                        return wrap_legacy_protocol("\r\n", spl::make_shared<cii::CIIProtocolStrategy>(channels_, cg_registry_));
                else if(boost::iequals(name, L"CLOCK"))
@@ -336,14 +345,8 @@ struct server::impl : boost::noncopyable
 
 server::server(std::promise<bool>& shutdown_server_now) : impl_(new impl(shutdown_server_now)){}
 void server::start() { impl_->start(); }
-const std::vector<spl::shared_ptr<video_channel>> server::channels() const
-{
-       return impl_->channels_;
-}
-std::shared_ptr<core::thumbnail_generator> server::get_thumbnail_generator() const {return impl_->thumbnail_generator_; }
-spl::shared_ptr<media_info_repository> server::get_media_info_repo() const { return impl_->media_info_repo_; }
 spl::shared_ptr<core::system_info_provider_repository> server::get_system_info_provider_repo() const { return impl_->system_info_provider_repo_; }
-spl::shared_ptr<core::cg_producer_registry> server::get_cg_registry() const { return impl_->cg_registry_; }
+spl::shared_ptr<protocol::amcp::amcp_command_repository> server::get_amcp_command_repository() const { return spl::make_shared_ptr(impl_->amcp_command_repo_); }
 core::monitor::subject& server::monitor_output() { return *impl_->monitor_subject_; }
 
 }
index a67010a33bf12888e00a9dff979e47fc461df7de..a8241813915836d0b5af3e223802fba288feb01a 100644 (file)
@@ -31,6 +31,8 @@
 
 #include <vector>
 
+FORWARD3(caspar, protocol, amcp, class amcp_command_repository);
+
 namespace caspar {
 
 class server final : public boost::noncopyable
@@ -38,11 +40,8 @@ class server final : public boost::noncopyable
 public:
        explicit server(std::promise<bool>& shutdown_server_now);
        void start();
-       const std::vector<spl::shared_ptr<core::video_channel>> channels() const;
-       std::shared_ptr<core::thumbnail_generator> get_thumbnail_generator() const;
-       spl::shared_ptr<core::media_info_repository> get_media_info_repo() const;
        spl::shared_ptr<core::system_info_provider_repository> get_system_info_provider_repo() const;
-       spl::shared_ptr<core::cg_producer_registry> get_cg_registry() const;
+       spl::shared_ptr<protocol::amcp::amcp_command_repository> get_amcp_command_repository() const;
 
        core::monitor::subject& monitor_output();
 private: