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"
31 #include <boost/thread.hpp>
32 #include <boost/range/algorithm/transform.hpp>
33 #include <boost/algorithm/string/predicate.hpp>
34 #include <boost/filesystem.hpp>
36 #include <tbb/atomic.h>
38 #include <common/diagnostics/graph.h>
40 #include "producer/frame_producer.h"
41 #include "consumer/frame_consumer.h"
42 #include "mixer/mixer.h"
43 #include "mixer/image/image_mixer.h"
44 #include "video_format.h"
45 #include "frame/frame.h"
46 #include "frame/draw_frame.h"
47 #include "frame/frame_transform.h"
49 namespace caspar { namespace core {
51 std::wstring get_relative_without_extension(
52 const boost::filesystem::wpath& file,
53 const boost::filesystem::wpath& relative_to)
55 auto result = file.stem();
57 boost::filesystem::wpath current_path = file;
61 current_path = current_path.parent_path();
63 if (boost::filesystem::equivalent(current_path, relative_to))
66 if (current_path.empty())
67 throw std::runtime_error("File not relative to folder");
69 result = current_path.filename() / result;
72 return result.wstring();
75 struct thumbnail_output
77 tbb::atomic<int> sleep_millis;
78 std::function<void (const_frame)> on_send;
80 thumbnail_output(int sleep_millis)
82 this->sleep_millis = sleep_millis;
85 void send(const_frame frame, std::shared_ptr<void> frame_and_ticket)
87 int current_sleep = sleep_millis;
89 if (current_sleep > 0)
90 boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
92 on_send(std::move(frame));
97 struct thumbnail_generator::impl
100 boost::filesystem::wpath media_path_;
101 boost::filesystem::wpath thumbnails_path_;
104 spl::shared_ptr<image_mixer> image_mixer_;
105 spl::shared_ptr<diagnostics::graph> graph_;
106 video_format_desc format_desc_;
107 spl::unique_ptr<thumbnail_output> output_;
109 thumbnail_creator thumbnail_creator_;
110 filesystem_monitor::ptr monitor_;
113 filesystem_monitor_factory& monitor_factory,
114 const boost::filesystem::wpath& media_path,
115 const boost::filesystem::wpath& thumbnails_path,
118 const video_format_desc& render_video_mode,
119 std::unique_ptr<image_mixer> image_mixer,
120 int generate_delay_millis,
121 const thumbnail_creator& thumbnail_creator)
122 : media_path_(media_path)
123 , thumbnails_path_(thumbnails_path)
126 , image_mixer_(std::move(image_mixer))
127 , format_desc_(render_video_mode)
128 , output_(spl::make_unique<thumbnail_output>(generate_delay_millis))
129 , mixer_(graph_, image_mixer_)
130 , thumbnail_creator_(thumbnail_creator)
131 , monitor_(monitor_factory.create(
133 filesystem_event::ALL,
135 [this] (filesystem_event event, const boost::filesystem::wpath& file)
137 this->on_file_event(event, file);
139 [this] (const std::set<boost::filesystem::wpath>& initial_files)
141 this->on_initial_files(initial_files);
144 graph_->set_text(L"thumbnail-channel");
145 graph_->auto_reset();
146 diagnostics::register_graph(graph_);
147 //monitor_->initial_scan_completion().get();
148 //output_->sleep_millis = 2000;
151 void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
153 using namespace boost::filesystem;
155 std::set<std::wstring> relative_without_extensions;
158 std::insert_iterator<std::set<std::wstring>>(
159 relative_without_extensions,
160 relative_without_extensions.end()),
161 boost::bind(&get_relative_without_extension, _1, media_path_));
163 for (boost::filesystem::wrecursive_directory_iterator iter(thumbnails_path_); iter != boost::filesystem::wrecursive_directory_iterator(); ++iter)
165 auto& path = iter->path();
167 if (!is_regular_file(path))
170 auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
171 bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension)
172 == relative_without_extensions.end();
174 if (no_corresponding_media_file)
175 remove(thumbnails_path_ / (relative_without_extension + L".png"));
179 void generate(const std::wstring& media_file)
181 using namespace boost::filesystem;
182 auto base_file = media_path_ / media_file;
183 auto folder = base_file.parent_path();
185 for (boost::filesystem::directory_iterator iter(folder); iter != boost::filesystem::directory_iterator(); ++iter)
187 auto stem = iter->path().stem();
189 if (boost::iequals(stem.wstring(), base_file.filename().wstring()))
190 monitor_->reemmit(iter->path());
196 monitor_->reemmit_all();
199 void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
203 case filesystem_event::CREATED:
204 if (needs_to_be_generated(file))
205 generate_thumbnail(file);
208 case filesystem_event::MODIFIED:
209 generate_thumbnail(file);
212 case filesystem_event::REMOVED:
213 auto relative_without_extension = get_relative_without_extension(file, media_path_);
214 boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
220 bool needs_to_be_generated(const boost::filesystem::wpath& file)
222 using namespace boost::filesystem;
224 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
226 if (!exists(png_file))
229 std::time_t media_file_mtime;
233 media_file_mtime = last_write_time(file);
243 return media_file_mtime != last_write_time(png_file);
247 // thumbnail probably removed.
252 void generate_thumbnail(const boost::filesystem::wpath& file)
254 auto media_file = get_relative_without_extension(file, media_path_);
255 auto png_file = thumbnails_path_ / (media_file + L".png");
256 std::promise<void> thumbnail_ready;
259 auto producer = frame_producer::empty();
263 producer = create_thumbnail_producer(image_mixer_, format_desc_, media_file);
265 catch (const boost::thread_interrupted&)
271 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
275 if (producer == frame_producer::empty())
277 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
281 boost::filesystem::create_directories(png_file.parent_path());
282 output_->on_send = [this, &png_file] (const_frame frame)
284 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
287 std::map<int, draw_frame> frames;
288 auto raw_frame = draw_frame::empty();
292 raw_frame = producer->create_thumbnail_frame();
294 catch (const boost::thread_interrupted&)
300 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
304 if (raw_frame == draw_frame::empty()
305 || raw_frame == draw_frame::late())
307 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
311 auto transformed_frame = draw_frame(raw_frame);
312 transformed_frame.transform().image_transform.fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
313 transformed_frame.transform().image_transform.fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
314 frames.insert(std::make_pair(0, transformed_frame));
316 std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*) { thumbnail_ready.set_value(); });
318 auto mixed_frame = mixer_(frames, format_desc_);
320 output_->send(std::move(mixed_frame), ticket);
323 thumbnail_ready.get_future().get();
325 if (boost::filesystem::exists(png_file))
327 // Adjust timestamp to match source file.
330 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
331 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
335 // One of the files was removed before the call to last_write_time.
339 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
343 thumbnail_generator::thumbnail_generator(
344 filesystem_monitor_factory& monitor_factory,
345 const boost::filesystem::wpath& media_path,
346 const boost::filesystem::wpath& thumbnails_path,
349 const video_format_desc& render_video_mode,
350 std::unique_ptr<image_mixer> image_mixer,
351 int generate_delay_millis,
352 const thumbnail_creator& thumbnail_creator)
359 std::move(image_mixer),
360 generate_delay_millis,
365 thumbnail_generator::~thumbnail_generator()
369 void thumbnail_generator::generate(const std::wstring& media_file)
371 impl_->generate(media_file);
374 void thumbnail_generator::generate_all()
376 impl_->generate_all();