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