<ClInclude Include="env.h" />\r
<ClInclude Include="except.h" />\r
<ClInclude Include="executor.h" />\r
+ <ClInclude Include="filesystem_monitor.h" />\r
<ClInclude Include="forward.h" />\r
<ClInclude Include="future.h" />\r
<ClInclude Include="future_fwd.h" />\r
<ClInclude Include="os\windows\windows.h" />\r
<ClInclude Include="page_locked_allocator.h" />\r
<ClInclude Include="param.h" />\r
+ <ClInclude Include="polling_filesystem_monitor.h" />\r
<ClInclude Include="prec_timer.h" />\r
<ClInclude Include="reactive.h" />\r
<ClInclude Include="semaphore.h" />\r
<ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Develop|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
<ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
</ClCompile>\r
+ <ClCompile Include="polling_filesystem_monitor.cpp" />\r
<ClCompile Include="prec_timer.cpp">\r
<ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Release|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
<ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">compiler/vs/disable_silly_warnings.h</ForcedIncludeFiles>\r
<ClCompile Include="compiler\vs\stack_walker.cpp">\r
<Filter>source\compiler\vs</Filter>\r
</ClCompile>\r
+ <ClCompile Include="polling_filesystem_monitor.cpp">\r
+ <Filter>source</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
<ItemGroup>\r
<ClInclude Include="compiler\vs\disable_silly_warnings.h">\r
<ClInclude Include="endian.h">\r
<Filter>source</Filter>\r
</ClInclude>\r
+ <ClInclude Include="filesystem_monitor.h">\r
+ <Filter>source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="polling_filesystem_monitor.h">\r
+ <Filter>source</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
tbb::spin_mutex mutex_;
std::wstring text_;
+ bool auto_reset_;
impl()
{
{
lines_[name].set_color(color);
}
+ void auto_reset()
+ {
+ lock(mutex_, [this]
+ {
+ auto_reset_ = true;
+ });
+ }
private:
void render(sf::RenderTarget& target)
const size_t text_offset = (text_size+text_margin*2)*2;
std::wstring text_str;
+ bool auto_reset;
+
{
tbb::spin_mutex::scoped_lock lock(mutex_);
text_str = text_;
+ auto_reset = auto_reset_;
}
sf::String text(text_str.c_str(), sf::Font::GetDefaultFont(), text_size);
//target.Draw(diagnostics::guide(0.0f, color(1.0f, 1.0f, 1.0f, 0.6f)));
for(auto it = lines_.begin(); it != lines_.end(); ++it)
+ {
target.Draw(it->second);
+ if(auto_reset)
+ it->second.set_value(0.0f);
+ }
glPopMatrix();
}
void graph::set_value(const std::string& name, double value){impl_->set_value(name, value);}
void graph::set_color(const std::string& name, int color){impl_->set_color(name, color);}
void graph::set_tag(const std::string& name){impl_->set_tag(name);}
-
+void graph::auto_reset(){impl_->auto_reset(); }
void register_graph(const spl::shared_ptr<graph>& graph)
{
context::register_drawable(graph->impl_);
void set_value(const std::string& name, double value);
void set_color(const std::string& name, int color);
void set_tag(const std::string& name);
+ void auto_reset();
private:
struct impl;
std::shared_ptr<impl> impl_;
std::wstring ftemplate;
std::wstring data;
std::wstring font;
+std::wstring thumbnails;
boost::property_tree::wptree pt;
void check_is_configured()
ftemplate = boost::filesystem3::complete(paths.get(L"template-path", initialPath + L"\\template\\")).wstring();
data = paths.get(L"data-path", initialPath + L"\\data\\");
font = paths.get(L"font-path", initialPath + L"\\fonts\\");
+ thumbnails = paths.get(L"thumbnails-path", initialPath + L"\\data\\");
try
{
auto data_path = boost::filesystem::path(data);
if(!boost::filesystem::exists(data_path))
boost::filesystem::create_directories(data_path);
+
+ auto thumbnails_path = boost::filesystem::path(thumbnails);
+ if(!boost::filesystem::exists(thumbnails_path))
+ boost::filesystem::create_directories(thumbnails_path);
}
catch(...)
{
return font;
}
+const std::wstring& thumbnails_folder()
+{
+ check_is_configured();
+ return thumbnails;
+}
+
#define QUOTE(str) #str
#define EXPAND_AND_QUOTE(str) QUOTE(str)
const std::wstring& log_folder();
const std::wstring& template_folder();
const std::wstring& data_folder();
+const std::wstring& thumbnails_folder();
const std::wstring& version();
const std::wstring& font_folder();
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include <functional>
+#include <set>
+
+#include <boost/filesystem.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/thread/future.hpp>
+
+#include "memory.h"
+
+namespace caspar {
+
+/**
+ * A handle to an active filesystem monitor. Will stop scanning when destroyed.
+ */
+class filesystem_monitor : boost::noncopyable
+{
+public:
+ typedef spl::shared_ptr<filesystem_monitor> ptr;
+
+ virtual ~filesystem_monitor() {}
+
+ /**
+ * @return a future made available when the initially available files have been
+ * processed.
+ */
+ virtual boost::unique_future<void> initial_files_processed() = 0;
+
+ /**
+ * Reemmit the already known files as MODIFIED events.
+ */
+ virtual void reemmit_all() = 0;
+
+ /**
+ * Reemmit a specific file as a MODIFIED event.
+ *
+ * @param file The file to reemmit.
+ */
+ virtual void reemmit(const boost::filesystem::wpath& file) = 0;
+};
+
+/**
+ * The possible filesystem events.
+ */
+enum filesystem_event
+{
+ CREATED = 1,
+ REMOVED = 2,
+ MODIFIED = 4,
+ // Only used for describing a bitmask where all events are wanted. Never used when calling a handler.
+ ALL = 7
+};
+
+/**
+ * Handles filesystem events.
+ * <p>
+ * Will always be called from the same thread each time it is called.
+ *
+ * @param event Can be CREATED, REMOVED or MODIFIED.
+ * @param file The file affected.
+ */
+typedef std::function<void (filesystem_event event, const boost::filesystem::wpath& file)> filesystem_monitor_handler;
+
+/**
+ * Called when the initially available files has been found.
+ * <p>
+ * Will be called from the same thread as filesystem_monitor_handler is called.
+ *
+ * @param initial_files The files that were initially available.
+ */
+typedef std::function<void (const std::set<boost::filesystem::wpath>& initial_files)> initial_files_handler;
+
+/**
+ * Factory for creating filesystem monitors.
+ */
+class filesystem_monitor_factory : boost::noncopyable
+{
+public:
+ virtual ~filesystem_monitor_factory() {}
+
+ /**
+ * Create a new monitor.
+ *
+ * @param folder_to_watch The folder to recursively watch.
+ * @param events_of_interest_mask A bitmask of the events to prenumerate on.
+ * @param report_already_existing Whether to initially report all files in
+ * the folder as CREATED or to only care
+ * about changes.
+ * @param handler The handler to call each time a change is
+ * detected (only events defined by
+ * events_of_interest_mask).
+ * @param initial_files_handler The optional handler to call when the
+ * initially available files has been
+ * discovered.
+ *
+ * @return The filesystem monitor handle.
+ */
+ virtual filesystem_monitor::ptr create(
+ const boost::filesystem::wpath& folder_to_watch,
+ filesystem_event events_of_interest_mask,
+ bool report_already_existing,
+ const filesystem_monitor_handler& handler,
+ const initial_files_handler& initial_files_handler =
+ [] (const std::set<boost::filesystem::wpath>&) { }) = 0;
+};
+
+}
\ No newline at end of file
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#include "stdafx.h"
+
+#include "polling_filesystem_monitor.h"
+
+#include <map>
+#include <set>
+#include <iostream>
+
+#include <boost/thread.hpp>
+#include <boost/foreach.hpp>
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/copy.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem/convenience.hpp>
+
+#include <tbb/atomic.h>
+#include <tbb/concurrent_queue.h>
+
+#include "executor.h"
+
+namespace caspar {
+
+class exception_protected_handler
+{
+ filesystem_monitor_handler handler_;
+public:
+ exception_protected_handler(const filesystem_monitor_handler& handler)
+ : handler_(handler)
+ {
+ }
+
+ void operator()(filesystem_event event, const boost::filesystem::wpath& file)
+ {
+ try
+ {
+ boost::this_thread::interruption_point();
+ handler_(event, file);
+ boost::this_thread::interruption_point();
+ }
+ catch (const boost::thread_interrupted&)
+ {
+ throw;
+ }
+ catch (...)
+ {
+ CASPAR_LOG_CURRENT_EXCEPTION();
+ }
+ }
+};
+
+class directory_monitor
+{
+ bool report_already_existing_;
+ boost::filesystem::wpath folder_;
+ filesystem_event events_mask_;
+ filesystem_monitor_handler handler_;
+ initial_files_handler initial_files_handler_;
+ bool first_scan_;
+ std::map<boost::filesystem::wpath, std::time_t> files_;
+ std::map<boost::filesystem::wpath, uintmax_t> being_written_sizes_;
+public:
+ directory_monitor(
+ bool report_already_existing,
+ const boost::filesystem::wpath& folder,
+ filesystem_event events_mask,
+ const filesystem_monitor_handler& handler,
+ const initial_files_handler& initial_files_handler)
+ : report_already_existing_(report_already_existing)
+ , folder_(folder)
+ , events_mask_(events_mask)
+ , handler_(exception_protected_handler(handler))
+ , initial_files_handler_(initial_files_handler)
+ , first_scan_(true)
+ {
+ }
+
+ void reemmit_all()
+ {
+ if ((events_mask_ & MODIFIED) == 0)
+ return;
+
+ BOOST_FOREACH(auto& file, files_)
+ handler_(MODIFIED, file.first);
+ }
+
+ void reemmit(const boost::filesystem::wpath& file)
+ {
+ if ((events_mask_ & MODIFIED) == 0)
+ return;
+
+ if (files_.find(file) != files_.end() && boost::filesystem::exists(file))
+ handler_(MODIFIED, file);
+ }
+
+ void scan(const boost::function<bool ()>& should_abort)
+ {
+ static const std::time_t NO_LONGER_WRITING_AGE = 3; // Assume std::time_t is expressed in seconds
+ using namespace boost::filesystem;
+
+ bool interested_in_removed = (events_mask_ & REMOVED) > 0;
+ bool interested_in_created = (events_mask_ & CREATED) > 0;
+ bool interested_in_modified = (events_mask_ & MODIFIED) > 0;
+
+ std::set<wpath> removed_files;
+ boost::copy(
+ files_ | boost::adaptors::map_keys,
+ std::insert_iterator<decltype(removed_files)>(removed_files, removed_files.end()));
+
+ std::set<wpath> initial_files;
+
+ for (boost::filesystem3::wrecursive_directory_iterator iter(folder_); iter != boost::filesystem3::wrecursive_directory_iterator(); ++iter)
+ {
+ if (should_abort())
+ return;
+
+ auto& path = iter->path();
+
+ if (is_directory(path))
+ continue;
+
+ auto now = std::time(nullptr);
+ std::time_t current_mtime;
+
+ try
+ {
+ current_mtime = last_write_time(path);
+ }
+ catch (...)
+ {
+ // Probably removed, will be captured the next round.
+ continue;
+ }
+
+ auto time_since_written_to = now - current_mtime;
+ bool no_longer_being_written_to = time_since_written_to >= NO_LONGER_WRITING_AGE;
+ auto previous_it = files_.find(path);
+ bool already_known = previous_it != files_.end();
+
+ if (already_known && no_longer_being_written_to)
+ {
+ bool modified = previous_it->second != current_mtime;
+
+ if (modified && can_read_file(path))
+ {
+ if (interested_in_modified)
+ handler_(MODIFIED, path);
+
+ files_[path] = current_mtime;
+ being_written_sizes_.erase(path);
+ }
+ }
+ else if (no_longer_being_written_to && can_read_file(path))
+ {
+ if (interested_in_created && (report_already_existing_ || !first_scan_))
+ handler_(CREATED, path);
+
+ if (first_scan_)
+ initial_files.insert(path);
+
+ files_.insert(std::make_pair(path, current_mtime));
+ being_written_sizes_.erase(path);
+ }
+
+ removed_files.erase(path);
+ }
+
+ BOOST_FOREACH(auto& path, removed_files)
+ {
+ files_.erase(path);
+ being_written_sizes_.erase(path);
+
+ if (interested_in_removed)
+ handler_(REMOVED, path);
+ }
+
+ if (first_scan_)
+ initial_files_handler_(initial_files);
+
+ first_scan_ = false;
+ }
+private:
+ bool can_read_file(const boost::filesystem::wpath& file)
+ {
+ boost::filesystem::wifstream stream(file);
+
+ return stream.is_open();
+ }
+};
+
+class polling_filesystem_monitor : public filesystem_monitor
+{
+ directory_monitor root_monitor_;
+ boost::thread scanning_thread_;
+ tbb::atomic<bool> running_;
+ int scan_interval_millis_;
+ boost::promise<void> initial_scan_completion_;
+ tbb::concurrent_queue<boost::filesystem::wpath> to_reemmit_;
+ tbb::atomic<bool> reemmit_all_;
+public:
+ polling_filesystem_monitor(
+ const boost::filesystem::wpath& folder_to_watch,
+ filesystem_event events_of_interest_mask,
+ bool report_already_existing,
+ int scan_interval_millis,
+ const filesystem_monitor_handler& handler,
+ const initial_files_handler& initial_files_handler)
+ : root_monitor_(report_already_existing, folder_to_watch, events_of_interest_mask, handler, initial_files_handler)
+ , scan_interval_millis_(scan_interval_millis)
+ {
+ running_ = true;
+ reemmit_all_ = false;
+ scanning_thread_ = boost::thread([this] { scanner(); });
+ }
+
+ virtual ~polling_filesystem_monitor()
+ {
+ running_ = false;
+ scanning_thread_.interrupt();
+ scanning_thread_.join();
+ }
+
+ virtual boost::unique_future<void> initial_files_processed()
+ {
+ return initial_scan_completion_.get_future();
+ }
+
+ virtual void reemmit_all()
+ {
+ reemmit_all_ = true;
+ }
+
+ virtual void reemmit(const boost::filesystem::wpath& file)
+ {
+ to_reemmit_.push(file);
+ }
+private:
+ void scanner()
+ {
+ win32_exception::install_handler();
+
+ //detail::SetThreadName(GetCurrentThreadId(), "polling_filesystem_monitor");
+
+ bool running = scan(false);
+ initial_scan_completion_.set_value();
+
+ if (running)
+ while (scan(true));
+ }
+
+ bool scan(bool sleep)
+ {
+ try
+ {
+ if (sleep)
+ boost::this_thread::sleep(boost::posix_time::milliseconds(scan_interval_millis_));
+
+ if (reemmit_all_.fetch_and_store(false))
+ root_monitor_.reemmit_all();
+ else
+ {
+ boost::filesystem::wpath file;
+
+ while (to_reemmit_.try_pop(file))
+ root_monitor_.reemmit(file);
+ }
+
+ root_monitor_.scan([=] { return !running_; });
+ }
+ catch (const boost::thread_interrupted&)
+ {
+ }
+ catch (...)
+ {
+ CASPAR_LOG_CURRENT_EXCEPTION();
+ }
+
+ return running_;
+ }
+};
+
+struct polling_filesystem_monitor_factory::impl
+{
+ int scan_interval_millis;
+
+ impl(int scan_interval_millis)
+ : scan_interval_millis(scan_interval_millis)
+ {
+ }
+};
+
+polling_filesystem_monitor_factory::polling_filesystem_monitor_factory(int scan_interval_millis)
+ : impl_(new impl(scan_interval_millis))
+{
+}
+
+polling_filesystem_monitor_factory::~polling_filesystem_monitor_factory()
+{
+}
+
+filesystem_monitor::ptr polling_filesystem_monitor_factory::create(
+ const boost::filesystem::wpath& folder_to_watch,
+ filesystem_event events_of_interest_mask,
+ bool report_already_existing,
+ const filesystem_monitor_handler& handler,
+ const initial_files_handler& initial_files_handler)
+{
+ return spl::make_shared<polling_filesystem_monitor>(
+ folder_to_watch,
+ events_of_interest_mask,
+ report_already_existing,
+ impl_->scan_interval_millis,
+ handler,
+ initial_files_handler);
+}
+
+}
\ No newline at end of file
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include "filesystem_monitor.h"
+
+namespace caspar {
+
+/**
+ * A portable filesystem monitor implementation which periodically polls the
+ * filesystem for changes. Will not react instantly but never misses any
+ * changes.
+ * <p>
+ * Will create a dedicated thread for each monitor created.
+ */
+class polling_filesystem_monitor_factory : public filesystem_monitor_factory
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @param scan_interval_millis The number of milliseconds between each
+ * scheduled scan. Lower values lowers the reaction
+ * time but causes more I/O.
+ */
+ polling_filesystem_monitor_factory(int scan_interval_millis = 5000);
+ virtual ~polling_filesystem_monitor_factory();
+ virtual filesystem_monitor::ptr create(
+ const boost::filesystem::wpath& folder_to_watch,
+ filesystem_event events_of_interest_mask,
+ bool report_already_existing,
+ const filesystem_monitor_handler& handler,
+ const initial_files_handler& initial_files_handler);
+private:
+ struct impl;
+ spl::unique_ptr<impl> impl_;
+};
+
+}
\ No newline at end of file
<ClInclude Include="producer\text\utils\texture_font.h" />\r
<ClInclude Include="producer\text\utils\text_info.h" />\r
<ClInclude Include="producer\variable.h" />\r
+ <ClInclude Include="thumbnail_generator.h" />\r
<ClInclude Include="video_channel.h" />\r
<ClInclude Include="consumer\output.h" />\r
<ClInclude Include="consumer\frame_consumer.h" />\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">..\..\..\StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\..\StdAfx.h</PrecompiledHeaderFile>\r
</ClCompile>\r
+ <ClCompile Include="thumbnail_generator.cpp" />\r
<ClCompile Include="video_channel.cpp" />\r
<ClCompile Include="consumer\frame_consumer.cpp">\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../StdAfx.h</PrecompiledHeaderFile>\r
<ClInclude Include="producer\text\utils\text_info.h">\r
<Filter>source\producer\text\utils</Filter>\r
</ClInclude>\r
+ <ClInclude Include="thumbnail_generator.h">\r
+ <Filter>source</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
<ItemGroup>\r
<ClCompile Include="producer\transition\transition_producer.cpp">\r
<ClCompile Include="producer\scene\expression_parser.cpp">\r
<Filter>source\producer\scene</Filter>\r
</ClCompile>\r
+ <ClCompile Include="thumbnail_generator.cpp">\r
+ <Filter>source</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
namespace caspar { namespace core {
std::vector<const producer_factory_t> g_factories;
+std::vector<const producer_factory_t> g_thumbnail_factories;
void register_producer_factory(const producer_factory_t& factory)
{
g_factories.push_back(factory);
}
+void register_thumbnail_producer_factory(const producer_factory_t& factory)
+{
+ g_thumbnail_factories.push_back(factory);
+}
constraints::constraints(double width, double height)
: width(width), height(height)
{
return draw_frame::still(last_frame_);
}
+ draw_frame create_thumbnail_frame()
+ {
+ return draw_frame::empty();
+ }
};
frame_producer_base::frame_producer_base() : impl_(new impl(*this))
{
return impl_->last_frame();
}
+draw_frame frame_producer_base::create_thumbnail_frame()
+{
+ return impl_->create_thumbnail_frame();
+}
boost::unique_future<std::wstring> frame_producer_base::call(const std::vector<std::wstring>&)
{
variable& get_variable(const std::wstring& name) override { CASPAR_THROW_EXCEPTION(not_supported()); }
const std::vector<std::wstring>& get_variables() const override { static std::vector<std::wstring> empty; return empty; }
draw_frame last_frame() {return draw_frame::empty();}
+ draw_frame create_thumbnail_frame() {return draw_frame::empty();}
constraints& pixel_constraints() override { static constraints c; return c; }
boost::property_tree::wptree info() const override
void leading_producer(const spl::shared_ptr<frame_producer>& producer) override {return producer_->leading_producer(producer);}
uint32_t nb_frames() const override {return producer_->nb_frames();}
class draw_frame last_frame() {return producer_->last_frame();}
+ draw_frame create_thumbnail_frame() {return producer_->create_thumbnail_frame();}
void subscribe(const monitor::observable::observer_ptr& o) {return producer_->subscribe(o);}
void unsubscribe(const monitor::observable::observer_ptr& o) {return producer_->unsubscribe(o);}
bool collides(double x, double y) {return producer_->collides(x, y);}
return spl::make_shared<destroy_producer_proxy>(std::move(producer));
}
-spl::shared_ptr<core::frame_producer> do_create_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params)
+spl::shared_ptr<core::frame_producer> do_create_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params, const std::vector<const producer_factory_t>& factories, bool throw_on_fail = false)
{
if(params.empty())
CASPAR_THROW_EXCEPTION(invalid_argument() << arg_name_info("params") << arg_value_info(""));
auto producer = frame_producer::empty();
- std::any_of(g_factories.begin(), g_factories.end(), [&](const producer_factory_t& factory) -> bool
+ std::any_of(factories.begin(), factories.end(), [&](const producer_factory_t& factory) -> bool
{
try
{
}
catch(...)
{
- CASPAR_LOG_CURRENT_EXCEPTION();
+ if(throw_on_fail)
+ throw;
+ else
+ CASPAR_LOG_CURRENT_EXCEPTION();
}
return producer != frame_producer::empty();
});
return producer;
}
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::wstring& media_file)
+{
+ std::vector<std::wstring> params;
+ params.push_back(media_file);
+
+ auto producer = do_create_producer(my_frame_factory, format_desc, params, g_thumbnail_factories, true);
+ auto key_producer = frame_producer::empty();
+
+ try // to find a key file.
+ {
+ auto params_copy = params;
+ if (params_copy.size() > 0)
+ {
+ params_copy[0] += L"_A";
+ key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_thumbnail_factories, true);
+ if (key_producer == frame_producer::empty())
+ {
+ params_copy[0] += L"LPHA";
+ key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_thumbnail_factories, true);
+ }
+ }
+ }
+ catch(...){}
+
+ if (producer != frame_producer::empty() && key_producer != frame_producer::empty())
+ return create_separated_producer(producer, key_producer);
+
+ return producer;
+}
+
spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<frame_factory>& my_frame_factory, const video_format_desc& format_desc, const std::vector<std::wstring>& params)
{
- auto producer = do_create_producer(my_frame_factory, format_desc, params);
+ auto producer = do_create_producer(my_frame_factory, format_desc, params, g_factories);
auto key_producer = frame_producer::empty();
try // to find a key file.
if(params_copy.size() > 0)
{
params_copy[0] += L"_A";
- key_producer = do_create_producer(my_frame_factory, format_desc, params_copy);
+ key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_factories);
if(key_producer == frame_producer::empty())
{
params_copy[0] += L"LPHA";
- key_producer = do_create_producer(my_frame_factory, format_desc, params_copy);
+ key_producer = do_create_producer(my_frame_factory, format_desc, params_copy, g_factories);
}
}
}
virtual uint32_t nb_frames() const = 0;
virtual uint32_t frame_number() const = 0;
virtual class draw_frame last_frame() = 0;
+ virtual class draw_frame create_thumbnail_frame() = 0;
virtual constraints& pixel_constraints() = 0;
virtual void leading_producer(const spl::shared_ptr<frame_producer>&) {}
};
uint32_t nb_frames() const override;
uint32_t frame_number() const override;
virtual class draw_frame last_frame() override;
+ virtual class draw_frame create_thumbnail_frame() override;
private:
virtual class draw_frame receive() override;
typedef std::function<spl::shared_ptr<core::frame_producer>(const spl::shared_ptr<class frame_factory>&, const video_format_desc& format_desc, const std::vector<std::wstring>&)> producer_factory_t;
void register_producer_factory(const producer_factory_t& factory); // Not thread-safe.
+void register_thumbnail_producer_factory(const producer_factory_t& factory); // Not thread-safe.
spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<frame_factory>&, const video_format_desc& format_desc, const std::vector<std::wstring>& params);
spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<frame_factory>&, const video_format_desc& format_desc, const std::wstring& params);
spl::shared_ptr<core::frame_producer> create_destroy_proxy(spl::shared_ptr<core::frame_producer> producer);
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<frame_factory>&, const video_format_desc& format_desc, const std::wstring& media_file);
}}
return draw_frame::mask(fill_producer_->last_frame(), key_producer_->last_frame());
}
+ draw_frame create_thumbnail_frame()
+ {
+ auto fill_frame = fill_producer_->create_thumbnail_frame();
+ auto key_frame = key_producer_->create_thumbnail_frame();
+
+ if (fill_frame == draw_frame::empty() || key_frame == draw_frame::empty())
+ return draw_frame::empty();
+
+ if (fill_frame == draw_frame::late() || key_frame == draw_frame::late())
+ return draw_frame::late();
+
+ return draw_frame::mask(fill_frame, key_frame);
+ }
+
constraints& pixel_constraints() override
{
return fill_producer_->pixel_constraints();
namespace caspar { namespace core {
spl::shared_ptr<class frame_producer> create_separated_producer(const spl::shared_ptr<class frame_producer>& fill, const spl::shared_ptr<class frame_producer>& key);
+spl::shared_ptr<class frame_producer> create_separated_thumbnail_producer(const spl::shared_ptr<class frame_producer>& fill, const spl::shared_ptr<class frame_producer>& key);
}}
\ No newline at end of file
float size;
color<float> color;
+ //int shadow_distance;
+ //int shadow_size;
+ //float shadow_spread;
+ //color<float> shadow_color;
int baseline_shift;
int tracking;
};
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#include "stdafx.h"
+
+#include "thumbnail_generator.h"
+
+#include <iostream>
+#include <iterator>
+#include <set>
+
+#include <boost/thread.hpp>
+#include <boost/range/algorithm/transform.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+
+#include <tbb/atomic.h>
+
+#include <common/diagnostics/graph.h>
+
+#include "producer/frame_producer.h"
+#include "consumer/frame_consumer.h"
+#include "mixer/mixer.h"
+#include "mixer/image/image_mixer.h"
+#include "video_format.h"
+#include "frame/frame.h"
+#include "frame/draw_frame.h"
+#include "frame/frame_transform.h"
+
+namespace caspar { namespace core {
+
+std::wstring get_relative_without_extension(
+ const boost::filesystem::wpath& file,
+ const boost::filesystem::wpath& relative_to)
+{
+ auto result = file.stem();
+
+ boost::filesystem::wpath current_path = file;
+
+ while (true)
+ {
+ current_path = current_path.parent_path();
+
+ if (boost::filesystem::equivalent(current_path, relative_to))
+ break;
+
+ if (current_path.empty())
+ throw std::runtime_error("File not relative to folder");
+
+ result = current_path.filename() / result;
+ }
+
+ return result.wstring();
+}
+
+struct thumbnail_output
+{
+ tbb::atomic<int> sleep_millis;
+ std::function<void (const_frame)> on_send;
+
+ thumbnail_output(int sleep_millis)
+ {
+ this->sleep_millis = sleep_millis;
+ }
+
+ void send(const_frame frame, std::shared_ptr<void> frame_and_ticket)
+ {
+ int current_sleep = sleep_millis;
+
+ if (current_sleep > 0)
+ boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
+
+ on_send(std::move(frame));
+ on_send = nullptr;
+ }
+};
+
+struct thumbnail_generator::impl
+{
+private:
+ boost::filesystem::wpath media_path_;
+ boost::filesystem::wpath thumbnails_path_;
+ int width_;
+ int height_;
+ spl::shared_ptr<image_mixer> image_mixer_;
+ spl::shared_ptr<diagnostics::graph> graph_;
+ video_format_desc format_desc_;
+ spl::unique_ptr<thumbnail_output> output_;
+ mixer mixer_;
+ thumbnail_creator thumbnail_creator_;
+ filesystem_monitor::ptr monitor_;
+public:
+ impl(
+ filesystem_monitor_factory& monitor_factory,
+ const boost::filesystem::wpath& media_path,
+ const boost::filesystem::wpath& thumbnails_path,
+ int width,
+ int height,
+ const video_format_desc& render_video_mode,
+ std::unique_ptr<image_mixer> image_mixer,
+ int generate_delay_millis,
+ const thumbnail_creator& thumbnail_creator)
+ : media_path_(media_path)
+ , thumbnails_path_(thumbnails_path)
+ , width_(width)
+ , height_(height)
+ , image_mixer_(std::move(image_mixer))
+ , format_desc_(render_video_mode)
+ , output_(spl::make_unique<thumbnail_output>(generate_delay_millis))
+ , mixer_(graph_, image_mixer_)
+ , thumbnail_creator_(thumbnail_creator)
+ , monitor_(monitor_factory.create(
+ media_path,
+ ALL,
+ true,
+ [this] (filesystem_event event, const boost::filesystem::wpath& file)
+ {
+ this->on_file_event(event, file);
+ },
+ [this] (const std::set<boost::filesystem::wpath>& initial_files)
+ {
+ this->on_initial_files(initial_files);
+ }))
+ {
+ graph_->set_text(L"thumbnail-channel");
+ graph_->auto_reset();
+ diagnostics::register_graph(graph_);
+ //monitor_->initial_scan_completion().get();
+ //output_->sleep_millis = 2000;
+ }
+
+ void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
+ {
+ using namespace boost::filesystem;
+
+ std::set<std::wstring> relative_without_extensions;
+ boost::transform(
+ initial_files,
+ std::insert_iterator<std::set<std::wstring>>(
+ relative_without_extensions,
+ relative_without_extensions.end()),
+ boost::bind(&get_relative_without_extension, _1, media_path_));
+
+ for (boost::filesystem3::wrecursive_directory_iterator iter(thumbnails_path_); iter != boost::filesystem3::wrecursive_directory_iterator(); ++iter)
+ {
+ auto& path = iter->path();
+
+ if (!is_regular_file(path))
+ continue;
+
+ auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
+ bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension)
+ == relative_without_extensions.end();
+
+ if (no_corresponding_media_file)
+ remove(thumbnails_path_ / (relative_without_extension + L".png"));
+ }
+ }
+
+ void generate(const std::wstring& media_file)
+ {
+ using namespace boost::filesystem;
+ auto base_file = media_path_ / media_file;
+ auto folder = base_file.parent_path();
+
+ for (boost::filesystem3::directory_iterator iter(folder); iter != boost::filesystem3::directory_iterator(); ++iter)
+ {
+ auto stem = iter->path().stem();
+
+ if (boost::iequals(stem.wstring(), base_file.filename().wstring()))
+ monitor_->reemmit(iter->path());
+ }
+ }
+
+ void generate_all()
+ {
+ monitor_->reemmit_all();
+ }
+
+ void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
+ {
+ switch (event)
+ {
+ case CREATED:
+ if (needs_to_be_generated(file))
+ generate_thumbnail(file);
+
+ break;
+ case MODIFIED:
+ generate_thumbnail(file);
+
+ break;
+ case REMOVED:
+ auto relative_without_extension = get_relative_without_extension(file, media_path_);
+ boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
+
+ break;
+ }
+ }
+
+ bool needs_to_be_generated(const boost::filesystem::wpath& file)
+ {
+ using namespace boost::filesystem;
+
+ auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
+
+ if (!exists(png_file))
+ return true;
+
+ std::time_t media_file_mtime;
+
+ try
+ {
+ media_file_mtime = last_write_time(file);
+ }
+ catch (...)
+ {
+ // Probably removed.
+ return false;
+ }
+
+ try
+ {
+ return media_file_mtime != last_write_time(png_file);
+ }
+ catch (...)
+ {
+ // thumbnail probably removed.
+ return true;
+ }
+ }
+
+ void generate_thumbnail(const boost::filesystem::wpath& file)
+ {
+ auto media_file = get_relative_without_extension(file, media_path_);
+ auto png_file = thumbnails_path_ / (media_file + L".png");
+ boost::promise<void> thumbnail_ready;
+
+ {
+ auto producer = frame_producer::empty();
+
+ try
+ {
+ producer = create_thumbnail_producer(image_mixer_, format_desc_, media_file);
+ }
+ catch (const boost::thread_interrupted&)
+ {
+ throw;
+ }
+ catch (...)
+ {
+ CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
+ return;
+ }
+
+ if (producer == frame_producer::empty())
+ {
+ CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
+ return;
+ }
+
+ boost::filesystem::create_directories(png_file.parent_path());
+ output_->on_send = [this, &png_file] (const_frame frame)
+ {
+ thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
+ };
+
+ std::map<int, draw_frame> frames;
+ auto raw_frame = draw_frame::empty();
+
+ try
+ {
+ raw_frame = producer->create_thumbnail_frame();
+ }
+ catch (const boost::thread_interrupted&)
+ {
+ throw;
+ }
+ catch (...)
+ {
+ CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
+ return;
+ }
+
+ if (raw_frame == draw_frame::empty()
+ || raw_frame == draw_frame::late())
+ {
+ CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
+ return;
+ }
+
+ auto transformed_frame = draw_frame(raw_frame);
+ transformed_frame.transform().image_transform.fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
+ transformed_frame.transform().image_transform.fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
+ frames.insert(std::make_pair(0, transformed_frame));
+
+ std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*) { thumbnail_ready.set_value(); });
+
+ auto mixed_frame = mixer_(frames, format_desc_);
+
+ output_->send(std::move(mixed_frame), ticket);
+ ticket.reset();
+ }
+ thumbnail_ready.get_future().get();
+
+ if (boost::filesystem::exists(png_file))
+ {
+ // Adjust timestamp to match source file.
+ try
+ {
+ boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
+ CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
+ }
+ catch (...)
+ {
+ // One of the files was removed before the call to last_write_time.
+ }
+ }
+ else
+ CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
+ }
+};
+
+thumbnail_generator::thumbnail_generator(
+ filesystem_monitor_factory& monitor_factory,
+ const boost::filesystem::wpath& media_path,
+ const boost::filesystem::wpath& thumbnails_path,
+ int width,
+ int height,
+ const video_format_desc& render_video_mode,
+ std::unique_ptr<image_mixer> image_mixer,
+ int generate_delay_millis,
+ const thumbnail_creator& thumbnail_creator)
+ : impl_(new impl(
+ monitor_factory,
+ media_path,
+ thumbnails_path,
+ width, height,
+ render_video_mode,
+ std::move(image_mixer),
+ generate_delay_millis,
+ thumbnail_creator))
+{
+}
+
+thumbnail_generator::~thumbnail_generator()
+{
+}
+
+void thumbnail_generator::generate(const std::wstring& media_file)
+{
+ impl_->generate(media_file);
+}
+
+void thumbnail_generator::generate_all()
+{
+ impl_->generate_all();
+}
+
+}}
\ No newline at end of file
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+
+#include "memory.h"
+#include <common/filesystem_monitor.h>
+
+namespace caspar { namespace core {
+
+class ogl_device;
+class read_frame;
+struct video_format_desc;
+
+typedef std::function<void (
+ const class const_frame& frame,
+ const video_format_desc& format_desc,
+ const boost::filesystem::wpath& output_file,
+ int width,
+ int height)> thumbnail_creator;
+
+class thumbnail_generator : boost::noncopyable
+{
+public:
+ thumbnail_generator(
+ filesystem_monitor_factory& monitor_factory,
+ const boost::filesystem::wpath& media_path,
+ const boost::filesystem::wpath& thumbnails_path,
+ int width,
+ int height,
+ const video_format_desc& render_video_mode,
+ std::unique_ptr<class image_mixer> image_mixer,
+ int generate_delay_millis,
+ const thumbnail_creator& thumbnail_creator);
+ ~thumbnail_generator();
+ void generate(const std::wstring& media_file);
+ void generate_all();
+private:
+ struct impl;
+ spl::unique_ptr<impl> impl_;
+};
+
+}}
\ No newline at end of file
#include <FreeImage.h>
#include <vector>
+#include <algorithm>
+
+#include "../util/image_view.h"
namespace caspar { namespace image {
-
+
+ void write_cropped_png(
+ const core::const_frame& frame,
+ const core::video_format_desc& format_desc,
+ const boost::filesystem::wpath& output_file,
+ int width,
+ int height)
+ {
+ auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Allocate(width, height, 32), FreeImage_Unload);
+ image_view<bgra_pixel> destination_view(FreeImage_GetBits(bitmap.get()), width, height);
+ image_view<bgra_pixel> complete_frame(const_cast<uint8_t*>(frame.image_data().begin()), format_desc.width, format_desc.height);
+ auto thumbnail_view = complete_frame.subview(0, 0, width, height);
+
+ std::copy(thumbnail_view.begin(), thumbnail_view.end(), destination_view.begin());
+ FreeImage_FlipVertical(bitmap.get());
+ FreeImage_SaveU(FIF_PNG, bitmap.get(), output_file.wstring().c_str(), 0);
+ }
+
struct image_consumer : public core::frame_consumer
{
std::wstring filename_;
{
try
{
- auto filename2 = u8(filename);
+ auto filename2 = filename;
if (filename2.empty())
- filename2 = u8(env::media_folder()) + boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time()) + ".png";
+ filename2 = env::media_folder() + boost::posix_time::to_iso_wstring(boost::posix_time::second_clock::local_time()) + L".png";
else
- filename2 = u8(env::media_folder()) + filename2 + ".png";
+ filename2 = env::media_folder() + filename2 + L".png";
auto bitmap = std::shared_ptr<FIBITMAP>(FreeImage_Allocate(static_cast<int>(frame.width()), static_cast<int>(frame.height()), 32), FreeImage_Unload);
A_memcpy(FreeImage_GetBits(bitmap.get()), frame.image_data().begin(), frame.image_data().size());
FreeImage_FlipVertical(bitmap.get());
- FreeImage_Save(FIF_PNG, bitmap.get(), filename2.c_str(), 0);
+ FreeImage_SaveU(FIF_PNG, bitmap.get(), filename2.c_str(), 0);
}
catch(...)
{
#include <common/memory.h>
#include <core/video_format.h>
-
+#include <core/consumer/frame_consumer.h>
#include <boost/property_tree/ptree.hpp>
+#include <boost/filesystem.hpp>
#include <string>
#include <vector>
namespace caspar {
-namespace core {
- class frame_consumer;
-}
-
namespace image {
+ void write_cropped_png(
+ const class core::const_frame& frame,
+ const core::video_format_desc& format_desc,
+ const boost::filesystem::wpath& output_file,
+ int width,
+ int height);
+
spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params);
}}
\ No newline at end of file
FreeImage_Initialise();
core::register_producer_factory(create_scroll_producer);
core::register_producer_factory(create_producer);
+ core::register_thumbnail_producer_factory(create_thumbnail_producer);
core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});
}
void uninit()
{
FreeImage_DeInitialise();
- core::register_producer_factory(create_scroll_producer);
- core::register_producer_factory(create_producer);
- core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});
+// core::register_producer_factory(create_scroll_producer);
+// core::register_producer_factory(create_producer);
+// core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_consumer(params);});
}
return frame_;
}
+ core::draw_frame create_thumbnail_frame() override
+ {
+ return frame_;
+ }
+
core::constraints& pixel_constraints() override
{
return constraints_;
}
};
-spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+spl::shared_ptr<core::frame_producer> create_raw_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
{
static const std::vector<std::wstring> extensions = list_of(L".png")(L".tga")(L".bmp")(L".jpg")(L".jpeg")(L".gif")(L".tiff")(L".tif")(L".jp2")(L".jpx")(L".j2k")(L".j2c");
std::wstring filename = env::media_folder() + L"\\" + params[0];
return spl::make_shared<image_producer>(frame_factory, filename + *ext);
}
+spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+{
+ return create_raw_producer(frame_factory, format_desc, params);
+}
+
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+{
+ return create_raw_producer(frame_factory, format_desc, params);
+}
}}
\ No newline at end of file
namespace caspar { namespace image {
spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params);
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params);
}}
\ No newline at end of file
#include <core/producer/layer.h>
#include <core/mixer/mixer.h>
#include <core/consumer/output.h>
+#include <core/thumbnail_generator.h>
#include <modules/reroute/producer/reroute_producer.h>
#include <modules/bluefish/bluefish.h>
#include <boost/locale.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/copy.hpp>
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/insert_linebreaks.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
#include <tbb/concurrent_unordered_map.h>
using namespace core;
+std::wstring read_file_base64(const boost::filesystem::wpath& file)
+{
+ using namespace boost::archive::iterators;
+
+ boost::filesystem::ifstream filestream(file, std::ios::binary);
+
+ if (!filestream)
+ return L"";
+
+ // From http://www.webbiscuit.co.uk/2012/04/02/base64-encoder-and-boost/
+
+ typedef
+ insert_linebreaks< // insert line breaks every 76 characters
+ base64_from_binary< // convert binary values to base64 characters
+ transform_width< // retrieve 6 bit integers from a sequence of 8 bit bytes
+ const unsigned char *,
+ 6,
+ 8
+ >
+ >,
+ 76
+ >
+ base64_iterator; // compose all the above operations in to a new iterator
+ auto length = boost::filesystem::file_size(file);
+ std::vector<char> bytes;
+ bytes.resize(length);
+ filestream.read(bytes.data(), length);
+
+ int padding = 0;
+
+ while (bytes.size() % 3 != 0)
+ {
+ ++padding;
+ bytes.push_back(0x00);
+ }
+
+ std::string result(base64_iterator(bytes.data()), base64_iterator(bytes.data() + length - padding));
+ result.insert(result.end(), padding, '=');
+
+ return std::wstring(result.begin(), result.end());
+}
+
std::wstring read_utf8_file(const boost::filesystem::wpath& file)
{
std::wstringstream result;
std::wstringstream reply(TEXT("201 DATA RETRIEVE OK\r\n"));
- reply << file_contents;
+ std::wstringstream file_contents_stream(file_contents);
+ std::wstring line;
+ while(std::getline(file_contents_stream, line))
+ reply << line << L"\n";
+
reply << "\r\n";
SetReplyString(reply.str());
return true;
return true;
}
+bool ThumbnailCommand::DoExecute()
+{
+ std::wstring command = boost::to_upper_copy(parameters()[0]);
+
+ if (command == TEXT("RETRIEVE"))
+ return DoExecuteRetrieve();
+ else if (command == TEXT("LIST"))
+ return DoExecuteList();
+ else if (command == TEXT("GENERATE"))
+ return DoExecuteGenerate();
+ else if (command == TEXT("GENERATE_ALL"))
+ return DoExecuteGenerateAll();
+
+ SetReplyString(TEXT("403 THUMBNAIL ERROR\r\n"));
+ return false;
+}
+
+bool ThumbnailCommand::DoExecuteRetrieve()
+{
+ if(parameters().size() < 2)
+ {
+ SetReplyString(TEXT("402 THUMBNAIL RETRIEVE ERROR\r\n"));
+ return false;
+ }
+
+ std::wstring filename = env::thumbnails_folder();
+ filename.append(parameters()[1]);
+ filename.append(TEXT(".png"));
+
+ std::wstring file_contents = read_file_base64(boost::filesystem::wpath(filename));
+
+ if (file_contents.empty())
+ {
+ SetReplyString(TEXT("404 THUMBNAIL RETRIEVE ERROR\r\n"));
+ return false;
+ }
+
+ std::wstringstream reply;
+
+ reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
+ reply << file_contents;
+ reply << L"\r\n";
+ SetReplyString(reply.str());
+ return true;
+}
+
+bool ThumbnailCommand::DoExecuteList()
+{
+ std::wstringstream replyString;
+ replyString << TEXT("200 THUMBNAIL LIST OK\r\n");
+
+ for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
+ {
+ if(boost::filesystem::is_regular_file(itr->path()))
+ {
+ if(!boost::iequals(itr->path().extension().wstring(), L".png"))
+ continue;
+
+ auto relativePath = boost::filesystem::wpath(itr->path().wstring().substr(env::thumbnails_folder().size()-1, itr->path().wstring().size()));
+
+ auto str = relativePath.replace_extension(L"").native();
+ if(str[0] == '\\' || str[0] == '/')
+ str = std::wstring(str.begin() + 1, str.end());
+
+ auto mtime = boost::filesystem::last_write_time(itr->path());
+ auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
+ auto file_size = boost::filesystem::file_size(itr->path());
+
+ replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
+ }
+ }
+
+ replyString << TEXT("\r\n");
+
+ SetReplyString(boost::to_upper_copy(replyString.str()));
+ return true;
+}
+
+bool ThumbnailCommand::DoExecuteGenerate()
+{
+ if (parameters().size() < 2)
+ {
+ SetReplyString(L"402 THUMBNAIL GENERATE ERROR\r\n");
+ return false;
+ }
+
+ if (thumb_gen_)
+ {
+ thumb_gen_->generate(parameters()[1]);
+ SetReplyString(L"202 THUMBNAIL GENERATE OK\r\n");
+ return true;
+ }
+ else
+ {
+ SetReplyString(L"500 THUMBNAIL GENERATE ERROR\r\n");
+ return false;
+ }
+}
+
+bool ThumbnailCommand::DoExecuteGenerateAll()
+{
+ if (thumb_gen_)
+ {
+ thumb_gen_->generate_all();
+ SetReplyString(L"202 THUMBNAIL GENERATE_ALL OK\r\n");
+ return true;
+ }
+ else
+ {
+ SetReplyString(L"500 THUMBNAIL GENERATE_ALL ERROR\r\n");
+ return false;
+ }
+}
} //namespace amcp
}} //namespace caspar
\ No newline at end of file
#include "AMCPCommand.h"
+#include <core/thumbnail_generator.h>
+
namespace caspar { namespace protocol {
std::wstring ListMedia();
SetCommand(IO::ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index) : AMCPChannelCommandBase(client, channel, channel_index, layer_index)
{}
-private:
std::wstring print() const { return L"SetCommand";}
bool DoExecute();
};
bool DoExecute();
};
+class ThumbnailCommand : public AMCPCommandBase<1>
+{
+public:
+ ThumbnailCommand::ThumbnailCommand(IO::ClientInfoPtr client, std::shared_ptr<core::thumbnail_generator> thumb_gen) : AMCPCommandBase(client), thumb_gen_(thumb_gen)
+ {}
+ std::wstring print() const { return L"ThumbnailCommand";}
+ bool DoExecute();
+
+private:
+ bool DoExecuteRetrieve();
+ bool DoExecuteList();
+ bool DoExecuteGenerate();
+ bool DoExecuteGenerateAll();
+
+ std::shared_ptr<core::thumbnail_generator> thumb_gen_;
+};
+
//class KillCommand : public AMCPCommand
//{
//public:
struct AMCPProtocolStrategy::impl
{
private:
- std::vector<channel_context> channels_;
- std::vector<AMCPCommandQueue::ptr_type> commandQueues_;
+ std::vector<channel_context> channels_;
+ std::vector<AMCPCommandQueue::ptr_type> commandQueues_;
+ std::shared_ptr<core::thumbnail_generator> thumb_gen_;
public:
- impl(const std::vector<spl::shared_ptr<core::video_channel>>& channels)
+ impl(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen) : thumb_gen_(thumb_gen)
{
commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
else if(s == TEXT("BYE")) return std::make_shared<ByeCommand>(client);
else if(s == TEXT("LOCK")) return std::make_shared<LockCommand>(client, channels_);
else if(s == TEXT("LOG")) return std::make_shared<LogCommand>(client);
+ else if(s == TEXT("THUMBNAIL")) return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
//else if(s == TEXT("KILL"))
//{
// result = AMCPCommandPtr(new KillCommand());
};
-AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels) : impl_(spl::make_unique<impl>(channels)) {}
+AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen) : impl_(spl::make_unique<impl>(channels, thumb_gen)) {}
AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
#include "../util/protocolstrategy.h"
#include <core/video_channel.h>
+#include <core/thumbnail_generator.h>
#include <common/memory.h>
class AMCPProtocolStrategy : public IO::IProtocolStrategy, boost::noncopyable
{
public:
- AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels);
+ AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen);
virtual ~AMCPProtocolStrategy();
virtual void Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo);
<log-path>log\</log-path>\r
<data-path>data\</data-path>\r
<template-path>templates\</template-path>\r
+ <thumbnails-path>thumbnails\</thumbnails-path>\r
</paths>\r
<flash>\r
<buffer-depth>4</buffer-depth>\r
</flash>\r
+ <thumbnails>\r
+ <generate-thumbnails>true</generate-thumbnails>\r
+ <width>256</width>\r
+ <height>144</height>\r
+ <video-grid>2</video-grid>\r
+ <scan-interval-millis>5000</scan-interval-millis>\r
+ <generate-delay-millis>2000</generate-delay-millis>\r
+ <video-mode>720p2500</video-mode>\r
+ </thumbnails>\r
<lock-clear-phrase>secret</lock-clear-phrase>\r
<channels>\r
<channel>\r
- <video-mode>576p2500</video-mode>\r
+ <video-mode>720p5000</video-mode>\r
<consumers>\r
<screen>\r
<device>1</device>\r
}
catch(...){}
- return EXCEPTION_CONTINUE_EXECUTION;
+ return EXCEPTION_CONTINUE_EXECUTION;
}
void run()
// Create a amcp parser for console commands.
//protocol::amcp::AMCPProtocolStrategy amcp(caspar_server.channels());
- auto amcp = spl::make_shared<caspar::IO::delimiter_based_chunking_strategy_factory<wchar_t>>(L"\r\n", spl::make_shared<caspar::IO::legacy_strategy_adapter_factory>(spl::make_shared<protocol::amcp::AMCPProtocolStrategy>(caspar_server.channels())))->create(console_client);
+ auto amcp = spl::make_shared<caspar::IO::delimiter_based_chunking_strategy_factory<wchar_t>>(L"\r\n", spl::make_shared<caspar::IO::legacy_strategy_adapter_factory>(spl::make_shared<protocol::amcp::AMCPProtocolStrategy>(caspar_server.channels(), caspar_server.get_thumbnail_generator())))->create(console_client);
std::wstring wcmd;
while(true)
#include <common/except.h>
#include <common/utf.h>
#include <common/memory.h>
+#include <common/polling_filesystem_monitor.h>
#include <core/video_channel.h>
#include <core/video_format.h>
#include <core/producer/scene/xml_scene_producer.h>
#include <core/producer/text/text_producer.h>
#include <core/consumer/output.h>
+#include <core/thumbnail_generator.h>
#include <modules/bluefish/bluefish.h>
#include <modules/decklink/decklink.h>
#include <modules/oal/oal.h>
#include <modules/screen/screen.h>
#include <modules/image/image.h>
+#include <modules/image/consumer/image_consumer.h>
#include <modules/psd/psd_scene_producer.h>
#include <modules/oal/consumer/oal_consumer.h>
accelerator::accelerator accelerator_;
std::vector<spl::shared_ptr<IO::AsyncEventServer>> async_servers_;
std::vector<spl::shared_ptr<video_channel>> channels_;
+ std::shared_ptr<thumbnail_generator> thumbnail_generator_;
impl()
: accelerator_(env::properties().get(L"configuration.accelerator", L"auto"))
setup_channels(env::properties());
CASPAR_LOG(info) << L"Initialized channels.";
+ setup_thumbnail_generation(env::properties());
+ CASPAR_LOG(info) << L"Initialized thumbnail generator.";
+
setup_controllers(env::properties());
CASPAR_LOG(info) << L"Initialized controllers.";
}
if(env::properties().get(L"configuration.channel-grid", false))
channels_.push_back(spl::make_shared<video_channel>(static_cast<int>(channels_.size()+1), core::video_format_desc(core::video_format::x576p2500), accelerator_.create_image_mixer()));
}
+
+ void setup_thumbnail_generation(const boost::property_tree::wptree& pt)
+ {
+ if (!pt.get(L"configuration.thumbnails.generate-thumbnails", true))
+ return;
+
+ auto scan_interval_millis = pt.get(L"configuration.thumbnails.scan-interval-millis", 5000);
+
+ polling_filesystem_monitor_factory monitor_factory(scan_interval_millis);
+ thumbnail_generator_.reset(new thumbnail_generator(
+ monitor_factory,
+ env::media_folder(),
+ env::thumbnails_folder(),
+ pt.get(L"configuration.thumbnails.width", 256),
+ pt.get(L"configuration.thumbnails.height", 144),
+ core::video_format_desc(pt.get(L"configuration.thumbnails.video-mode", L"720p2500")),
+ accelerator_.create_image_mixer(),
+ pt.get(L"configuration.thumbnails.generate-delay-millis", 2000),
+ &image::write_cropped_png));
+
+ CASPAR_LOG(info) << L"Initialized thumbnail generator.";
+ }
void setup_controllers(const boost::property_tree::wptree& pt)
{
using namespace IO;
if(boost::iequals(name, L"AMCP"))
- return wrap_legacy_protocol("\r\n", spl::make_shared<amcp::AMCPProtocolStrategy>(channels_));
+ return wrap_legacy_protocol("\r\n", spl::make_shared<amcp::AMCPProtocolStrategy>(channels_, thumbnail_generator_));
else if(boost::iequals(name, L"CII"))
return wrap_legacy_protocol("\r\n", spl::make_shared<cii::CIIProtocolStrategy>(channels_));
else if(boost::iequals(name, L"CLOCK"))
{
return impl_->channels_;
}
+std::shared_ptr<core::thumbnail_generator> server::get_thumbnail_generator() const {return impl_->thumbnail_generator_; }
void server::subscribe(const monitor::observable::observer_ptr& o){impl_->event_subject_.subscribe(o);}
void server::unsubscribe(const monitor::observable::observer_ptr& o){impl_->event_subject_.unsubscribe(o);}
namespace core {
class video_channel;
+ class thumbnail_generator;
}
class server sealed : public monitor::observable
public:
server();
const std::vector<spl::shared_ptr<core::video_channel>> channels() const;
-
+ std::shared_ptr<core::thumbnail_generator> get_thumbnail_generator() const;
// monitor::observable
void subscribe(const monitor::observable::observer_ptr& o) override;