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