2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG (www.casparcg.com).
\r
6 * CasparCG is free software: you can redistribute it and/or modify
\r
7 * it under the terms of the GNU General Public License as published by
\r
8 * the Free Software Foundation, either version 3 of the License, or
\r
9 * (at your option) any later version.
\r
11 * CasparCG is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU General Public License for more details.
\r
16 * You should have received a copy of the GNU General Public License
\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
\r
19 * Author: Helge Norberg, helge.norberg@svt.se
\r
24 #include "thumbnail_generator.h"
\r
30 #include <boost/thread.hpp>
\r
31 #include <boost/range/algorithm/transform.hpp>
\r
32 #include <boost/algorithm/string/predicate.hpp>
\r
34 #include <tbb/atomic.h>
\r
36 #include "producer/frame_producer.h"
\r
37 #include "consumer/frame_consumer.h"
\r
38 #include "mixer/mixer.h"
\r
39 #include "video_format.h"
\r
40 #include "producer/frame/basic_frame.h"
\r
41 #include "producer/frame/frame_transform.h"
\r
43 namespace caspar { namespace core {
\r
45 std::wstring get_relative_without_extension(
\r
46 const boost::filesystem::wpath& file,
\r
47 const boost::filesystem::wpath& relative_to)
\r
49 auto result = file.stem();
\r
51 boost::filesystem::wpath current_path = file;
\r
55 current_path = current_path.parent_path();
\r
57 if (boost::filesystem::equivalent(current_path, relative_to))
\r
60 if (current_path.empty())
\r
61 throw std::runtime_error("File not relative to folder");
\r
63 result = current_path.filename() + L"/" + result;
\r
69 struct thumbnail_output : public mixer::target_t
\r
71 tbb::atomic<int> sleep_millis;
\r
72 std::function<void (const safe_ptr<read_frame>& frame)> on_send;
\r
74 thumbnail_output(int sleep_millis)
\r
76 this->sleep_millis = sleep_millis;
\r
79 void send(const std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>& frame_and_ticket)
\r
81 int current_sleep = sleep_millis;
\r
83 if (current_sleep > 0)
\r
84 boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
\r
86 on_send(frame_and_ticket.first);
\r
91 struct thumbnail_generator::implementation
\r
94 boost::filesystem::wpath media_path_;
\r
95 boost::filesystem::wpath thumbnails_path_;
\r
98 safe_ptr<ogl_device> ogl_;
\r
99 safe_ptr<diagnostics::graph> graph_;
\r
100 video_format_desc format_desc_;
\r
101 safe_ptr<thumbnail_output> output_;
\r
102 safe_ptr<mixer> mixer_;
\r
103 thumbnail_creator thumbnail_creator_;
\r
104 filesystem_monitor::ptr monitor_;
\r
107 filesystem_monitor_factory& monitor_factory,
\r
108 const boost::filesystem::wpath& media_path,
\r
109 const boost::filesystem::wpath& thumbnails_path,
\r
112 const video_format_desc& render_video_mode,
\r
113 const safe_ptr<ogl_device>& ogl,
\r
114 int generate_delay_millis,
\r
115 const thumbnail_creator& thumbnail_creator)
\r
116 : media_path_(media_path)
\r
117 , thumbnails_path_(thumbnails_path)
\r
121 , format_desc_(render_video_mode)
\r
122 , output_(new thumbnail_output(generate_delay_millis))
\r
123 , mixer_(new mixer(graph_, output_, format_desc_, ogl))
\r
124 , thumbnail_creator_(thumbnail_creator)
\r
125 , monitor_(monitor_factory.create(
\r
129 [this] (filesystem_event event, const boost::filesystem::wpath& file)
\r
131 this->on_file_event(event, file);
\r
133 [this] (const std::set<boost::filesystem::wpath>& initial_files)
\r
135 this->on_initial_files(initial_files);
\r
138 graph_->set_text(L"thumbnail-channel");
\r
139 graph_->auto_reset();
\r
140 diagnostics::register_graph(graph_);
\r
141 //monitor_->initial_scan_completion().get();
\r
142 //output_->sleep_millis = 2000;
\r
145 void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
\r
147 using namespace boost::filesystem;
\r
149 std::set<std::wstring> relative_without_extensions;
\r
152 std::insert_iterator<std::set<std::wstring>>(
\r
153 relative_without_extensions,
\r
154 relative_without_extensions.end()),
\r
155 boost::bind(&get_relative_without_extension, _1, media_path_));
\r
157 for (wrecursive_directory_iterator iter(thumbnails_path_); iter != wrecursive_directory_iterator(); ++iter)
\r
159 auto& path = iter->path();
\r
161 if (!is_regular_file(path))
\r
164 auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
\r
165 bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension)
\r
166 == relative_without_extensions.end();
\r
168 if (no_corresponding_media_file)
\r
169 remove(thumbnails_path_ / (relative_without_extension + L".png"));
\r
173 void generate(const std::wstring& media_file)
\r
175 using namespace boost::filesystem;
\r
176 auto base_file = media_path_ / media_file;
\r
177 auto folder = base_file.parent_path();
\r
179 for (wdirectory_iterator iter(folder); iter != wdirectory_iterator(); ++iter)
\r
181 auto stem = iter->path().stem();
\r
183 if (boost::iequals(stem, base_file.filename()))
\r
184 monitor_->reemmit(iter->path());
\r
188 void generate_all()
\r
190 monitor_->reemmit_all();
\r
193 void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
\r
198 if (needs_to_be_generated(file))
\r
199 generate_thumbnail(file);
\r
203 generate_thumbnail(file);
\r
207 auto relative_without_extension = get_relative_without_extension(file, media_path_);
\r
208 boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
\r
214 bool needs_to_be_generated(const boost::filesystem::wpath& file)
\r
216 using namespace boost::filesystem;
\r
218 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
\r
220 if (!exists(png_file))
\r
223 std::time_t media_file_mtime;
\r
227 media_file_mtime = last_write_time(file);
\r
231 // Probably removed.
\r
237 return media_file_mtime != last_write_time(png_file);
\r
241 // thumbnail probably removed.
\r
246 void generate_thumbnail(const boost::filesystem::wpath& file)
\r
248 auto media_file = get_relative_without_extension(file, media_path_);
\r
249 auto png_file = thumbnails_path_ / (media_file + L".png");
\r
250 boost::promise<void> thumbnail_ready;
\r
253 auto producer = frame_producer::empty();
\r
257 producer = create_thumbnail_producer(mixer_, media_file);
\r
259 catch (const boost::thread_interrupted&)
\r
265 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
\r
269 if (producer == frame_producer::empty())
\r
271 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
\r
275 boost::filesystem::create_directories(png_file.parent_path());
\r
276 output_->on_send = [this, &png_file] (const safe_ptr<read_frame>& frame)
\r
278 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
\r
281 std::map<int, safe_ptr<basic_frame>> frames;
\r
282 auto raw_frame = basic_frame::empty();
\r
286 raw_frame = producer->create_thumbnail_frame();
\r
288 catch (const boost::thread_interrupted&)
\r
294 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
\r
298 if (raw_frame == basic_frame::empty()
\r
299 || raw_frame == basic_frame::empty()
\r
300 || raw_frame == basic_frame::eof()
\r
301 || raw_frame == basic_frame::late())
\r
303 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
\r
307 auto transformed_frame = make_safe<basic_frame>(raw_frame);
\r
308 transformed_frame->get_frame_transform().fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
\r
309 transformed_frame->get_frame_transform().fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
\r
310 frames.insert(std::make_pair(0, transformed_frame));
\r
312 std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*)
\r
314 thumbnail_ready.set_value();
\r
317 mixer_->send(std::make_pair(frames, ticket));
\r
320 thumbnail_ready.get_future().get();
\r
322 if (boost::filesystem::exists(png_file))
\r
324 // Adjust timestamp to match source file.
\r
327 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
\r
328 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
\r
332 // One of the files was removed before the call to last_write_time.
\r
336 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
\r
340 thumbnail_generator::thumbnail_generator(
\r
341 filesystem_monitor_factory& monitor_factory,
\r
342 const boost::filesystem::wpath& media_path,
\r
343 const boost::filesystem::wpath& thumbnails_path,
\r
346 const video_format_desc& render_video_mode,
\r
347 const safe_ptr<ogl_device>& ogl,
\r
348 int generate_delay_millis,
\r
349 const thumbnail_creator& thumbnail_creator)
\r
350 : impl_(new implementation(
\r
357 generate_delay_millis,
\r
358 thumbnail_creator))
\r
362 thumbnail_generator::~thumbnail_generator()
\r
366 void thumbnail_generator::generate(const std::wstring& media_file)
\r
368 impl_->generate(media_file);
\r
371 void thumbnail_generator::generate_all()
\r
373 impl_->generate_all();
\r