]> git.sesse.net Git - casparcg/blob - core/thumbnail_generator.cpp
* Added RxCpp library for LINQ api, replacing Boost.Range based iterations where...
[casparcg] / core / thumbnail_generator.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Helge Norberg, helge.norberg@svt.se
20 */
21
22 #include "stdafx.h"
23
24 #include "thumbnail_generator.h"
25
26 #include <iostream>
27 #include <iterator>
28 #include <set>
29 #include <future>
30
31 #include <boost/thread.hpp>
32 #include <boost/algorithm/string/predicate.hpp>
33 #include <boost/filesystem.hpp>
34
35 #include <tbb/atomic.h>
36
37 #include <common/diagnostics/graph.h>
38
39 #include "producer/frame_producer.h"
40 #include "consumer/frame_consumer.h"
41 #include "mixer/mixer.h"
42 #include "mixer/image/image_mixer.h"
43 #include "video_format.h"
44 #include "frame/frame.h"
45 #include "frame/draw_frame.h"
46 #include "frame/frame_transform.h"
47
48 namespace caspar { namespace core {
49
50 std::wstring get_relative_without_extension(
51                 const boost::filesystem::wpath& file,
52                 const boost::filesystem::wpath& relative_to)
53 {
54         auto result = file.stem();
55
56         boost::filesystem::wpath current_path = file;
57
58         while (true)
59         {
60                 current_path = current_path.parent_path();
61
62                 if (boost::filesystem::equivalent(current_path, relative_to))
63                         break;
64
65                 if (current_path.empty())
66                         throw std::runtime_error("File not relative to folder");
67
68                 result = current_path.filename() / result;
69         }
70
71         return result.wstring();
72 }
73
74 struct thumbnail_output
75 {
76         tbb::atomic<int> sleep_millis;
77         std::function<void (const_frame)> on_send;
78
79         thumbnail_output(int sleep_millis)
80         {
81                 this->sleep_millis = sleep_millis;
82         }
83
84         void send(const_frame frame, std::shared_ptr<void> frame_and_ticket)
85         {
86                 int current_sleep = sleep_millis;
87
88                 if (current_sleep > 0)
89                         boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
90
91                 on_send(std::move(frame));
92                 on_send = nullptr;
93         }
94 };
95
96 struct thumbnail_generator::impl
97 {
98 private:
99         boost::filesystem::wpath media_path_;
100         boost::filesystem::wpath thumbnails_path_;
101         int width_;
102         int height_;
103         spl::shared_ptr<image_mixer> image_mixer_;
104         spl::shared_ptr<diagnostics::graph> graph_;
105         video_format_desc format_desc_;
106         spl::unique_ptr<thumbnail_output> output_;
107         mixer mixer_;
108         thumbnail_creator thumbnail_creator_;
109         filesystem_monitor::ptr monitor_;
110 public:
111         impl(
112                         filesystem_monitor_factory& monitor_factory,
113                         const boost::filesystem::wpath& media_path,
114                         const boost::filesystem::wpath& thumbnails_path,
115                         int width,
116                         int height,
117                         const video_format_desc& render_video_mode,
118                         std::unique_ptr<image_mixer> image_mixer,
119                         int generate_delay_millis,
120                         const thumbnail_creator& thumbnail_creator)
121                 : media_path_(media_path)
122                 , thumbnails_path_(thumbnails_path)
123                 , width_(width)
124                 , height_(height)
125                 , image_mixer_(std::move(image_mixer))
126                 , format_desc_(render_video_mode)
127                 , output_(spl::make_unique<thumbnail_output>(generate_delay_millis))
128                 , mixer_(graph_, image_mixer_)
129                 , thumbnail_creator_(thumbnail_creator)
130                 , monitor_(monitor_factory.create(
131                                 media_path,
132                                 filesystem_event::ALL,
133                                 true,
134                                 [this] (filesystem_event event, const boost::filesystem::wpath& file)
135                                 {
136                                         this->on_file_event(event, file);
137                                 },
138                                 [this] (const std::set<boost::filesystem::wpath>& initial_files) 
139                                 {
140                                         this->on_initial_files(initial_files);
141                                 }))
142         {
143                 graph_->set_text(L"thumbnail-channel");
144                 graph_->auto_reset();
145                 diagnostics::register_graph(graph_);
146                 //monitor_->initial_scan_completion().get();
147                 //output_->sleep_millis = 2000;
148         }
149
150         void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
151         {
152                 using namespace boost::filesystem;
153
154                 std::set<std::wstring> relative_without_extensions;
155                 boost::transform(
156                                 initial_files,
157                                 std::insert_iterator<std::set<std::wstring>>(
158                                                 relative_without_extensions,
159                                                 relative_without_extensions.end()),
160                                 boost::bind(&get_relative_without_extension, _1, media_path_));
161
162                 for (boost::filesystem::wrecursive_directory_iterator iter(thumbnails_path_); iter != boost::filesystem::wrecursive_directory_iterator(); ++iter)
163                 {
164                         auto& path = iter->path();
165
166                         if (!is_regular_file(path))
167                                 continue;
168
169                         auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);
170                         bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension) 
171                                         == relative_without_extensions.end();
172
173                         if (no_corresponding_media_file)
174                                 remove(thumbnails_path_ / (relative_without_extension + L".png"));
175                 }
176         }
177
178         void generate(const std::wstring& media_file)
179         {
180                 using namespace boost::filesystem;
181                 auto base_file = media_path_ / media_file;
182                 auto folder = base_file.parent_path();
183
184                 for (boost::filesystem::directory_iterator iter(folder); iter != boost::filesystem::directory_iterator(); ++iter)
185                 {
186                         auto stem = iter->path().stem();
187
188                         if (boost::iequals(stem.wstring(), base_file.filename().wstring()))
189                                 monitor_->reemmit(iter->path());
190                 }
191         }
192
193         void generate_all()
194         {
195                 monitor_->reemmit_all();
196         }
197
198         void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
199         {
200                 switch (event)
201                 {
202                 case filesystem_event::CREATED:
203                         if (needs_to_be_generated(file))
204                                 generate_thumbnail(file);
205
206                         break;
207                 case filesystem_event::MODIFIED:
208                         generate_thumbnail(file);
209
210                         break;
211                 case filesystem_event::REMOVED:
212                         auto relative_without_extension = get_relative_without_extension(file, media_path_);
213                         boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
214
215                         break;
216                 }
217         }
218
219         bool needs_to_be_generated(const boost::filesystem::wpath& file)
220         {
221                 using namespace boost::filesystem;
222
223                 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
224
225                 if (!exists(png_file))
226                         return true;
227
228                 std::time_t media_file_mtime;
229
230                 try
231                 {
232                         media_file_mtime = last_write_time(file);
233                 }
234                 catch (...)
235                 {
236                         // Probably removed.
237                         return false;
238                 }
239
240                 try
241                 {
242                         return media_file_mtime != last_write_time(png_file);
243                 }
244                 catch (...)
245                 {
246                         // thumbnail probably removed.
247                         return true;
248                 }
249         }
250
251         void generate_thumbnail(const boost::filesystem::wpath& file)
252         {
253                 auto media_file = get_relative_without_extension(file, media_path_);
254                 auto png_file = thumbnails_path_ / (media_file + L".png");
255                 std::promise<void> thumbnail_ready;
256
257                 {
258                         auto producer = frame_producer::empty();
259
260                         try
261                         {
262                                 producer = create_thumbnail_producer(image_mixer_, format_desc_, media_file);
263                         }
264                         catch (const boost::thread_interrupted&)
265                         {
266                                 throw;
267                         }
268                         catch (...)
269                         {
270                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
271                                 return;
272                         }
273
274                         if (producer == frame_producer::empty())
275                         {
276                                 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
277                                 return;
278                         }
279
280                         boost::filesystem::create_directories(png_file.parent_path());
281                         output_->on_send = [this, &png_file] (const_frame frame)
282                         {
283                                 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
284                         };
285
286                         std::map<int, draw_frame> frames;
287                         auto raw_frame = draw_frame::empty();
288
289                         try
290                         {
291                                 raw_frame = producer->create_thumbnail_frame();
292                         }
293                         catch (const boost::thread_interrupted&)
294                         {
295                                 throw;
296                         }
297                         catch (...)
298                         {
299                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
300                                 return;
301                         }
302
303                         if (raw_frame == draw_frame::empty()
304                                         || raw_frame == draw_frame::late())
305                         {
306                                 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
307                                 return;
308                         }
309
310                         auto transformed_frame = draw_frame(raw_frame);
311                         transformed_frame.transform().image_transform.fill_scale[0] = static_cast<double>(width_) / format_desc_.width;
312                         transformed_frame.transform().image_transform.fill_scale[1] = static_cast<double>(height_) / format_desc_.height;
313                         frames.insert(std::make_pair(0, transformed_frame));
314
315                         std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*) { thumbnail_ready.set_value(); });
316
317                         auto mixed_frame = mixer_(frames, format_desc_);
318
319                         output_->send(std::move(mixed_frame), ticket);
320                         ticket.reset();
321                 }
322                 thumbnail_ready.get_future().get();
323
324                 if (boost::filesystem::exists(png_file))
325                 {
326                         // Adjust timestamp to match source file.
327                         try
328                         {
329                                 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
330                                 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
331                         }
332                         catch (...)
333                         {
334                                 // One of the files was removed before the call to last_write_time.
335                         }
336                 }
337                 else
338                         CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
339         }
340 };
341
342 thumbnail_generator::thumbnail_generator(
343                 filesystem_monitor_factory& monitor_factory,
344                 const boost::filesystem::wpath& media_path,
345                 const boost::filesystem::wpath& thumbnails_path,
346                 int width,
347                 int height,
348                 const video_format_desc& render_video_mode,
349                 std::unique_ptr<image_mixer> image_mixer,
350                 int generate_delay_millis,
351                 const thumbnail_creator& thumbnail_creator)
352                 : impl_(new impl(
353                                 monitor_factory,
354                                 media_path,
355                                 thumbnails_path,
356                                 width, height,
357                                 render_video_mode,
358                                 std::move(image_mixer),
359                                 generate_delay_millis,
360                                 thumbnail_creator))
361 {
362 }
363
364 thumbnail_generator::~thumbnail_generator()
365 {
366 }
367
368 void thumbnail_generator::generate(const std::wstring& media_file)
369 {
370         impl_->generate(media_file);
371 }
372
373 void thumbnail_generator::generate_all()
374 {
375         impl_->generate_all();
376 }
377
378 }}