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