]> git.sesse.net Git - casparcg/commitdiff
3607983: Added support for generating thumbnails
authorhellgore <hellgore@362d55ac-95cf-4e76-9f9a-cbaa9c17b72d>
Thu, 14 Mar 2013 09:21:20 +0000 (09:21 +0000)
committerhellgore <hellgore@362d55ac-95cf-4e76-9f9a-cbaa9c17b72d>
Thu, 14 Mar 2013 09:21:20 +0000 (09:21 +0000)
git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/trunk@3772 362d55ac-95cf-4e76-9f9a-cbaa9c17b72d

43 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/filesystem_monitor.h [new file with mode: 0644]
common/filesystem/polling_filesystem_monitor.cpp [new file with mode: 0644]
common/filesystem/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/thumbnail_generator.cpp [new file with mode: 0644]
core/thumbnail_generator.h [new file with mode: 0644]
modules/decklink/producer/decklink_producer.cpp
modules/ffmpeg/ffmpeg.cpp
modules/ffmpeg/ffmpeg.h
modules/ffmpeg/producer/ffmpeg_producer.cpp
modules/ffmpeg/producer/ffmpeg_producer.h
modules/ffmpeg/producer/input/input.cpp
modules/ffmpeg/producer/input/input.h
modules/ffmpeg/producer/muxer/frame_muxer.cpp
modules/ffmpeg/producer/muxer/frame_muxer.h
modules/ffmpeg/producer/util/util.cpp
modules/ffmpeg/producer/util/util.h
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
modules/image/util/image_loader.cpp
protocol/amcp/AMCPCommand.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 2c74d97553c302c6f6acf8e55a569659ad0cfa68..9dbc3db9a859c78f42d69207daa32056c437d106 100644 (file)
     <ClInclude Include="diagnostics\graph.h" />\r
     <ClInclude Include="exception\exceptions.h" />\r
     <ClInclude Include="exception\win32_exception.h" />\r
+    <ClInclude Include="filesystem\filesystem_monitor.h" />\r
+    <ClInclude Include="filesystem\polling_filesystem_monitor.h" />\r
     <ClInclude Include="gl\gl_check.h" />\r
     <ClInclude Include="log\log.h" />\r
     <ClInclude Include="memory\memclr.h" />\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
     </ClCompile>\r
+    <ClCompile Include="filesystem\polling_filesystem_monitor.cpp">\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+      <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+    </ClCompile>\r
     <ClCompile Include="gl\gl_check.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
index 8773a0e26fdd77a6989ab19a8dc0e942012de629..ae7610cb629ffc11a4a8632473ec27fdbdc2ad52 100644 (file)
@@ -37,6 +37,9 @@
     <Filter Include="source\compiler\vs">\r
       <UniqueIdentifier>{28c25c8a-1277-4d2c-9e85-5af33f9938ea}</UniqueIdentifier>\r
     </Filter>\r
+    <Filter Include="source\filesystem">\r
+      <UniqueIdentifier>{bbca8bcd-702a-4fb7-827a-00b248fcc7f7}</UniqueIdentifier>\r
+    </Filter>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClCompile Include="exception\win32_exception.cpp">\r
@@ -61,6 +64,9 @@
     <ClCompile Include="utility\tweener.cpp">\r
       <Filter>source\utility</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="filesystem\polling_filesystem_monitor.cpp">\r
+      <Filter>source\filesystem</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="exception\exceptions.h">\r
     <ClInclude Include="concurrency\future_util.h">\r
       <Filter>source\concurrency</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="filesystem\filesystem_monitor.h">\r
+      <Filter>source\filesystem</Filter>\r
+    </ClInclude>\r
+    <ClInclude Include="filesystem\polling_filesystem_monitor.h">\r
+      <Filter>source\filesystem</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
 </Project>
\ No newline at end of file
index 6eac2c00e198adb52ebb878e1e7a25a50345eb22..1df5577024751d99b58340e65cb55dc275f80dac 100644 (file)
@@ -266,6 +266,7 @@ struct graph::impl : public drawable
 \r
        tbb::spin_mutex mutex_;\r
        std::wstring text_;\r
+       bool auto_reset_;\r
 \r
        impl()\r
        {\r
@@ -294,6 +295,14 @@ struct graph::impl : public drawable
        {\r
                lines_[name].set_color(color);\r
        }\r
+\r
+       void auto_reset()\r
+       {\r
+               lock(mutex_, [this]\r
+               {\r
+                       auto_reset_ = true;\r
+               });\r
+       }\r
                \r
 private:\r
        void render(sf::RenderTarget& target)\r
@@ -303,9 +312,11 @@ private:
                const size_t text_offset = (text_size+text_margin*2)*2;\r
 \r
                std::wstring text_str;\r
+               bool auto_reset;\r
                {\r
                        tbb::spin_mutex::scoped_lock lock(mutex_);\r
                        text_str = text_;\r
+                       auto_reset = auto_reset_;\r
                }\r
 \r
                sf::String text(text_str.c_str(), sf::Font::GetDefaultFont(), text_size);\r
@@ -361,8 +372,13 @@ private:
                        //target.Draw(diagnostics::guide(1.0f, color(1.0f, 1.0f, 1.0f, 0.6f)));\r
                        //target.Draw(diagnostics::guide(0.0f, color(1.0f, 1.0f, 1.0f, 0.6f)));\r
 \r
-                       for(auto it = lines_.begin(); it != lines_.end(); ++it)         \r
+                       for(auto it = lines_.begin(); it != lines_.end(); ++it)\r
+                       {\r
                                target.Draw(it->second);\r
+\r
+                               if (auto_reset)\r
+                                       it->second.set_value(0.0f);\r
+                       }\r
                \r
                glPopMatrix();\r
        }\r
@@ -379,6 +395,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);}\r
 void graph::set_color(const std::string& name, int color){impl_->set_color(name, color);}\r
 void graph::set_tag(const std::string& name){impl_->set_tag(name);}\r
+void graph::auto_reset(){impl_->auto_reset();}\r
 \r
 void register_graph(const safe_ptr<graph>& graph)\r
 {\r
index 78a8aa8b20e8b4270dd1f986fa2dec55e35957cf..c64b2718e53758b85e1ded2397a2355ad8191521 100644 (file)
@@ -40,6 +40,7 @@ public:
        void set_value(const std::string& name, double value);\r
        void set_color(const std::string& name, int color);\r
        void set_tag(const std::string& name);\r
+       void auto_reset();\r
 private:\r
        struct impl;\r
        std::shared_ptr<impl> impl_;\r
index 91d0969d3334abe69ec0360e9df39b83267d970a..c89cb3420b7d97e670aaf5d52e4d3440869c4933 100644 (file)
@@ -45,6 +45,7 @@ std::wstring media;
 std::wstring log;\r
 std::wstring ftemplate;\r
 std::wstring data;\r
+std::wstring thumbnails;\r
 boost::property_tree::wptree pt;\r
 \r
 void check_is_configured()\r
@@ -67,6 +68,7 @@ void configure(const std::wstring& filename)
                log = widen(paths.get(L"log-path", initialPath + L"\\log\\"));\r
                ftemplate = complete(wpath(widen(paths.get(L"template-path", initialPath + L"\\template\\")))).string();                \r
                data = widen(paths.get(L"data-path", initialPath + L"\\data\\"));\r
+               thumbnails = widen(paths.get(L"thumbnails-path", initialPath + L"\\thumbnails\\"));\r
 \r
                try\r
                {\r
@@ -113,6 +115,10 @@ void configure(const std::wstring& filename)
                auto data_path = boost::filesystem::wpath(data);\r
                if(!boost::filesystem::exists(data_path))\r
                        boost::filesystem::create_directory(data_path);\r
+               \r
+               auto thumbnails_path = boost::filesystem::wpath(thumbnails);\r
+               if(!boost::filesystem::exists(thumbnails_path))\r
+                       boost::filesystem::create_directory(thumbnails_path);\r
        }\r
        catch(...)\r
        {\r
@@ -145,6 +151,12 @@ const std::wstring& data_folder()
        return data;\r
 }\r
 \r
+const std::wstring& thumbnails_folder()\r
+{\r
+       check_is_configured();\r
+       return thumbnails;\r
+}\r
+\r
 #define QUOTE(str) #str\r
 #define EXPAND_AND_QUOTE(str) QUOTE(str)\r
 \r
index 9a50b47abf08056911f20b2e4589db46d5ff27e0..de0c280e40f8f265e6baed8edf1f40462c95ce7b 100644 (file)
@@ -33,6 +33,7 @@ const std::wstring& media_folder();
 const std::wstring& log_folder();\r
 const std::wstring& template_folder();\r
 const std::wstring& data_folder();\r
+const std::wstring& thumbnails_folder();\r
 const std::wstring& version();\r
 \r
 const boost::property_tree::wptree& properties();\r
diff --git a/common/filesystem/filesystem_monitor.h b/common/filesystem/filesystem_monitor.h
new file mode 100644 (file)
index 0000000..ad0834f
--- /dev/null
@@ -0,0 +1,129 @@
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\r
+* This file is part of CasparCG (www.casparcg.com).\r
+*\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
+*\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#pragma once\r
+\r
+#include <functional>\r
+#include <set>\r
+\r
+#include <boost/filesystem.hpp>\r
+#include <boost/noncopyable.hpp>\r
+#include <boost/thread/future.hpp>\r
+\r
+#include "../memory/safe_ptr.h"\r
+\r
+namespace caspar {\r
+\r
+/**\r
+ * A handle to an active filesystem monitor. Will stop scanning when destroyed.\r
+ */\r
+class filesystem_monitor : boost::noncopyable\r
+{\r
+public:\r
+       typedef safe_ptr<filesystem_monitor> ptr;\r
+\r
+       virtual ~filesystem_monitor() {}\r
+\r
+       /**\r
+        * @return a future made available when the initially available files have been\r
+        *         processed.\r
+        */\r
+       virtual boost::unique_future<void> initial_files_processed() = 0;\r
+\r
+       /**\r
+        * Reemmit the already known files as MODIFIED events.\r
+        */\r
+       virtual void reemmit_all() = 0;\r
+\r
+       /**\r
+        * Reemmit a specific file as a MODIFIED event.\r
+        *\r
+        * @param file The file to reemmit.\r
+        */\r
+       virtual void reemmit(const boost::filesystem::wpath& file) = 0;\r
+};\r
+\r
+/**\r
+ * The possible filesystem events.\r
+ */\r
+enum filesystem_event\r
+{\r
+       CREATED = 1,\r
+       REMOVED = 2,\r
+       MODIFIED = 4,\r
+       // Only used for describing a bitmask where all events are wanted. Never used when calling a handler.\r
+       ALL = 7\r
+};\r
+\r
+/**\r
+ * Handles filesystem events.\r
+ * <p>\r
+ * Will always be called from the same thread each time it is called.\r
+ *\r
+ * @param event Can be CREATED, REMOVED or MODIFIED.\r
+ * @param file  The file affected.\r
+ */\r
+typedef std::function<void (filesystem_event event, const boost::filesystem::wpath& file)> filesystem_monitor_handler;\r
+\r
+/**\r
+ * Called when the initially available files has been found.\r
+ * <p>\r
+ * Will be called from the same thread as filesystem_monitor_handler is called.\r
+ *\r
+ * @param initial_files The files that were initially available.\r
+ */\r
+typedef std::function<void (const std::set<boost::filesystem::wpath>& initial_files)> initial_files_handler;\r
+\r
+/**\r
+ * Factory for creating filesystem monitors.\r
+ */\r
+class filesystem_monitor_factory : boost::noncopyable\r
+{\r
+public:\r
+       virtual ~filesystem_monitor_factory() {}\r
+\r
+       /**\r
+        * Create a new monitor.\r
+        *\r
+        * @param folder_to_watch         The folder to recursively watch.\r
+        * @param events_of_interest_mask A bitmask of the events to prenumerate on.\r
+        * @param report_already_existing Whether to initially report all files in\r
+        *                                the folder as CREATED or to only care\r
+        *                                about changes.\r
+        * @param handler                 The handler to call each time a change is\r
+        *                                detected (only events defined by\r
+        *                                events_of_interest_mask).\r
+        * @param initial_files_handler   The optional handler to call when the\r
+        *                                initially available files has been\r
+        *                                discovered.\r
+        *\r
+        * @return The filesystem monitor handle.\r
+        */\r
+       virtual filesystem_monitor::ptr create(\r
+                       const boost::filesystem::wpath& folder_to_watch,\r
+                       filesystem_event events_of_interest_mask,\r
+                       bool report_already_existing,\r
+                       const filesystem_monitor_handler& handler,\r
+                       const initial_files_handler& initial_files_handler =\r
+                                       [] (const std::set<boost::filesystem::wpath>&) { }) = 0;\r
+};\r
+\r
+}\r
diff --git a/common/filesystem/polling_filesystem_monitor.cpp b/common/filesystem/polling_filesystem_monitor.cpp
new file mode 100644 (file)
index 0000000..20b78a8
--- /dev/null
@@ -0,0 +1,335 @@
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\r
+* This file is part of CasparCG (www.casparcg.com).\r
+*\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
+*\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#include "../stdafx.h"\r
+\r
+#include "polling_filesystem_monitor.h"\r
+\r
+#include <map>\r
+#include <set>\r
+#include <iostream>\r
+\r
+#include <boost/thread.hpp>\r
+#include <boost/foreach.hpp>\r
+#include <boost/range/adaptor/map.hpp>\r
+#include <boost/range/algorithm/copy.hpp>\r
+#include <boost/filesystem/fstream.hpp>\r
+\r
+#include <tbb/atomic.h>\r
+#include <tbb/concurrent_queue.h>\r
+\r
+#include "../concurrency/executor.h"\r
+\r
+namespace caspar {\r
+\r
+class exception_protected_handler\r
+{\r
+       filesystem_monitor_handler handler_;\r
+public:\r
+       exception_protected_handler(const filesystem_monitor_handler& handler)\r
+               : handler_(handler)\r
+       {\r
+       }\r
+\r
+       void operator()(filesystem_event event, const boost::filesystem::wpath& file)\r
+       {\r
+               try\r
+               {\r
+                       boost::this_thread::interruption_point();\r
+                       handler_(event, file);\r
+                       boost::this_thread::interruption_point();\r
+               }\r
+               catch (const boost::thread_interrupted&)\r
+               {\r
+                       throw;\r
+               }\r
+               catch (...)\r
+               {\r
+                       CASPAR_LOG_CURRENT_EXCEPTION();\r
+               }\r
+       }\r
+};\r
+\r
+class directory_monitor\r
+{\r
+       bool report_already_existing_;\r
+       boost::filesystem::wpath folder_;\r
+       filesystem_event events_mask_;\r
+       filesystem_monitor_handler handler_;\r
+       initial_files_handler initial_files_handler_;\r
+       bool first_scan_;\r
+       std::map<boost::filesystem::wpath, std::time_t> files_;\r
+       std::map<boost::filesystem::wpath, uintmax_t> being_written_sizes_;\r
+public:\r
+       directory_monitor(\r
+                       bool report_already_existing,\r
+                       const boost::filesystem::wpath& folder,\r
+                       filesystem_event events_mask,\r
+                       const filesystem_monitor_handler& handler,\r
+                       const initial_files_handler& initial_files_handler)\r
+               : report_already_existing_(report_already_existing)\r
+               , folder_(folder)\r
+               , events_mask_(events_mask)\r
+               , handler_(exception_protected_handler(handler))\r
+               , initial_files_handler_(initial_files_handler)\r
+               , first_scan_(true)\r
+       {\r
+       }\r
+\r
+       void reemmit_all()\r
+       {\r
+               if ((events_mask_ & MODIFIED) == 0)\r
+                       return;\r
+\r
+               BOOST_FOREACH(auto& file, files_)\r
+                       handler_(MODIFIED, file.first);\r
+       }\r
+\r
+       void reemmit(const boost::filesystem::wpath& file)\r
+       {\r
+               if ((events_mask_ & MODIFIED) == 0)\r
+                       return;\r
+\r
+               if (files_.find(file) != files_.end() && boost::filesystem::exists(file))\r
+                       handler_(MODIFIED, file);\r
+       }\r
+\r
+       void scan(const boost::function<bool ()>& should_abort)\r
+       {\r
+               static const std::time_t NO_LONGER_WRITING_AGE = 3; // Assume std::time_t is expressed in seconds\r
+               using namespace boost::filesystem;\r
+\r
+               bool interested_in_removed = (events_mask_ & REMOVED) > 0;\r
+               bool interested_in_created = (events_mask_ & CREATED) > 0;\r
+               bool interested_in_modified = (events_mask_ & MODIFIED) > 0;\r
+\r
+               std::set<wpath> removed_files;\r
+               boost::copy(\r
+                               files_ | boost::adaptors::map_keys,\r
+                               std::insert_iterator<decltype(removed_files)>(removed_files, removed_files.end()));\r
+\r
+               std::set<wpath> initial_files;\r
+\r
+               for (wrecursive_directory_iterator iter(folder_); iter != wrecursive_directory_iterator(); ++iter)\r
+               {\r
+                       if (should_abort())\r
+                               return;\r
+\r
+                       auto& path = iter->path();\r
+\r
+                       if (is_directory(path))\r
+                               continue;\r
+\r
+                       auto now = std::time(nullptr);\r
+                       std::time_t current_mtime;\r
+                       \r
+                       try\r
+                       {\r
+                               current_mtime = last_write_time(path);\r
+                       }\r
+                       catch (...)\r
+                       {\r
+                               // Probably removed, will be captured the next round.\r
+                               continue;\r
+                       }\r
+\r
+                       auto time_since_written_to = now - current_mtime;\r
+                       bool no_longer_being_written_to = time_since_written_to >= NO_LONGER_WRITING_AGE;\r
+                       auto previous_it = files_.find(path);\r
+                       bool already_known = previous_it != files_.end();\r
+\r
+                       if (already_known && no_longer_being_written_to)\r
+                       {\r
+                               bool modified = previous_it->second != current_mtime;\r
+\r
+                               if (modified && can_read_file(path))\r
+                               {\r
+                                       if (interested_in_modified)\r
+                                               handler_(MODIFIED, path);\r
+\r
+                                       files_[path] = current_mtime;\r
+                                       being_written_sizes_.erase(path);\r
+                               }\r
+                       }\r
+                       else if (no_longer_being_written_to && can_read_file(path))\r
+                       {\r
+                               if (interested_in_created && (report_already_existing_ || !first_scan_))\r
+                                       handler_(CREATED, path);\r
+\r
+                               if (first_scan_)\r
+                                       initial_files.insert(path);\r
+\r
+                               files_.insert(std::make_pair(path, current_mtime));\r
+                               being_written_sizes_.erase(path);\r
+                       }\r
+\r
+                       removed_files.erase(path);\r
+               }\r
+\r
+               BOOST_FOREACH(auto& path, removed_files)\r
+               {\r
+                       files_.erase(path);\r
+                       being_written_sizes_.erase(path);\r
+\r
+                       if (interested_in_removed)\r
+                               handler_(REMOVED, path);\r
+               }\r
+\r
+               if (first_scan_)\r
+                       initial_files_handler_(initial_files);\r
+\r
+               first_scan_ = false;\r
+       }\r
+private:\r
+       bool can_read_file(const boost::filesystem::wpath& file)\r
+       {\r
+               boost::filesystem::wifstream stream(file);\r
+\r
+               return stream.is_open();\r
+       }\r
+};\r
+\r
+class polling_filesystem_monitor : public filesystem_monitor\r
+{\r
+       directory_monitor root_monitor_;\r
+       boost::thread scanning_thread_;\r
+       tbb::atomic<bool> running_;\r
+       int scan_interval_millis_;\r
+       boost::promise<void> initial_scan_completion_;\r
+       tbb::concurrent_queue<boost::filesystem::wpath> to_reemmit_;\r
+       tbb::atomic<bool> reemmit_all_;\r
+public:\r
+       polling_filesystem_monitor(\r
+                       const boost::filesystem::wpath& folder_to_watch,\r
+                       filesystem_event events_of_interest_mask,\r
+                       bool report_already_existing,\r
+                       int scan_interval_millis,\r
+                       const filesystem_monitor_handler& handler,\r
+                       const initial_files_handler& initial_files_handler)\r
+               : root_monitor_(report_already_existing, folder_to_watch, events_of_interest_mask, handler, initial_files_handler)\r
+               , scan_interval_millis_(scan_interval_millis)\r
+       {\r
+               running_ = true;\r
+               reemmit_all_ = false;\r
+               scanning_thread_ = boost::thread([this] { scanner(); });\r
+       }\r
+\r
+       virtual ~polling_filesystem_monitor()\r
+       {\r
+               running_ = false;\r
+               scanning_thread_.interrupt();\r
+               scanning_thread_.join();\r
+       }\r
+\r
+       virtual boost::unique_future<void> initial_files_processed()\r
+       {\r
+               return initial_scan_completion_.get_future();\r
+       }\r
+\r
+       virtual void reemmit_all()\r
+       {\r
+               reemmit_all_ = true;\r
+       }\r
+\r
+       virtual void reemmit(const boost::filesystem::wpath& file)\r
+       {\r
+               to_reemmit_.push(file);\r
+       }\r
+private:\r
+       void scanner()\r
+       {\r
+               win32_exception::install_handler();\r
+               detail::SetThreadName(GetCurrentThreadId(), "polling_filesystem_monitor");\r
+\r
+               bool running = scan(false);\r
+               initial_scan_completion_.set_value();\r
+\r
+               if (running)\r
+                       while (scan(true));\r
+       }\r
+\r
+       bool scan(bool sleep)\r
+       {\r
+               try\r
+               {\r
+                       if (sleep)\r
+                               boost::this_thread::sleep(boost::posix_time::milliseconds(scan_interval_millis_));\r
+\r
+                       if (reemmit_all_.fetch_and_store(false))\r
+                               root_monitor_.reemmit_all();\r
+                       else\r
+                       {\r
+                               boost::filesystem::wpath file;\r
+\r
+                               while (to_reemmit_.try_pop(file))\r
+                                       root_monitor_.reemmit(file);\r
+                       }\r
+\r
+                       root_monitor_.scan([=] { return !running_; });\r
+               }\r
+               catch (const boost::thread_interrupted&)\r
+               {\r
+               }\r
+               catch (...)\r
+               {\r
+                       CASPAR_LOG_CURRENT_EXCEPTION();\r
+               }\r
+\r
+               return running_;\r
+       }\r
+};\r
+\r
+struct polling_filesystem_monitor_factory::implementation\r
+{\r
+       int scan_interval_millis;\r
+\r
+       implementation(int scan_interval_millis)\r
+               : scan_interval_millis(scan_interval_millis)\r
+       {\r
+       }\r
+};\r
+\r
+polling_filesystem_monitor_factory::polling_filesystem_monitor_factory(int scan_interval_millis)\r
+       : impl_(new implementation(scan_interval_millis))\r
+{\r
+}\r
+\r
+polling_filesystem_monitor_factory::~polling_filesystem_monitor_factory()\r
+{\r
+}\r
+\r
+filesystem_monitor::ptr polling_filesystem_monitor_factory::create(\r
+               const boost::filesystem::wpath& folder_to_watch,\r
+               filesystem_event events_of_interest_mask,\r
+               bool report_already_existing,\r
+               const filesystem_monitor_handler& handler,\r
+               const initial_files_handler& initial_files_handler)\r
+{\r
+       return make_safe<polling_filesystem_monitor>(\r
+                       folder_to_watch,\r
+                       events_of_interest_mask,\r
+                       report_already_existing,\r
+                       impl_->scan_interval_millis,\r
+                       handler,\r
+                       initial_files_handler);\r
+}\r
+\r
+}\r
diff --git a/common/filesystem/polling_filesystem_monitor.h b/common/filesystem/polling_filesystem_monitor.h
new file mode 100644 (file)
index 0000000..d3b6701
--- /dev/null
@@ -0,0 +1,58 @@
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\r
+* This file is part of CasparCG (www.casparcg.com).\r
+*\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
+*\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#pragma once\r
+\r
+#include "filesystem_monitor.h"\r
+\r
+namespace caspar {\r
+\r
+/**\r
+ * A portable filesystem monitor implementation which periodically polls the\r
+ * filesystem for changes. Will not react instantly but never misses any\r
+ * changes.\r
+ * <p>\r
+ * Will create a dedicated thread for each monitor created.\r
+ */\r
+class polling_filesystem_monitor_factory : public filesystem_monitor_factory\r
+{\r
+public:\r
+       /**\r
+        * Constructor.\r
+        *\r
+        * @param scan_interval_millis The number of milliseconds between each\r
+        *                             scheduled scan. Lower values lowers the reaction\r
+        *                             time but causes more I/O.\r
+        */\r
+       polling_filesystem_monitor_factory(int scan_interval_millis = 5000);\r
+       virtual ~polling_filesystem_monitor_factory();\r
+       virtual filesystem_monitor::ptr create(\r
+                       const boost::filesystem::wpath& folder_to_watch,\r
+                       filesystem_event events_of_interest_mask,\r
+                       bool report_already_existing,\r
+                       const filesystem_monitor_handler& handler,\r
+                       const initial_files_handler& initial_files_handler);\r
+private:\r
+       struct implementation;\r
+       safe_ptr<implementation> impl_;\r
+};\r
+\r
+}\r
index 8a7388400f05ac87dc31010eb8b745fddaa8dcee..872801ac6289965692d706f22862b95a71d35e56 100644 (file)
     <ClInclude Include="mixer\image\shader\image_shader.h" />\r
     <ClInclude Include="producer\channel\channel_producer.h" />\r
     <ClInclude Include="producer\playlist\playlist_producer.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)'=='Develop|Win32'">../../stdafx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../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)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
index d5916b9a829c103f229f30a8df5b023b25a33e75..1a7f733e3c2b47b1e3cc49164a33a1c7ba2fab17 100644 (file)
     <ClInclude Include="producer\channel\channel_producer.h">\r
       <Filter>source\producer\channel</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\channel\channel_producer.cpp">\r
       <Filter>source\producer\channel</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 ed27c816b5a2fbaf79a815c72fec992c366c42eb..02cc30f3a3bf409a33dfd257a64dd97d84cacd92 100644 (file)
@@ -37,6 +37,7 @@
 namespace caspar { namespace core {\r
        \r
 std::vector<const producer_factory_t> g_factories;\r
+std::vector<const producer_factory_t> g_thumbnail_factories;\r
        \r
 class destroy_producer_proxy : public frame_producer\r
 {      \r
@@ -97,6 +98,7 @@ public:
 \r
        virtual safe_ptr<basic_frame>                                                           receive(int hints) override                                                                                             {return (*producer_)->receive(hints);}\r
        virtual safe_ptr<basic_frame>                                                           last_frame() const override                                                                                             {return (*producer_)->last_frame();}\r
+       virtual safe_ptr<basic_frame>                                                           create_thumbnail_frame() override                                                                               {return (*producer_)->create_thumbnail_frame();}\r
        virtual std::wstring                                                                            print() const override                                                                                                  {return (*producer_)->print();}\r
        virtual boost::property_tree::wptree                                            info() const override                                                                                                   {return (*producer_)->info();}\r
        virtual boost::unique_future<std::wstring>                                      call(const std::wstring& str) override                                                                  {return (*producer_)->call(str);}\r
@@ -130,6 +132,7 @@ public:
 \r
        virtual safe_ptr<basic_frame>                                                           receive(int hints) override                                                                                             {return (producer_)->receive(hints);}\r
        virtual safe_ptr<basic_frame>                                                           last_frame() const override                                                                                             {return (producer_)->last_frame();}\r
+       virtual safe_ptr<basic_frame>                                                           create_thumbnail_frame() override                                                                               {return (producer_)->create_thumbnail_frame();}\r
        virtual std::wstring                                                                            print() const override                                                                                                  {return (producer_)->print();}\r
        virtual boost::property_tree::wptree                                            info() const override                                                                                                   {return (producer_)->info();}\r
        virtual boost::unique_future<std::wstring>                                      call(const std::wstring& str) override                                                                  {return (producer_)->call(str);}\r
@@ -158,6 +161,7 @@ public:
        \r
        virtual safe_ptr<basic_frame> receive(int){return frame_;}\r
        virtual safe_ptr<core::basic_frame> last_frame() const{return frame_;}\r
+       virtual safe_ptr<core::basic_frame> create_thumbnail_frame() {return frame_;}\r
        virtual std::wstring print() const{return L"dummy[" + print_ + L"]";}\r
        virtual uint32_t nb_frames() const {return nb_frames_;} \r
        virtual boost::property_tree::wptree info() const override\r
@@ -188,7 +192,12 @@ const safe_ptr<frame_producer>& frame_producer::empty() // nothrow
 {\r
        static safe_ptr<frame_producer> producer = make_safe<empty_frame_producer>();\r
        return producer;\r
-}      \r
+}\r
+\r
+safe_ptr<basic_frame> frame_producer::create_thumbnail_frame()\r
+{\r
+       return basic_frame::empty();\r
+}\r
 \r
 safe_ptr<basic_frame> receive_and_follow(safe_ptr<frame_producer>& producer, int hints)\r
 {      \r
@@ -215,13 +224,18 @@ void register_producer_factory(const producer_factory_t& factory)
        g_factories.push_back(factory);\r
 }\r
 \r
-safe_ptr<core::frame_producer> do_create_producer(const safe_ptr<frame_factory>& my_frame_factory, const std::vector<std::wstring>& params)\r
+void register_thumbnail_producer_factory(const producer_factory_t& factory)\r
+{\r
+       g_thumbnail_factories.push_back(factory);\r
+}\r
+\r
+safe_ptr<core::frame_producer> do_create_producer(const safe_ptr<frame_factory>& my_frame_factory, const std::vector<std::wstring>& params, const std::vector<const producer_factory_t>& factories, bool throw_on_fail = false)\r
 {\r
        if(params.empty())\r
                BOOST_THROW_EXCEPTION(invalid_argument() << arg_name_info("params") << arg_value_info(""));\r
        \r
        auto producer = frame_producer::empty();\r
-       std::any_of(g_factories.begin(), g_factories.end(), [&](const producer_factory_t& factory) -> bool\r
+       std::any_of(factories.begin(), factories.end(), [&](const producer_factory_t& factory) -> bool\r
                {\r
                        try\r
                        {\r
@@ -229,7 +243,10 @@ safe_ptr<core::frame_producer> do_create_producer(const safe_ptr<frame_factory>&
                        }\r
                        catch(...)\r
                        {\r
-                               CASPAR_LOG_CURRENT_EXCEPTION();\r
+                               if (throw_on_fail)\r
+                                       throw;\r
+                               else\r
+                                       CASPAR_LOG_CURRENT_EXCEPTION();\r
                        }\r
                        return producer != frame_producer::empty();\r
                });\r
@@ -243,10 +260,9 @@ safe_ptr<core::frame_producer> do_create_producer(const safe_ptr<frame_factory>&
        return producer;\r
 }\r
 \r
-\r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<frame_factory>& my_frame_factory, const std::vector<std::wstring>& params)\r
 {      \r
-       auto producer = do_create_producer(my_frame_factory, params);\r
+       auto producer = do_create_producer(my_frame_factory, params, g_factories);\r
        auto key_producer = frame_producer::empty();\r
        \r
        try // to find a key file.\r
@@ -255,11 +271,11 @@ safe_ptr<core::frame_producer> create_producer(const safe_ptr<frame_factory>& my
                if(params_copy.size() > 0)\r
                {\r
                        params_copy[0] += L"_A";\r
-                       key_producer = do_create_producer(my_frame_factory, params_copy);                       \r
+                       key_producer = do_create_producer(my_frame_factory, params_copy, g_factories);                  \r
                        if(key_producer == frame_producer::empty())\r
                        {\r
                                params_copy[0] += L"LPHA";\r
-                               key_producer = do_create_producer(my_frame_factory, params_copy);       \r
+                               key_producer = do_create_producer(my_frame_factory, params_copy, g_factories);  \r
                        }\r
                }\r
        }\r
@@ -279,6 +295,35 @@ safe_ptr<core::frame_producer> create_producer(const safe_ptr<frame_factory>& my
        return producer;\r
 }\r
 \r
+safe_ptr<core::frame_producer> create_thumbnail_producer(const safe_ptr<frame_factory>& my_frame_factory, const std::wstring& media_file)\r
+{\r
+       std::vector<std::wstring> params;\r
+       params.push_back(media_file);\r
+\r
+       auto producer = do_create_producer(my_frame_factory, params, g_thumbnail_factories, true);\r
+       auto key_producer = frame_producer::empty();\r
+       \r
+       try // to find a key file.\r
+       {\r
+               auto params_copy = params;\r
+               if (params_copy.size() > 0)\r
+               {\r
+                       params_copy[0] += L"_A";\r
+                       key_producer = do_create_producer(my_frame_factory, params_copy, g_thumbnail_factories, true);\r
+                       if (key_producer == frame_producer::empty())\r
+                       {\r
+                               params_copy[0] += L"LPHA";\r
+                               key_producer = do_create_producer(my_frame_factory, params_copy, g_thumbnail_factories, true);\r
+                       }\r
+               }\r
+       }\r
+       catch(...){}\r
+\r
+       if (producer != frame_producer::empty() && key_producer != frame_producer::empty())\r
+               return create_separated_thumbnail_producer(producer, key_producer);\r
+       \r
+       return producer;\r
+}\r
 \r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<frame_factory>& factory, const std::wstring& params)\r
 {\r
index 5a4f92649bb3c796c476661814b2b5b50903cef3..a29e78d03a175fe224d5a4a3de7c54c5beef3be8 100644 (file)
@@ -72,6 +72,7 @@ public:
        \r
        virtual safe_ptr<basic_frame> receive(int hints) = 0;\r
        virtual safe_ptr<core::basic_frame> last_frame() const = 0;\r
+       virtual safe_ptr<basic_frame> create_thumbnail_frame();\r
 \r
        static const safe_ptr<frame_producer>& empty(); // nothrow\r
 };\r
@@ -80,9 +81,11 @@ safe_ptr<basic_frame> receive_and_follow(safe_ptr<frame_producer>& producer, int
 \r
 typedef std::function<safe_ptr<core::frame_producer>(const safe_ptr<frame_factory>&, const std::vector<std::wstring>&)> producer_factory_t;\r
 void register_producer_factory(const producer_factory_t& factory); // Not thread-safe.\r
+void register_thumbnail_producer_factory(const producer_factory_t& factory); // Not thread-safe.\r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<frame_factory>&, const std::vector<std::wstring>& params);\r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<frame_factory>&, const std::wstring& params);\r
 safe_ptr<core::frame_producer> create_producer_destroy_proxy(safe_ptr<core::frame_producer> producer);\r
 safe_ptr<core::frame_producer> create_producer_print_proxy(safe_ptr<core::frame_producer> producer);\r
+safe_ptr<core::frame_producer> create_thumbnail_producer(const safe_ptr<frame_factory>& factory, const std::wstring& media_file);\r
 \r
 }}\r
index 9d12fdeb9022433ee5f36d8348ea9d9cc79bee29..9ef26c6f0e5a8566028cd7a1feeb1a68634bc1fa 100644 (file)
@@ -81,6 +81,23 @@ struct separated_producer : public frame_producer
                return disable_audio(last_frame_);\r
        }\r
 \r
+       virtual safe_ptr<basic_frame> create_thumbnail_frame() override\r
+       {\r
+               auto fill_frame = fill_producer_->create_thumbnail_frame();\r
+               auto key_frame = key_producer_->create_thumbnail_frame();\r
+\r
+               if (fill_frame == basic_frame::empty() || key_frame == basic_frame::empty())\r
+                       return basic_frame::empty();\r
+\r
+               if (fill_frame == basic_frame::eof() || key_frame == basic_frame::eof())\r
+                       return basic_frame::eof();\r
+\r
+               if (fill_frame == basic_frame::late() || key_frame == basic_frame::late())\r
+                       return basic_frame::late();\r
+\r
+               return basic_frame::fill_and_key(fill_frame, key_frame);\r
+       }\r
+\r
        virtual uint32_t nb_frames() const override\r
        {\r
                return std::min(fill_producer_->nb_frames(), key_producer_->nb_frames());\r
@@ -107,5 +124,10 @@ safe_ptr<frame_producer> create_separated_producer(const safe_ptr<frame_producer
                        make_safe<separated_producer>(fill, key));\r
 }\r
 \r
+safe_ptr<frame_producer> create_separated_thumbnail_producer(const safe_ptr<frame_producer>& fill, const safe_ptr<frame_producer>& key)\r
+{\r
+       return make_safe<separated_producer>(fill, key);\r
+}\r
+\r
 }}\r
 \r
index 1efd6a2f0ab2a858670ba4194504143e2843793e..3937243479cc9bb8acbcc055a3cd46d1d0c9a0d6 100644 (file)
@@ -29,5 +29,6 @@
 namespace caspar { namespace core {\r
        \r
 safe_ptr<frame_producer> create_separated_producer(const safe_ptr<frame_producer>& fill, const safe_ptr<frame_producer>& key);\r
+safe_ptr<frame_producer> create_separated_thumbnail_producer(const safe_ptr<frame_producer>& fill, const safe_ptr<frame_producer>& key);\r
 \r
 }}
\ No newline at end of file
diff --git a/core/thumbnail_generator.cpp b/core/thumbnail_generator.cpp
new file mode 100644 (file)
index 0000000..d98495f
--- /dev/null
@@ -0,0 +1,376 @@
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\r
+* This file is part of CasparCG (www.casparcg.com).\r
+*\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
+*\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#include "stdafx.h"\r
+\r
+#include "thumbnail_generator.h"\r
+\r
+#include <iostream>\r
+#include <iterator>\r
+#include <set>\r
+\r
+#include <boost/thread.hpp>\r
+#include <boost/range/algorithm/transform.hpp>\r
+#include <boost/algorithm/string/predicate.hpp>\r
+\r
+#include <tbb/atomic.h>\r
+\r
+#include "producer/frame_producer.h"\r
+#include "consumer/frame_consumer.h"\r
+#include "mixer/mixer.h"\r
+#include "video_format.h"\r
+#include "producer/frame/basic_frame.h"\r
+#include "producer/frame/frame_transform.h"\r
+\r
+namespace caspar { namespace core {\r
+\r
+std::wstring get_relative_without_extension(\r
+               const boost::filesystem::wpath& file,\r
+               const boost::filesystem::wpath& relative_to)\r
+{\r
+       auto result = file.stem();\r
+               \r
+       boost::filesystem::wpath current_path = file;\r
+\r
+       while (true)\r
+       {\r
+               current_path = current_path.parent_path();\r
+\r
+               if (boost::filesystem::equivalent(current_path, relative_to))\r
+                       break;\r
+\r
+               if (current_path.empty())\r
+                       throw std::runtime_error("File not relative to folder");\r
+\r
+               result = current_path.filename() + L"/" + result;\r
+       }\r
+\r
+       return result;\r
+}\r
+\r
+struct thumbnail_output : public mixer::target_t\r
+{\r
+       tbb::atomic<int> sleep_millis;\r
+       std::function<void (const safe_ptr<read_frame>& frame)> on_send;\r
+\r
+       thumbnail_output(int sleep_millis)\r
+       {\r
+               this->sleep_millis = sleep_millis;\r
+       }\r
+\r
+       void send(const std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>& frame_and_ticket)\r
+       {\r
+               int current_sleep = sleep_millis;\r
+\r
+               if (current_sleep > 0)\r
+                       boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));\r
+\r
+               on_send(frame_and_ticket.first);\r
+               on_send = nullptr;\r
+       }\r
+};\r
+\r
+struct thumbnail_generator::implementation\r
+{\r
+private:\r
+       boost::filesystem::wpath media_path_;\r
+       boost::filesystem::wpath thumbnails_path_;\r
+       int width_;\r
+       int height_;\r
+       safe_ptr<ogl_device> ogl_;\r
+       safe_ptr<diagnostics::graph> graph_;\r
+       video_format_desc format_desc_;\r
+       safe_ptr<thumbnail_output> output_;\r
+       safe_ptr<mixer> mixer_;\r
+       thumbnail_creator thumbnail_creator_;\r
+       filesystem_monitor::ptr monitor_;\r
+public:\r
+       implementation(\r
+                       filesystem_monitor_factory& monitor_factory,\r
+                       const boost::filesystem::wpath& media_path,\r
+                       const boost::filesystem::wpath& thumbnails_path,\r
+                       int width,\r
+                       int height,\r
+                       const video_format_desc& render_video_mode,\r
+                       const safe_ptr<ogl_device>& ogl,\r
+                       int generate_delay_millis,\r
+                       const thumbnail_creator& thumbnail_creator)\r
+               : media_path_(media_path)\r
+               , thumbnails_path_(thumbnails_path)\r
+               , width_(width)\r
+               , height_(height)\r
+               , ogl_(ogl)\r
+               , format_desc_(render_video_mode)\r
+               , output_(new thumbnail_output(generate_delay_millis))\r
+               , mixer_(new mixer(graph_, output_, format_desc_, ogl))\r
+               , thumbnail_creator_(thumbnail_creator)\r
+               , monitor_(monitor_factory.create(\r
+                               media_path,\r
+                               ALL,\r
+                               true,\r
+                               [this] (filesystem_event event, const boost::filesystem::wpath& file)\r
+                               {\r
+                                       this->on_file_event(event, file);\r
+                               },\r
+                               [this] (const std::set<boost::filesystem::wpath>& initial_files) \r
+                               {\r
+                                       this->on_initial_files(initial_files);\r
+                               }))\r
+       {\r
+               graph_->set_text(L"thumbnail-channel");\r
+               graph_->auto_reset();\r
+               diagnostics::register_graph(graph_);\r
+               //monitor_->initial_scan_completion().get();\r
+               //output_->sleep_millis = 2000;\r
+       }\r
+\r
+       void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)\r
+       {\r
+               using namespace boost::filesystem;\r
+\r
+               std::set<std::wstring> relative_without_extensions;\r
+               boost::transform(\r
+                               initial_files,\r
+                               std::insert_iterator<std::set<std::wstring>>(\r
+                                               relative_without_extensions,\r
+                                               relative_without_extensions.end()),\r
+                               boost::bind(&get_relative_without_extension, _1, media_path_));\r
+\r
+               for (wrecursive_directory_iterator iter(thumbnails_path_); iter != wrecursive_directory_iterator(); ++iter)\r
+               {\r
+                       auto& path = iter->path();\r
+\r
+                       if (!is_regular_file(path))\r
+                               continue;\r
+\r
+                       auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);\r
+                       bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension) \r
+                                       == relative_without_extensions.end();\r
+\r
+                       if (no_corresponding_media_file)\r
+                               remove(thumbnails_path_ / (relative_without_extension + L".png"));\r
+               }\r
+       }\r
+\r
+       void generate(const std::wstring& media_file)\r
+       {\r
+               using namespace boost::filesystem;\r
+               auto base_file = media_path_ / media_file;\r
+               auto folder = base_file.parent_path();\r
+\r
+               for (wdirectory_iterator iter(folder); iter != wdirectory_iterator(); ++iter)\r
+               {\r
+                       auto stem = iter->path().stem();\r
+\r
+                       if (boost::iequals(stem, base_file.filename()))\r
+                               monitor_->reemmit(iter->path());\r
+               }\r
+       }\r
+\r
+       void generate_all()\r
+       {\r
+               monitor_->reemmit_all();\r
+       }\r
+\r
+       void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)\r
+       {\r
+               switch (event)\r
+               {\r
+               case CREATED:\r
+                       if (needs_to_be_generated(file))\r
+                               generate_thumbnail(file);\r
+\r
+                       break;\r
+               case MODIFIED:\r
+                       generate_thumbnail(file);\r
+\r
+                       break;\r
+               case REMOVED:\r
+                       auto relative_without_extension = get_relative_without_extension(file, media_path_);\r
+                       boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));\r
+\r
+                       break;\r
+               }\r
+       }\r
+\r
+       bool needs_to_be_generated(const boost::filesystem::wpath& file)\r
+       {\r
+               using namespace boost::filesystem;\r
+\r
+               auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");\r
+\r
+               if (!exists(png_file))\r
+                       return true;\r
+\r
+               std::time_t media_file_mtime;\r
+\r
+               try\r
+               {\r
+                       media_file_mtime = last_write_time(file);\r
+               }\r
+               catch (...)\r
+               {\r
+                       // Probably removed.\r
+                       return false;\r
+               }\r
+\r
+               try\r
+               {\r
+                       return media_file_mtime != last_write_time(png_file);\r
+               }\r
+               catch (...)\r
+               {\r
+                       // thumbnail probably removed.\r
+                       return true;\r
+               }\r
+       }\r
+\r
+       void generate_thumbnail(const boost::filesystem::wpath& file)\r
+       {\r
+               auto media_file = get_relative_without_extension(file, media_path_);\r
+               auto png_file = thumbnails_path_ / (media_file + L".png");\r
+               boost::promise<void> thumbnail_ready;\r
+\r
+               {\r
+                       auto producer = frame_producer::empty();\r
+\r
+                       try\r
+                       {\r
+                               producer = create_thumbnail_producer(mixer_, media_file);\r
+                       }\r
+                       catch (const boost::thread_interrupted&)\r
+                       {\r
+                               throw;\r
+                       }\r
+                       catch (...)\r
+                       {\r
+                               CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;\r
+                               return;\r
+                       }\r
+\r
+                       if (producer == frame_producer::empty())\r
+                       {\r
+                               CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;\r
+                               return;\r
+                       }\r
+\r
+                       boost::filesystem::create_directories(png_file.parent_path());\r
+                       output_->on_send = [this, &png_file] (const safe_ptr<read_frame>& frame)\r
+                       {\r
+                               thumbnail_creator_(frame, format_desc_, png_file, width_, height_);\r
+                       };\r
+\r
+                       std::map<int, safe_ptr<basic_frame>> frames;\r
+                       auto raw_frame = basic_frame::empty();\r
+\r
+                       try\r
+                       {\r
+                               raw_frame = producer->create_thumbnail_frame();\r
+                       }\r
+                       catch (const boost::thread_interrupted&)\r
+                       {\r
+                               throw;\r
+                       }\r
+                       catch (...)\r
+                       {\r
+                               CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;\r
+                               return;\r
+                       }\r
+\r
+                       if (raw_frame == basic_frame::empty()\r
+                                       || raw_frame == basic_frame::empty()\r
+                                       || raw_frame == basic_frame::eof()\r
+                                       || raw_frame == basic_frame::late())\r
+                       {\r
+                               CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;\r
+                               return;\r
+                       }\r
+\r
+                       auto transformed_frame = make_safe<basic_frame>(raw_frame);\r
+                       transformed_frame->get_frame_transform().fill_scale[0] = static_cast<double>(width_) / format_desc_.width;\r
+                       transformed_frame->get_frame_transform().fill_scale[1] = static_cast<double>(height_) / format_desc_.height;\r
+                       frames.insert(std::make_pair(0, transformed_frame));\r
+\r
+                       std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*)\r
+                       {\r
+                               thumbnail_ready.set_value();\r
+                       });\r
+\r
+                       mixer_->send(std::make_pair(frames, ticket));\r
+                       ticket.reset();\r
+               }\r
+               thumbnail_ready.get_future().get();\r
+\r
+               if (boost::filesystem::exists(png_file))\r
+               {\r
+                       // Adjust timestamp to match source file.\r
+                       try\r
+                       {\r
+                               boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));\r
+                               CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;\r
+                       }\r
+                       catch (...)\r
+                       {\r
+                               // One of the files was removed before the call to last_write_time.\r
+                       }\r
+               }\r
+               else\r
+                       CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;\r
+       }\r
+};\r
+\r
+thumbnail_generator::thumbnail_generator(\r
+               filesystem_monitor_factory& monitor_factory,\r
+               const boost::filesystem::wpath& media_path,\r
+               const boost::filesystem::wpath& thumbnails_path,\r
+               int width,\r
+               int height,\r
+               const video_format_desc& render_video_mode,\r
+               const safe_ptr<ogl_device>& ogl,\r
+               int generate_delay_millis,\r
+               const thumbnail_creator& thumbnail_creator)\r
+               : impl_(new implementation(\r
+                               monitor_factory,\r
+                               media_path,\r
+                               thumbnails_path,\r
+                               width, height,\r
+                               render_video_mode,\r
+                               ogl,\r
+                               generate_delay_millis,\r
+                               thumbnail_creator))\r
+{\r
+}\r
+\r
+thumbnail_generator::~thumbnail_generator()\r
+{\r
+}\r
+\r
+void thumbnail_generator::generate(const std::wstring& media_file)\r
+{\r
+       impl_->generate(media_file);\r
+}\r
+\r
+void thumbnail_generator::generate_all()\r
+{\r
+       impl_->generate_all();\r
+}\r
+\r
+}}\r
diff --git a/core/thumbnail_generator.h b/core/thumbnail_generator.h
new file mode 100644 (file)
index 0000000..2b9ec03
--- /dev/null
@@ -0,0 +1,63 @@
+/*\r
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
+*\r
+* This file is part of CasparCG (www.casparcg.com).\r
+*\r
+* CasparCG is free software: you can redistribute it and/or modify\r
+* it under the terms of the GNU General Public License as published by\r
+* the Free Software Foundation, either version 3 of the License, or\r
+* (at your option) any later version.\r
+*\r
+* CasparCG is distributed in the hope that it will be useful,\r
+* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+* GNU General Public License for more details.\r
+*\r
+* You should have received a copy of the GNU General Public License\r
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
+*/\r
+\r
+#pragma once\r
+\r
+#include <boost/noncopyable.hpp>\r
+\r
+#include <common/memory/safe_ptr.h>\r
+#include <common/filesystem/filesystem_monitor.h>\r
+\r
+namespace caspar { namespace core {\r
+\r
+class ogl_device;\r
+class read_frame;\r
+struct video_format_desc;\r
+\r
+typedef std::function<void (\r
+               const safe_ptr<read_frame>& frame,\r
+               const video_format_desc& format_desc,\r
+               const boost::filesystem::wpath& output_file,\r
+               int width,\r
+               int height)> thumbnail_creator;\r
+\r
+class thumbnail_generator : boost::noncopyable\r
+{\r
+public:\r
+       thumbnail_generator(\r
+                       filesystem_monitor_factory& monitor_factory,\r
+                       const boost::filesystem::wpath& media_path,\r
+                       const boost::filesystem::wpath& thumbnails_path,\r
+                       int width,\r
+                       int height,\r
+                       const video_format_desc& render_video_mode,\r
+                       const safe_ptr<ogl_device>& ogl,\r
+                       int generate_delay_millis,\r
+                       const thumbnail_creator& thumbnail_creator);\r
+       ~thumbnail_generator();\r
+       void generate(const std::wstring& media_file);\r
+       void generate_all();\r
+private:\r
+       struct implementation;\r
+       safe_ptr<implementation> impl_;\r
+};\r
+\r
+}}\r
index bde7795f4aee17dd2843a5b6bbd2569c8b8fcdc0..2723a6489326ce49d75c7749b9cc7671cf89b1f0 100644 (file)
@@ -113,7 +113,7 @@ public:
                , filter_(filter)\r
                , format_desc_(format_desc)\r
                , audio_cadence_(format_desc.audio_cadence)\r
-               , muxer_(format_desc.fps, frame_factory, filter)\r
+               , muxer_(format_desc.fps, frame_factory, false, filter)\r
                , sync_buffer_(format_desc.audio_cadence.size())\r
                , frame_factory_(frame_factory)\r
        {               \r
index c11babbd1c6371bbd610f83e96bab675268a9c14..16a1a970e413afb5cbfef06705f1c26bcd99e2f2 100644 (file)
@@ -31,6 +31,8 @@
 \r
 #include <tbb/recursive_mutex.h>\r
 \r
+#include <boost/thread.hpp>\r
+\r
 #if defined(_MSC_VER)\r
 #pragma warning (disable : 4244)\r
 #pragma warning (disable : 4603)\r
@@ -156,6 +158,43 @@ void log_callback(void* ptr, int level, const char* fmt, va_list vl)
     //colored_fputs(av_clip(level>>3, 0, 6), line);\r
 }\r
 \r
+boost::thread_specific_ptr<bool>& get_disable_logging_for_thread()\r
+{\r
+       static boost::thread_specific_ptr<bool> disable_logging_for_thread;\r
+\r
+       return disable_logging_for_thread;\r
+}\r
+\r
+void disable_logging_for_thread()\r
+{\r
+       if (get_disable_logging_for_thread().get() == nullptr)\r
+               get_disable_logging_for_thread().reset(new bool); // bool value does not matter\r
+}\r
+\r
+bool is_logging_already_disabled_for_thread()\r
+{\r
+       return get_disable_logging_for_thread().get() != nullptr;\r
+}\r
+\r
+std::shared_ptr<void> temporary_disable_logging_for_thread(bool disable)\r
+{\r
+       if (!disable || is_logging_already_disabled_for_thread())\r
+               return std::shared_ptr<void>();\r
+\r
+       disable_logging_for_thread();\r
+\r
+       return std::shared_ptr<void>(nullptr, [] (void*)\r
+       {\r
+               get_disable_logging_for_thread().release(); // Only works correctly if destructed in same thread as original caller.\r
+       });\r
+}\r
+\r
+void log_for_thread(void* ptr, int level, const char* fmt, va_list vl)\r
+{\r
+       //if (get_disable_logging_for_thread().get() == nullptr) // It does not matter what the value of the bool is\r
+               log_callback(ptr, level, fmt, vl);\r
+}\r
+\r
 //static int query_yadif_formats(AVFilterContext *ctx)\r
 //{\r
 //    static const int pix_fmts[] = {\r
@@ -198,7 +237,7 @@ void log_callback(void* ptr, int level, const char* fmt, va_list vl)
 void init()\r
 {\r
        av_lockmgr_register(ffmpeg_lock_callback);\r
-       av_log_set_callback(log_callback);\r
+       av_log_set_callback(log_for_thread);\r
 \r
     avfilter_register_all();\r
        //fix_yadif_filter_format_query();\r
@@ -209,6 +248,7 @@ void init()
        \r
        core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});\r
        core::register_producer_factory(create_producer);\r
+       core::register_thumbnail_producer_factory(create_thumbnail_producer);\r
 }\r
 \r
 void uninit()\r
index 0c491878a1bfabaea372b7bd7df1573047f8d258..604802fd34284800b849afe76f23db880f68d2f2 100644 (file)
 #pragma once\r
 \r
 #include <string>\r
+#include <memory>\r
 \r
 namespace caspar { namespace ffmpeg {\r
 \r
 void init();\r
 void uninit();\r
+void disable_logging_for_thread();\r
+std::shared_ptr<void> temporary_disable_logging_for_thread(bool disable);\r
 \r
 std::wstring get_avcodec_version();\r
 std::wstring get_avformat_version();\r
index 7b98d4135d8a13f439f648a784bbe25adc97aa52..e5b03432121fe83210e2220da6089f57b3ea888f 100644 (file)
@@ -24,6 +24,7 @@
 #include "ffmpeg_producer.h"\r
 \r
 #include "../ffmpeg_error.h"\r
+#include "../ffmpeg.h"\r
 \r
 #include "muxer/frame_muxer.h"\r
 #include "input/input.h"\r
@@ -69,6 +70,8 @@ struct ffmpeg_producer : public core::frame_producer
        const safe_ptr<core::frame_factory>                                                     frame_factory_;\r
        const core::video_format_desc                                                           format_desc_;\r
 \r
+       std::shared_ptr<void>                                                                           initial_logger_disabler_;\r
+\r
        input                                                                                                           input_; \r
        std::unique_ptr<video_decoder>                                                          video_decoder_;\r
        std::unique_ptr<audio_decoder>                                                          audio_decoder_; \r
@@ -77,6 +80,7 @@ struct ffmpeg_producer : public core::frame_producer
        const double                                                                                            fps_;\r
        const uint32_t                                                                                          start_;\r
        const uint32_t                                                                                          length_;\r
+       const bool                                                                                                      thumbnail_mode_;\r
 \r
        safe_ptr<core::basic_frame>                                                                     last_frame_;\r
        \r
@@ -86,25 +90,28 @@ struct ffmpeg_producer : public core::frame_producer
        uint32_t                                                                                                        file_frame_number_;\r
        \r
 public:\r
-       explicit ffmpeg_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, const std::wstring& filter, bool loop, uint32_t start, uint32_t length\r
+       explicit ffmpeg_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, const std::wstring& filter, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode)\r
                : filename_(filename)\r
                , frame_factory_(frame_factory)         \r
                , format_desc_(frame_factory->get_video_format_desc())\r
-               , input_(graph_, filename_, loop, start, length)\r
+               , initial_logger_disabler_(temporary_disable_logging_for_thread(thumbnail_mode))\r
+               , input_(graph_, filename_, loop, start, length, thumbnail_mode)\r
                , fps_(read_fps(*input_.context(), format_desc_.fps))\r
                , start_(start)\r
                , length_(length)\r
+               , thumbnail_mode_(thumbnail_mode)\r
                , last_frame_(core::basic_frame::empty())\r
                , frame_number_(0)\r
        {\r
                graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));\r
                graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f));   \r
                diagnostics::register_graph(graph_);\r
-               \r
+       \r
                try\r
                {\r
                        video_decoder_.reset(new video_decoder(input_.context()));\r
-                       CASPAR_LOG(info) << print() << L" " << video_decoder_->print();\r
+                       if (!thumbnail_mode_)\r
+                               CASPAR_LOG(info) << print() << L" " << video_decoder_->print();\r
                }\r
                catch(averror_stream_not_found&)\r
                {\r
@@ -112,36 +119,53 @@ public:
                }\r
                catch(...)\r
                {\r
-                       CASPAR_LOG_CURRENT_EXCEPTION();\r
-                       CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video.";        \r
+                       if (!thumbnail_mode_)\r
+                       {\r
+                               CASPAR_LOG_CURRENT_EXCEPTION();\r
+                               CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video.";        \r
+                       }\r
                }\r
 \r
-               try\r
-               {\r
-                       audio_decoder_.reset(new audio_decoder(input_.context(), frame_factory->get_video_format_desc()));\r
-                       CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();\r
-               }\r
-               catch(averror_stream_not_found&)\r
+               if (!thumbnail_mode_)\r
                {\r
-                       //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio.";   \r
+                       try\r
+                       {\r
+                               audio_decoder_.reset(new audio_decoder(input_.context(), frame_factory->get_video_format_desc()));\r
+                               CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();\r
+                       }\r
+                       catch(averror_stream_not_found&)\r
+                       {\r
+                               //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio.";   \r
+                       }\r
+                       catch(...)\r
+                       {\r
+                               CASPAR_LOG_CURRENT_EXCEPTION();\r
+                               CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";               \r
+                       }\r
                }\r
-               catch(...)\r
-               {\r
-                       CASPAR_LOG_CURRENT_EXCEPTION();\r
-                       CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";               \r
-               }       \r
 \r
                if(!video_decoder_ && !audio_decoder_)\r
                        BOOST_THROW_EXCEPTION(averror_stream_not_found() << msg_info("No streams found"));\r
 \r
-               muxer_.reset(new frame_muxer(fps_, frame_factory, filter));\r
+               muxer_.reset(new frame_muxer(fps_, frame_factory, thumbnail_mode_, filter));\r
        }\r
 \r
        // frame_producer\r
        \r
        virtual safe_ptr<core::basic_frame> receive(int hints) override\r
+       {\r
+               return render_frame(hints).first;\r
+       }\r
+\r
+       virtual safe_ptr<core::basic_frame> last_frame() const override\r
+       {\r
+               return disable_audio(last_frame_);\r
+       }\r
+\r
+       std::pair<safe_ptr<core::basic_frame>, uint32_t> render_frame(int hints)\r
        {               \r
                frame_timer_.restart();\r
+               auto disable_logging = temporary_disable_logging_for_thread(thumbnail_mode_);\r
                                \r
                for(int n = 0; n < 16 && frame_buffer_.size() < 2; ++n)\r
                        try_decode_frame(hints);\r
@@ -149,12 +173,12 @@ public:
                graph_->set_value("frame-time", frame_timer_.elapsed()*format_desc_.fps*0.5);\r
                                \r
                if(frame_buffer_.empty() && input_.eof())\r
-                       return last_frame();\r
+                       return std::make_pair(last_frame(), -1);\r
 \r
                if(frame_buffer_.empty())\r
                {\r
                        graph_->set_tag("underflow");   \r
-                       return core::basic_frame::late();                       \r
+                       return std::make_pair(core::basic_frame::late(), -1);\r
                }\r
                \r
                auto frame = frame_buffer_.front(); \r
@@ -165,12 +189,101 @@ public:
 \r
                graph_->set_text(print());\r
 \r
-               return last_frame_ = frame.first;\r
+               last_frame_ = frame.first;\r
+\r
+               return frame;\r
        }\r
 \r
-       virtual safe_ptr<core::basic_frame> last_frame() const override\r
+       safe_ptr<core::basic_frame> render_specific_frame(uint32_t file_position, int hints)\r
        {\r
-               return disable_audio(last_frame_);\r
+               // Some trial and error and undeterministic stuff here\r
+               static const int NUM_RETRIES = 32;\r
+               \r
+               if (file_position > 0) // Assume frames are requested in sequential order,\r
+                                          // therefore no seeking should be necessary for the first frame.\r
+               {\r
+                       input_.seek(file_position > 1 ? file_position - 2: file_position).get();\r
+                       boost::this_thread::sleep(boost::posix_time::milliseconds(40));\r
+               }\r
+\r
+               for (int i = 0; i < NUM_RETRIES; ++i)\r
+               {\r
+                       boost::this_thread::sleep(boost::posix_time::milliseconds(40));\r
+               \r
+                       auto frame = render_frame(hints);\r
+\r
+                       if (frame.second == std::numeric_limits<uint32_t>::max())\r
+                       {\r
+                               // Retry\r
+                               continue;\r
+                       }\r
+                       else if (frame.second == file_position + 1 || frame.second == file_position)\r
+                               return frame.first;\r
+                       else if (frame.second > file_position + 1)\r
+                       {\r
+                               CASPAR_LOG(trace) << print() << L" " << frame.second << L" received, wanted " << file_position + 1;\r
+                               int64_t adjusted_seek = file_position - (frame.second - file_position + 1);\r
+\r
+                               if (adjusted_seek > 1 && file_position > 0)\r
+                               {\r
+                                       CASPAR_LOG(trace) << print() << L" adjusting to " << adjusted_seek;\r
+                                       input_.seek(static_cast<uint32_t>(adjusted_seek) - 1).get();\r
+                                       boost::this_thread::sleep(boost::posix_time::milliseconds(40));\r
+                               }\r
+                               else\r
+                                       return frame.first;\r
+                       }\r
+               }\r
+\r
+               CASPAR_LOG(trace) << print() << " Giving up finding frame at " << file_position;\r
+               return core::basic_frame::empty();\r
+       }\r
+\r
+       virtual safe_ptr<core::basic_frame> create_thumbnail_frame() override\r
+       {\r
+               auto disable_logging = temporary_disable_logging_for_thread(thumbnail_mode_);\r
+               auto total_frames = nb_frames();\r
+               auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);\r
+\r
+               if (grid < 1)\r
+               {\r
+                       CASPAR_LOG(error) << L"configuration/thumbnails/video-grid cannot be less than 1";\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1"));\r
+               }\r
+\r
+               if (grid == 1)\r
+               {\r
+                       return render_specific_frame(total_frames / 2, 0/*DEINTERLACE_HINT*/);\r
+               }\r
+\r
+               auto num_snapshots = grid * grid;\r
+\r
+               std::vector<safe_ptr<core::basic_frame>> frames;\r
+\r
+               for (int i = 0; i < num_snapshots; ++i)\r
+               {\r
+                       int x = i % grid;\r
+                       int y = i / grid;\r
+                       int desired_frame;\r
+                       \r
+                       if (i == 0)\r
+                               desired_frame = 0; // first\r
+                       else if (i == num_snapshots - 1)\r
+                               desired_frame = total_frames - 1; // last\r
+                       else\r
+                               // evenly distributed across the file.\r
+                               desired_frame = total_frames * i / (num_snapshots - 1);\r
+\r
+                       auto frame = render_specific_frame(desired_frame, 0/*DEINTERLACE_HINT*/);\r
+                       frame->get_frame_transform().fill_scale[0] = 1.0 / static_cast<double>(grid);\r
+                       frame->get_frame_transform().fill_scale[1] = 1.0 / static_cast<double>(grid);\r
+                       frame->get_frame_transform().fill_translation[0] = 1.0 / static_cast<double>(grid) * x;\r
+                       frame->get_frame_transform().fill_translation[1] = 1.0 / static_cast<double>(grid) * y;\r
+\r
+                       frames.push_back(frame);\r
+               }\r
+\r
+               return make_safe<core::basic_frame>(frames);\r
        }\r
 \r
        virtual uint32_t nb_frames() const override\r
@@ -311,7 +424,8 @@ public:
 \r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
 {              \r
-       auto filename = probe_stem(env::media_folder() + L"\\" + params.at(0));\r
+       static const std::vector<std::wstring> invalid_exts = boost::assign::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")(L".swf")(L".ct");\r
+       auto filename = probe_stem(env::media_folder() + L"\\" + params.at(0), invalid_exts);\r
 \r
        if(filename.empty())\r
                return core::frame_producer::empty();\r
@@ -324,7 +438,25 @@ safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factor
        boost::replace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1");\r
        boost::replace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1");\r
        \r
-       return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length));\r
+       return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length, false));\r
+}\r
+\r
+safe_ptr<core::frame_producer> create_thumbnail_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
+{              \r
+       static const std::vector<std::wstring> invalid_exts = boost::assign::list_of\r
+                       (L".png")(L".tga")(L".bmp")(L".jpg")(L".jpeg")(L".gif")(L".tiff")(L".tif")(L".jp2")(L".jpx")(L".j2k")(L".j2c")(L".swf")(L".ct")\r
+                       (L".wav")(L".mp3"); // audio shall not have thumbnails\r
+       auto filename = probe_stem(env::media_folder() + L"\\" + params.at(0), invalid_exts);\r
+\r
+       if(filename.empty())\r
+               return core::frame_producer::empty();\r
+       \r
+       auto loop               = false;\r
+       auto start              = 0;\r
+       auto length             = std::numeric_limits<uint32_t>::max();\r
+       auto filter_str = L"";\r
+               \r
+       return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length, true));\r
 }\r
 \r
 }}
\ No newline at end of file
index ebfb5b3c32f76baa3bff3c6c1e1a67f46664b0ca..66aaf71d88115b895e5042f6f8f12ca801e02274 100644 (file)
@@ -38,5 +38,6 @@ struct frame_factory;
 namespace ffmpeg {\r
 \r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params);\r
+safe_ptr<core::frame_producer> create_thumbnail_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params);\r
 \r
 }}
\ No newline at end of file
index 0ab791584d193a569a2a649ab16da3e01e1f195c..849af4f19f7748639b58bd6f21c6d99fef15001a 100644 (file)
 \r
 #include "../util/util.h"\r
 #include "../../ffmpeg_error.h"\r
+#include "../../ffmpeg.h"\r
 \r
 #include <core/video_format.h>\r
 \r
 #include <common/diagnostics/graph.h>\r
 #include <common/concurrency/executor.h>\r
+#include <common/concurrency/future_util.h>\r
 #include <common/exception/exceptions.h>\r
 #include <common/exception/win32_exception.h>\r
 \r
@@ -72,6 +74,7 @@ struct input::implementation : boost::noncopyable
        const std::wstring                                                                                      filename_;\r
        const uint32_t                                                                                          start_;         \r
        const uint32_t                                                                                          length_;\r
+       const bool                                                                                                      thumbnail_mode_;\r
        tbb::atomic<bool>                                                                                       loop_;\r
        uint32_t                                                                                                        frame_number_;\r
        \r
@@ -80,16 +83,23 @@ struct input::implementation : boost::noncopyable
                \r
        executor                                                                                                        executor_;\r
        \r
-       explicit implementation(const safe_ptr<diagnostics::graph> graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length) \r
+       explicit implementation(const safe_ptr<diagnostics::graph> graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode\r
                : graph_(graph)\r
                , format_context_(open_input(filename))         \r
                , default_stream_index_(av_find_default_stream_index(format_context_.get()))\r
                , filename_(filename)\r
                , start_(start)\r
                , length_(length)\r
+               , thumbnail_mode_(thumbnail_mode)\r
                , frame_number_(0)\r
                , executor_(print())\r
-       {               \r
+       {\r
+               if (thumbnail_mode_)\r
+                       executor_.invoke([]\r
+                       {\r
+                               disable_logging_for_thread();\r
+                       });\r
+\r
                loop_                   = loop;\r
                buffer_size_    = 0;\r
 \r
@@ -120,9 +130,22 @@ struct input::implementation : boost::noncopyable
                return result;\r
        }\r
 \r
-       void seek(uint32_t target)\r
+       std::ptrdiff_t get_max_buffer_count() const\r
+       {\r
+               return thumbnail_mode_ ? 1 : MAX_BUFFER_COUNT;\r
+       }\r
+\r
+       std::ptrdiff_t get_min_buffer_count() const\r
        {\r
-               executor_.begin_invoke([=]\r
+               return thumbnail_mode_ ? 0 : MIN_BUFFER_COUNT;\r
+       }\r
+\r
+       boost::unique_future<bool> seek(uint32_t target)\r
+       {\r
+               if (!executor_.is_running())\r
+                       return wrap_as_future(false);\r
+\r
+               return executor_.begin_invoke([=]() -> bool\r
                {\r
                        std::shared_ptr<AVPacket> packet;\r
                        while(buffer_.try_pop(packet) && packet)\r
@@ -131,6 +154,8 @@ struct input::implementation : boost::noncopyable
                        queued_seek(target);\r
 \r
                        tick();\r
+\r
+                       return true;\r
                }, high_priority);\r
        }\r
        \r
@@ -141,7 +166,7 @@ struct input::implementation : boost::noncopyable
        \r
        bool full() const\r
        {\r
-               return (buffer_size_ > MAX_BUFFER_SIZE || buffer_.size() > MAX_BUFFER_COUNT) && buffer_.size() > MIN_BUFFER_COUNT;\r
+               return (buffer_size_ > MAX_BUFFER_SIZE || buffer_.size() > get_max_buffer_count()) && buffer_.size() > get_min_buffer_count();\r
        }\r
 \r
        void tick()\r
@@ -203,7 +228,8 @@ struct input::implementation : boost::noncopyable
                        }\r
                        catch(...)\r
                        {\r
-                               CASPAR_LOG_CURRENT_EXCEPTION();\r
+                               if (!thumbnail_mode_)\r
+                                       CASPAR_LOG_CURRENT_EXCEPTION();\r
                                executor_.stop();\r
                        }\r
                });\r
@@ -211,7 +237,8 @@ struct input::implementation : boost::noncopyable
                        \r
        void queued_seek(const uint32_t target)\r
        {       \r
-               CASPAR_LOG(debug) << print() << " Seeking: " << target;\r
+               if (!thumbnail_mode_)\r
+                       CASPAR_LOG(debug) << print() << " Seeking: " << target;\r
 \r
                int flags = AVSEEK_FLAG_FRAME;\r
                if(target == 0)\r
@@ -251,12 +278,12 @@ struct input::implementation : boost::noncopyable
        }\r
 };\r
 \r
-input::input(const safe_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length) \r
-       : impl_(new implementation(graph, filename, loop, start, length)){}\r
+input::input(const safe_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode\r
+       : impl_(new implementation(graph, filename, loop, start, length, thumbnail_mode)){}\r
 bool input::eof() const {return !impl_->executor_.is_running();}\r
 bool input::try_pop(std::shared_ptr<AVPacket>& packet){return impl_->try_pop(packet);}\r
 safe_ptr<AVFormatContext> input::context(){return impl_->format_context_;}\r
 void input::loop(bool value){impl_->loop_ = value;}\r
 bool input::loop() const{return impl_->loop_;}\r
-void input::seek(uint32_t target){impl_->seek(target);}\r
+boost::unique_future<bool> input::seek(uint32_t target){return impl_->seek(target);}\r
 }}\r
index 197fb117dc225d91582085a4722073f5a498e012..69984c6ade2536980235917044e00c5f9f143045 100644 (file)
@@ -28,6 +28,7 @@
 #include <cstdint>\r
 \r
 #include <boost/noncopyable.hpp>\r
+#include <boost/thread/future.hpp>\r
 \r
 struct AVFormatContext;\r
 struct AVPacket;\r
@@ -45,7 +46,7 @@ namespace ffmpeg {
 class input : boost::noncopyable\r
 {\r
 public:\r
-       explicit input(const safe_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length);\r
+       explicit input(const safe_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode);\r
 \r
        bool try_pop(std::shared_ptr<AVPacket>& packet);\r
        bool eof() const;\r
@@ -53,7 +54,7 @@ public:
        void loop(bool value);\r
        bool loop() const;\r
 \r
-       void seek(uint32_t target);\r
+       boost::unique_future<bool> seek(uint32_t target);\r
 \r
        safe_ptr<AVFormatContext> context();\r
 private:\r
index 86143143b7c0ad8509c9477fb32542d74a2d2ee8..bb9114b035b1dfb873fab08965095379295ee52a 100644 (file)
@@ -81,9 +81,10 @@ struct frame_muxer::implementation : boost::noncopyable
        \r
        filter                                                                                  filter_;\r
        const std::wstring                                                              filter_str_;\r
+       const bool                                                                              thumbnail_mode_;\r
        bool                                                                                    force_deinterlacing_;\r
                \r
-       implementation(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filter_str)\r
+       implementation(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filter_str, bool thumbnail_mode)\r
                : display_mode_(display_mode::invalid)\r
                , in_fps_(in_fps)\r
                , format_desc_(frame_factory->get_video_format_desc())\r
@@ -92,6 +93,7 @@ struct frame_muxer::implementation : boost::noncopyable
                , audio_cadence_(format_desc_.audio_cadence)\r
                , frame_factory_(frame_factory)\r
                , filter_str_(filter_str)\r
+               , thumbnail_mode_(thumbnail_mode)\r
                , force_deinterlacing_(false)\r
        {\r
                video_streams_.push(std::queue<safe_ptr<write_frame>>());\r
@@ -320,7 +322,8 @@ struct frame_muxer::implementation : boost::noncopyable
                           display_mode_ != display_mode::deinterlace_bob && \r
                           display_mode_ != display_mode::deinterlace_bob_reinterlace)                  \r
                        {       \r
-                               CASPAR_LOG(info) << L"[frame_muxer] Automatically started non bob-deinterlacing. Consider starting producer with bob-deinterlacing (FILTER DEINTERLACE_BOB) for smoothest playback.";\r
+                               if (!thumbnail_mode_)\r
+                                       CASPAR_LOG(info) << L"[frame_muxer] Automatically started non bob-deinterlacing. Consider starting producer with bob-deinterlacing (FILTER DEINTERLACE_BOB) for smoothest playback.";\r
                                display_mode_ = display_mode::deinterlace;\r
                        }\r
 \r
@@ -332,7 +335,8 @@ struct frame_muxer::implementation : boost::noncopyable
 \r
                if(display_mode_ == display_mode::invalid)\r
                {\r
-                       CASPAR_LOG(warning) << L"[frame_muxer] Auto-transcode: Failed to detect display-mode.";\r
+                       if (!thumbnail_mode_)\r
+                               CASPAR_LOG(warning) << L"[frame_muxer] Auto-transcode: Failed to detect display-mode.";\r
                        display_mode_ = display_mode::simple;\r
                }\r
                        \r
@@ -346,7 +350,8 @@ struct frame_muxer::implementation : boost::noncopyable
                                        video_streams_.back().push(make_write_frame(this, make_safe_ptr(av_frame), frame_factory_, 0));\r
                        }\r
                        filter_ = filter(filter_str);\r
-                       CASPAR_LOG(info) << L"[frame_muxer] " << display_mode::print(display_mode_) << L" " << print_mode(frame->width, frame->height, in_fps_, frame->interlaced_frame > 0);\r
+                       if (!thumbnail_mode_)\r
+                               CASPAR_LOG(info) << L"[frame_muxer] " << display_mode::print(display_mode_) << L" " << print_mode(frame->width, frame->height, in_fps_, frame->interlaced_frame > 0);\r
                }\r
        }\r
        \r
@@ -373,8 +378,8 @@ struct frame_muxer::implementation : boost::noncopyable
        }\r
 };\r
 \r
-frame_muxer::frame_muxer(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filter)\r
-       : impl_(new implementation(in_fps, frame_factory, filter)){}\r
+frame_muxer::frame_muxer(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, bool thumbnail_mode, const std::wstring& filter)\r
+       : impl_(new implementation(in_fps, frame_factory, filter, thumbnail_mode)){}\r
 void frame_muxer::push(const std::shared_ptr<AVFrame>& video_frame, int hints){impl_->push(video_frame, hints);}\r
 void frame_muxer::push(const std::shared_ptr<core::audio_buffer>& audio_samples){return impl_->push(audio_samples);}\r
 std::shared_ptr<basic_frame> frame_muxer::poll(){return impl_->poll();}\r
index 50759aea0e4771281c5cd8f7f960281e1317a354..6d3671bc38d5d2251706b11b1ba93b27359dfcb5 100644 (file)
@@ -48,7 +48,7 @@ namespace ffmpeg {
 class frame_muxer : boost::noncopyable\r
 {\r
 public:\r
-       frame_muxer(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filter = L"");\r
+       frame_muxer(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, bool thumbnail_mode, const std::wstring& filter = L"");\r
        \r
        void push(const std::shared_ptr<AVFrame>& video_frame, int hints = 0);\r
        void push(const std::shared_ptr<core::audio_buffer>& audio_samples);\r
index a5616b89ab80e17c2b192344381fbb9abb78d61b..ceaa8393c43483a05d4aecaf6664085375de4b79 100644 (file)
@@ -463,19 +463,18 @@ std::wstring print_mode(size_t width, size_t height, double fps, bool interlaced
        return boost::lexical_cast<std::wstring>(width) + L"x" + boost::lexical_cast<std::wstring>(height) + (!interlaced ? L"p" : L"i") + fps_ss.str();\r
 }\r
 \r
-bool is_valid_file(const std::wstring filename)\r
-{                      \r
-       static const std::vector<std::wstring> invalid_exts = boost::assign::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")(L".swf")(L".ct");\r
-       static std::vector<std::wstring>           valid_exts   = boost::assign::list_of(L".m2t")(L".mov")(L".mp4")(L".dv")(L".flv")(L".mpg")(L".wav")(L".mp3")(L".dnxhd")(L".h264")(L".prores");\r
+bool is_valid_file(const std::wstring filename, const std::vector<std::wstring>& invalid_exts)\r
+{\r
+       static std::vector<std::wstring> valid_exts = boost::assign::list_of(L".m2t")(L".mov")(L".mp4")(L".dv")(L".flv")(L".mpg")(L".wav")(L".mp3")(L".dnxhd")(L".h264")(L".prores");\r
 \r
        auto ext = boost::to_lower_copy(boost::filesystem::wpath(filename).extension());\r
                \r
-       if(std::find(valid_exts.begin(), valid_exts.end(), ext) != valid_exts.end())\r
-               return true;    \r
-       \r
        if(std::find(invalid_exts.begin(), invalid_exts.end(), ext) != invalid_exts.end())\r
                return false;   \r
 \r
+       if(std::find(valid_exts.begin(), valid_exts.end(), ext) != valid_exts.end())\r
+               return true;    \r
+\r
        auto filename2 = narrow(filename);\r
 \r
        if(boost::filesystem::path(filename2).extension() == ".m2t")\r
@@ -499,13 +498,20 @@ bool is_valid_file(const std::wstring filename)
        return av_probe_input_format2(&pb, true, &score) != nullptr;\r
 }\r
 \r
-std::wstring probe_stem(const std::wstring stem)\r
+bool is_valid_file(const std::wstring filename)\r
+{\r
+       static const std::vector<std::wstring> invalid_exts = boost::assign::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")(L".swf")(L".ct");\r
+       \r
+       return is_valid_file(filename, invalid_exts);\r
+}\r
+\r
+std::wstring probe_stem(const std::wstring stem, const std::vector<std::wstring>& invalid_exts)\r
 {\r
        auto stem2 = boost::filesystem2::wpath(stem);\r
        auto dir = stem2.parent_path();\r
        for(auto it = boost::filesystem2::wdirectory_iterator(dir); it != boost::filesystem2::wdirectory_iterator(); ++it)\r
        {\r
-               if(boost::iequals(it->path().stem(), stem2.filename()) && is_valid_file(it->path().file_string()))\r
+               if(boost::iequals(it->path().stem(), stem2.filename()) && is_valid_file(it->path().file_string(), invalid_exts))\r
                        return it->path().file_string();\r
        }\r
        return L"";\r
index 2960c2e94a1af35e89201831f400082b7e11228f..c830427feaa51d611e07f7e69390d2eea372ccab 100644 (file)
@@ -71,7 +71,8 @@ double read_fps(AVFormatContext& context, double fail_value);
 \r
 std::wstring print_mode(size_t width, size_t height, double fps, bool interlaced);\r
 \r
-std::wstring probe_stem(const std::wstring stem);\r
+std::wstring probe_stem(const std::wstring stem, const std::vector<std::wstring>& invalid_exts);\r
+bool is_valid_file(const std::wstring filename, const std::vector<std::wstring>& invalid_exts);\r
 bool is_valid_file(const std::wstring filename);\r
 \r
 }}
\ No newline at end of file
index ace16295c6197c2e9ebcd666a57b1e0c49262399..ce3d3689464b37e2e5481ec9144afdd53fce84af 100644 (file)
 \r
 #include <FreeImage.h>\r
 #include <vector>\r
+#include <algorithm>\r
+\r
+#include "../util/image_view.h"\r
 \r
 namespace caspar { namespace image {\r
-       \r
+\r
+void write_cropped_png(\r
+               const safe_ptr<core::read_frame>& frame,\r
+               const core::video_format_desc& format_desc,\r
+               const boost::filesystem::wpath& output_file,\r
+               int width,\r
+               int height)\r
+{\r
+       auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Allocate(width, height, 32), FreeImage_Unload);\r
+       image_view<bgra_pixel> destination_view(FreeImage_GetBits(bitmap.get()), width, height);\r
+       image_view<bgra_pixel> complete_frame(const_cast<uint8_t*>(frame->image_data().begin()), format_desc.width, format_desc.height);\r
+       auto thumbnail_view = complete_frame.subview(0, 0, width, height);\r
+\r
+       std::copy(thumbnail_view.begin(), thumbnail_view.end(), destination_view.begin());\r
+       FreeImage_FlipVertical(bitmap.get());\r
+       FreeImage_SaveU(FIF_PNG, bitmap.get(), output_file.string().c_str(), 0);\r
+}\r
+\r
 struct image_consumer : public core::frame_consumer\r
 {\r
        core::video_format_desc format_desc_;\r
@@ -68,17 +88,17 @@ public:
                {\r
                        try\r
                        {\r
-                               std::string filename2 = narrow(filename);\r
+                               auto filename2 = filename;\r
 \r
                                if (filename2.empty())\r
-                                       filename2 = narrow(env::media_folder()) +  boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time()) + ".png";\r
+                                       filename2 = env::media_folder() + widen(boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time())) + L".png";\r
                                else\r
-                                       filename2 = narrow(env::media_folder()) + filename2 + ".png";\r
+                                       filename2 = env::media_folder() + filename2 + L".png";\r
 \r
                                auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Allocate(format_desc.width, format_desc.height, 32), FreeImage_Unload);\r
                                memcpy(FreeImage_GetBits(bitmap.get()), frame->image_data().begin(), frame->image_size());\r
                                FreeImage_FlipVertical(bitmap.get());\r
-                               FreeImage_Save(FIF_PNG, bitmap.get(), filename2.c_str(), 0);\r
+                               FreeImage_SaveU(FIF_PNG, bitmap.get(), filename2.c_str(), 0);\r
                        }\r
                        catch(...)\r
                        {\r
index 7a3aa1f00d071d5c0cd38a9fe471b9a57750224d..d5e482b6f54ecd985be404d0da4dc4399e30f392 100644 (file)
@@ -26,6 +26,7 @@
 #include <core/video_format.h>\r
 \r
 #include <boost/property_tree/ptree.hpp>\r
+#include <boost/filesystem.hpp>\r
 \r
 #include <string>\r
 #include <vector>\r
@@ -34,10 +35,19 @@ namespace caspar {
 \r
 namespace core {\r
        struct frame_consumer;\r
+       class read_frame;\r
+       struct video_format_desc;\r
 }\r
 \r
 namespace image {\r
-       \r
+\r
+void write_cropped_png(\r
+               const safe_ptr<core::read_frame>& frame,\r
+               const core::video_format_desc& format_desc,\r
+               const boost::filesystem::wpath& output_file,\r
+               int width,\r
+               int height);\r
+\r
 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params);\r
 \r
 }}
\ No newline at end of file
index 63b6e3d489306cb0c820299ddf371b211df70a3a..82517a4fc5077fba1d2292a3cf72856dddbe8903 100644 (file)
@@ -38,6 +38,7 @@ void init()
 {\r
        core::register_producer_factory(create_scroll_producer);\r
        core::register_producer_factory(create_producer);\r
+       core::register_thumbnail_producer_factory(create_thumbnail_producer);\r
        core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});\r
 }\r
 \r
index bae34e38ca3d41b11fe57c7baee36dd1cbfa7772..0f562bc2ce17a61c54932b36abfd269aadcc7ea5 100644 (file)
@@ -76,6 +76,11 @@ struct image_producer : public core::frame_producer
                return frame_;\r
        }\r
 \r
+       virtual safe_ptr<core::basic_frame> create_thumbnail_frame() override\r
+       {\r
+               return frame_;\r
+       }\r
+               \r
        virtual std::wstring print() const override\r
        {\r
                return L"image_producer[" + filename_ + L"]";\r
@@ -90,7 +95,7 @@ struct image_producer : public core::frame_producer
        }\r
 };\r
 \r
-safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
+safe_ptr<core::frame_producer> create_raw_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
 {\r
        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");\r
        std::wstring filename = env::media_folder() + L"\\" + params[0];\r
@@ -103,9 +108,22 @@ safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factor
        if(ext == extensions.end())\r
                return core::frame_producer::empty();\r
 \r
-       return create_producer_print_proxy(\r
-                       make_safe<image_producer>(frame_factory, filename + L"." + *ext));\r
+       return make_safe<image_producer>(frame_factory, filename + L"." + *ext);\r
 }\r
 \r
+safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
+{\r
+       auto raw_producer = create_raw_producer(frame_factory, params);\r
+\r
+       if (raw_producer == core::frame_producer::empty())\r
+               return raw_producer;\r
+\r
+       return create_producer_print_proxy(raw_producer);\r
+}\r
+\r
+safe_ptr<core::frame_producer> create_thumbnail_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params)\r
+{\r
+       return create_raw_producer(frame_factory, params);\r
+}\r
 \r
 }}
\ No newline at end of file
index 036950a7a8e9198eff64963e0738d0c9a87e618c..3d95d080fe4f58fa5414debbf2f0e8326783593f 100644 (file)
@@ -29,5 +29,6 @@
 namespace caspar { namespace image {\r
 \r
 safe_ptr<core::frame_producer> create_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params);\r
+safe_ptr<core::frame_producer> create_thumbnail_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::vector<std::wstring>& params);\r
 \r
 }}
\ No newline at end of file
index 42f1f3c7dc73766fa4d718ed526d53a639737fed..c876edc716150d4316ab2f337ed632f8f1d99f56 100644 (file)
 \r
 namespace caspar { namespace image {\r
 \r
-std::shared_ptr<FIBITMAP> load_image(const std::string& filename)\r
+std::shared_ptr<FIBITMAP> load_image(const std::wstring& filename)\r
 {\r
        if(!boost::filesystem::exists(filename))\r
-               BOOST_THROW_EXCEPTION(file_not_found() << boost::errinfo_file_name(filename));\r
+               BOOST_THROW_EXCEPTION(file_not_found() << boost::errinfo_file_name(narrow(filename)));\r
 \r
        FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;\r
-       fif = FreeImage_GetFileType(filename.c_str(), 0);\r
+       fif = FreeImage_GetFileTypeU(filename.c_str(), 0);\r
        if(fif == FIF_UNKNOWN) \r
-               fif = FreeImage_GetFIFFromFilename(filename.c_str());\r
+               fif = FreeImage_GetFIFFromFilenameU(filename.c_str());\r
                \r
        if(fif == FIF_UNKNOWN || !FreeImage_FIFSupportsReading(fif)) \r
                BOOST_THROW_EXCEPTION(invalid_argument() << msg_info("Unsupported image format."));\r
                \r
-       auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Load(fif, filename.c_str(), 0), FreeImage_Unload);\r
+       auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_LoadU(fif, filename.c_str(), 0), FreeImage_Unload);\r
                  \r
        if(FreeImage_GetBPP(bitmap.get()) != 32)\r
        {\r
@@ -58,9 +58,9 @@ std::shared_ptr<FIBITMAP> load_image(const std::string& filename)
        return bitmap;\r
 }\r
 \r
-std::shared_ptr<FIBITMAP> load_image(const std::wstring& filename)\r
+std::shared_ptr<FIBITMAP> load_image(const std::string& filename)\r
 {\r
-       return load_image(narrow(filename));\r
+       return load_image(widen(filename));\r
 }\r
 \r
 }}
\ No newline at end of file
index a1b691f5cbcb0510494c9991e84899a3d5eb9502..6fc9fffb1c838443be63cd86a0ee4bf49b1bf673 100644 (file)
@@ -25,6 +25,7 @@
 \r
 #include <core/consumer/frame_consumer.h>\r
 #include <core/video_channel.h>\r
+#include <core/thumbnail_generator.h>\r
 \r
 #include <boost/algorithm/string.hpp>\r
 \r
@@ -64,6 +65,9 @@ namespace amcp {
                void SetChannels(const std::vector<safe_ptr<core::video_channel>>& channels){channels_ = channels;}\r
                const std::vector<safe_ptr<core::video_channel>>& GetChannels() { return channels_; }\r
 \r
+               void SetThumbGenerator(const std::shared_ptr<core::thumbnail_generator>& thumb_gen) {thumb_gen_ = thumb_gen;}\r
+               std::shared_ptr<core::thumbnail_generator> GetThumbGenerator() { return thumb_gen_; }\r
+\r
                void SetChannelIndex(unsigned int channelIndex){channelIndex_ = channelIndex;}\r
                unsigned int GetChannelIndex(){return channelIndex_;}\r
 \r
@@ -92,6 +96,7 @@ namespace amcp {
                IO::ClientInfoPtr pClientInfo_;\r
                std::shared_ptr<core::video_channel> pChannel_;\r
                std::vector<safe_ptr<core::video_channel>> channels_;\r
+               std::shared_ptr<core::thumbnail_generator> thumb_gen_;\r
                AMCPCommandScheduling scheduling_;\r
                std::wstring replyString_;\r
        };\r
index cae1534c89b1c7a1cb9d436fbd91db787d065fdc..2483ac2197362b6d82bfc2f7b09ff34483231584 100644 (file)
@@ -76,6 +76,9 @@
 #include <boost/locale.hpp>\r
 #include <boost/range/adaptor/transformed.hpp>\r
 #include <boost/range/algorithm/copy.hpp>\r
+#include <boost/archive/iterators/base64_from_binary.hpp>\r
+#include <boost/archive/iterators/insert_linebreaks.hpp>\r
+#include <boost/archive/iterators/transform_width.hpp>\r
 \r
 #include <tbb/concurrent_unordered_map.h>\r
 \r
@@ -105,6 +108,48 @@ namespace caspar { namespace protocol {
 \r
 using namespace core;\r
 \r
+std::wstring read_file_base64(const boost::filesystem::wpath& file)\r
+{\r
+       using namespace boost::archive::iterators;\r
+\r
+       boost::filesystem::ifstream filestream(file, std::ios::binary);\r
+\r
+       if (!filestream)\r
+               return L"";\r
+\r
+       // From http://www.webbiscuit.co.uk/2012/04/02/base64-encoder-and-boost/\r
+               \r
+       typedef\r
+               insert_linebreaks<         // insert line breaks every 76 characters\r
+                       base64_from_binary<    // convert binary values to base64 characters\r
+                               transform_width<   // retrieve 6 bit integers from a sequence of 8 bit bytes\r
+                                       const unsigned char *,\r
+                                       6,\r
+                                       8\r
+                               >\r
+                       >,\r
+                       76\r
+               >\r
+        base64_iterator; // compose all the above operations in to a new iterator\r
+       auto length = boost::filesystem::file_size(file);\r
+       std::vector<char> bytes;\r
+       bytes.resize(length);\r
+       filestream.read(bytes.data(), length);\r
+\r
+       int padding = 0;\r
+\r
+       while (bytes.size() % 3 != 0)\r
+       {\r
+               ++padding;\r
+               bytes.push_back(0x00);\r
+       }\r
+\r
+       std::string result(base64_iterator(bytes.data()), base64_iterator(bytes.data() + length - padding));\r
+       result.insert(result.end(), padding, '=');\r
+\r
+       return widen(result);\r
+}\r
+\r
 std::wstring read_utf8_file(const boost::filesystem::wpath& file)\r
 {\r
        std::wstringstream result;\r
@@ -1339,18 +1384,19 @@ bool DataCommand::DoExecuteRetrieve()
 \r
        std::wstring file_contents = read_file(boost::filesystem::wpath(filename));\r
 \r
-       //std::wifstream datafile(filename.c_str());\r
-\r
        if (file_contents.empty()) \r
        {\r
                SetReplyString(TEXT("404 DATA RETRIEVE ERROR\r\n"));\r
                return false;\r
        }\r
 \r
-       std::wstringstream reply(TEXT("201 DATA RETRIEVE OK\r\n"));\r
-       /*std::wstring line;\r
+       std::wstringstream reply;\r
+       reply << TEXT("201 DATA RETRIEVE OK\r\n");\r
+\r
+       std::wstringstream file_contents_stream(file_contents);\r
+       std::wstring line;\r
        bool bFirstLine = true;\r
-       while(std::getline(datafile, line))\r
+       while(std::getline(file_contents_stream, line))\r
        {\r
                if(!bFirstLine)\r
                        reply << "\\n";\r
@@ -1359,9 +1405,7 @@ bool DataCommand::DoExecuteRetrieve()
 \r
                reply << line;\r
        }\r
-       datafile.close();*/\r
 \r
-       reply << file_contents;\r
        reply << "\r\n";\r
        SetReplyString(reply.str());\r
        return true;\r
@@ -1424,6 +1468,124 @@ bool DataCommand::DoExecuteList()
        return true;\r
 }\r
 \r
+bool ThumbnailCommand::DoExecute()\r
+{\r
+       std::wstring command = _parameters[0];\r
+\r
+       if (command == TEXT("RETRIEVE"))\r
+               return DoExecuteRetrieve();\r
+       else if (command == TEXT("LIST"))\r
+               return DoExecuteList();\r
+       else if (command == TEXT("GENERATE"))\r
+               return DoExecuteGenerate();\r
+       else if (command == TEXT("GENERATE_ALL"))\r
+               return DoExecuteGenerateAll();\r
+\r
+       SetReplyString(TEXT("403 THUMBNAIL ERROR\r\n"));\r
+       return false;\r
+}\r
+\r
+bool ThumbnailCommand::DoExecuteRetrieve() \r
+{\r
+       if(_parameters.size() < 2) \r
+       {\r
+               SetReplyString(TEXT("402 THUMBNAIL RETRIEVE ERROR\r\n"));\r
+               return false;\r
+       }\r
+\r
+       std::wstring filename = env::thumbnails_folder();\r
+       filename.append(_parameters[1]);\r
+       filename.append(TEXT(".png"));\r
+\r
+       std::wstring file_contents = read_file_base64(boost::filesystem::wpath(filename));\r
+\r
+       if (file_contents.empty())\r
+       {\r
+               SetReplyString(TEXT("404 THUMBNAIL RETRIEVE ERROR\r\n"));\r
+               return false;\r
+       }\r
+\r
+       std::wstringstream reply;\r
+\r
+       reply << L"201 THUMBNAIL RETRIEVE OK\r\n";\r
+       reply << file_contents;\r
+       reply << L"\r\n";\r
+       SetReplyString(reply.str());\r
+       return true;\r
+}\r
+\r
+bool ThumbnailCommand::DoExecuteList()\r
+{\r
+       std::wstringstream replyString;\r
+       replyString << TEXT("200 THUMBNAIL LIST OK\r\n");\r
+\r
+       for (boost::filesystem::wrecursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)\r
+       {                       \r
+               if(boost::filesystem::is_regular_file(itr->path()))\r
+               {\r
+                       if(!boost::iequals(itr->path().extension(), L".png"))\r
+                               continue;\r
+                       \r
+                       auto relativePath = boost::filesystem::wpath(itr->path().file_string().substr(env::thumbnails_folder().size()-1, itr->path().file_string().size()));\r
+                       \r
+                       auto str = relativePath.replace_extension(L"").external_file_string();\r
+                       if(str[0] == '\\' || str[0] == '/')\r
+                               str = std::wstring(str.begin() + 1, str.end());\r
+\r
+                       auto mtime = boost::filesystem::last_write_time(itr->path());\r
+                       auto mtime_readable = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(mtime));\r
+                       auto file_size = boost::filesystem::file_size(itr->path());\r
+\r
+                       replyString << L"\"" << str << L"\" " << widen(mtime_readable) << L" " << file_size << L"\r\n";\r
+               }\r
+       }\r
+       \r
+       replyString << TEXT("\r\n");\r
+\r
+       SetReplyString(boost::to_upper_copy(replyString.str()));\r
+       return true;\r
+}\r
+\r
+bool ThumbnailCommand::DoExecuteGenerate()\r
+{\r
+       if (_parameters.size() < 2) \r
+       {\r
+               SetReplyString(L"402 THUMBNAIL GENERATE ERROR\r\n");\r
+               return false;\r
+       }\r
+\r
+       auto thumb_gen = GetThumbGenerator();\r
+\r
+       if (thumb_gen)\r
+       {\r
+               thumb_gen->generate(_parameters[1]);\r
+               SetReplyString(L"200 THUMBNAIL GENERATE OK\r\n");\r
+               return true;\r
+       }\r
+       else\r
+       {\r
+               SetReplyString(L"500 THUMBNAIL GENERATE ERROR\r\n");\r
+               return false;\r
+       }\r
+}\r
+\r
+bool ThumbnailCommand::DoExecuteGenerateAll()\r
+{\r
+       auto thumb_gen = GetThumbGenerator();\r
+\r
+       if (thumb_gen)\r
+       {\r
+               thumb_gen->generate_all();\r
+               SetReplyString(L"200 THUMBNAIL GENERATE_ALL OK\r\n");\r
+               return true;\r
+       }\r
+       else\r
+       {\r
+               SetReplyString(L"500 THUMBNAIL GENERATE_ALL ERROR\r\n");\r
+               return false;\r
+       }\r
+}\r
+\r
 bool CinfCommand::DoExecute()\r
 {\r
        std::wstringstream replyString;\r
index 0204c98fd8e82643b3c4c734a1756abcd6091f47..e1f5b519c93fbd7199cd5703fa2b4f1a933a82c9 100644 (file)
@@ -149,6 +149,16 @@ class DataCommand : public AMCPCommandBase<false, AddToQueue, 1>
        bool DoExecuteList();\r
 };\r
 \r
+class ThumbnailCommand : public AMCPCommandBase<false, AddToQueue, 1>\r
+{\r
+       std::wstring print() const { return L"ThumbnailCommand";}\r
+       bool DoExecute();\r
+       bool DoExecuteRetrieve();\r
+       bool DoExecuteList();\r
+       bool DoExecuteGenerate();\r
+       bool DoExecuteGenerateAll();\r
+};\r
+\r
 class ClsCommand : public AMCPCommandBase<false, AddToQueue, 0>\r
 {\r
        std::wstring print() const { return L"ClsCommand";}\r
index b94a4e4f18443447e99dc135c234485225288310..42d635675849fd6c9a98da73595b36126a885e33 100644 (file)
@@ -53,7 +53,9 @@ inline std::shared_ptr<core::video_channel> GetChannelSafe(unsigned int index, c
        return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;\r
 }\r
 \r
-AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<safe_ptr<core::video_channel>>& channels) : channels_(channels) {\r
+AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<safe_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen)\r
+               : channels_(channels)\r
+               , thumb_gen_(thumb_gen) {\r
        AMCPCommandQueuePtr pGeneralCommandQueue(new AMCPCommandQueue());\r
        commandQueues_.push_back(pGeneralCommandQueue);\r
 \r
@@ -187,6 +189,7 @@ AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring&
                        else\r
                        {\r
                                pCommand->SetChannels(channels_);\r
+                               pCommand->SetThumbGenerator(thumb_gen_);\r
                                //Set scheduling\r
                                if(commandSwitch.size() > 0) {\r
                                        transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);\r
@@ -323,6 +326,7 @@ AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)
        else if(s == TEXT("VERSION"))           return std::make_shared<VersionCommand>();\r
        else if(s == TEXT("BYE"))                       return std::make_shared<ByeCommand>();\r
        else if(s == TEXT("SET"))                       return std::make_shared<SetCommand>();\r
+       else if(s == TEXT("THUMBNAIL"))         return std::make_shared<ThumbnailCommand>();\r
        //else if(s == TEXT("MONITOR"))\r
        //{\r
        //      result = AMCPCommandPtr(new MonitorCommand());\r
index 3297019c48f3cd21195c1f6f0e47a64fc09f05ae..624d5ff07df967265a99906a3a53d090d3ff029b 100644 (file)
@@ -23,6 +23,7 @@
 \r
 #include "../util/protocolstrategy.h"\r
 #include <core/video_channel.h>\r
+#include <core/thumbnail_generator.h>\r
 \r
 #include "AMCPCommand.h"\r
 #include "AMCPCommandQueue.h"\r
@@ -46,7 +47,7 @@ class AMCPProtocolStrategy : public IO::IProtocolStrategy, boost::noncopyable
        AMCPProtocolStrategy& operator=(const AMCPProtocolStrategy&);\r
 \r
 public:\r
-       AMCPProtocolStrategy(const std::vector<safe_ptr<core::video_channel>>& channels);\r
+       AMCPProtocolStrategy(const std::vector<safe_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen);\r
        virtual ~AMCPProtocolStrategy();\r
 \r
        virtual void Parse(const TCHAR* pData, int charCount, IO::ClientInfoPtr pClientInfo);\r
@@ -66,6 +67,7 @@ private:
        bool QueueCommand(AMCPCommandPtr);\r
 \r
        std::vector<safe_ptr<core::video_channel>> channels_;\r
+       std::shared_ptr<core::thumbnail_generator> thumb_gen_;\r
        std::vector<AMCPCommandQueuePtr> commandQueues_;\r
        static const std::wstring MessageDelimiter;\r
 };\r
index 5f28b633adb31cb87209fb5636a6a9b813e33a49..bc389891a72399ee3c5eb63fe5b9eff56fe0f29d 100644 (file)
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <configuration>\r
   <paths>\r
-    <media-path>media\</media-path>\r
+    <media-path>c:\caspar\_media\</media-path>\r
     <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
   <channels>\r
     <channel>\r
 <flash>\r
     <buffer-depth>auto [auto|1..]</buffer-depth>\r
 </flash>\r
+<thumbnails>\r
+    <generate-thumbnails>true [true|false]</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
 <channels>\r
     <channel>\r
         <video-mode> PAL [PAL|NTSC|576p2500|720p2398|720p2400|720p2500|720p5000|720p2997|720p5994|720p3000|720p6000|1080p2398|1080p2400|1080i5000|1080i5994|1080i6000|1080p2500|1080p2997|1080p3000|1080p5000|1080p5994|1080p6000] </video-mode>\r
index 1d2ab27a3730a688745168a1f160d87bf79ca13e..4e40290e0f8ffd2ba599f27804bc9d5764c50b49 100644 (file)
@@ -257,7 +257,7 @@ int main(int argc, wchar_t* argv[])
                        caspar::server caspar_server;\r
                                \r
                        // Create a amcp parser for console commands.\r
-                       caspar::protocol::amcp::AMCPProtocolStrategy amcp(caspar_server.get_channels());\r
+                       caspar::protocol::amcp::AMCPProtocolStrategy amcp(caspar_server.get_channels(), caspar_server.get_thumbnail_generator());\r
 \r
                        // Create a dummy client which prints amcp responses to console.\r
                        auto console_client = std::make_shared<caspar::IO::ConsoleClientInfo>();\r
index bd3b5677d4d22f2e55557f2dc460af5f7590a708..d59493d15ce33f6512fdb2f2cbce8a426faa88d5 100644 (file)
 \r
 #include "server.h"\r
 \r
+#include <memory>\r
+\r
 #include <common/env.h>\r
 #include <common/exception/exceptions.h>\r
 #include <common/utility/string.h>\r
+#include <common/filesystem/polling_filesystem_monitor.h>\r
 \r
 #include <core/mixer/gpu/ogl_device.h>\r
 #include <core/video_channel.h>\r
 #include <core/producer/stage.h>\r
 #include <core/consumer/output.h>\r
+#include <core/thumbnail_generator.h>\r
 \r
 #include <modules/bluefish/bluefish.h>\r
 #include <modules/decklink/decklink.h>\r
@@ -39,6 +43,7 @@
 #include <modules/ogl/ogl.h>\r
 #include <modules/silverlight/silverlight.h>\r
 #include <modules/image/image.h>\r
+#include <modules/image/consumer/image_consumer.h>\r
 \r
 #include <modules/oal/consumer/oal_consumer.h>\r
 #include <modules/bluefish/consumer/bluefish_consumer.h>\r
@@ -50,7 +55,8 @@
 #include <protocol/cii/CIIProtocolStrategy.h>\r
 #include <protocol/CLK/CLKProtocolStrategy.h>\r
 #include <protocol/util/AsyncEventServer.h>\r
-#include <protocol/util/stateful_protocol_strategy_wrapper.h>\r\r
+#include <protocol/util/stateful_protocol_strategy_wrapper.h>\r
+\r
 #include <boost/algorithm/string.hpp>\r
 #include <boost/lexical_cast.hpp>\r
 #include <boost/foreach.hpp>\r
@@ -67,6 +73,7 @@ struct server::implementation : boost::noncopyable
        safe_ptr<ogl_device>                                            ogl_;\r
        std::vector<safe_ptr<IO::AsyncEventServer>> async_servers_;     \r
        std::vector<safe_ptr<video_channel>>            channels_;\r
+       std::shared_ptr<thumbnail_generator>            thumbnail_generator_;\r
 \r
        implementation()                \r
                : ogl_(ogl_device::create())\r
@@ -95,6 +102,8 @@ struct server::implementation : boost::noncopyable
                setup_channels(env::properties());\r
                CASPAR_LOG(info) << L"Initialized channels.";\r
 \r
+               setup_thumbnail_generation(env::properties());\r
+\r
                setup_controllers(env::properties());\r
                CASPAR_LOG(info) << L"Initialized controllers.";\r
        }\r
@@ -175,10 +184,32 @@ struct server::implementation : boost::noncopyable
                }\r
        }\r
 \r
+       void setup_thumbnail_generation(const boost::property_tree::wptree& pt)\r
+       {\r
+               if (!pt.get(L"configuration.thumbnails.generate-thumbnails", true))\r
+                       return;\r
+\r
+               auto scan_interval_millis = pt.get(L"configuration.thumbnails.scan-interval-millis", 5000);\r
+\r
+               polling_filesystem_monitor_factory monitor_factory(scan_interval_millis);\r
+               thumbnail_generator_.reset(new thumbnail_generator(\r
+                               monitor_factory, \r
+                               env::media_folder(),\r
+                               env::thumbnails_folder(),\r
+                               pt.get(L"configuration.thumbnails.width", 256),\r
+                               pt.get(L"configuration.thumbnails.height", 144),\r
+                               core::video_format_desc::get(pt.get(L"configuration.thumbnails.video-mode", L"720p2500")),\r
+                               ogl_,\r
+                               pt.get(L"configuration.thumbnails.generate-delay-millis", 2000),\r
+                               &image::write_cropped_png));\r
+\r
+               CASPAR_LOG(info) << L"Initialized thumbnail generator.";\r
+       }\r
+\r
        safe_ptr<IO::IProtocolStrategy> create_protocol(const std::wstring& name) const\r
        {\r
                if(boost::iequals(name, L"AMCP"))\r
-                       return make_safe<amcp::AMCPProtocolStrategy>(channels_);\r
+                       return make_safe<amcp::AMCPProtocolStrategy>(channels_, thumbnail_generator_);\r
                else if(boost::iequals(name, L"CII"))\r
                        return make_safe<cii::CIIProtocolStrategy>(channels_);\r
                else if(boost::iequals(name, L"CLOCK"))\r
@@ -199,4 +230,9 @@ const std::vector<safe_ptr<video_channel>> server::get_channels() const
        return impl_->channels_;\r
 }\r
 \r
+std::shared_ptr<thumbnail_generator> server::get_thumbnail_generator() const\r
+{\r
+       return impl_->thumbnail_generator_;\r
+}\r
+\r
 }
\ No newline at end of file
index 9ee3a7f12ff51eccac385241cbe3ac8539f3fe2c..3d83d5f77f5d3548732b30dd4d504d6b088b79f7 100644 (file)
@@ -32,6 +32,7 @@ namespace caspar {
 \r
 namespace core {\r
        class video_channel;\r
+       class thumbnail_generator;\r
 }\r
 \r
 class server : boost::noncopyable\r
@@ -39,6 +40,7 @@ class server : boost::noncopyable
 public:\r
        server();\r
        const std::vector<safe_ptr<core::video_channel>> get_channels() const;\r
+       std::shared_ptr<core::thumbnail_generator> get_thumbnail_generator() const;\r
 private:\r
        struct implementation;\r
        safe_ptr<implementation> impl_;\r