2 * Copyright 2013 Sveriges Television AB http://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 "mixer/audio/audio_util.h"
\r
40 #include "video_format.h"
\r
41 #include "producer/frame/basic_frame.h"
\r
42 #include "producer/frame/frame_transform.h"
\r
44 namespace caspar { namespace core {
\r
46 std::wstring get_relative_without_extension(
\r
47 const boost::filesystem::wpath& file,
\r
48 const boost::filesystem::wpath& relative_to)
\r
50 auto result = file.stem();
\r
52 boost::filesystem::wpath current_path = file;
\r
56 current_path = current_path.parent_path();
\r
58 if (boost::filesystem::equivalent(current_path, relative_to))
\r
61 if (current_path.empty())
\r
62 throw std::runtime_error("File not relative to folder");
\r
64 result = current_path.filename() + L"/" + result;
\r
70 struct thumbnail_output : public mixer::target_t
\r
72 tbb::atomic<int> sleep_millis;
\r
73 std::function<void (const safe_ptr<read_frame>& frame)> on_send;
\r
75 thumbnail_output(int sleep_millis)
\r
77 this->sleep_millis = sleep_millis;
\r
80 void send(const std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>& frame_and_ticket)
\r
82 int current_sleep = sleep_millis;
\r
84 if (current_sleep > 0)
\r
85 boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
\r
87 on_send(frame_and_ticket.first);
\r
92 struct thumbnail_generator::implementation
\r
95 boost::filesystem::wpath media_path_;
\r
96 boost::filesystem::wpath thumbnails_path_;
\r
99 safe_ptr<ogl_device> ogl_;
\r
100 safe_ptr<diagnostics::graph> graph_;
\r
101 video_format_desc format_desc_;
\r
102 safe_ptr<thumbnail_output> output_;
\r
103 safe_ptr<mixer> mixer_;
\r
104 thumbnail_creator thumbnail_creator_;
\r
105 filesystem_monitor::ptr monitor_;
\r
108 filesystem_monitor_factory& monitor_factory,
\r
109 const boost::filesystem::wpath& media_path,
\r
110 const boost::filesystem::wpath& thumbnails_path,
\r
113 const video_format_desc& render_video_mode,
\r
114 const safe_ptr<ogl_device>& ogl,
\r
115 int generate_delay_millis,
\r
116 const thumbnail_creator& thumbnail_creator)
\r
117 : media_path_(media_path)
\r
118 , thumbnails_path_(thumbnails_path)
\r
122 , format_desc_(render_video_mode)
\r
123 , output_(new thumbnail_output(generate_delay_millis))
\r
124 , mixer_(new mixer(
\r
129 channel_layout::stereo()))
\r
130 , thumbnail_creator_(thumbnail_creator)
\r
131 , monitor_(monitor_factory.create(
\r
135 [this] (filesystem_event event, const boost::filesystem::wpath& file)
\r
137 this->on_file_event(event, file);
\r
139 [this] (const std::set<boost::filesystem::wpath>& initial_files)
\r
141 this->on_initial_files(initial_files);
\r
144 graph_->set_text(L"thumbnail-channel");
\r
145 graph_->auto_reset();
\r
146 diagnostics::register_graph(graph_);
\r
147 //monitor_->initial_scan_completion().get();
\r
148 //output_->sleep_millis = 2000;
\r
151 void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
\r
153 using namespace boost::filesystem;
\r
155 std::set<std::wstring> relative_without_extensions;
\r
158 std::insert_iterator<std::set<std::wstring>>(
\r
159 relative_without_extensions,
\r
160 relative_without_extensions.end()),
\r
161 boost::bind(&get_relative_without_extension, _1, media_path_));
\r
163 for (wrecursive_directory_iterator iter(thumbnails_path_); iter != wrecursive_directory_iterator(); ++iter)
\r
165 auto& path = iter->path();
\r
167 if (!is_regular_file(path))
\r
170 auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
\r
171 bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension)
\r
172 == relative_without_extensions.end();
\r
174 if (no_corresponding_media_file)
\r
175 remove(thumbnails_path_ / (relative_without_extension + L".png"));
\r
179 void generate(const std::wstring& media_file)
\r
181 using namespace boost::filesystem;
\r
182 auto base_file = media_path_ / media_file;
\r
183 auto folder = base_file.parent_path();
\r
185 for (wdirectory_iterator iter(folder); iter != wdirectory_iterator(); ++iter)
\r
187 auto stem = iter->path().stem();
\r
189 if (boost::iequals(stem, base_file.filename()))
\r
190 monitor_->reemmit(iter->path());
\r
194 void generate_all()
\r
196 monitor_->reemmit_all();
\r
199 void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
\r
204 if (needs_to_be_generated(file))
\r
205 generate_thumbnail(file);
\r
209 generate_thumbnail(file);
\r
213 auto relative_without_extension = get_relative_without_extension(file, media_path_);
\r
214 boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
\r
220 bool needs_to_be_generated(const boost::filesystem::wpath& file)
\r
222 using namespace boost::filesystem;
\r
224 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
\r
226 if (!exists(png_file))
\r
229 std::time_t media_file_mtime;
\r
233 media_file_mtime = last_write_time(file);
\r
237 // Probably removed.
\r
243 return media_file_mtime != last_write_time(png_file);
\r
247 // thumbnail probably removed.
\r
252 void generate_thumbnail(const boost::filesystem::wpath& file)
\r
254 auto media_file = get_relative_without_extension(file, media_path_);
\r
255 auto png_file = thumbnails_path_ / (media_file + L".png");
\r
256 boost::promise<void> thumbnail_ready;
\r
259 auto producer = frame_producer::empty();
\r
263 producer = create_thumbnail_producer(mixer_, media_file);
\r
265 catch (const boost::thread_interrupted&)
\r
271 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
\r
275 if (producer == frame_producer::empty())
\r
277 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
\r
281 boost::filesystem::create_directories(png_file.parent_path());
\r
282 output_->on_send = [this, &png_file] (const safe_ptr<read_frame>& frame)
\r
284 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
\r
287 std::map<int, safe_ptr<basic_frame>> frames;
\r
288 auto raw_frame = basic_frame::empty();
\r
292 raw_frame = producer->create_thumbnail_frame();
\r
294 catch (const boost::thread_interrupted&)
\r
300 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
\r
304 if (raw_frame == basic_frame::empty()
\r
305 || raw_frame == basic_frame::empty()
\r
306 || raw_frame == basic_frame::eof()
\r
307 || raw_frame == basic_frame::late())
\r
309 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
\r
313 auto transformed_frame = make_safe<basic_frame>(raw_frame);
\r
314 transformed_frame->get_frame_transform().fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
\r
315 transformed_frame->get_frame_transform().fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
\r
316 frames.insert(std::make_pair(0, transformed_frame));
\r
318 std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*)
\r
320 thumbnail_ready.set_value();
\r
323 mixer_->send(std::make_pair(frames, ticket));
\r
326 thumbnail_ready.get_future().get();
\r
328 if (boost::filesystem::exists(png_file))
\r
330 // Adjust timestamp to match source file.
\r
333 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
\r
334 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
\r
338 // One of the files was removed before the call to last_write_time.
\r
342 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
\r
346 thumbnail_generator::thumbnail_generator(
\r
347 filesystem_monitor_factory& monitor_factory,
\r
348 const boost::filesystem::wpath& media_path,
\r
349 const boost::filesystem::wpath& thumbnails_path,
\r
352 const video_format_desc& render_video_mode,
\r
353 const safe_ptr<ogl_device>& ogl,
\r
354 int generate_delay_millis,
\r
355 const thumbnail_creator& thumbnail_creator)
\r
356 : impl_(new implementation(
\r
363 generate_delay_millis,
\r
364 thumbnail_creator))
\r
368 thumbnail_generator::~thumbnail_generator()
\r
372 void thumbnail_generator::generate(const std::wstring& media_file)
\r
374 impl_->generate(media_file);
\r
377 void thumbnail_generator::generate_all()
\r
379 impl_->generate_all();
\r