]> git.sesse.net Git - casparcg/commitdiff
Manually merged 0edf4e5 (excluding changes to ffmpeg) from master
authorniklaspandersson <niklas.p.andersson@svt.se>
Thu, 29 Aug 2013 13:39:56 +0000 (15:39 +0200)
committerniklaspandersson <niklas.p.andersson@svt.se>
Thu, 29 Aug 2013 13:39:56 +0000 (15:39 +0200)
31 files changed:
common/common.vcxproj
common/common.vcxproj.filters
common/diagnostics/graph.cpp
common/diagnostics/graph.h
common/env.cpp
common/env.h
common/filesystem_monitor.h [new file with mode: 0644]
common/polling_filesystem_monitor.cpp [new file with mode: 0644]
common/polling_filesystem_monitor.h [new file with mode: 0644]
core/core.vcxproj
core/core.vcxproj.filters
core/producer/frame_producer.cpp
core/producer/frame_producer.h
core/producer/separated/separated_producer.cpp
core/producer/separated/separated_producer.h
core/producer/text/utils/text_info.h
core/thumbnail_generator.cpp [new file with mode: 0644]
core/thumbnail_generator.h [new file with mode: 0644]
modules/image/consumer/image_consumer.cpp
modules/image/consumer/image_consumer.h
modules/image/image.cpp
modules/image/producer/image_producer.cpp
modules/image/producer/image_producer.h
protocol/amcp/AMCPCommandsImpl.cpp
protocol/amcp/AMCPCommandsImpl.h
protocol/amcp/AMCPProtocolStrategy.cpp
protocol/amcp/AMCPProtocolStrategy.h
shell/casparcg.config
shell/main.cpp
shell/server.cpp
shell/server.h

index 4002216e8180460a6b7634d53496605f37c45963..8be47e5badcfa59f7a5271599804aa1f19b948b0 100644 (file)
     <ClInclude Include="env.h" />\r
     <ClInclude Include="except.h" />\r
     <ClInclude Include="executor.h" />\r
+    <ClInclude Include="filesystem_monitor.h" />\r
     <ClInclude Include="forward.h" />\r
     <ClInclude Include="future.h" />\r
     <ClInclude Include="future_fwd.h" />\r
     <ClInclude Include="os\windows\windows.h" />\r
     <ClInclude Include="page_locked_allocator.h" />\r
     <ClInclude Include="param.h" />\r
+    <ClInclude Include="polling_filesystem_monitor.h" />\r
     <ClInclude Include="prec_timer.h" />\r
     <ClInclude Include="reactive.h" />\r
     <ClInclude Include="semaphore.h" />\r
       <ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Develop|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
       <ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
     </ClCompile>\r
+    <ClCompile Include="polling_filesystem_monitor.cpp" />\r
     <ClCompile Include="prec_timer.cpp">\r
       <ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Release|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
       <ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
index 3914fc76621a8637be092938c6876f7aa32110f4..6392e7af01df4e96281c57a68123439948247782 100644 (file)
@@ -55,6 +55,9 @@
     <ClCompile Include="compiler\vs\stack_walker.cpp">\r
       <Filter>source\compiler\vs</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="polling_filesystem_monitor.cpp">\r
+      <Filter>source</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="compiler\vs\disable_silly_warnings.h">\r
     <ClInclude Include="endian.h">\r
       <Filter>source</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="filesystem_monitor.h">\r
+      <Filter>source</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="polling_filesystem_monitor.h">\r
+      <Filter>source</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
 </Project>
\ No newline at end of file
index e89e26ff037551f205eaa6c35124a01089a85fb9..d6557122c58b886ea6f0079e743df2490dd6dc15 100644 (file)
@@ -269,6 +269,7 @@ struct graph::impl : public drawable
 
        tbb::spin_mutex mutex_;
        std::wstring text_;
+       bool auto_reset_;
 
        impl()
        {
@@ -297,6 +298,13 @@ struct graph::impl : public drawable
        {
                lines_[name].set_color(color);
        }
+       void auto_reset()
+       {
+               lock(mutex_, [this]
+               {
+                       auto_reset_ = true;
+               });
+       }
                
 private:
        void render(sf::RenderTarget& target)
@@ -306,9 +314,12 @@ private:
                const size_t text_offset = (text_size+text_margin*2)*2;
 
                std::wstring text_str;
+               bool auto_reset;
+
                {
                        tbb::spin_mutex::scoped_lock lock(mutex_);
                        text_str = text_;
+                       auto_reset = auto_reset_;
                }
 
                sf::String text(text_str.c_str(), sf::Font::GetDefaultFont(), text_size);
@@ -365,7 +376,11 @@ private:
                        //target.Draw(diagnostics::guide(0.0f, color(1.0f, 1.0f, 1.0f, 0.6f)));
 
                        for(auto it = lines_.begin(); it != lines_.end(); ++it)         
+                       {
                                target.Draw(it->second);
+                               if(auto_reset)
+                                       it->second.set_value(0.0f);
+                       }
                
                glPopMatrix();
        }
@@ -382,7 +397,7 @@ void graph::set_text(const std::wstring& value){impl_->set_text(value);}
 void graph::set_value(const std::string& name, double value){impl_->set_value(name, value);}
 void graph::set_color(const std::string& name, int color){impl_->set_color(name, color);}
 void graph::set_tag(const std::string& name){impl_->set_tag(name);}
-
+void graph::auto_reset(){impl_->auto_reset(); }
 void register_graph(const spl::shared_ptr<graph>& graph)
 {
        context::register_drawable(graph->impl_);
index e52a2c9d769091c81a1b9cfe955b4227c568afac..3558c4b6d112f40559e5938a6b2e5e3cb080a2b1 100644 (file)
@@ -42,6 +42,7 @@ public:
        void set_value(const std::string& name, double value);
        void set_color(const std::string& name, int color);
        void set_tag(const std::string& name);
+       void auto_reset();
 private:
        struct impl;
        std::shared_ptr<impl> impl_;
index 5aca238f94d4fdf267e2bf8f70f3d60cd231433f..cf69aae4be428426a6aae92915e788d13eeb32f5 100644 (file)
@@ -44,6 +44,7 @@ std::wstring log;
 std::wstring ftemplate;
 std::wstring data;
 std::wstring font;
+std::wstring thumbnails;
 boost::property_tree::wptree pt;
 
 void check_is_configured()
@@ -67,6 +68,7 @@ void configure(const std::wstring& filename)
                ftemplate       = boost::filesystem3::complete(paths.get(L"template-path", initialPath + L"\\template\\")).wstring();           
                data            = paths.get(L"data-path", initialPath + L"\\data\\");
                font            = paths.get(L"font-path", initialPath + L"\\fonts\\");
+               thumbnails      = paths.get(L"thumbnails-path", initialPath + L"\\data\\");
 
                try
                {
@@ -120,6 +122,10 @@ void configure(const std::wstring& filename)
                auto data_path = boost::filesystem::path(data);
                if(!boost::filesystem::exists(data_path))
                        boost::filesystem::create_directories(data_path);
+
+               auto thumbnails_path = boost::filesystem::path(thumbnails);
+               if(!boost::filesystem::exists(thumbnails_path))
+                       boost::filesystem::create_directories(thumbnails_path);
        }
        catch(...)
        {
@@ -158,6 +164,12 @@ const std::wstring& font_folder()
        return font;
 }
 
+const std::wstring& thumbnails_folder()
+{
+       check_is_configured();
+       return thumbnails;
+}
+
 #define QUOTE(str) #str
 #define EXPAND_AND_QUOTE(str) QUOTE(str)
 
index f68de259f31fe2f8b657726a9802080e1f5b2863..7be75018e49209e34a1ad99e41e55527b6c9703e 100644 (file)
@@ -33,6 +33,7 @@ const std::wstring& media_folder();
 const std::wstring& log_folder();
 const std::wstring& template_folder();
 const std::wstring& data_folder();
+const std::wstring& thumbnails_folder();
 const std::wstring& version();
 
 const std::wstring& font_folder();
diff --git a/common/filesystem_monitor.h b/common/filesystem_monitor.h
new file mode 100644 (file)
index 0000000..c69b4f3
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+* 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 <functional>
+#include <set>
+
+#include <boost/filesystem.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/thread/future.hpp>
+
+#include "memory.h"
+
+namespace caspar {
+
+/**
+ * A handle to an active filesystem monitor. Will stop scanning when destroyed.
+ */
+class filesystem_monitor : boost::noncopyable
+{
+public:
+       typedef spl::shared_ptr<filesystem_monitor> ptr;
+
+       virtual ~filesystem_monitor() {}
+
+       /**
+        * @return a future made available when the initially available files have been
+        *         processed.
+        */
+       virtual boost::unique_future<void> initial_files_processed() = 0;
+
+       /**
+        * Reemmit the already known files as MODIFIED events.
+        */
+       virtual void reemmit_all() = 0;
+
+       /**
+        * Reemmit a specific file as a MODIFIED event.
+        *
+        * @param file The file to reemmit.
+        */
+       virtual void reemmit(const boost::filesystem::wpath& file) = 0;
+};
+
+/**
+ * The possible filesystem events.
+ */
+enum filesystem_event
+{
+       CREATED = 1,
+       REMOVED = 2,
+       MODIFIED = 4,
+       // Only used for describing a bitmask where all events are wanted. Never used when calling a handler.
+       ALL = 7
+};
+
+/**
+ * Handles filesystem events.
+ * <p>
+ * Will always be called from the same thread each time it is called.
+ *
+ * @param event Can be CREATED, REMOVED or MODIFIED.
+ * @param file  The file affected.
+ */
+typedef std::function<void (filesystem_event event, const boost::filesystem::wpath& file)> filesystem_monitor_handler;
+
+/**
+ * Called when the initially available files has been found.
+ * <p>
+ * Will be called from the same thread as filesystem_monitor_handler is called.
+ *
+ * @param initial_files The files that were initially available.
+ */
+typedef std::function<void (const std::set<boost::filesystem::wpath>& initial_files)> initial_files_handler;
+
+/**
+ * Factory for creating filesystem monitors.
+ */
+class filesystem_monitor_factory : boost::noncopyable
+{
+public:
+       virtual ~filesystem_monitor_factory() {}
+
+       /**
+        * Create a new monitor.
+        *
+        * @param folder_to_watch         The folder to recursively watch.
+        * @param events_of_interest_mask A bitmask of the events to prenumerate on.
+        * @param report_already_existing Whether to initially report all files in
+        *                                the folder as CREATED or to only care
+        *                                about changes.
+        * @param handler                 The handler to call each time a change is
+        *                                detected (only events defined by
+        *                                events_of_interest_mask).
+        * @param initial_files_handler   The optional handler to call when the
+        *                                initially available files has been
+        *                                discovered.
+        *
+        * @return The filesystem monitor handle.
+        */
+       virtual filesystem_monitor::ptr create(
+                       const boost::filesystem::wpath& folder_to_watch,
+                       filesystem_event events_of_interest_mask,
+                       bool report_already_existing,
+                       const filesystem_monitor_handler& handler,
+                       const initial_files_handler& initial_files_handler =
+                                       [] (const std::set<boost::filesystem::wpath>&) { }) = 0;
+};
+
+}
\ No newline at end of file
diff --git a/common/polling_filesystem_monitor.cpp b/common/polling_filesystem_monitor.cpp
new file mode 100644 (file)
index 0000000..95e7c97
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+* 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 "polling_filesystem_monitor.h"
+
+#include <map>
+#include <set>
+#include <iostream>
+
+#include <boost/thread.hpp>
+#include <boost/foreach.hpp>
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/copy.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem/convenience.hpp>
+
+#include <tbb/atomic.h>
+#include <tbb/concurrent_queue.h>
+
+#include "executor.h"
+
+namespace caspar {
+
+class exception_protected_handler
+{
+       filesystem_monitor_handler handler_;
+public:
+       exception_protected_handler(const filesystem_monitor_handler& handler)
+               : handler_(handler)
+       {
+       }
+
+       void operator()(filesystem_event event, const boost::filesystem::wpath& file)
+       {
+               try
+               {
+                       boost::this_thread::interruption_point();
+                       handler_(event, file);
+                       boost::this_thread::interruption_point();
+               }
+               catch (const boost::thread_interrupted&)
+               {
+                       throw;
+               }
+               catch (...)
+               {
+                       CASPAR_LOG_CURRENT_EXCEPTION();
+               }
+       }
+};
+
+class directory_monitor
+{
+       bool report_already_existing_;
+       boost::filesystem::wpath folder_;
+       filesystem_event events_mask_;
+       filesystem_monitor_handler handler_;
+       initial_files_handler initial_files_handler_;
+       bool first_scan_;
+       std::map<boost::filesystem::wpath, std::time_t> files_;
+       std::map<boost::filesystem::wpath, uintmax_t> being_written_sizes_;
+public:
+       directory_monitor(
+                       bool report_already_existing,
+                       const boost::filesystem::wpath& folder,
+                       filesystem_event events_mask,
+                       const filesystem_monitor_handler& handler,
+                       const initial_files_handler& initial_files_handler)
+               : report_already_existing_(report_already_existing)
+               , folder_(folder)
+               , events_mask_(events_mask)
+               , handler_(exception_protected_handler(handler))
+               , initial_files_handler_(initial_files_handler)
+               , first_scan_(true)
+       {
+       }
+
+       void reemmit_all()
+       {
+               if ((events_mask_ & MODIFIED) == 0)
+                       return;
+
+               BOOST_FOREACH(auto& file, files_)
+                       handler_(MODIFIED, file.first);
+       }
+
+       void reemmit(const boost::filesystem::wpath& file)
+       {
+               if ((events_mask_ & MODIFIED) == 0)
+                       return;
+
+               if (files_.find(file) != files_.end() && boost::filesystem::exists(file))
+                       handler_(MODIFIED, file);
+       }
+
+       void scan(const boost::function<bool ()>& should_abort)
+       {
+               static const std::time_t NO_LONGER_WRITING_AGE = 3; // Assume std::time_t is expressed in seconds
+               using namespace boost::filesystem;
+
+               bool interested_in_removed = (events_mask_ & REMOVED) > 0;
+               bool interested_in_created = (events_mask_ & CREATED) > 0;
+               bool interested_in_modified = (events_mask_ & MODIFIED) > 0;
+
+               std::set<wpath> removed_files;
+               boost::copy(
+                               files_ | boost::adaptors::map_keys,
+                               std::insert_iterator<decltype(removed_files)>(removed_files, removed_files.end()));
+
+               std::set<wpath> initial_files;
+
+               for (boost::filesystem3::wrecursive_directory_iterator iter(folder_); iter != boost::filesystem3::wrecursive_directory_iterator(); ++iter)
+               {
+                       if (should_abort())
+                               return;
+
+                       auto& path = iter->path();
+
+                       if (is_directory(path))
+                               continue;
+
+                       auto now = std::time(nullptr);
+                       std::time_t current_mtime;
+
+                       try
+                       {
+                               current_mtime = last_write_time(path);
+                       }
+                       catch (...)
+                       {
+                               // Probably removed, will be captured the next round.
+                               continue;
+                       }
+
+                       auto time_since_written_to = now - current_mtime;
+                       bool no_longer_being_written_to = time_since_written_to >= NO_LONGER_WRITING_AGE;
+                       auto previous_it = files_.find(path);
+                       bool already_known = previous_it != files_.end();
+
+                       if (already_known && no_longer_being_written_to)
+                       {
+                               bool modified = previous_it->second != current_mtime;
+
+                               if (modified && can_read_file(path))
+                               {
+                                       if (interested_in_modified)
+                                               handler_(MODIFIED, path);
+
+                                       files_[path] = current_mtime;
+                                       being_written_sizes_.erase(path);
+                               }
+                       }
+                       else if (no_longer_being_written_to && can_read_file(path))
+                       {
+                               if (interested_in_created && (report_already_existing_ || !first_scan_))
+                                       handler_(CREATED, path);
+
+                               if (first_scan_)
+                                       initial_files.insert(path);
+
+                               files_.insert(std::make_pair(path, current_mtime));
+                               being_written_sizes_.erase(path);
+                       }
+
+                       removed_files.erase(path);
+               }
+
+               BOOST_FOREACH(auto& path, removed_files)
+               {
+                       files_.erase(path);
+                       being_written_sizes_.erase(path);
+
+                       if (interested_in_removed)
+                               handler_(REMOVED, path);
+               }
+
+               if (first_scan_)
+                       initial_files_handler_(initial_files);
+
+               first_scan_ = false;
+       }
+private:
+       bool can_read_file(const boost::filesystem::wpath& file)
+       {
+               boost::filesystem::wifstream stream(file);
+
+               return stream.is_open();
+       }
+};
+
+class polling_filesystem_monitor : public filesystem_monitor
+{
+       directory_monitor root_monitor_;
+       boost::thread scanning_thread_;
+       tbb::atomic<bool> running_;
+       int scan_interval_millis_;
+       boost::promise<void> initial_scan_completion_;
+       tbb::concurrent_queue<boost::filesystem::wpath> to_reemmit_;
+       tbb::atomic<bool> reemmit_all_;
+public:
+       polling_filesystem_monitor(
+                       const boost::filesystem::wpath& folder_to_watch,
+                       filesystem_event events_of_interest_mask,
+                       bool report_already_existing,
+                       int scan_interval_millis,
+                       const filesystem_monitor_handler& handler,
+                       const initial_files_handler& initial_files_handler)
+               : root_monitor_(report_already_existing, folder_to_watch, events_of_interest_mask, handler, initial_files_handler)
+               , scan_interval_millis_(scan_interval_millis)
+       {
+               running_ = true;
+               reemmit_all_ = false;
+               scanning_thread_ = boost::thread([this] { scanner(); });
+       }
+
+       virtual ~polling_filesystem_monitor()
+       {
+               running_ = false;
+               scanning_thread_.interrupt();
+               scanning_thread_.join();
+       }
+
+       virtual boost::unique_future<void> initial_files_processed()
+       {
+               return initial_scan_completion_.get_future();
+       }
+
+       virtual void reemmit_all()
+       {
+               reemmit_all_ = true;
+       }
+
+       virtual void reemmit(const boost::filesystem::wpath& file)
+       {
+               to_reemmit_.push(file);
+       }
+private:
+       void scanner()
+       {
+               win32_exception::install_handler();
+
+               //detail::SetThreadName(GetCurrentThreadId(), "polling_filesystem_monitor");
+
+               bool running = scan(false);
+               initial_scan_completion_.set_value();
+
+               if (running)
+                       while (scan(true));
+       }
+
+       bool scan(bool sleep)
+       {
+               try
+               {
+                       if (sleep)
+                               boost::this_thread::sleep(boost::posix_time::milliseconds(scan_interval_millis_));
+
+                       if (reemmit_all_.fetch_and_store(false))
+                               root_monitor_.reemmit_all();
+                       else
+                       {
+                               boost::filesystem::wpath file;
+
+                               while (to_reemmit_.try_pop(file))
+                                       root_monitor_.reemmit(file);
+                       }
+
+                       root_monitor_.scan([=] { return !running_; });
+               }
+               catch (const boost::thread_interrupted&)
+               {
+               }
+               catch (...)
+               {
+                       CASPAR_LOG_CURRENT_EXCEPTION();
+               }
+
+               return running_;
+       }
+};
+
+struct polling_filesystem_monitor_factory::impl
+{
+       int scan_interval_millis;
+
+       impl(int scan_interval_millis)
+               : scan_interval_millis(scan_interval_millis)
+       {
+       }
+};
+
+polling_filesystem_monitor_factory::polling_filesystem_monitor_factory(int scan_interval_millis)
+       : impl_(new impl(scan_interval_millis))
+{
+}
+
+polling_filesystem_monitor_factory::~polling_filesystem_monitor_factory()
+{
+}
+
+filesystem_monitor::ptr polling_filesystem_monitor_factory::create(
+               const boost::filesystem::wpath& folder_to_watch,
+               filesystem_event events_of_interest_mask,
+               bool report_already_existing,
+               const filesystem_monitor_handler& handler,
+               const initial_files_handler& initial_files_handler)
+{
+       return spl::make_shared<polling_filesystem_monitor>(
+                       folder_to_watch,
+                       events_of_interest_mask,
+                       report_already_existing,
+                       impl_->scan_interval_millis,
+                       handler,
+                       initial_files_handler);
+}
+
+}
\ No newline at end of file
diff --git a/common/polling_filesystem_monitor.h b/common/polling_filesystem_monitor.h
new file mode 100644 (file)
index 0000000..10afbaa
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+* 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 "filesystem_monitor.h"
+
+namespace caspar {
+
+/**
+ * A portable filesystem monitor implementation which periodically polls the
+ * filesystem for changes. Will not react instantly but never misses any
+ * changes.
+ * <p>
+ * Will create a dedicated thread for each monitor created.
+ */
+class polling_filesystem_monitor_factory : public filesystem_monitor_factory
+{
+public:
+       /**
+        * Constructor.
+        *
+        * @param scan_interval_millis The number of milliseconds between each
+        *                             scheduled scan. Lower values lowers the reaction
+        *                             time but causes more I/O.
+        */
+       polling_filesystem_monitor_factory(int scan_interval_millis = 5000);
+       virtual ~polling_filesystem_monitor_factory();
+       virtual filesystem_monitor::ptr create(
+                       const boost::filesystem::wpath& folder_to_watch,
+                       filesystem_event events_of_interest_mask,
+                       bool report_already_existing,
+                       const filesystem_monitor_handler& handler,
+                       const initial_files_handler& initial_files_handler);
+private:
+       struct impl;
+       spl::unique_ptr<impl> impl_;
+};
+
+}
\ No newline at end of file
index feeb25c42a873773db1e60bd4054c43b3792111e..d6aebfa60a0d4b33dff8473e2e69f7a589a47f99 100644 (file)
     <ClInclude Include="producer\text\utils\texture_font.h" />\r
     <ClInclude Include="producer\text\utils\text_info.h" />\r
     <ClInclude Include="producer\variable.h" />\r
+    <ClInclude Include="thumbnail_generator.h" />\r
     <ClInclude Include="video_channel.h" />\r
     <ClInclude Include="consumer\output.h" />\r
     <ClInclude Include="consumer\frame_consumer.h" />\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">..\..\..\StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\..\StdAfx.h</PrecompiledHeaderFile>\r
     </ClCompile>\r
+    <ClCompile Include="thumbnail_generator.cpp" />\r
     <ClCompile Include="video_channel.cpp" />\r
     <ClCompile Include="consumer\frame_consumer.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../StdAfx.h</PrecompiledHeaderFile>\r
index b8dd8418b8bb1b153ff3f56e133228085eb6b9e0..2fb7c2abfd2bed60edffcd1ebc0d6695ea31dbd2 100644 (file)
     <ClInclude Include="producer\text\utils\text_info.h">\r
       <Filter>source\producer\text\utils</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="thumbnail_generator.h">\r
+      <Filter>source</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClCompile Include="producer\transition\transition_producer.cpp">\r
     <ClCompile Include="producer\scene\expression_parser.cpp">\r
       <Filter>source\producer\scene</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="thumbnail_generator.cpp">\r
+      <Filter>source</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
 </Project>
\ No newline at end of file
index e3c7e16964a741d57177d1906a343ca93d0a3320..572dd7a57e57f1832a4d51117be26be56ba19799 100644 (file)
 namespace caspar { namespace core {
        
 std::vector<const producer_factory_t> g_factories;
+std::vector<const producer_factory_t> g_thumbnail_factories;
 
 void register_producer_factory(const producer_factory_t& factory)
 {
        g_factories.push_back(factory);
 }
+void register_thumbnail_producer_factory(const producer_factory_t& factory)
+{
+       g_thumbnail_factories.push_back(factory);
+}
 
 constraints::constraints(double width, double height)
        : width(width), height(height)
@@ -95,6 +100,10 @@ struct frame_producer_base::impl
        {
                return draw_frame::still(last_frame_);
        }
+       draw_frame create_thumbnail_frame()
+       {
+               return draw_frame::empty();
+       }
 };
 
 frame_producer_base::frame_producer_base() : impl_(new impl(*this))
@@ -115,6 +124,10 @@ draw_frame frame_producer_base::last_frame()
 {
        return impl_->last_frame();
 }
+draw_frame frame_producer_base::create_thumbnail_frame()
+{
+       return impl_->create_thumbnail_frame();
+}
 
 boost::unique_future<std::wstring> frame_producer_base::call(const std::vector<std::wstring>&) 
 {
@@ -162,6 +175,7 @@ const spl::shared_ptr<frame_producer>& frame_producer::empty()
                variable& get_variable(const std::wstring& name) override { CASPAR_THROW_EXCEPTION(not_supported()); }
                const std::vector<std::wstring>& get_variables() const override { static std::vector<std::wstring> empty; return empty; }
                draw_frame last_frame() {return draw_frame::empty();}
+               draw_frame create_thumbnail_frame() {return draw_frame::empty();}
                constraints& pixel_constraints() override { static constraints c; return c; }
        
                boost::property_tree::wptree info() const override
@@ -235,6 +249,7 @@ public:
        void                                                                                            leading_producer(const spl::shared_ptr<frame_producer>& producer) override              {return producer_->leading_producer(producer);}
        uint32_t                                                                                        nb_frames() const override                                                                                                              {return producer_->nb_frames();}
        class draw_frame                                                                        last_frame()                                                                                                                                    {return producer_->last_frame();}
+       draw_frame                                                                                      create_thumbnail_frame()                                                                                                                {return producer_->create_thumbnail_frame();}
        void                                                                                            subscribe(const monitor::observable::observer_ptr& o)                                                   {return producer_->subscribe(o);}
        void                                                                                            unsubscribe(const monitor::observable::observer_ptr& o)                                                 {return producer_->unsubscribe(o);}
        bool                                                                                            collides(double x, double y)                                                                                                    {return producer_->collides(x, y);}
@@ -247,13 +262,13 @@ spl::shared_ptr<core::frame_producer> create_destroy_proxy(spl::shared_ptr<core:
        return spl::make_shared<destroy_producer_proxy>(std::move(producer));
 }
 
-spl::shared_ptr<core::frame_producer> do_create_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params)
+spl::shared_ptr<core::frame_producer> do_create_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params, const std::vector<const producer_factory_t>& factories, bool throw_on_fail = false)
 {
        if(params.empty())
                CASPAR_THROW_EXCEPTION(invalid_argument() << arg_name_info("params") << arg_value_info(""));
        
        auto producer = frame_producer::empty();
-       std::any_of(g_factories.begin(), g_factories.end(), [&](const producer_factory_t& factory) -> bool
+       std::any_of(factories.begin(), factories.end(), [&](const producer_factory_t& factory) -> bool
                {
                        try
                        {
@@ -261,7 +276,10 @@ spl::shared_ptr<core::frame_producer> do_create_producer(const spl::shared_ptr<f
                        }
                        catch(...)
                        {
-                               CASPAR_LOG_CURRENT_EXCEPTION();
+                               if(throw_on_fail)
+                                       throw;
+                               else
+                                       CASPAR_LOG_CURRENT_EXCEPTION();
                        }
                        return producer != frame_producer::empty();
                });
@@ -278,9 +296,39 @@ spl::shared_ptr<core::frame_producer> do_create_producer(const spl::shared_ptr<f
        return producer;
 }
 
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::wstring& media_file)
+{
+  std::vector<std::wstring> params;
+  params.push_back(media_file);
+
+  auto producer = do_create_producer(my_frame_factory, format_desc, params, g_thumbnail_factories, true);
+  auto key_producer = frame_producer::empty();
+  
+  try // to find a key file.
+  {
+    auto params_copy = params;
+    if (params_copy.size() > 0)
+    {
+      params_copy[0] += L"_A";
+      key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_thumbnail_factories, true);
+      if (key_producer == frame_producer::empty())
+      {
+        params_copy[0] += L"LPHA";
+        key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_thumbnail_factories, true);
+      }
+    }
+  }
+  catch(...){}
+
+  if (producer != frame_producer::empty() && key_producer != frame_producer::empty())
+    return create_separated_producer(producer, key_producer);
+  
+  return producer;
+}
+
 spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params)
 {      
-       auto producer = do_create_producer(my_frame_factory, format_desc, params);
+       auto producer = do_create_producer(my_frame_factory, format_desc, params, g_factories);
        auto key_producer = frame_producer::empty();
        
        try // to find a key file.
@@ -289,11 +337,11 @@ spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<fram
                if(params_copy.size() > 0)
                {
                        params_copy[0] += L"_A";
-                       key_producer = do_create_producer(my_frame_factory, format_desc, params_copy);                  
+                       key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_factories);                     
                        if(key_producer == frame_producer::empty())
                        {
                                params_copy[0] += L"LPHA";
-                               key_producer = do_create_producer(my_frame_factory, format_desc, params_copy);  
+                               key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_factories);     
                        }
                }
        }
index 14a391466e20dccb381d8d5826ff862a8ce3158f..cbec012bbcbd080b64cb2aad40a7424e96fdbc48 100644 (file)
@@ -97,6 +97,7 @@ public:
        virtual uint32_t                                                        nb_frames() const = 0;
        virtual uint32_t                                                        frame_number() const = 0;
        virtual class draw_frame                                        last_frame() = 0;
+       virtual class draw_frame                                        create_thumbnail_frame() = 0;
        virtual constraints&                                            pixel_constraints() = 0;
        virtual void                                                            leading_producer(const spl::shared_ptr<frame_producer>&) {}  
 };
@@ -121,6 +122,7 @@ public:
        uint32_t                                        nb_frames() const override;
        uint32_t                                        frame_number() const override;
        virtual class draw_frame        last_frame() override;
+       virtual class draw_frame        create_thumbnail_frame() override;
 
 private:
        virtual class draw_frame        receive() override;
@@ -133,10 +135,12 @@ private:
 
 typedef std::function<spl::shared_ptr<core::frame_producer>(const spl::shared_ptr<class frame_factory>&, const video_format_desc& format_desc, const std::vector<std::wstring>&)> producer_factory_t;
 void register_producer_factory(const producer_factory_t& factory); // Not thread-safe.
+void register_thumbnail_producer_factory(const producer_factory_t& factory); // Not thread-safe.
 
 spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<frame_factory>&, const video_format_desc& format_desc, const std::vector<std::wstring>& params);
 spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<frame_factory>&, const video_format_desc& format_desc, const std::wstring& params);
 
 spl::shared_ptr<core::frame_producer> create_destroy_proxy(spl::shared_ptr<core::frame_producer> producer);
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<frame_factory>&, const video_format_desc& format_desc, const std::wstring& media_file);
 
 }}
index 44b6e5fbf225bdc6dd3bfcd8b772fddb77356949..f2848a6b050393816a4755221c1f8690cfeb9158 100644 (file)
@@ -89,6 +89,20 @@ public:
                return draw_frame::mask(fill_producer_->last_frame(), key_producer_->last_frame());
        }
 
+       draw_frame create_thumbnail_frame()
+       {
+               auto fill_frame = fill_producer_->create_thumbnail_frame();
+               auto key_frame = key_producer_->create_thumbnail_frame();
+
+               if (fill_frame == draw_frame::empty() || key_frame == draw_frame::empty())
+                       return draw_frame::empty();
+
+               if (fill_frame == draw_frame::late() || key_frame == draw_frame::late())
+                       return draw_frame::late();
+
+               return draw_frame::mask(fill_frame, key_frame);
+       }
+
        constraints& pixel_constraints() override
        {
                return fill_producer_->pixel_constraints();
index 5f7fec7ac7f8b9d5f7d3e569013964364e78ff55..4f660bcf083828d08fb8f603bf0d4dd6543195fc 100644 (file)
@@ -28,5 +28,6 @@
 namespace caspar { namespace core {
        
 spl::shared_ptr<class frame_producer> create_separated_producer(const spl::shared_ptr<class frame_producer>& fill, const spl::shared_ptr<class frame_producer>& key);
+spl::shared_ptr<class frame_producer> create_separated_thumbnail_producer(const spl::shared_ptr<class frame_producer>& fill, const spl::shared_ptr<class frame_producer>& key);
 
 }}
\ No newline at end of file
index bc1ade0e567b255fe4796089951c0403b2460e27..5896e0a6b659a8e085eb011e983893882f96a77f 100644 (file)
@@ -12,6 +12,10 @@ namespace caspar { namespace core { namespace text {
 
                float size;
                color<float> color;
+               //int shadow_distance;
+               //int shadow_size;
+               //float shadow_spread;
+               //color<float> shadow_color;
                int baseline_shift;
                int tracking;
        };
diff --git a/core/thumbnail_generator.cpp b/core/thumbnail_generator.cpp
new file mode 100644 (file)
index 0000000..de05a90
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+* 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 "thumbnail_generator.h"
+
+#include <iostream>
+#include <iterator>
+#include <set>
+
+#include <boost/thread.hpp>
+#include <boost/range/algorithm/transform.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+
+#include <tbb/atomic.h>
+
+#include <common/diagnostics/graph.h>
+
+#include "producer/frame_producer.h"
+#include "consumer/frame_consumer.h"
+#include "mixer/mixer.h"
+#include "mixer/image/image_mixer.h"
+#include "video_format.h"
+#include "frame/frame.h"
+#include "frame/draw_frame.h"
+#include "frame/frame_transform.h"
+
+namespace caspar { namespace core {
+
+std::wstring get_relative_without_extension(
+               const boost::filesystem::wpath& file,
+               const boost::filesystem::wpath& relative_to)
+{
+       auto result = file.stem();
+
+       boost::filesystem::wpath current_path = file;
+
+       while (true)
+       {
+               current_path = current_path.parent_path();
+
+               if (boost::filesystem::equivalent(current_path, relative_to))
+                       break;
+
+               if (current_path.empty())
+                       throw std::runtime_error("File not relative to folder");
+
+               result = current_path.filename() / result;
+       }
+
+       return result.wstring();
+}
+
+struct thumbnail_output
+{
+       tbb::atomic<int> sleep_millis;
+       std::function<void (const_frame)> on_send;
+
+       thumbnail_output(int sleep_millis)
+       {
+               this->sleep_millis = sleep_millis;
+       }
+
+       void send(const_frame frame, std::shared_ptr<void> frame_and_ticket)
+       {
+               int current_sleep = sleep_millis;
+
+               if (current_sleep > 0)
+                       boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
+
+               on_send(std::move(frame));
+               on_send = nullptr;
+       }
+};
+
+struct thumbnail_generator::impl
+{
+private:
+       boost::filesystem::wpath media_path_;
+       boost::filesystem::wpath thumbnails_path_;
+       int width_;
+       int height_;
+       spl::shared_ptr<image_mixer> image_mixer_;
+       spl::shared_ptr<diagnostics::graph> graph_;
+       video_format_desc format_desc_;
+       spl::unique_ptr<thumbnail_output> output_;
+       mixer mixer_;
+       thumbnail_creator thumbnail_creator_;
+       filesystem_monitor::ptr monitor_;
+public:
+       impl(
+                       filesystem_monitor_factory& monitor_factory,
+                       const boost::filesystem::wpath& media_path,
+                       const boost::filesystem::wpath& thumbnails_path,
+                       int width,
+                       int height,
+                       const video_format_desc& render_video_mode,
+                       std::unique_ptr<image_mixer> image_mixer,
+                       int generate_delay_millis,
+                       const thumbnail_creator& thumbnail_creator)
+               : media_path_(media_path)
+               , thumbnails_path_(thumbnails_path)
+               , width_(width)
+               , height_(height)
+               , image_mixer_(std::move(image_mixer))
+               , format_desc_(render_video_mode)
+               , output_(spl::make_unique<thumbnail_output>(generate_delay_millis))
+               , mixer_(graph_, image_mixer_)
+               , thumbnail_creator_(thumbnail_creator)
+               , monitor_(monitor_factory.create(
+                               media_path,
+                               ALL,
+                               true,
+                               [this] (filesystem_event event, const boost::filesystem::wpath& file)
+                               {
+                                       this->on_file_event(event, file);
+                               },
+                               [this] (const std::set<boost::filesystem::wpath>& initial_files) 
+                               {
+                                       this->on_initial_files(initial_files);
+                               }))
+       {
+               graph_->set_text(L"thumbnail-channel");
+               graph_->auto_reset();
+               diagnostics::register_graph(graph_);
+               //monitor_->initial_scan_completion().get();
+               //output_->sleep_millis = 2000;
+       }
+
+       void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
+       {
+               using namespace boost::filesystem;
+
+               std::set<std::wstring> relative_without_extensions;
+               boost::transform(
+                               initial_files,
+                               std::insert_iterator<std::set<std::wstring>>(
+                                               relative_without_extensions,
+                                               relative_without_extensions.end()),
+                               boost::bind(&get_relative_without_extension, _1, media_path_));
+
+               for (boost::filesystem3::wrecursive_directory_iterator iter(thumbnails_path_); iter != boost::filesystem3::wrecursive_directory_iterator(); ++iter)
+               {
+                       auto& path = iter->path();
+
+                       if (!is_regular_file(path))
+                               continue;
+
+                       auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
+                       bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension) 
+                                       == relative_without_extensions.end();
+
+                       if (no_corresponding_media_file)
+                               remove(thumbnails_path_ / (relative_without_extension + L".png"));
+               }
+       }
+
+       void generate(const std::wstring& media_file)
+       {
+               using namespace boost::filesystem;
+               auto base_file = media_path_ / media_file;
+               auto folder = base_file.parent_path();
+
+               for (boost::filesystem3::directory_iterator iter(folder); iter != boost::filesystem3::directory_iterator(); ++iter)
+               {
+                       auto stem = iter->path().stem();
+
+                       if (boost::iequals(stem.wstring(), base_file.filename().wstring()))
+                               monitor_->reemmit(iter->path());
+               }
+       }
+
+       void generate_all()
+       {
+               monitor_->reemmit_all();
+       }
+
+       void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
+       {
+               switch (event)
+               {
+               case CREATED:
+                       if (needs_to_be_generated(file))
+                               generate_thumbnail(file);
+
+                       break;
+               case MODIFIED:
+                       generate_thumbnail(file);
+
+                       break;
+               case REMOVED:
+                       auto relative_without_extension = get_relative_without_extension(file, media_path_);
+                       boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
+
+                       break;
+               }
+       }
+
+       bool needs_to_be_generated(const boost::filesystem::wpath& file)
+       {
+               using namespace boost::filesystem;
+
+               auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
+
+               if (!exists(png_file))
+                       return true;
+
+               std::time_t media_file_mtime;
+
+               try
+               {
+                       media_file_mtime = last_write_time(file);
+               }
+               catch (...)
+               {
+                       // Probably removed.
+                       return false;
+               }
+
+               try
+               {
+                       return media_file_mtime != last_write_time(png_file);
+               }
+               catch (...)
+               {
+                       // thumbnail probably removed.
+                       return true;
+               }
+       }
+
+       void generate_thumbnail(const boost::filesystem::wpath& file)
+       {
+               auto media_file = get_relative_without_extension(file, media_path_);
+               auto png_file = thumbnails_path_ / (media_file + L".png");
+               boost::promise<void> thumbnail_ready;
+
+               {
+                       auto producer = frame_producer::empty();
+
+                       try
+                       {
+                               producer = create_thumbnail_producer(image_mixer_, format_desc_, media_file);
+                       }
+                       catch (const boost::thread_interrupted&)
+                       {
+                               throw;
+                       }
+                       catch (...)
+                       {
+                               CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
+                               return;
+                       }
+
+                       if (producer == frame_producer::empty())
+                       {
+                               CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
+                               return;
+                       }
+
+                       boost::filesystem::create_directories(png_file.parent_path());
+                       output_->on_send = [this, &png_file] (const_frame frame)
+                       {
+                               thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
+                       };
+
+                       std::map<int, draw_frame> frames;
+                       auto raw_frame = draw_frame::empty();
+
+                       try
+                       {
+                               raw_frame = producer->create_thumbnail_frame();
+                       }
+                       catch (const boost::thread_interrupted&)
+                       {
+                               throw;
+                       }
+                       catch (...)
+                       {
+                               CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
+                               return;
+                       }
+
+                       if (raw_frame == draw_frame::empty()
+                                       || raw_frame == draw_frame::late())
+                       {
+                               CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
+                               return;
+                       }
+
+                       auto transformed_frame = draw_frame(raw_frame);
+                       transformed_frame.transform().image_transform.fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
+                       transformed_frame.transform().image_transform.fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
+                       frames.insert(std::make_pair(0, transformed_frame));
+
+                       std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*) { thumbnail_ready.set_value(); });
+
+                       auto mixed_frame = mixer_(frames, format_desc_);
+
+                       output_->send(std::move(mixed_frame), ticket);
+                       ticket.reset();
+               }
+               thumbnail_ready.get_future().get();
+
+               if (boost::filesystem::exists(png_file))
+               {
+                       // Adjust timestamp to match source file.
+                       try
+                       {
+                               boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
+                               CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
+                       }
+                       catch (...)
+                       {
+                               // One of the files was removed before the call to last_write_time.
+                       }
+               }
+               else
+                       CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
+       }
+};
+
+thumbnail_generator::thumbnail_generator(
+               filesystem_monitor_factory& monitor_factory,
+               const boost::filesystem::wpath& media_path,
+               const boost::filesystem::wpath& thumbnails_path,
+               int width,
+               int height,
+               const video_format_desc& render_video_mode,
+               std::unique_ptr<image_mixer> image_mixer,
+               int generate_delay_millis,
+               const thumbnail_creator& thumbnail_creator)
+               : impl_(new impl(
+                               monitor_factory,
+                               media_path,
+                               thumbnails_path,
+                               width, height,
+                               render_video_mode,
+                               std::move(image_mixer),
+                               generate_delay_millis,
+                               thumbnail_creator))
+{
+}
+
+thumbnail_generator::~thumbnail_generator()
+{
+}
+
+void thumbnail_generator::generate(const std::wstring& media_file)
+{
+       impl_->generate(media_file);
+}
+
+void thumbnail_generator::generate_all()
+{
+       impl_->generate_all();
+}
+
+}}
\ No newline at end of file
diff --git a/core/thumbnail_generator.h b/core/thumbnail_generator.h
new file mode 100644 (file)
index 0000000..7cf3f3e
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+* 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 <boost/noncopyable.hpp>
+
+#include "memory.h"
+#include <common/filesystem_monitor.h>
+
+namespace caspar { namespace core {
+
+class ogl_device;
+class read_frame;
+struct video_format_desc;
+
+typedef std::function<void (
+               const class const_frame& frame,
+               const video_format_desc& format_desc,
+               const boost::filesystem::wpath& output_file,
+               int width,
+               int height)> thumbnail_creator;
+
+class thumbnail_generator : boost::noncopyable
+{
+public:
+       thumbnail_generator(
+                       filesystem_monitor_factory& monitor_factory,
+                       const boost::filesystem::wpath& media_path,
+                       const boost::filesystem::wpath& thumbnails_path,
+                       int width,
+                       int height,
+                       const video_format_desc& render_video_mode,
+                       std::unique_ptr<class image_mixer> image_mixer,
+                       int generate_delay_millis,
+                       const thumbnail_creator& thumbnail_creator);
+       ~thumbnail_generator();
+       void generate(const std::wstring& media_file);
+       void generate_all();
+private:
+       struct impl;
+       spl::unique_ptr<impl> impl_;
+};
+
+}}
\ No newline at end of file
index 27bd19f2d6c055046350c8e94c7d7a68dedb77ab..4c76a0785aa7957b7f36b0ac3d2a336b6ea034e0 100644 (file)
 #include <FreeImage.h>
 
 #include <vector>
+#include <algorithm>
+
+#include "../util/image_view.h"
 
 namespace caspar { namespace image {
-       
+
+       void write_cropped_png(
+               const core::const_frame& frame,
+               const core::video_format_desc& format_desc,
+               const boost::filesystem::wpath& output_file,
+               int width,
+               int height)
+       {
+               auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Allocate(width, height, 32), FreeImage_Unload);
+               image_view<bgra_pixel> destination_view(FreeImage_GetBits(bitmap.get()), width, height);
+               image_view<bgra_pixel> complete_frame(const_cast<uint8_t*>(frame.image_data().begin()), format_desc.width, format_desc.height);
+               auto thumbnail_view = complete_frame.subview(0, 0, width, height);
+
+               std::copy(thumbnail_view.begin(), thumbnail_view.end(), destination_view.begin());
+               FreeImage_FlipVertical(bitmap.get());
+               FreeImage_SaveU(FIF_PNG, bitmap.get(), output_file.wstring().c_str(), 0);
+       }
+
 struct image_consumer : public core::frame_consumer
 {
        std::wstring filename_;
@@ -69,17 +89,17 @@ public:
                {
                        try
                        {
-                               auto filename2 = u8(filename);
+                               auto filename2 = filename;
                                
                                if (filename2.empty())
-                                       filename2 = u8(env::media_folder()) +  boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time()) + ".png";
+                                       filename2 = env::media_folder() +  boost::posix_time::to_iso_wstring(boost::posix_time::second_clock::local_time()) + L".png";
                                else
-                                       filename2 = u8(env::media_folder()) + filename2 + ".png";
+                                       filename2 = env::media_folder() + filename2 + L".png";
 
                                auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Allocate(static_cast<int>(frame.width()), static_cast<int>(frame.height()), 32), FreeImage_Unload);
                                A_memcpy(FreeImage_GetBits(bitmap.get()), frame.image_data().begin(), frame.image_data().size());
                                FreeImage_FlipVertical(bitmap.get());
-                               FreeImage_Save(FIF_PNG, bitmap.get(), filename2.c_str(), 0);
+                               FreeImage_SaveU(FIF_PNG, bitmap.get(), filename2.c_str(), 0);
                        }
                        catch(...)
                        {
index 260ed3ede5dbe3bec17eb29d534b3cea3868de49..7ee8a4570243ebe9b0500768ad612a6802ce506e 100644 (file)
 #include <common/memory.h>
 
 #include <core/video_format.h>
-
+#include <core/consumer/frame_consumer.h>
 #include <boost/property_tree/ptree.hpp>
+#include <boost/filesystem.hpp>
 
 #include <string>
 #include <vector>
 
 namespace caspar { 
 
-namespace core {
-       class frame_consumer;
-}
-
 namespace image {
        
+       void write_cropped_png(
+               const class core::const_frame& frame,
+               const core::video_format_desc& format_desc,
+               const boost::filesystem::wpath& output_file,
+               int width,
+               int height);
+
 spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params);
 
 }}
\ No newline at end of file
index b738801cef39152954e25425f22684b7fa28e9d2..5d0552e2b546f938614b3789f778b116fe28784e 100644 (file)
@@ -39,15 +39,16 @@ void init()
        FreeImage_Initialise();
        core::register_producer_factory(create_scroll_producer);
        core::register_producer_factory(create_producer);
+       core::register_thumbnail_producer_factory(create_thumbnail_producer);
        core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});
 }
 
 void uninit()
 {
        FreeImage_DeInitialise();
-       core::register_producer_factory(create_scroll_producer);
-       core::register_producer_factory(create_producer);
-       core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});
+//     core::register_producer_factory(create_scroll_producer);
+//     core::register_producer_factory(create_producer);
+//     core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});
 }
 
 
index 6b8ea158f8b4773d26f3ed8e859f31923151c662..abcb4414e1344f8946133d1b97ff2e2575481e25 100644 (file)
@@ -83,6 +83,11 @@ struct image_producer : public core::frame_producer_base
                return frame_;
        }
 
+       core::draw_frame create_thumbnail_frame() override
+       {
+               return frame_;
+       }
+
        core::constraints& pixel_constraints() override
        {
                return constraints_;
@@ -117,7 +122,7 @@ struct image_producer : public core::frame_producer_base
        }
 };
 
-spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+spl::shared_ptr<core::frame_producer> create_raw_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
 {
        static const std::vector<std::wstring> extensions = list_of(L".png")(L".tga")(L".bmp")(L".jpg")(L".jpeg")(L".gif")(L".tiff")(L".tif")(L".jp2")(L".jpx")(L".j2k")(L".j2c");
        std::wstring filename = env::media_folder() + L"\\" + params[0];
@@ -133,5 +138,14 @@ spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core
        return spl::make_shared<image_producer>(frame_factory, filename + *ext);
 }
 
+spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+{
+       return create_raw_producer(frame_factory, format_desc, params);
+}
+
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+{
+       return create_raw_producer(frame_factory, format_desc, params);
+}
 
 }}
\ No newline at end of file
index b994fd46165be870a8a87d6be2bc43180e284e26..093094df407ce888ab99d92f0ac345f97cf1a70a 100644 (file)
@@ -29,5 +29,6 @@
 namespace caspar { namespace image {
 
 spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params);
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params);
 
 }}
\ No newline at end of file
index f902931373de217b6a240afa022ca81efd5ab403..13f0d581d78e3cc8a3ae5f3f5e1d8886795e3e8a 100644 (file)
@@ -44,6 +44,7 @@
 #include <core/producer/layer.h>
 #include <core/mixer/mixer.h>
 #include <core/consumer/output.h>
+#include <core/thumbnail_generator.h>
 
 #include <modules/reroute/producer/reroute_producer.h>
 #include <modules/bluefish/bluefish.h>
@@ -75,6 +76,9 @@
 #include <boost/locale.hpp>
 #include <boost/range/adaptor/transformed.hpp>
 #include <boost/range/algorithm/copy.hpp>
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/insert_linebreaks.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
 
 #include <tbb/concurrent_unordered_map.h>
 
@@ -105,6 +109,48 @@ namespace caspar { namespace protocol {
 
 using namespace core;
 
+std::wstring read_file_base64(const boost::filesystem::wpath& file)
+{
+       using namespace boost::archive::iterators;
+
+       boost::filesystem::ifstream filestream(file, std::ios::binary);
+
+       if (!filestream)
+               return L"";
+
+       // From http://www.webbiscuit.co.uk/2012/04/02/base64-encoder-and-boost/
+
+       typedef
+               insert_linebreaks<         // insert line breaks every 76 characters
+               base64_from_binary<    // convert binary values to base64 characters
+               transform_width<   // retrieve 6 bit integers from a sequence of 8 bit bytes
+               const unsigned char *,
+               6,
+               8
+               >
+               >,
+               76
+               >
+               base64_iterator; // compose all the above operations in to a new iterator
+       auto length = boost::filesystem::file_size(file);
+       std::vector<char> bytes;
+       bytes.resize(length);
+       filestream.read(bytes.data(), length);
+
+       int padding = 0;
+
+       while (bytes.size() % 3 != 0)
+       {
+               ++padding;
+               bytes.push_back(0x00);
+       }
+
+       std::string result(base64_iterator(bytes.data()), base64_iterator(bytes.data() + length - padding));
+       result.insert(result.end(), padding, '=');
+
+       return std::wstring(result.begin(), result.end());
+}
+
 std::wstring read_utf8_file(const boost::filesystem::wpath& file)
 {
        std::wstringstream result;
@@ -1332,7 +1378,11 @@ bool DataCommand::DoExecuteRetrieve()
 
        std::wstringstream reply(TEXT("201 DATA RETRIEVE OK\r\n"));
 
-       reply << file_contents;
+       std::wstringstream file_contents_stream(file_contents);
+       std::wstring line;
+       while(std::getline(file_contents_stream, line))
+               reply << line << L"\n";
+
        reply << "\r\n";
        SetReplyString(reply.str());
        return true;
@@ -1805,6 +1855,119 @@ bool LockCommand::DoExecute()
        return true;
 }
 
+bool ThumbnailCommand::DoExecute()
+{
+       std::wstring command = boost::to_upper_copy(parameters()[0]);
+
+       if (command == TEXT("RETRIEVE"))
+               return DoExecuteRetrieve();
+       else if (command == TEXT("LIST"))
+               return DoExecuteList();
+       else if (command == TEXT("GENERATE"))
+               return DoExecuteGenerate();
+       else if (command == TEXT("GENERATE_ALL"))
+               return DoExecuteGenerateAll();
+
+       SetReplyString(TEXT("403 THUMBNAIL ERROR\r\n"));
+       return false;
+}
+
+bool ThumbnailCommand::DoExecuteRetrieve() 
+{
+       if(parameters().size() < 2) 
+       {
+               SetReplyString(TEXT("402 THUMBNAIL RETRIEVE ERROR\r\n"));
+               return false;
+       }
+
+       std::wstring filename = env::thumbnails_folder();
+       filename.append(parameters()[1]);
+       filename.append(TEXT(".png"));
+
+       std::wstring file_contents = read_file_base64(boost::filesystem::wpath(filename));
+
+       if (file_contents.empty())
+       {
+               SetReplyString(TEXT("404 THUMBNAIL RETRIEVE ERROR\r\n"));
+               return false;
+       }
+
+       std::wstringstream reply;
+
+       reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
+       reply << file_contents;
+       reply << L"\r\n";
+       SetReplyString(reply.str());
+       return true;
+}
+
+bool ThumbnailCommand::DoExecuteList()
+{
+       std::wstringstream replyString;
+       replyString << TEXT("200 THUMBNAIL LIST OK\r\n");
+
+       for (boost::filesystem::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::wpath(itr->path().wstring().substr(env::thumbnails_folder().size()-1, itr->path().wstring().size()));
+
+                       auto str = relativePath.replace_extension(L"").native();
+                       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 << TEXT("\r\n");
+
+       SetReplyString(boost::to_upper_copy(replyString.str()));
+       return true;
+}
+
+bool ThumbnailCommand::DoExecuteGenerate()
+{
+       if (parameters().size() < 2) 
+       {
+               SetReplyString(L"402 THUMBNAIL GENERATE ERROR\r\n");
+               return false;
+       }
+
+       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;
+       }
+}
 
 }      //namespace amcp
 }}     //namespace caspar
\ No newline at end of file
index f01b8c1b3ceddd3fd195a89d72fa0ffd282fb19a..d0ec2fb8728647e8d2e86f999ab29e05f592c6df 100644 (file)
@@ -25,6 +25,8 @@
 
 #include "AMCPCommand.h"
 
+#include <core/thumbnail_generator.h>
+
 namespace caspar { namespace protocol {
        
 std::wstring ListMedia();
@@ -279,7 +281,6 @@ public:
        SetCommand(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"SetCommand";}
        bool DoExecute();
 };
@@ -292,6 +293,23 @@ public:
        bool DoExecute();
 };
 
+class ThumbnailCommand : public AMCPCommandBase<1>
+{
+public:
+       ThumbnailCommand::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 AMCPCommand
 //{
 //public:
index a25a686017b340aca9211aea80d55e071d0fced5..d72516a369b7632a79f5bc412a475bb854205c34 100644 (file)
@@ -50,11 +50,12 @@ using IO::ClientInfoPtr;
 struct AMCPProtocolStrategy::impl
 {
 private:
-       std::vector<channel_context> channels_;
-       std::vector<AMCPCommandQueue::ptr_type> commandQueues_;
+       std::vector<channel_context>                            channels_;
+       std::vector<AMCPCommandQueue::ptr_type>         commandQueues_;
+       std::shared_ptr<core::thumbnail_generator>      thumb_gen_;
 
 public:
-       impl(const std::vector<spl::shared_ptr<core::video_channel>>& channels)
+       impl(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen) : thumb_gen_(thumb_gen)
        {
                commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
 
@@ -355,6 +356,7 @@ private:
                else if(s == TEXT("BYE"))                       return std::make_shared<ByeCommand>(client);
                else if(s == TEXT("LOCK"))                      return std::make_shared<LockCommand>(client, channels_);
                else if(s == TEXT("LOG"))                       return std::make_shared<LogCommand>(client);
+               else if(s == TEXT("THUMBNAIL"))         return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
                //else if(s == TEXT("KILL"))
                //{
                //      result = AMCPCommandPtr(new KillCommand());
@@ -387,7 +389,7 @@ private:
 };
 
 
-AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels) : impl_(spl::make_unique<impl>(channels)) {}
+AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen) : impl_(spl::make_unique<impl>(channels, thumb_gen)) {}
 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
 
index cdcf1acd18361c99f175f70b338d2bdaf1fe398d..d0d59d80959cddc768bbc2b599844f7df034e039 100644 (file)
@@ -24,6 +24,7 @@
 #include "../util/protocolstrategy.h"
 
 #include <core/video_channel.h>
+#include <core/thumbnail_generator.h>
 
 #include <common/memory.h>
 
@@ -36,7 +37,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);
+       AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen);
        virtual ~AMCPProtocolStrategy();
 
        virtual void Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo);
index 06752f9254a41c5e92eb63c3abfde4eeaeb0a9b4..d65391b3be910e99223785d5d7f70cdc26d48ac9 100644 (file)
@@ -5,14 +5,24 @@
     <log-path>log\</log-path>\r
     <data-path>data\</data-path>\r
     <template-path>templates\</template-path>\r
+    <thumbnails-path>thumbnails\</thumbnails-path>\r
   </paths>\r
   <flash>\r
     <buffer-depth>4</buffer-depth>\r
   </flash>\r
+  <thumbnails>\r
+    <generate-thumbnails>true</generate-thumbnails>\r
+    <width>256</width>\r
+    <height>144</height>\r
+    <video-grid>2</video-grid>\r
+    <scan-interval-millis>5000</scan-interval-millis>\r
+    <generate-delay-millis>2000</generate-delay-millis>\r
+    <video-mode>720p2500</video-mode>\r
+  </thumbnails>\r
   <lock-clear-phrase>secret</lock-clear-phrase>\r
   <channels>\r
     <channel>\r
-      <video-mode>576p2500</video-mode>\r
+      <video-mode>720p5000</video-mode>\r
       <consumers>\r
         <screen>\r
           <device>1</device>\r
index d0383409b5dcf5a2b7eadc09acc25925cb6944c5..875ae4aca19b01205d612b6a25e79cbefee23358 100644 (file)
@@ -176,7 +176,7 @@ LONG WINAPI UserUnhandledExceptionFilter(EXCEPTION_POINTERS* info)
        }
        catch(...){}
 
-    return EXCEPTION_CONTINUE_EXECUTION;
+       return EXCEPTION_CONTINUE_EXECUTION;
 }
 
 void run()
@@ -201,7 +201,7 @@ void run()
        
        // Create a amcp parser for console commands.
        //protocol::amcp::AMCPProtocolStrategy amcp(caspar_server.channels());
-       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())))->create(console_client);
+       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())))->create(console_client);
 
        std::wstring wcmd;
        while(true)
index 8af07fb207216d892c1a8dc2fa166a75bc96e090..fd1e62b7a0d85b3b8a713febc61d6ed570040b5f 100644 (file)
@@ -28,6 +28,7 @@
 #include <common/except.h>
 #include <common/utf.h>
 #include <common/memory.h>
+#include <common/polling_filesystem_monitor.h>
 
 #include <core/video_channel.h>
 #include <core/video_format.h>
@@ -37,6 +38,7 @@
 #include <core/producer/scene/xml_scene_producer.h>
 #include <core/producer/text/text_producer.h>
 #include <core/consumer/output.h>
+#include <core/thumbnail_generator.h>
 
 #include <modules/bluefish/bluefish.h>
 #include <modules/decklink/decklink.h>
@@ -45,6 +47,7 @@
 #include <modules/oal/oal.h>
 #include <modules/screen/screen.h>
 #include <modules/image/image.h>
+#include <modules/image/consumer/image_consumer.h>
 #include <modules/psd/psd_scene_producer.h>
 
 #include <modules/oal/consumer/oal_consumer.h>
@@ -77,6 +80,7 @@ struct server::impl : boost::noncopyable
        accelerator::accelerator                                                        accelerator_;
        std::vector<spl::shared_ptr<IO::AsyncEventServer>>      async_servers_; 
        std::vector<spl::shared_ptr<video_channel>>                     channels_;
+       std::shared_ptr<thumbnail_generator>                            thumbnail_generator_;
 
        impl()          
                : accelerator_(env::properties().get(L"configuration.accelerator", L"auto"))
@@ -114,6 +118,9 @@ struct server::impl : boost::noncopyable
                setup_channels(env::properties());
                CASPAR_LOG(info) << L"Initialized channels.";
 
+               setup_thumbnail_generation(env::properties());
+               CASPAR_LOG(info) << L"Initialized thumbnail generator.";
+
                setup_controllers(env::properties());
                CASPAR_LOG(info) << L"Initialized controllers.";
        }
@@ -173,6 +180,28 @@ struct server::impl : boost::noncopyable
                if(env::properties().get(L"configuration.channel-grid", false))
                        channels_.push_back(spl::make_shared<video_channel>(static_cast<int>(channels_.size()+1), core::video_format_desc(core::video_format::x576p2500), accelerator_.create_image_mixer()));
        }
+
+       void setup_thumbnail_generation(const boost::property_tree::wptree& pt)
+       {
+               if (!pt.get(L"configuration.thumbnails.generate-thumbnails", true))
+                       return;
+
+               auto scan_interval_millis = pt.get(L"configuration.thumbnails.scan-interval-millis", 5000);
+
+               polling_filesystem_monitor_factory monitor_factory(scan_interval_millis);
+               thumbnail_generator_.reset(new thumbnail_generator(
+                       monitor_factory, 
+                       env::media_folder(),
+                       env::thumbnails_folder(),
+                       pt.get(L"configuration.thumbnails.width", 256),
+                       pt.get(L"configuration.thumbnails.height", 144),
+                       core::video_format_desc(pt.get(L"configuration.thumbnails.video-mode", L"720p2500")),
+                       accelerator_.create_image_mixer(),
+                       pt.get(L"configuration.thumbnails.generate-delay-millis", 2000),
+                       &image::write_cropped_png));
+
+               CASPAR_LOG(info) << L"Initialized thumbnail generator.";
+       }
                
        void setup_controllers(const boost::property_tree::wptree& pt)
        {               
@@ -211,7 +240,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_));
+                       return wrap_legacy_protocol("\r\n", spl::make_shared<amcp::AMCPProtocolStrategy>(channels_, thumbnail_generator_));
                else if(boost::iequals(name, L"CII"))
                        return wrap_legacy_protocol("\r\n", spl::make_shared<cii::CIIProtocolStrategy>(channels_));
                else if(boost::iequals(name, L"CLOCK"))
@@ -230,6 +259,7 @@ 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_; }
 void server::subscribe(const monitor::observable::observer_ptr& o){impl_->event_subject_.subscribe(o);}
 void server::unsubscribe(const monitor::observable::observer_ptr& o){impl_->event_subject_.unsubscribe(o);}
 
index aecb7ebdfb86d86f493ae8027873f88c5c79edd7..987043c4a93b5ac1f09437b3ad37667b2b3d1ab5 100644 (file)
@@ -33,6 +33,7 @@ namespace caspar {
 
 namespace core {
        class video_channel;
+       class thumbnail_generator;
 }
 
 class server sealed : public monitor::observable
@@ -41,7 +42,7 @@ class server sealed : public monitor::observable
 public:
        server();
        const std::vector<spl::shared_ptr<core::video_channel>> channels() const;
-
+       std::shared_ptr<core::thumbnail_generator> get_thumbnail_generator() const;
        // monitor::observable
 
        void subscribe(const monitor::observable::observer_ptr& o) override;