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
43 #include "producer/media_info/media_info.h"
\r
44 #include "producer/media_info/media_info_repository.h"
\r
46 namespace caspar { namespace core {
\r
48 std::wstring get_relative_without_extension(
\r
49 const boost::filesystem::wpath& file,
\r
50 const boost::filesystem::wpath& relative_to)
\r
52 auto result = file.stem();
\r
54 boost::filesystem::wpath current_path = file;
\r
58 current_path = current_path.parent_path();
\r
60 if (boost::filesystem::equivalent(current_path, relative_to))
\r
63 if (current_path.empty())
\r
64 throw std::runtime_error("File not relative to folder");
\r
66 result = current_path.filename() + L"/" + result;
\r
72 struct thumbnail_output : public mixer::target_t
\r
74 tbb::atomic<int> sleep_millis;
\r
75 std::function<void (const safe_ptr<read_frame>& frame)> on_send;
\r
77 thumbnail_output(int sleep_millis)
\r
79 this->sleep_millis = sleep_millis;
\r
82 void send(const std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>& frame_and_ticket)
\r
84 int current_sleep = sleep_millis;
\r
86 if (current_sleep > 0)
\r
87 boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
\r
89 on_send(frame_and_ticket.first);
\r
94 struct thumbnail_generator::implementation
\r
97 boost::filesystem::wpath media_path_;
\r
98 boost::filesystem::wpath thumbnails_path_;
\r
101 safe_ptr<ogl_device> ogl_;
\r
102 safe_ptr<diagnostics::graph> graph_;
\r
103 video_format_desc format_desc_;
\r
104 safe_ptr<thumbnail_output> output_;
\r
105 safe_ptr<mixer> mixer_;
\r
106 thumbnail_creator thumbnail_creator_;
\r
107 safe_ptr<media_info_repository> media_info_repo_;
\r
108 filesystem_monitor::ptr monitor_;
\r
111 filesystem_monitor_factory& monitor_factory,
\r
112 const boost::filesystem::wpath& media_path,
\r
113 const boost::filesystem::wpath& thumbnails_path,
\r
116 const video_format_desc& render_video_mode,
\r
117 const safe_ptr<ogl_device>& ogl,
\r
118 int generate_delay_millis,
\r
119 const thumbnail_creator& thumbnail_creator,
\r
120 safe_ptr<media_info_repository> media_info_repo)
\r
121 : media_path_(media_path)
\r
122 , thumbnails_path_(thumbnails_path)
\r
126 , format_desc_(render_video_mode)
\r
127 , output_(new thumbnail_output(generate_delay_millis))
\r
128 , mixer_(new mixer(
\r
133 channel_layout::stereo()))
\r
134 , thumbnail_creator_(thumbnail_creator)
\r
135 , media_info_repo_(std::move(media_info_repo))
\r
136 , monitor_(monitor_factory.create(
\r
140 [this] (filesystem_event event, const boost::filesystem::wpath& file)
\r
142 this->on_file_event(event, file);
\r
144 [this] (const std::set<boost::filesystem::wpath>& initial_files)
\r
146 this->on_initial_files(initial_files);
\r
149 graph_->set_text(L"thumbnail-channel");
\r
150 graph_->auto_reset();
\r
151 diagnostics::register_graph(graph_);
\r
152 //monitor_->initial_scan_completion().get();
\r
153 //output_->sleep_millis = 2000;
\r
156 void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
\r
158 using namespace boost::filesystem;
\r
160 std::set<std::wstring> relative_without_extensions;
\r
163 std::insert_iterator<std::set<std::wstring>>(
\r
164 relative_without_extensions,
\r
165 relative_without_extensions.end()),
\r
166 boost::bind(&get_relative_without_extension, _1, media_path_));
\r
168 for (wrecursive_directory_iterator iter(thumbnails_path_); iter != wrecursive_directory_iterator(); ++iter)
\r
170 auto& path = iter->path();
\r
172 if (!is_regular_file(path))
\r
175 auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
\r
176 bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension)
\r
177 == relative_without_extensions.end();
\r
179 if (no_corresponding_media_file)
\r
180 remove(thumbnails_path_ / (relative_without_extension + L".png"));
\r
184 void generate(const std::wstring& media_file)
\r
186 using namespace boost::filesystem;
\r
187 auto base_file = media_path_ / media_file;
\r
188 auto folder = base_file.parent_path();
\r
190 for (wdirectory_iterator iter(folder); iter != wdirectory_iterator(); ++iter)
\r
192 auto stem = iter->path().stem();
\r
194 if (boost::iequals(stem, base_file.filename()))
\r
195 monitor_->reemmit(iter->path());
\r
199 void generate_all()
\r
201 monitor_->reemmit_all();
\r
204 void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
\r
209 if (needs_to_be_generated(file))
\r
210 generate_thumbnail(file);
\r
214 generate_thumbnail(file);
\r
218 auto relative_without_extension = get_relative_without_extension(file, media_path_);
\r
219 boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
\r
220 media_info_repo_->remove(file.file_string());
\r
226 bool needs_to_be_generated(const boost::filesystem::wpath& file)
\r
228 using namespace boost::filesystem;
\r
230 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
\r
232 if (!exists(png_file))
\r
235 std::time_t media_file_mtime;
\r
239 media_file_mtime = last_write_time(file);
\r
243 // Probably removed.
\r
249 return media_file_mtime != last_write_time(png_file);
\r
253 // thumbnail probably removed.
\r
258 void generate_thumbnail(const boost::filesystem::wpath& file)
\r
260 auto media_file = get_relative_without_extension(file, media_path_);
\r
261 auto png_file = thumbnails_path_ / (media_file + L".png");
\r
262 boost::promise<void> thumbnail_ready;
\r
265 auto producer = frame_producer::empty();
\r
269 producer = create_thumbnail_producer(mixer_, media_file);
\r
271 catch (const boost::thread_interrupted&)
\r
277 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
\r
281 if (producer == frame_producer::empty())
\r
283 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
\r
287 boost::filesystem::create_directories(png_file.parent_path());
\r
288 output_->on_send = [this, &png_file] (const safe_ptr<read_frame>& frame)
\r
290 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
\r
293 std::map<int, safe_ptr<basic_frame>> frames;
\r
294 auto raw_frame = basic_frame::empty();
\r
298 raw_frame = producer->create_thumbnail_frame();
\r
299 media_info_repo_->remove(file.file_string());
\r
300 media_info_repo_->get(file.file_string());
\r
302 catch (const boost::thread_interrupted&)
\r
308 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
\r
312 if (raw_frame == basic_frame::empty()
\r
313 || raw_frame == basic_frame::empty()
\r
314 || raw_frame == basic_frame::eof()
\r
315 || raw_frame == basic_frame::late())
\r
317 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
\r
321 auto transformed_frame = make_safe<basic_frame>(raw_frame);
\r
322 transformed_frame->get_frame_transform().fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
\r
323 transformed_frame->get_frame_transform().fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
\r
324 frames.insert(std::make_pair(0, transformed_frame));
\r
326 std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*)
\r
328 thumbnail_ready.set_value();
\r
331 mixer_->send(std::make_pair(frames, ticket));
\r
334 thumbnail_ready.get_future().get();
\r
336 if (boost::filesystem::exists(png_file))
\r
338 // Adjust timestamp to match source file.
\r
341 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
\r
342 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
\r
346 // One of the files was removed before the call to last_write_time.
\r
350 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
\r
354 thumbnail_generator::thumbnail_generator(
\r
355 filesystem_monitor_factory& monitor_factory,
\r
356 const boost::filesystem::wpath& media_path,
\r
357 const boost::filesystem::wpath& thumbnails_path,
\r
360 const video_format_desc& render_video_mode,
\r
361 const safe_ptr<ogl_device>& ogl,
\r
362 int generate_delay_millis,
\r
363 const thumbnail_creator& thumbnail_creator,
\r
364 safe_ptr<media_info_repository> media_info_repo)
\r
365 : impl_(new implementation(
\r
372 generate_delay_millis,
\r
378 thumbnail_generator::~thumbnail_generator()
\r
382 void thumbnail_generator::generate(const std::wstring& media_file)
\r
384 impl_->generate(media_file);
\r
387 void thumbnail_generator::generate_all()
\r
389 impl_->generate_all();
\r