1 /*MT*
2
3 MediaTomb - http://www.mediatomb.cc/
4
5 fallback_layout.cc - this file is part of MediaTomb.
6
7 Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
8 Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>
9
10 Copyright (C) 2006-2010 Gena Batyan <bgeradz@mediatomb.cc>,
11 Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
12 Leonhard Wimmer <leo@mediatomb.cc>
13
14 MediaTomb is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License version 2
16 as published by the Free Software Foundation.
17
18 MediaTomb is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 version 2 along with MediaTomb; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
26
27 $Id$
28 */
29
30 /// \file builtin_layout.cc
31
32 #include "builtin_layout.h" // API
33
34 #include <regex>
35
36 #include "config/config_manager.h"
37 #include "content/content_manager.h"
38 #include "metadata/metadata_handler.h"
39 #include "util/string_converter.h"
40
41 #ifdef ONLINE_SERVICES
42
43 #include "content/onlineservice/online_service.h"
44
45 #ifdef SOPCAST
46 #include "content/onlineservice/sopcast_content_handler.h"
47 #endif
48
49 #ifdef ATRAILERS
50 #include "content/onlineservice/atrailers_content_handler.h"
51 #endif
52
53 #endif //ONLINE_SERVICES
54
add(const std::shared_ptr<CdsObject> & obj,const std::pair<int,bool> & parentID,bool use_ref)55 void BuiltinLayout::add(const std::shared_ptr<CdsObject>& obj, const std::pair<int, bool>& parentID, bool use_ref)
56 {
57 obj->setParentID(parentID.first);
58 if (use_ref)
59 obj->setFlag(OBJECT_FLAG_USE_RESOURCE_REF);
60 obj->setID(INVALID_OBJECT_ID);
61
62 content->addObject(obj, parentID.second);
63 }
64
esc(std::string str)65 std::string BuiltinLayout::esc(std::string str)
66 {
67 return escape(std::move(str), VIRTUAL_CONTAINER_ESCAPE, VIRTUAL_CONTAINER_SEPARATOR);
68 }
69
addVideo(const std::shared_ptr<CdsObject> & obj,const fs::path & rootpath)70 void BuiltinLayout::addVideo(const std::shared_ptr<CdsObject>& obj, const fs::path& rootpath)
71 {
72 auto f2i = StringConverter::f2i(config);
73 auto id = content->addContainerChain("/Video/All Video");
74
75 if (obj->getID() != INVALID_OBJECT_ID) {
76 obj->setRefID(obj->getID());
77 add(obj, id);
78 } else {
79 add(obj, id);
80 obj->setRefID(obj->getID());
81 }
82
83 auto meta = obj->getMetaData();
84
85 std::string date = getValueOrDefault(meta, MetadataHandler::getMetaFieldName(M_CREATION_DATE));
86 if (!date.empty()) {
87 std::string year, month;
88 auto m = std::numeric_limits<std::size_t>::max();
89 auto y = date.find('-');
90 if (y != std::string::npos) {
91 year = date.substr(0, y);
92 month = date.substr(y + 1);
93 m = month.find('-');
94 if (m != std::string::npos)
95 month = month.substr(0, m);
96 }
97
98 std::string chain;
99 if ((y > 0) && (m > 0)) {
100 chain = fmt::format("/Video/Year/{}/{}", esc(year), esc(month));
101 id = content->addContainerChain(chain);
102 add(obj, id);
103 }
104
105 chain = fmt::format("/Video/Date/{}", esc(date));
106 id = content->addContainerChain(chain);
107 add(obj, id);
108 }
109
110 fs::path dir;
111 if (!rootpath.empty()) {
112 // make location relative to rootpath: "/home/.../Videos/Action/a.mkv" with rootpath "/home/.../Videos" -> "Action"
113 dir = fs::relative(obj->getLocation().parent_path(), config->getBoolOption(CFG_IMPORT_LAYOUT_PARENT_PATH) ? rootpath.parent_path() : rootpath);
114 dir = f2i->convert(dir);
115 } else
116 dir = esc(f2i->convert(getLastPath(obj->getLocation())));
117
118 if (!dir.empty()) {
119 id = content->addContainerChain(fmt::format("/Video/Directories/{}", dir.string().c_str()));
120 add(obj, id);
121 }
122 }
123
addImage(const std::shared_ptr<CdsObject> & obj,const fs::path & rootpath)124 void BuiltinLayout::addImage(const std::shared_ptr<CdsObject>& obj, const fs::path& rootpath)
125 {
126 auto f2i = StringConverter::f2i(config);
127
128 auto id = content->addContainerChain("/Photos/All Photos");
129 if (obj->getID() != INVALID_OBJECT_ID) {
130 obj->setRefID(obj->getID());
131 add(obj, id);
132 } else {
133 add(obj, id);
134 obj->setRefID(obj->getID());
135 }
136
137 auto meta = obj->getMetaData();
138
139 std::string date = getValueOrDefault(meta, MetadataHandler::getMetaFieldName(M_DATE));
140 if (!date.empty()) {
141 std::string year, month;
142 auto m = std::numeric_limits<std::size_t>::max();
143 auto y = date.find('-');
144 if (y != std::string::npos) {
145 year = date.substr(0, y);
146 month = date.substr(y + 1);
147 m = month.find('-');
148 if (m != std::string::npos)
149 month = month.substr(0, m);
150 }
151
152 std::string chain;
153 if ((y > 0) && (m > 0)) {
154 chain = fmt::format("/Photos/Year/{}/{}", esc(year), esc(month));
155 id = content->addContainerChain(chain);
156 add(obj, id);
157 }
158
159 chain = fmt::format("/Photos/Date/{}", esc(date));
160 id = content->addContainerChain(chain);
161 add(obj, id);
162 }
163
164 fs::path dir;
165 if (!rootpath.empty()) {
166 // make location relative to rootpath: "/home/.../Photos/Action/a.mkv" with rootpath "/home/.../Photos" -> "Action"
167 dir = fs::relative(obj->getLocation().parent_path(), config->getBoolOption(CFG_IMPORT_LAYOUT_PARENT_PATH) ? rootpath.parent_path() : rootpath);
168 dir = f2i->convert(dir);
169 } else
170 dir = esc(f2i->convert(getLastPath(obj->getLocation())));
171
172 if (!dir.empty()) {
173 id = content->addContainerChain(fmt::format("/Photos/Directories/{}", dir.string().c_str()));
174 add(obj, id);
175 }
176 }
177
addAudio(const std::shared_ptr<CdsObject> & obj,const fs::path & rootpath)178 void BuiltinLayout::addAudio(const std::shared_ptr<CdsObject>& obj, const fs::path& rootpath)
179 {
180 auto f2i = StringConverter::f2i(config);
181
182 std::string desc;
183
184 std::string title = obj->getMetaData(M_TITLE);
185 if (title.empty())
186 title = obj->getTitle();
187
188 std::string artist = obj->getMetaData(M_ARTIST);
189 std::string artist_full;
190 if (!artist.empty()) {
191 artist_full = artist;
192 desc = artist;
193 } else
194 artist = "Unknown";
195
196 std::string album = obj->getMetaData(M_ALBUM);
197 std::string album_full;
198 if (!album.empty()) {
199 desc = fmt::format("{}, {}", desc, album);
200 album_full = album;
201 } else {
202 album = "Unknown";
203 }
204
205 if (!desc.empty())
206 desc = fmt::format("{}, ", desc);
207 desc = fmt::format("{}{}", desc, title);
208
209 std::string date = obj->getMetaData(M_DATE);
210 std::string albumDate;
211 if (!date.empty()) {
212 auto i = date.find('-');
213 if (i != std::string::npos)
214 date = date.substr(0, i);
215
216 desc = fmt::format("{}, {}", desc, date);
217 albumDate = esc(date);
218 } else {
219 date = "Unknown";
220 albumDate = "Unknown";
221 }
222 obj->removeMetaData(M_UPNP_DATE);
223 obj->addMetaData(M_UPNP_DATE, albumDate);
224
225 std::string genre = obj->getMetaData(M_GENRE);
226 if (!genre.empty()) {
227 genre = mapGenre(genre);
228 desc = fmt::format("{}, {}", desc, genre);
229 } else {
230 genre = "Unknown";
231 }
232
233 std::string description = obj->getMetaData(M_DESCRIPTION);
234 if (description.empty()) {
235 obj->addMetaData(M_DESCRIPTION, desc);
236 }
237
238 std::string composer = obj->getMetaData(M_COMPOSER);
239 if (composer.empty())
240 composer = "None";
241
242 auto id = content->addContainerChain("/Audio/All Audio");
243 obj->setTitle(title);
244
245 // we get the main object here, so the object that we will add below
246 // will be a reference of the main object, that's why we set the ref
247 // id to the object id - the add function will clear out the object
248 // id
249 if (obj->getID() != INVALID_OBJECT_ID) {
250 obj->setRefID(obj->getID());
251 add(obj, id);
252 } else {
253 // the object is not yet in the database (probably we got it from a
254 // playlist script, so we set the ref id after adding - it will be used
255 // for all consequent virtual objects
256 add(obj, id);
257 obj->setRefID(obj->getID());
258 }
259
260 artist = esc(artist);
261
262 std::string chain = fmt::format("/Audio/Artists/{}/All Songs", artist);
263
264 id = content->addContainerChain(chain);
265 add(obj, id);
266
267 std::string temp;
268 if (!artist_full.empty())
269 temp = artist_full;
270
271 if (!album_full.empty())
272 temp = fmt::format("{} - {} - ", temp, album_full);
273 else
274 temp = fmt::format("{} - ", temp);
275
276 album = esc(album);
277 chain = fmt::format("/Audio/Artists/{}/{}", artist, album);
278 id = content->addContainerChain(chain, UPNP_CLASS_MUSIC_ALBUM, obj->getID(), obj);
279 add(obj, id);
280
281 chain = fmt::format("/Audio/Albums/{}", album);
282 id = content->addContainerChain(chain, UPNP_CLASS_MUSIC_ALBUM, obj->getID(), obj);
283 add(obj, id);
284
285 chain = fmt::format("/Audio/Genres/{}", esc(genre));
286 id = content->addContainerChain(chain, UPNP_CLASS_MUSIC_GENRE);
287 add(obj, id);
288
289 chain = fmt::format("/Audio/Composers/{}", esc(composer));
290 id = content->addContainerChain(chain, UPNP_CLASS_MUSIC_COMPOSER);
291 add(obj, id);
292
293 chain = fmt::format("/Audio/Year/{}", esc(date));
294 id = content->addContainerChain(chain);
295 add(obj, id);
296
297 obj->setTitle(fmt::format("{}{}", temp, title));
298
299 id = content->addContainerChain("/Audio/All - full name");
300 add(obj, id);
301
302 chain = fmt::format("/Audio/Artists/{}/All - full name", artist);
303 id = content->addContainerChain(chain);
304 add(obj, id);
305
306 obj->setTitle(title);
307 fs::path dir;
308 if (!rootpath.empty()) {
309 // make location relative to rootpath: "/home/.../Audio/Action/a.mp3" with rootpath "/home/.../Audio" -> "Action"
310 dir = fs::relative(obj->getLocation().parent_path(), config->getBoolOption(CFG_IMPORT_LAYOUT_PARENT_PATH) ? rootpath.parent_path() : rootpath);
311 dir = f2i->convert(dir);
312 } else
313 dir = esc(f2i->convert(getLastPath(obj->getLocation())));
314
315 if (!dir.empty()) {
316 id = content->addContainerChain(fmt::format("/Audio/Directories/{}", dir.string().c_str()));
317 add(obj, id);
318 }
319 }
320
321 #ifdef SOPCAST
addSopCast(const std::shared_ptr<CdsObject> & obj)322 void BuiltinLayout::addSopCast(const std::shared_ptr<CdsObject>& obj)
323 {
324 #define SP_VPATH "/Online Services/SopCast"
325 bool ref_set = false;
326
327 if (obj->getID() != INVALID_OBJECT_ID) {
328 obj->setRefID(obj->getID());
329 ref_set = true;
330 }
331
332 auto id = content->addContainerChain(SP_VPATH "/All Channels");
333 add(obj, id, ref_set);
334 if (!ref_set) {
335 obj->setRefID(obj->getID());
336 ref_set = true;
337 }
338
339 auto temp = obj->getAuxData(SOPCAST_AUXDATA_GROUP);
340 if (!temp.empty()) {
341 id = content->addContainerChain(fmt::format(SP_VPATH "/Groups/{}", esc(temp)));
342 add(obj, id, ref_set);
343 }
344 }
345 #endif
346
347 #ifdef ATRAILERS
addATrailers(const std::shared_ptr<CdsObject> & obj)348 void BuiltinLayout::addATrailers(const std::shared_ptr<CdsObject>& obj)
349 {
350 #define AT_VPATH "/Online Services/Apple Trailers"
351 auto id = content->addContainerChain(AT_VPATH "/All Trailers");
352
353 if (obj->getID() != INVALID_OBJECT_ID) {
354 obj->setRefID(obj->getID());
355 add(obj, id);
356 } else {
357 add(obj, id);
358 obj->setRefID(obj->getID());
359 }
360
361 auto meta = obj->getMetaData();
362
363 std::string temp = getValueOrDefault(meta, MetadataHandler::getMetaFieldName(M_GENRE));
364 auto genreAr = splitString(temp, ',');
365 for (auto&& genre : genreAr) {
366 trimStringInPlace(genre);
367 genre = mapGenre(genre);
368 if (genre.empty())
369 continue;
370
371 id = content->addContainerChain(fmt::format(AT_VPATH "/Genres/{}", esc(genre)));
372 add(obj, id);
373 }
374
375 temp = getValueOrDefault(meta, MetadataHandler::getMetaFieldName(M_DATE));
376 if (temp.length() >= 7) {
377 id = content->addContainerChain(fmt::format(AT_VPATH "/Release Date/{}", esc(temp.substr(0, 7))));
378 add(obj, id);
379 }
380
381 temp = obj->getAuxData(ATRAILERS_AUXDATA_POST_DATE);
382 if (temp.length() >= 7) {
383 id = content->addContainerChain(fmt::format(AT_VPATH "/Post Date/{}", esc(temp.substr(0, 7))));
384 add(obj, id);
385 }
386 }
387 #endif
388
BuiltinLayout(std::shared_ptr<ContentManager> content)389 BuiltinLayout::BuiltinLayout(std::shared_ptr<ContentManager> content)
390 : Layout(std::move(content))
391 , genreMap(config->getDictionaryOption(CFG_IMPORT_SCRIPTING_IMPORT_GENRE_MAP))
392 {
393 #ifdef ENABLE_PROFILING
394 PROF_INIT_GLOBAL(layout_profiling, "builtin layout");
395 #endif
396 }
397
mapGenre(const std::string & genre)398 std::string BuiltinLayout::mapGenre(const std::string& genre)
399 {
400 for (auto&& [from, to] : genreMap) {
401 if (std::regex_match(genre, std::regex(from, std::regex::ECMAScript | std::regex::icase))) {
402 return std::regex_replace(genre, std::regex(from, std::regex::ECMAScript | std::regex::icase), to);
403 }
404 }
405 return genre;
406 }
407
processCdsObject(const std::shared_ptr<CdsObject> & obj,const fs::path & rootpath,const std::string & mimetype,const std::string & content_type)408 void BuiltinLayout::processCdsObject(const std::shared_ptr<CdsObject>& obj, const fs::path& rootpath, const std::string& mimetype, const std::string& content_type)
409 {
410 log_debug("Process CDS Object: {}", obj->getTitle().c_str());
411 #ifdef ENABLE_PROFILING
412 PROF_START(&layout_profiling);
413 #endif
414 auto clone = CdsObject::createObject(obj->getObjectType());
415 obj->copyTo(clone);
416 clone->setVirtual(true);
417
418 #ifdef ONLINE_SERVICES
419 if (clone->getFlag(OBJECT_FLAG_ONLINE_SERVICE)) {
420 auto service = service_type_t(std::stoi(clone->getAuxData(ONLINE_SERVICE_AUX_ID)));
421
422 switch (service) {
423 #ifdef SOPCAST
424 case OS_SopCast:
425 addSopCast(clone);
426 break;
427 #endif
428 #ifdef ATRAILERS
429 case OS_ATrailers:
430 addATrailers(clone);
431 break;
432 #endif
433 case OS_Max:
434 default:
435 log_warning("No handler for service type");
436 break;
437 }
438 } else {
439 #endif
440
441 if (startswith(mimetype, "video"))
442 addVideo(clone, rootpath);
443 else if (startswith(mimetype, "image"))
444 addImage(clone, rootpath);
445 else if ((startswith(mimetype, "audio") && (content_type != CONTENT_TYPE_PLAYLIST)))
446 addAudio(clone, rootpath);
447 else if (content_type == CONTENT_TYPE_OGG) {
448 if (obj->getFlag(OBJECT_FLAG_OGG_THEORA))
449 addVideo(clone, rootpath);
450 else
451 addAudio(clone, rootpath);
452 }
453
454 #ifdef ONLINE_SERVICES
455 }
456 #endif
457 #ifdef ENABLE_PROFILING
458 PROF_END(&layout_profiling);
459 #endif
460 }
461
462 #ifdef ENABLE_PROFILING
~BuiltinLayout()463 BuiltinLayout::~BuiltinLayout()
464 {
465 PROF_PRINT(&layout_profiling);
466 }
467 #endif
468