2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Helge Norberg, helge.norberg@svt.se
24 #include "thumbnail_generator.h"
32 #include <boost/algorithm/string/predicate.hpp>
33 #include <boost/filesystem.hpp>
34 #include <boost/thread.hpp>
36 #include <tbb/atomic.h>
38 #include <common/diagnostics/graph.h>
39 #include <common/filesystem.h>
41 #include "producer/frame_producer.h"
42 #include "producer/cg_proxy.h"
43 #include "consumer/frame_consumer.h"
44 #include "mixer/mixer.h"
45 #include "mixer/image/image_mixer.h"
46 #include "video_format.h"
47 #include "frame/frame.h"
48 #include "frame/draw_frame.h"
49 #include "frame/frame_transform.h"
50 #include "frame/audio_channel_layout.h"
51 #include "producer/media_info/media_info.h"
52 #include "producer/media_info/media_info_repository.h"
54 namespace caspar { namespace core {
56 struct thumbnail_output
58 tbb::atomic<int> sleep_millis;
59 std::function<void (const_frame)> on_send;
61 thumbnail_output(int sleep_millis)
63 this->sleep_millis = sleep_millis;
66 void send(const_frame frame, std::shared_ptr<void> frame_and_ticket)
68 int current_sleep = sleep_millis;
70 if (current_sleep > 0)
71 std::this_thread::sleep_for(std::chrono::milliseconds(current_sleep));
73 on_send(std::move(frame));
78 struct thumbnail_generator::impl
81 boost::filesystem::path media_path_;
82 boost::filesystem::path thumbnails_path_;
85 spl::shared_ptr<image_mixer> image_mixer_;
86 spl::shared_ptr<diagnostics::graph> graph_;
87 video_format_desc format_desc_;
88 spl::unique_ptr<thumbnail_output> output_;
90 thumbnail_creator thumbnail_creator_;
91 spl::shared_ptr<media_info_repository> media_info_repo_;
92 spl::shared_ptr<const frame_producer_registry> producer_registry_;
93 spl::shared_ptr<const cg_producer_registry> cg_registry_;
95 filesystem_monitor::ptr monitor_;
98 filesystem_monitor_factory& monitor_factory,
99 const boost::filesystem::path& media_path,
100 const boost::filesystem::path& thumbnails_path,
103 const video_format_desc& render_video_mode,
104 std::unique_ptr<image_mixer> image_mixer,
105 int generate_delay_millis,
106 const thumbnail_creator& thumbnail_creator,
107 spl::shared_ptr<media_info_repository> media_info_repo,
108 spl::shared_ptr<const frame_producer_registry> producer_registry,
109 spl::shared_ptr<const cg_producer_registry> cg_registry,
111 : media_path_(media_path)
112 , thumbnails_path_(thumbnails_path)
115 , image_mixer_(std::move(image_mixer))
116 , format_desc_(render_video_mode)
117 , output_(spl::make_unique<thumbnail_output>(generate_delay_millis))
118 , mixer_(0, graph_, image_mixer_)
119 , thumbnail_creator_(thumbnail_creator)
120 , media_info_repo_(std::move(media_info_repo))
121 , producer_registry_(std::move(producer_registry))
122 , cg_registry_(std::move(cg_registry))
124 , monitor_(monitor_factory.create(
126 filesystem_event::ALL,
128 [this] (filesystem_event event, const boost::filesystem::path& file)
130 this->on_file_event(event, file);
132 [this] (const std::set<boost::filesystem::path>& initial_files)
134 this->on_initial_files(initial_files);
137 graph_->set_text(L"thumbnail-channel");
138 graph_->auto_reset();
139 diagnostics::register_graph(graph_);
140 //monitor_->initial_scan_completion().get();
141 //output_->sleep_millis = 2000;
144 void on_initial_files(const std::set<boost::filesystem::path>& initial_files)
146 using namespace boost::filesystem;
148 std::set<std::wstring> relative_without_extensions;
151 std::insert_iterator<std::set<std::wstring>>(
152 relative_without_extensions,
153 relative_without_extensions.end()),
154 [&](const path& p) { return get_relative_without_extension(p, media_path_).wstring(); });
156 for (boost::filesystem::wrecursive_directory_iterator iter(thumbnails_path_); iter != boost::filesystem::wrecursive_directory_iterator(); ++iter)
158 auto& path = iter->path();
160 if (!is_regular_file(path))
163 auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
164 bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension.wstring())
165 == relative_without_extensions.end();
167 if (no_corresponding_media_file)
168 remove(thumbnails_path_ / (relative_without_extension.wstring() + L".png"));
172 void generate(const std::wstring& media_file)
174 using namespace boost::filesystem;
176 auto base_file = media_path_ / media_file;
177 auto folder = base_file.parent_path();
180 for (boost::filesystem::directory_iterator iter(folder); iter != boost::filesystem::directory_iterator(); ++iter)
182 auto stem = iter->path().stem();
184 if (boost::iequals(stem.wstring(), base_file.filename().wstring()))
186 monitor_->reemmit(iter->path());
192 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Media file " + media_file + L" not found"));
197 monitor_->reemmit_all();
200 void on_file_event(filesystem_event event, const boost::filesystem::path& file)
204 case filesystem_event::CREATED:
205 if (needs_to_be_generated(file))
206 generate_thumbnail(file);
209 case filesystem_event::MODIFIED:
210 generate_thumbnail(file);
213 case filesystem_event::REMOVED:
214 auto relative_without_extension = get_relative_without_extension(file, media_path_);
215 boost::filesystem::remove(thumbnails_path_ / (relative_without_extension.wstring() + L".png"));
216 media_info_repo_->remove(file.wstring());
222 bool needs_to_be_generated(const boost::filesystem::path& file)
224 using namespace boost::filesystem;
226 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_).wstring() + L".png");
228 if (!exists(png_file))
231 std::time_t media_file_mtime;
235 media_file_mtime = last_write_time(file);
245 return media_file_mtime != last_write_time(png_file);
249 // thumbnail probably removed.
254 void generate_thumbnail(const boost::filesystem::path& file)
256 auto media_file_with_extension = get_relative(file, media_path_);
257 auto media_file = get_relative_without_extension(file, media_path_);
258 auto png_file = thumbnails_path_ / (media_file.wstring() + L".png");
259 std::promise<void> thumbnail_ready;
262 boost::filesystem::create_directories(png_file.parent_path());
263 output_->on_send = [this, &png_file] (const_frame frame)
265 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
268 std::map<int, draw_frame> frames;
269 auto raw_frame = draw_frame::empty();
273 raw_frame = producer_registry_->create_thumbnail(frame_producer_dependencies(image_mixer_, {}, format_desc_, producer_registry_, cg_registry_), media_file.wstring());
274 media_info_repo_->remove(file.wstring());
275 media_info_repo_->get(file.wstring());
277 catch (const boost::thread_interrupted&)
283 CASPAR_LOG_CURRENT_EXCEPTION_AT_LEVEL(trace);
284 CASPAR_LOG(info) << L"Thumbnail producer failed to create thumbnail for " << media_file_with_extension << L". Turn on log level trace to see more information.";
288 if (raw_frame == draw_frame::empty()
289 || raw_frame == draw_frame::late())
291 CASPAR_LOG(debug) << L"No thumbnail producer for " << media_file_with_extension;
295 auto transformed_frame = draw_frame(raw_frame);
296 transformed_frame.transform().image_transform.fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
297 transformed_frame.transform().image_transform.fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
298 transformed_frame.transform().image_transform.use_mipmap = mipmap_;
299 frames.insert(std::make_pair(0, transformed_frame));
301 std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*) { thumbnail_ready.set_value(); });
303 auto mixed_frame = mixer_(std::move(frames), format_desc_, audio_channel_layout(2, L"stereo", L""));
305 output_->send(std::move(mixed_frame), ticket);
308 thumbnail_ready.get_future().get();
310 if (boost::filesystem::exists(png_file))
312 // Adjust timestamp to match source file.
315 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
316 CASPAR_LOG(info) << L"Generated thumbnail for " << media_file_with_extension;
320 // One of the files was removed before the call to last_write_time.
324 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file_with_extension;
328 thumbnail_generator::thumbnail_generator(
329 filesystem_monitor_factory& monitor_factory,
330 const boost::filesystem::path& media_path,
331 const boost::filesystem::path& thumbnails_path,
334 const video_format_desc& render_video_mode,
335 std::unique_ptr<image_mixer> image_mixer,
336 int generate_delay_millis,
337 const thumbnail_creator& thumbnail_creator,
338 spl::shared_ptr<media_info_repository> media_info_repo,
339 spl::shared_ptr<const frame_producer_registry> producer_registry,
340 spl::shared_ptr<const cg_producer_registry> cg_registry,
348 std::move(image_mixer),
349 generate_delay_millis,
358 thumbnail_generator::~thumbnail_generator()
362 void thumbnail_generator::generate(const std::wstring& media_file)
364 impl_->generate(media_file);
367 void thumbnail_generator::generate_all()
369 impl_->generate_all();