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