]> git.sesse.net Git - casparcg/blob - core/thumbnail_generator.cpp
Update README.txt
[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                 : media_path_(media_path)\r
122                 , thumbnails_path_(thumbnails_path)\r
123                 , width_(width)\r
124                 , height_(height)\r
125                 , ogl_(ogl)\r
126                 , format_desc_(render_video_mode)\r
127                 , output_(new thumbnail_output(generate_delay_millis))\r
128                 , mixer_(new mixer(\r
129                                 graph_,\r
130                                 output_,\r
131                                 format_desc_,\r
132                                 ogl,\r
133                                 channel_layout::stereo()))\r
134                 , thumbnail_creator_(thumbnail_creator)\r
135                 , media_info_repo_(std::move(media_info_repo))\r
136                 , monitor_(monitor_factory.create(\r
137                                 media_path,\r
138                                 ALL,\r
139                                 true,\r
140                                 [this] (filesystem_event event, const boost::filesystem::wpath& file)\r
141                                 {\r
142                                         this->on_file_event(event, file);\r
143                                 },\r
144                                 [this] (const std::set<boost::filesystem::wpath>& initial_files) \r
145                                 {\r
146                                         this->on_initial_files(initial_files);\r
147                                 }))\r
148         {\r
149                 graph_->set_text(L"thumbnail-channel");\r
150                 graph_->auto_reset();\r
151                 diagnostics::register_graph(graph_);\r
152                 //monitor_->initial_scan_completion().get();\r
153                 //output_->sleep_millis = 2000;\r
154         }\r
155 \r
156         void on_initial_files(const std::set<boost::filesystem::wpath>& initial_files)\r
157         {\r
158                 using namespace boost::filesystem;\r
159 \r
160                 std::set<std::wstring> relative_without_extensions;\r
161                 boost::transform(\r
162                                 initial_files,\r
163                                 std::insert_iterator<std::set<std::wstring>>(\r
164                                                 relative_without_extensions,\r
165                                                 relative_without_extensions.end()),\r
166                                 boost::bind(&get_relative_without_extension, _1, media_path_));\r
167 \r
168                 for (wrecursive_directory_iterator iter(thumbnails_path_); iter != wrecursive_directory_iterator(); ++iter)\r
169                 {\r
170                         auto& path = iter->path();\r
171 \r
172                         if (!is_regular_file(path))\r
173                                 continue;\r
174 \r
175                         auto relative_without_extension = get_relative_without_extension(path, thumbnails_path_);\r
176                         bool no_corresponding_media_file = relative_without_extensions.find(relative_without_extension) \r
177                                         == relative_without_extensions.end();\r
178 \r
179                         if (no_corresponding_media_file)\r
180                                 remove(thumbnails_path_ / (relative_without_extension + L".png"));\r
181                 }\r
182         }\r
183 \r
184         void generate(const std::wstring& media_file)\r
185         {\r
186                 using namespace boost::filesystem;\r
187                 auto base_file = media_path_ / media_file;\r
188                 auto folder = base_file.parent_path();\r
189 \r
190                 for (wdirectory_iterator iter(folder); iter != wdirectory_iterator(); ++iter)\r
191                 {\r
192                         auto stem = iter->path().stem();\r
193 \r
194                         if (boost::iequals(stem, base_file.filename()))\r
195                                 monitor_->reemmit(iter->path());\r
196                 }\r
197         }\r
198 \r
199         void generate_all()\r
200         {\r
201                 monitor_->reemmit_all();\r
202         }\r
203 \r
204         void on_file_event(filesystem_event event, const boost::filesystem::wpath& file)\r
205         {\r
206                 switch (event)\r
207                 {\r
208                 case CREATED:\r
209                         if (needs_to_be_generated(file))\r
210                                 generate_thumbnail(file);\r
211 \r
212                         break;\r
213                 case MODIFIED:\r
214                         generate_thumbnail(file);\r
215 \r
216                         break;\r
217                 case REMOVED:\r
218                         auto relative_without_extension = get_relative_without_extension(file, media_path_);\r
219                         boost::filesystem::remove(thumbnails_path_ / (relative_without_extension + L".png"));\r
220                         media_info_repo_->remove(file.file_string());\r
221 \r
222                         break;\r
223                 }\r
224         }\r
225 \r
226         bool needs_to_be_generated(const boost::filesystem::wpath& file)\r
227         {\r
228                 using namespace boost::filesystem;\r
229 \r
230                 auto png_file = thumbnails_path_ / (get_relative_without_extension(file, media_path_) + L".png");\r
231 \r
232                 if (!exists(png_file))\r
233                         return true;\r
234 \r
235                 std::time_t media_file_mtime;\r
236 \r
237                 try\r
238                 {\r
239                         media_file_mtime = last_write_time(file);\r
240                 }\r
241                 catch (...)\r
242                 {\r
243                         // Probably removed.\r
244                         return false;\r
245                 }\r
246 \r
247                 try\r
248                 {\r
249                         return media_file_mtime != last_write_time(png_file);\r
250                 }\r
251                 catch (...)\r
252                 {\r
253                         // thumbnail probably removed.\r
254                         return true;\r
255                 }\r
256         }\r
257 \r
258         void generate_thumbnail(const boost::filesystem::wpath& file)\r
259         {\r
260                 auto media_file = get_relative_without_extension(file, media_path_);\r
261                 auto png_file = thumbnails_path_ / (media_file + L".png");\r
262                 boost::promise<void> thumbnail_ready;\r
263 \r
264                 {\r
265                         auto producer = frame_producer::empty();\r
266 \r
267                         try\r
268                         {\r
269                                 producer = create_thumbnail_producer(mixer_, media_file);\r
270                         }\r
271                         catch (const boost::thread_interrupted&)\r
272                         {\r
273                                 throw;\r
274                         }\r
275                         catch (...)\r
276                         {\r
277                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to initialize for " << media_file;\r
278                                 return;\r
279                         }\r
280 \r
281                         if (producer == frame_producer::empty())\r
282                         {\r
283                                 CASPAR_LOG(trace) << L"No appropriate thumbnail producer found for " << media_file;\r
284                                 return;\r
285                         }\r
286 \r
287                         boost::filesystem::create_directories(png_file.parent_path());\r
288                         output_->on_send = [this, &png_file] (const safe_ptr<read_frame>& frame)\r
289                         {\r
290                                 thumbnail_creator_(frame, format_desc_, png_file, width_, height_);\r
291                         };\r
292 \r
293                         std::map<int, safe_ptr<basic_frame>> frames;\r
294                         auto raw_frame = basic_frame::empty();\r
295 \r
296                         try\r
297                         {\r
298                                 raw_frame = producer->create_thumbnail_frame();\r
299                                 media_info_repo_->remove(file.file_string());\r
300                                 media_info_repo_->get(file.file_string());\r
301                         }\r
302                         catch (const boost::thread_interrupted&)\r
303                         {\r
304                                 throw;\r
305                         }\r
306                         catch (...)\r
307                         {\r
308                                 CASPAR_LOG(debug) << L"Thumbnail producer failed to create thumbnail for " << media_file;\r
309                                 return;\r
310                         }\r
311 \r
312                         if (raw_frame == basic_frame::empty()\r
313                                         || raw_frame == basic_frame::empty()\r
314                                         || raw_frame == basic_frame::eof()\r
315                                         || raw_frame == basic_frame::late())\r
316                         {\r
317                                 CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;\r
318                                 return;\r
319                         }\r
320 \r
321                         auto transformed_frame = make_safe<basic_frame>(raw_frame);\r
322                         transformed_frame->get_frame_transform().fill_scale[0] = static_cast<double>(width_) / format_desc_.width;\r
323                         transformed_frame->get_frame_transform().fill_scale[1] = static_cast<double>(height_) / format_desc_.height;\r
324                         frames.insert(std::make_pair(0, transformed_frame));\r
325 \r
326                         std::shared_ptr<void> ticket(nullptr, [&thumbnail_ready](void*)\r
327                         {\r
328                                 thumbnail_ready.set_value();\r
329                         });\r
330 \r
331                         mixer_->send(std::make_pair(frames, ticket));\r
332                         ticket.reset();\r
333                 }\r
334                 thumbnail_ready.get_future().get();\r
335 \r
336                 if (boost::filesystem::exists(png_file))\r
337                 {\r
338                         // Adjust timestamp to match source file.\r
339                         try\r
340                         {\r
341                                 boost::filesystem::last_write_time(png_file, boost::filesystem::last_write_time(file));\r
342                                 CASPAR_LOG(debug) << L"Generated thumbnail for " << media_file;\r
343                         }\r
344                         catch (...)\r
345                         {\r
346                                 // One of the files was removed before the call to last_write_time.\r
347                         }\r
348                 }\r
349                 else\r
350                         CASPAR_LOG(debug) << L"No thumbnail generated for " << media_file;\r
351         }\r
352 };\r
353 \r
354 thumbnail_generator::thumbnail_generator(\r
355                 filesystem_monitor_factory& monitor_factory,\r
356                 const boost::filesystem::wpath& media_path,\r
357                 const boost::filesystem::wpath& thumbnails_path,\r
358                 int width,\r
359                 int height,\r
360                 const video_format_desc& render_video_mode,\r
361                 const safe_ptr<ogl_device>& ogl,\r
362                 int generate_delay_millis,\r
363                 const thumbnail_creator& thumbnail_creator,\r
364                 safe_ptr<media_info_repository> media_info_repo)\r
365                 : impl_(new implementation(\r
366                                 monitor_factory,\r
367                                 media_path,\r
368                                 thumbnails_path,\r
369                                 width, height,\r
370                                 render_video_mode,\r
371                                 ogl,\r
372                                 generate_delay_millis,\r
373                                 thumbnail_creator,\r
374                                 media_info_repo))\r
375 {\r
376 }\r
377 \r
378 thumbnail_generator::~thumbnail_generator()\r
379 {\r
380 }\r
381 \r
382 void thumbnail_generator::generate(const std::wstring& media_file)\r
383 {\r
384         impl_->generate(media_file);\r
385 }\r
386 \r
387 void thumbnail_generator::generate_all()\r
388 {\r
389         impl_->generate_all();\r
390 }\r
391 \r
392 }}\r