<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
<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
<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
\r
tbb::spin_mutex mutex_;\r
std::wstring text_;\r
+ bool auto_reset_;\r
\r
impl()\r
{\r
{\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
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
//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
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
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
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
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
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
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
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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
<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
<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
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
\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
\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
\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
{\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
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
}\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
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
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
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
\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
\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
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
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
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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
, 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
\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
//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
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
\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
#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
#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
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
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
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
}\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
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
\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
\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
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
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
\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
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
\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
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
queued_seek(target);\r
\r
tick();\r
+\r
+ return true;\r
}, high_priority);\r
}\r
\r
\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
}\r
catch(...)\r
{\r
- CASPAR_LOG_CURRENT_EXCEPTION();\r
+ if (!thumbnail_mode_)\r
+ CASPAR_LOG_CURRENT_EXCEPTION();\r
executor_.stop();\r
}\r
});\r
\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
}\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
#include <cstdint>\r
\r
#include <boost/noncopyable.hpp>\r
+#include <boost/thread/future.hpp>\r
\r
struct AVFormatContext;\r
struct AVPacket;\r
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
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
\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
, 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
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
\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
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
}\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
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
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
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
\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
\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
{\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
#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
\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
{\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
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
}\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
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
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
\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
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
\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
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
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
#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
\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
\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
\r
reply << line;\r
}\r
- datafile.close();*/\r
\r
- reply << file_contents;\r
reply << "\r\n";\r
SetReplyString(reply.str());\r
return true;\r
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
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
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
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
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
\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
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
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
<?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
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
\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
#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
#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
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
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
}\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
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
\r
namespace core {\r
class video_channel;\r
+ class thumbnail_generator;\r
}\r
\r
class server : boost::noncopyable\r
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