]> git.sesse.net Git - casparcg/blob - core/thumbnail_generator.cpp
* Upgraded to Visual Studio 2013
[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/range/algorithm/transform.hpp>
33 #include <boost/algorithm/string/predicate.hpp>
34 #include <boost/filesystem.hpp>
35
36 #include <tbb/atomic.h>
37
38 #include <common/diagnostics/graph.h>
39
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"
48
49 namespace caspar { namespace core {
50
51 std::wstring get_relative_without_extension(
52                 const boost::filesystem::wpath& file,
53                 const boost::filesystem::wpath& relative_to)
54 {
55         auto result = file.stem();
56
57         boost::filesystem::wpath current_path = file;
58
59         while (true)
60         {
61                 current_path = current_path.parent_path();
62
63                 if (boost::filesystem::equivalent(current_path, relative_to))
64                         break;
65
66                 if (current_path.empty())
67                         throw std::runtime_error("File not relative to folder");
68
69                 result = current_path.filename() / result;
70         }
71
72         return result.wstring();
73 }
74
75 struct thumbnail_output
76 {
77         tbb::atomic<int> sleep_millis;
78         std::function<void (const_frame)> on_send;
79
80         thumbnail_output(int sleep_millis)
81         {
82                 this->sleep_millis = sleep_millis;
83         }
84
85         void send(const_frame frame, std::shared_ptr<void> frame_and_ticket)
86         {
87                 int current_sleep = sleep_millis;
88
89                 if (current_sleep > 0)
90                         boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));
91
92                 on_send(std::move(frame));
93                 on_send = nullptr;
94         }
95 };
96
97 struct thumbnail_generator::impl
98 {
99 private:
100         boost::filesystem::wpath media_path_;
101         boost::filesystem::wpath thumbnails_path_;
102         int width_;
103         int height_;
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_;
108         mixer mixer_;
109         thumbnail_creator thumbnail_creator_;
110         filesystem_monitor::ptr monitor_;
111 public:
112         impl(
113                         filesystem_monitor_factory& monitor_factory,
114                         const boost::filesystem::wpath& media_path,
115                         const boost::filesystem::wpath& thumbnails_path,
116                         int width,
117                         int height,
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)
124                 , width_(width)
125                 , height_(height)
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(
132                                 media_path,
133                                 ALL,
134                                 true,
135                                 [this] (filesystem_event event, const boost::filesystem::wpath& file)
136                                 {
137                                         this->on_file_event(event, file);
138                                 },
139                                 [this] (const std::set<boost::filesystem::wpath>& initial_files) 
140                                 {
141                                         this->on_initial_files(initial_files);
142                                 }))
143         {
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;
149         }
150
151         void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)
152         {
153                 using namespace boost::filesystem;
154
155                 std::set<std::wstring> relative_without_extensions;
156                 boost::transform(
157                                 initial_files,
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_));
162
163                 for (boost::filesystem::wrecursive_directory_iterator iter(thumbnails_path_); iter != boost::filesystem::wrecursive_directory_iterator(); ++iter)
164                 {
165                         auto& path = iter->path();
166
167                         if (!is_regular_file(path))
168                                 continue;
169
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();
173
174                         if (no_corresponding_media_file)
175                                 remove(thumbnails_path_ / (relative_without_extension + L".png"));
176                 }
177         }
178
179         void generate(const std::wstring& media_file)
180         {
181                 using namespace boost::filesystem;
182                 auto base_file = media_path_ / media_file;
183                 auto folder = base_file.parent_path();
184
185                 for (boost::filesystem::directory_iterator iter(folder); iter != boost::filesystem::directory_iterator(); ++iter)
186                 {
187                         auto stem = iter->path().stem();
188
189                         if (boost::iequals(stem.wstring(), base_file.filename().wstring()))
190                                 monitor_->reemmit(iter->path());
191                 }
192         }
193
194         void generate_all()
195         {
196                 monitor_->reemmit_all();
197         }
198
199         void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)
200         {
201                 switch (event)
202                 {
203                 case CREATED:
204                         if (needs_to_be_generated(file))
205                                 generate_thumbnail(file);
206
207                         break;
208                 case MODIFIED:
209                         generate_thumbnail(file);
210
211                         break;
212                 case REMOVED:
213                         auto relative_without_extension = get_relative_without_extension(file, media_path_);
214                         boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));
215
216                         break;
217                 }
218         }
219
220         bool needs_to_be_generated(const boost::filesystem::wpath& file)
221         {
222                 using namespace boost::filesystem;
223
224                 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");
225
226                 if (!exists(png_file))
227                         return true;
228
229                 std::time_t media_file_mtime;
230
231                 try
232                 {
233                         media_file_mtime = last_write_time(file);
234                 }
235                 catch (...)
236                 {
237                         // Probably removed.
238                         return false;
239                 }
240
241                 try
242                 {
243                         return media_file_mtime != last_write_time(png_file);
244                 }
245                 catch (...)
246                 {
247                         // thumbnail probably removed.
248                         return true;
249                 }
250         }
251
252         void generate_thumbnail(const boost::filesystem::wpath& file)
253         {
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;
257
258                 {
259                         auto producer = frame_producer::empty();
260
261                         try
262                         {
263                                 producer = create_thumbnail_producer(image_mixer_, format_desc_, media_file);
264                         }
265                         catch (const boost::thread_interrupted&)
266                         {
267                                 throw;
268                         }
269                         catch (...)
270                         {
271                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;
272                                 return;
273                         }
274
275                         if (producer == frame_producer::empty())
276                         {
277                                 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;
278                                 return;
279                         }
280
281                         boost::filesystem::create_directories(png_file.parent_path());
282                         output_->on_send = [this, &png_file] (const_frame frame)
283                         {
284                                 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);
285                         };
286
287                         std::map<int, draw_frame> frames;
288                         auto raw_frame = draw_frame::empty();
289
290                         try
291                         {
292                                 raw_frame = producer->create_thumbnail_frame();
293                         }
294                         catch (const boost::thread_interrupted&)
295                         {
296                                 throw;
297                         }
298                         catch (...)
299                         {
300                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;
301                                 return;
302                         }
303
304                         if (raw_frame == draw_frame::empty()
305                                         || raw_frame == draw_frame::late())
306                         {
307                                 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
308                                 return;
309                         }
310
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));
315
316                         std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*) { thumbnail_ready.set_value(); });
317
318                         auto mixed_frame = mixer_(frames, format_desc_);
319
320                         output_->send(std::move(mixed_frame), ticket);
321                         ticket.reset();
322                 }
323                 thumbnail_ready.get_future().get();
324
325                 if (boost::filesystem::exists(png_file))
326                 {
327                         // Adjust timestamp to match source file.
328                         try
329                         {
330                                 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));
331                                 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;
332                         }
333                         catch (...)
334                         {
335                                 // One of the files was removed before the call to last_write_time.
336                         }
337                 }
338                 else
339                         CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;
340         }
341 };
342
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,
347                 int width,
348                 int height,
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)
353                 : impl_(new impl(
354                                 monitor_factory,
355                                 media_path,
356                                 thumbnails_path,
357                                 width, height,
358                                 render_video_mode,
359                                 std::move(image_mixer),
360                                 generate_delay_millis,
361                                 thumbnail_creator))
362 {
363 }
364
365 thumbnail_generator::~thumbnail_generator()
366 {
367 }
368
369 void thumbnail_generator::generate(const std::wstring& media_file)
370 {
371         impl_->generate(media_file);
372 }
373
374 void thumbnail_generator::generate_all()
375 {
376         impl_->generate_all();
377 }
378
379 }}