]> git.sesse.net Git - casparcg/blob - core/thumbnail_generator.cpp
build scripts
[casparcg] / core / thumbnail_generator.cpp
1 /*\r
2 * Copyright 2013 Sveriges Television AB http://casparcg.com/\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\r
5 *\r
6 * CasparCG is free software: you can redistribute it and/or modify\r
7 * it under the terms of the GNU General Public License as published by\r
8 * the Free Software Foundation, either version 3 of the License, or\r
9 * (at your option) any later version.\r
10 *\r
11 * CasparCG is distributed in the hope that it will be useful,\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 * GNU General Public License for more details.\r
15 *\r
16 * You should have received a copy of the GNU General Public License\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 * Author: Helge Norberg, helge.norberg@svt.se\r
20 */\r
21 \r
22 #include "stdafx.h"\r
23 \r
24 #include "thumbnail_generator.h"\r
25 \r
26 #include <iostream>\r
27 #include <iterator>\r
28 #include <set>\r
29 \r
30 #include <boost/thread.hpp>\r
31 #include <boost/range/algorithm/transform.hpp>\r
32 #include <boost/algorithm/string/predicate.hpp>\r
33 \r
34 #include <tbb/atomic.h>\r
35 \r
36 #include "producer/frame_producer.h"\r
37 #include "consumer/frame_consumer.h"\r
38 #include "mixer/mixer.h"\r
39 #include "mixer/audio/audio_util.h"\r
40 #include "video_format.h"\r
41 #include "producer/frame/basic_frame.h"\r
42 #include "producer/frame/frame_transform.h"\r
43 #include "producer/media_info/media_info.h"\r
44 #include "producer/media_info/media_info_repository.h"\r
45 \r
46 namespace caspar { namespace core {\r
47 \r
48 std::wstring get_relative_without_extension(\r
49                 const boost::filesystem::wpath& file,\r
50                 const boost::filesystem::wpath& relative_to)\r
51 {\r
52         auto result = file.stem();\r
53                 \r
54         boost::filesystem::wpath current_path = file;\r
55 \r
56         while (true)\r
57         {\r
58                 current_path = current_path.parent_path();\r
59 \r
60                 if (boost::filesystem::equivalent(current_path, relative_to))\r
61                         break;\r
62 \r
63                 if (current_path.empty())\r
64                         throw std::runtime_error("File not relative to folder");\r
65 \r
66                 result = current_path.filename() + L"/" + result;\r
67         }\r
68 \r
69         return result;\r
70 }\r
71 \r
72 struct thumbnail_output : public mixer::target_t\r
73 {\r
74         tbb::atomic<int> sleep_millis;\r
75         std::function<void (const safe_ptr<read_frame>& frame)> on_send;\r
76 \r
77         thumbnail_output(int sleep_millis)\r
78         {\r
79                 this->sleep_millis = sleep_millis;\r
80         }\r
81 \r
82         void send(const std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>& frame_and_ticket)\r
83         {\r
84                 int current_sleep = sleep_millis;\r
85 \r
86                 if (current_sleep > 0)\r
87                         boost::this_thread::sleep(boost::posix_time::milliseconds(current_sleep));\r
88 \r
89                 on_send(frame_and_ticket.first);\r
90                 on_send = nullptr;\r
91         }\r
92 };\r
93 \r
94 struct thumbnail_generator::implementation\r
95 {\r
96 private:\r
97         boost::filesystem::wpath media_path_;\r
98         boost::filesystem::wpath thumbnails_path_;\r
99         int width_;\r
100         int height_;\r
101         safe_ptr<ogl_device> ogl_;\r
102         safe_ptr<diagnostics::graph> graph_;\r
103         video_format_desc format_desc_;\r
104         safe_ptr<thumbnail_output> output_;\r
105         safe_ptr<mixer> mixer_;\r
106         thumbnail_creator thumbnail_creator_;\r
107         safe_ptr<media_info_repository> media_info_repo_;\r
108         filesystem_monitor::ptr monitor_;\r
109 public:\r
110         implementation(\r
111                         filesystem_monitor_factory& monitor_factory,\r
112                         const boost::filesystem::wpath& media_path,\r
113                         const boost::filesystem::wpath& thumbnails_path,\r
114                         int width,\r
115                         int height,\r
116                         const video_format_desc& render_video_mode,\r
117                         const safe_ptr<ogl_device>& ogl,\r
118                         int generate_delay_millis,\r
119                         const thumbnail_creator& thumbnail_creator,\r
120                         safe_ptr<media_info_repository> media_info_repo,\r
121                         bool mipmap)\r
122                 : media_path_(media_path)\r
123                 , thumbnails_path_(thumbnails_path)\r
124                 , width_(width)\r
125                 , height_(height)\r
126                 , ogl_(ogl)\r
127                 , format_desc_(render_video_mode)\r
128                 , output_(new thumbnail_output(generate_delay_millis))\r
129                 , mixer_(new mixer(\r
130                                 graph_,\r
131                                 output_,\r
132                                 format_desc_,\r
133                                 ogl,\r
134                                 channel_layout::stereo()))\r
135                 , thumbnail_creator_(thumbnail_creator)\r
136                 , media_info_repo_(std::move(media_info_repo))\r
137                 , monitor_(monitor_factory.create(\r
138                                 media_path,\r
139                                 ALL,\r
140                                 true,\r
141                                 [this] (filesystem_event event, const boost::filesystem::wpath& file)\r
142                                 {\r
143                                         this->on_file_event(event, file);\r
144                                 },\r
145                                 [this] (const std::set<boost::filesystem::wpath>& initial_files) \r
146                                 {\r
147                                         this->on_initial_files(initial_files);\r
148                                 }))\r
149         {\r
150                 graph_->set_text(L"thumbnail-channel");\r
151                 graph_->auto_reset();\r
152                 diagnostics::register_graph(graph_);\r
153                 mixer_->set_mipmap(0, mipmap);\r
154                 //monitor_->initial_scan_completion().get();\r
155                 //output_->sleep_millis = 2000;\r
156         }\r
157 \r
158         void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)\r
159         {\r
160                 using namespace boost::filesystem;\r
161 \r
162                 std::set<std::wstring> relative_without_extensions;\r
163                 boost::transform(\r
164                                 initial_files,\r
165                                 std::insert_iterator<std::set<std::wstring>>(\r
166                                                 relative_without_extensions,\r
167                                                 relative_without_extensions.end()),\r
168                                 boost::bind(&get_relative_without_extension, _1, media_path_));\r
169 \r
170                 for (wrecursive_directory_iterator iter(thumbnails_path_); iter != wrecursive_directory_iterator(); ++iter)\r
171                 {\r
172                         auto& path = iter->path();\r
173 \r
174                         if (!is_regular_file(path))\r
175                                 continue;\r
176 \r
177                         auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);\r
178                         bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension) \r
179                                         == relative_without_extensions.end();\r
180 \r
181                         if (no_corresponding_media_file)\r
182                                 remove(thumbnails_path_ / (relative_without_extension + L".png"));\r
183                 }\r
184         }\r
185 \r
186         void generate(const std::wstring& media_file)\r
187         {\r
188                 using namespace boost::filesystem;\r
189                 auto base_file = media_path_ / media_file;\r
190                 auto folder = base_file.parent_path();\r
191 \r
192                 for (wdirectory_iterator iter(folder); iter != wdirectory_iterator(); ++iter)\r
193                 {\r
194                         auto stem = iter->path().stem();\r
195 \r
196                         if (boost::iequals(stem, base_file.filename()))\r
197                                 monitor_->reemmit(iter->path());\r
198                 }\r
199         }\r
200 \r
201         void generate_all()\r
202         {\r
203                 monitor_->reemmit_all();\r
204         }\r
205 \r
206         void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)\r
207         {\r
208                 switch (event)\r
209                 {\r
210                 case CREATED:\r
211                         if (needs_to_be_generated(file))\r
212                                 generate_thumbnail(file);\r
213 \r
214                         break;\r
215                 case MODIFIED:\r
216                         generate_thumbnail(file);\r
217 \r
218                         break;\r
219                 case REMOVED:\r
220                         auto relative_without_extension = get_relative_without_extension(file, media_path_);\r
221                         boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));\r
222                         media_info_repo_->remove(file.file_string());\r
223 \r
224                         break;\r
225                 }\r
226         }\r
227 \r
228         bool needs_to_be_generated(const boost::filesystem::wpath& file)\r
229         {\r
230                 using namespace boost::filesystem;\r
231 \r
232                 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");\r
233 \r
234                 if (!exists(png_file))\r
235                         return true;\r
236 \r
237                 std::time_t media_file_mtime;\r
238 \r
239                 try\r
240                 {\r
241                         media_file_mtime = last_write_time(file);\r
242                 }\r
243                 catch (...)\r
244                 {\r
245                         // Probably removed.\r
246                         return false;\r
247                 }\r
248 \r
249                 try\r
250                 {\r
251                         return media_file_mtime != last_write_time(png_file);\r
252                 }\r
253                 catch (...)\r
254                 {\r
255                         // thumbnail probably removed.\r
256                         return true;\r
257                 }\r
258         }\r
259 \r
260         void generate_thumbnail(const boost::filesystem::wpath& file)\r
261         {\r
262                 auto media_file = get_relative_without_extension(file, media_path_);\r
263                 auto png_file = thumbnails_path_ / (media_file + L".png");\r
264                 boost::promise<void> thumbnail_ready;\r
265 \r
266                 {\r
267                         auto producer = frame_producer::empty();\r
268 \r
269                         try\r
270                         {\r
271                                 producer = create_thumbnail_producer(mixer_->get_frame_factory(0), media_file);\r
272                         }\r
273                         catch (const boost::thread_interrupted&)\r
274                         {\r
275                                 throw;\r
276                         }\r
277                         catch (...)\r
278                         {\r
279                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;\r
280                                 return;\r
281                         }\r
282 \r
283                         if (producer == frame_producer::empty())\r
284                         {\r
285                                 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;\r
286                                 return;\r
287                         }\r
288 \r
289                         boost::filesystem::create_directories(png_file.parent_path());\r
290                         output_->on_send = [this, &png_file] (const safe_ptr<read_frame>& frame)\r
291                         {\r
292                                 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);\r
293                         };\r
294 \r
295                         std::map<int, safe_ptr<basic_frame>> frames;\r
296                         auto raw_frame = basic_frame::empty();\r
297 \r
298                         try\r
299                         {\r
300                                 raw_frame = producer->create_thumbnail_frame();\r
301                                 media_info_repo_->remove(file.file_string());\r
302                                 media_info_repo_->get(file.file_string());\r
303                         }\r
304                         catch (const boost::thread_interrupted&)\r
305                         {\r
306                                 throw;\r
307                         }\r
308                         catch (...)\r
309                         {\r
310                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;\r
311                                 return;\r
312                         }\r
313 \r
314                         if (raw_frame == basic_frame::empty()\r
315                                         || raw_frame == basic_frame::empty()\r
316                                         || raw_frame == basic_frame::eof()\r
317                                         || raw_frame == basic_frame::late())\r
318                         {\r
319                                 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;\r
320                                 return;\r
321                         }\r
322 \r
323                         auto transformed_frame = make_safe<basic_frame>(raw_frame);\r
324                         transformed_frame->get_frame_transform().fill_scale[0] = static_cast<double>(width_) / format_desc_.width;\r
325                         transformed_frame->get_frame_transform().fill_scale[1] = static_cast<double>(height_) / format_desc_.height;\r
326                         frames.insert(std::make_pair(0, transformed_frame));\r
327 \r
328                         std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*)\r
329                         {\r
330                                 thumbnail_ready.set_value();\r
331                         });\r
332 \r
333                         mixer_->send(std::make_pair(frames, ticket));\r
334                         ticket.reset();\r
335                 }\r
336                 thumbnail_ready.get_future().get();\r
337 \r
338                 if (boost::filesystem::exists(png_file))\r
339                 {\r
340                         // Adjust timestamp to match source file.\r
341                         try\r
342                         {\r
343                                 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));\r
344                                 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;\r
345                         }\r
346                         catch (...)\r
347                         {\r
348                                 // One of the files was removed before the call to last_write_time.\r
349                         }\r
350                 }\r
351                 else\r
352                         CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;\r
353         }\r
354 };\r
355 \r
356 thumbnail_generator::thumbnail_generator(\r
357                 filesystem_monitor_factory& monitor_factory,\r
358                 const boost::filesystem::wpath& media_path,\r
359                 const boost::filesystem::wpath& thumbnails_path,\r
360                 int width,\r
361                 int height,\r
362                 const video_format_desc& render_video_mode,\r
363                 const safe_ptr<ogl_device>& ogl,\r
364                 int generate_delay_millis,\r
365                 const thumbnail_creator& thumbnail_creator,\r
366                 safe_ptr<media_info_repository> media_info_repo,\r
367                 bool mipmap)\r
368                 : impl_(new implementation(\r
369                                 monitor_factory,\r
370                                 media_path,\r
371                                 thumbnails_path,\r
372                                 width, height,\r
373                                 render_video_mode,\r
374                                 ogl,\r
375                                 generate_delay_millis,\r
376                                 thumbnail_creator,\r
377                                 media_info_repo,\r
378                                 mipmap))\r
379 {\r
380 }\r
381 \r
382 thumbnail_generator::~thumbnail_generator()\r
383 {\r
384 }\r
385 \r
386 void thumbnail_generator::generate(const std::wstring& media_file)\r
387 {\r
388         impl_->generate(media_file);\r
389 }\r
390 \r
391 void thumbnail_generator::generate_all()\r
392 {\r
393         impl_->generate_all();\r
394 }\r
395 \r
396 }}\r