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