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