1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "VideoThumbLoader.h"
10
11 #include "FileItem.h"
12 #include "GUIUserMessages.h"
13 #include "ServiceBroker.h"
14 #include "TextureCache.h"
15 #include "URL.h"
16 #include "cores/VideoPlayer/DVDFileInfo.h"
17 #include "cores/VideoSettings.h"
18 #include "filesystem/Directory.h"
19 #include "filesystem/DirectoryCache.h"
20 #include "filesystem/StackDirectory.h"
21 #include "guilib/GUIComponent.h"
22 #include "guilib/GUIWindowManager.h"
23 #include "guilib/StereoscopicsManager.h"
24 #include "music/MusicDatabase.h"
25 #include "music/tags/MusicInfoTag.h"
26 #include "settings/AdvancedSettings.h"
27 #include "settings/SettingUtils.h"
28 #include "settings/Settings.h"
29 #include "settings/SettingsComponent.h"
30 #include "utils/EmbeddedArt.h"
31 #include "utils/StringUtils.h"
32 #include "utils/URIUtils.h"
33 #include "utils/log.h"
34 #include "video/VideoDatabase.h"
35 #include "video/VideoInfoTag.h"
36 #include "video/tags/VideoInfoTagLoaderFactory.h"
37
38 #include <algorithm>
39 #include <cstdlib>
40 #include <utility>
41
42 using namespace XFILE;
43 using namespace VIDEO;
44
CThumbExtractor(const CFileItem & item,const std::string & listpath,bool thumb,const std::string & target,int64_t pos,bool fillStreamDetails)45 CThumbExtractor::CThumbExtractor(const CFileItem& item,
46 const std::string& listpath,
47 bool thumb,
48 const std::string& target,
49 int64_t pos,
50 bool fillStreamDetails)
51 {
52 m_listpath = listpath;
53 m_target = target;
54 m_thumb = thumb;
55 m_item = item;
56 m_pos = pos;
57 m_fillStreamDetails = fillStreamDetails;
58
59 if (item.IsVideoDb() && item.HasVideoInfoTag())
60 m_item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath);
61
62 if (m_item.IsStack())
63 m_item.SetPath(CStackDirectory::GetFirstStackedFile(m_item.GetPath()));
64 }
65
66 CThumbExtractor::~CThumbExtractor() = default;
67
operator ==(const CJob * job) const68 bool CThumbExtractor::operator==(const CJob* job) const
69 {
70 if (strcmp(job->GetType(),GetType()) == 0)
71 {
72 const CThumbExtractor* jobExtract = dynamic_cast<const CThumbExtractor*>(job);
73 if (jobExtract && jobExtract->m_listpath == m_listpath
74 && jobExtract->m_target == m_target)
75 return true;
76 }
77 return false;
78 }
79
DoWork()80 bool CThumbExtractor::DoWork()
81 {
82 if (m_item.IsLiveTV()
83 // Due to a pvr addon api design flaw (no support for multiple concurrent streams
84 // per addon instance), pvr recording thumbnail extraction does not work (reliably).
85 || URIUtils::IsPVRRecording(m_item.GetDynPath())
86 || URIUtils::IsUPnP(m_item.GetPath())
87 || URIUtils::IsBluray(m_item.GetPath())
88 || URIUtils::IsPlugin(m_item.GetDynPath()) // plugin path not fully resolved
89 || m_item.IsBDFile()
90 || m_item.IsDVD()
91 || m_item.IsDiscImage()
92 || m_item.IsDVDFile(false, true)
93 || m_item.IsInternetStream()
94 || m_item.IsDiscStub()
95 || m_item.IsPlayList())
96 return false;
97
98 // For HTTP/FTP we only allow extraction when on a LAN
99 if (URIUtils::IsRemote(m_item.GetPath()) &&
100 !URIUtils::IsOnLAN(m_item.GetPath()) &&
101 (URIUtils::IsFTP(m_item.GetPath()) ||
102 URIUtils::IsHTTP(m_item.GetPath())))
103 return false;
104
105 bool result=false;
106 if (m_thumb)
107 {
108 CLog::Log(LOGDEBUG,"%s - trying to extract thumb from video file %s", __FUNCTION__, CURL::GetRedacted(m_item.GetPath()).c_str());
109 // construct the thumb cache file
110 CTextureDetails details;
111 details.file = CTextureCache::GetCacheFile(m_target) + ".jpg";
112 result = CDVDFileInfo::ExtractThumb(m_item, details, m_fillStreamDetails ? &m_item.GetVideoInfoTag()->m_streamDetails : nullptr, m_pos);
113 if (result)
114 {
115 CTextureCache::GetInstance().AddCachedTexture(m_target, details);
116 m_item.SetProperty("HasAutoThumb", true);
117 m_item.SetProperty("AutoThumbImage", m_target);
118 m_item.SetArt("thumb", m_target);
119
120 CVideoInfoTag* info = m_item.GetVideoInfoTag();
121 if (info->m_iDbId > 0 && !info->m_type.empty())
122 {
123 CVideoDatabase db;
124 if (db.Open())
125 {
126 db.SetArtForItem(info->m_iDbId, info->m_type, "thumb", m_item.GetArt("thumb"));
127 db.Close();
128 }
129 }
130 }
131 }
132 else if (!m_item.IsPlugin() &&
133 (!m_item.HasVideoInfoTag() ||
134 !m_item.GetVideoInfoTag()->HasStreamDetails()))
135 {
136 // No tag or no details set, so extract them
137 CLog::Log(LOGDEBUG,"%s - trying to extract filestream details from video file %s", __FUNCTION__, CURL::GetRedacted(m_item.GetPath()).c_str());
138 result = CDVDFileInfo::GetFileStreamDetails(&m_item);
139 }
140
141 if (result)
142 {
143 CVideoInfoTag* info = m_item.GetVideoInfoTag();
144 CVideoDatabase db;
145 if (db.Open())
146 {
147 if (URIUtils::IsStack(m_listpath))
148 {
149 // Don't know the total time of the stack, so set duration to zero to avoid confusion
150 info->m_streamDetails.SetVideoDuration(0, 0);
151
152 // Restore original stack path
153 m_item.SetPath(m_listpath);
154 }
155
156 if (info->m_iFileId < 0)
157 db.SetStreamDetailsForFile(info->m_streamDetails, !info->m_strFileNameAndPath.empty() ? info->m_strFileNameAndPath : m_item.GetPath());
158 else
159 db.SetStreamDetailsForFileId(info->m_streamDetails, info->m_iFileId);
160
161 // overwrite the runtime value if the one from streamdetails is available
162 if (info->m_iDbId > 0
163 && info->GetStaticDuration() != info->GetDuration())
164 {
165 info->SetDuration(info->GetDuration());
166
167 // store the updated information in the database
168 db.SetDetailsForItem(info->m_iDbId, info->m_type, *info, m_item.GetArt());
169 }
170
171 db.Close();
172 }
173 return true;
174 }
175
176 return false;
177 }
178
CVideoThumbLoader()179 CVideoThumbLoader::CVideoThumbLoader() :
180 CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
181 {
182 m_videoDatabase = new CVideoDatabase();
183 }
184
~CVideoThumbLoader()185 CVideoThumbLoader::~CVideoThumbLoader()
186 {
187 StopThread();
188 delete m_videoDatabase;
189 }
190
OnLoaderStart()191 void CVideoThumbLoader::OnLoaderStart()
192 {
193 m_videoDatabase->Open();
194 m_artCache.clear();
195 CThumbLoader::OnLoaderStart();
196 }
197
OnLoaderFinish()198 void CVideoThumbLoader::OnLoaderFinish()
199 {
200 m_videoDatabase->Close();
201 m_artCache.clear();
202 CThumbLoader::OnLoaderFinish();
203 }
204
SetupRarOptions(CFileItem & item,const std::string & path)205 static void SetupRarOptions(CFileItem& item, const std::string& path)
206 {
207 std::string path2(path);
208 if (item.IsVideoDb() && item.HasVideoInfoTag())
209 path2 = item.GetVideoInfoTag()->m_strFileNameAndPath;
210 CURL url(path2);
211 std::string opts = url.GetOptions();
212 if (opts.find("flags") != std::string::npos)
213 return;
214 if (opts.size())
215 opts += "&flags=8";
216 else
217 opts = "?flags=8";
218 url.SetOptions(opts);
219 if (item.IsVideoDb() && item.HasVideoInfoTag())
220 item.GetVideoInfoTag()->m_strFileNameAndPath = url.Get();
221 else
222 item.SetPath(url.Get());
223 g_directoryCache.ClearDirectory(url.GetWithoutFilename());
224 }
225
226 namespace
227 {
GetSettingListAsString(const std::string & settingID)228 std::vector<std::string> GetSettingListAsString(const std::string& settingID)
229 {
230 std::vector<CVariant> values =
231 CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(settingID);
232 std::vector<std::string> result;
233 std::transform(values.begin(), values.end(), std::back_inserter(result),
234 [](const CVariant& s) { return s.asString(); });
235 return result;
236 }
237
238 const std::map<std::string, std::vector<std::string>> artTypeDefaults = {
239 {MediaTypeEpisode, {"thumb"}},
240 {MediaTypeTvShow, {"poster", "fanart", "banner"}},
241 {MediaTypeSeason, {"poster", "fanart", "banner"}},
242 {MediaTypeMovie, {"poster", "fanart"}},
243 {MediaTypeVideoCollection, {"poster", "fanart"}},
244 {MediaTypeMusicVideo, {"poster", "fanart"}},
245 {MediaTypeNone, { "poster", "fanart", "banner", "thumb" }},
246 };
247
248 const std::vector<std::string> artTypeDefaultsFallback = {};
249
GetArtTypeDefault(const std::string & mediaType)250 const std::vector<std::string>& GetArtTypeDefault(const std::string& mediaType)
251 {
252 auto defaults = artTypeDefaults.find(mediaType);
253 if (defaults != artTypeDefaults.end())
254 return defaults->second;
255 return artTypeDefaultsFallback;
256 }
257
258 const std::map<std::string, std::string> artTypeSettings = {
259 {MediaTypeEpisode, CSettings::SETTING_VIDEOLIBRARY_EPISODEART_WHITELIST},
260 {MediaTypeTvShow, CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST},
261 {MediaTypeSeason, CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST},
262 {MediaTypeMovie, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST},
263 {MediaTypeVideoCollection, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST},
264 {MediaTypeMusicVideo, CSettings::SETTING_VIDEOLIBRARY_MUSICVIDEOART_WHITELIST},
265 };
266 } // namespace
267
GetArtTypes(const std::string & type)268 std::vector<std::string> CVideoThumbLoader::GetArtTypes(const std::string &type)
269 {
270 int artworkLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
271 CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL);
272 if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE)
273 {
274 return {};
275 }
276
277 std::vector<std::string> result = GetArtTypeDefault(type);
278 if (artworkLevel != CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_CUSTOM)
279 {
280 return result;
281 }
282
283 auto settings = artTypeSettings.find(type);
284 if (settings == artTypeSettings.end())
285 return result;
286
287 for (auto& artType : GetSettingListAsString(settings->second))
288 {
289 if (find(result.begin(), result.end(), artType) == result.end())
290 result.push_back(artType);
291 }
292
293 return result;
294 }
295
IsValidArtType(const std::string & potentialArtType)296 bool CVideoThumbLoader::IsValidArtType(const std::string& potentialArtType)
297 {
298 return !potentialArtType.empty() && potentialArtType.length() <= 25 &&
299 std::find_if_not(
300 potentialArtType.begin(), potentialArtType.end(),
301 StringUtils::isasciialphanum
302 ) == potentialArtType.end();
303 }
304
IsArtTypeInWhitelist(const std::string & artType,const std::vector<std::string> & whitelist,bool exact)305 bool CVideoThumbLoader::IsArtTypeInWhitelist(const std::string& artType, const std::vector<std::string>& whitelist, bool exact)
306 {
307 // whitelist contains art "families", 'fanart' also matches 'fanart1', 'fanart2', and so on
308 std::string compareArtType = artType;
309 if (!exact)
310 StringUtils::TrimRight(compareArtType, "0123456789");
311
312 return std::find(whitelist.begin(), whitelist.end(), compareArtType) != whitelist.end();
313 }
314
315 /**
316 * Look for a thumbnail for pItem. If one does not exist, look for an autogenerated
317 * thumbnail. If that does not exist, attempt to autogenerate one. Finally, check
318 * for the existence of fanart and set properties accordingly.
319 * @return: true if pItem has been modified
320 */
LoadItem(CFileItem * pItem)321 bool CVideoThumbLoader::LoadItem(CFileItem* pItem)
322 {
323 bool result = LoadItemCached(pItem);
324 result |= LoadItemLookup(pItem);
325
326 return result;
327 }
328
LoadItemCached(CFileItem * pItem)329 bool CVideoThumbLoader::LoadItemCached(CFileItem* pItem)
330 {
331 if (pItem->m_bIsShareOrDrive
332 || pItem->IsParentFolder())
333 return false;
334
335 m_videoDatabase->Open();
336
337 if (!pItem->HasVideoInfoTag() || !pItem->GetVideoInfoTag()->HasStreamDetails()) // no stream details
338 {
339 if ((pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_iFileId >= 0) // file (or maybe folder) is in the database
340 || (!pItem->m_bIsFolder && pItem->IsVideo())) // Some other video file for which we haven't yet got any database details
341 {
342 if (m_videoDatabase->GetStreamDetails(*pItem))
343 pItem->SetInvalid();
344 }
345 }
346
347 // video db items normally have info in the database
348 if (pItem->HasVideoInfoTag() && !pItem->GetProperty("libraryartfilled").asBoolean())
349 {
350 FillLibraryArt(*pItem);
351
352 if (!pItem->GetVideoInfoTag()->m_type.empty() &&
353 pItem->GetVideoInfoTag()->m_type != MediaTypeMovie &&
354 pItem->GetVideoInfoTag()->m_type != MediaTypeTvShow &&
355 pItem->GetVideoInfoTag()->m_type != MediaTypeEpisode &&
356 pItem->GetVideoInfoTag()->m_type != MediaTypeMusicVideo)
357 {
358 m_videoDatabase->Close();
359 return true; // nothing else to be done
360 }
361 }
362
363 // if we have no art, look for it all
364 std::map<std::string, std::string> artwork = pItem->GetArt();
365 if (artwork.empty())
366 {
367 std::vector<std::string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
368 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
369 artTypes.emplace_back("thumb"); // always look for "thumb" art for files
370 for (std::vector<std::string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
371 {
372 std::string type = *i;
373 std::string art = GetCachedImage(*pItem, type);
374 if (!art.empty())
375 artwork.insert(std::make_pair(type, art));
376 }
377 pItem->AppendArt(artwork);
378 }
379
380 m_videoDatabase->Close();
381
382 return true;
383 }
384
LoadItemLookup(CFileItem * pItem)385 bool CVideoThumbLoader::LoadItemLookup(CFileItem* pItem)
386 {
387 if (pItem->m_bIsShareOrDrive || pItem->IsParentFolder() || pItem->GetPath() == "add")
388 return false;
389
390 if (pItem->HasVideoInfoTag() &&
391 !pItem->GetVideoInfoTag()->m_type.empty() &&
392 pItem->GetVideoInfoTag()->m_type != MediaTypeMovie &&
393 pItem->GetVideoInfoTag()->m_type != MediaTypeTvShow &&
394 pItem->GetVideoInfoTag()->m_type != MediaTypeEpisode &&
395 pItem->GetVideoInfoTag()->m_type != MediaTypeMusicVideo)
396 return false; // Nothing to do here
397
398 DetectAndAddMissingItemData(*pItem);
399
400 m_videoDatabase->Open();
401
402 std::map<std::string, std::string> artwork = pItem->GetArt();
403 std::vector<std::string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
404 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
405 artTypes.emplace_back("thumb"); // always look for "thumb" art for files
406 for (std::vector<std::string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
407 {
408 std::string type = *i;
409 if (!pItem->HasArt(type))
410 {
411 std::string art = GetLocalArt(*pItem, type, type=="fanart");
412 if (!art.empty()) // cache it
413 {
414 SetCachedImage(*pItem, type, art);
415 CTextureCache::GetInstance().BackgroundCacheImage(art);
416 artwork.insert(std::make_pair(type, art));
417 }
418 else
419 {
420 // If nothing was found, try embedded art
421 if (pItem->HasVideoInfoTag() && !pItem->GetVideoInfoTag()->m_coverArt.empty())
422 {
423 for (auto& it : pItem->GetVideoInfoTag()->m_coverArt)
424 {
425 if (it.m_type == type)
426 {
427 art = CTextureUtils::GetWrappedImageURL(pItem->GetPath(), "video_" + type);
428 artwork.insert(std::make_pair(type, art));
429 }
430 }
431 }
432 }
433 }
434 }
435 pItem->AppendArt(artwork);
436
437 // We can only extract flags/thumbs for file-like items
438 if (!pItem->m_bIsFolder && pItem->IsVideo())
439 {
440 // An auto-generated thumb may have been cached on a different device - check we have it here
441 std::string url = pItem->GetArt("thumb");
442 if (StringUtils::StartsWith(url, "image://video@") && !CTextureCache::GetInstance().HasCachedImage(url))
443 pItem->SetArt("thumb", "");
444
445 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
446 if (!pItem->HasArt("thumb"))
447 {
448 // create unique thumb for auto generated thumbs
449 std::string thumbURL = GetEmbeddedThumbURL(*pItem);
450 if (CTextureCache::GetInstance().HasCachedImage(thumbURL))
451 {
452 CTextureCache::GetInstance().BackgroundCacheImage(thumbURL);
453 pItem->SetProperty("HasAutoThumb", true);
454 pItem->SetProperty("AutoThumbImage", thumbURL);
455 pItem->SetArt("thumb", thumbURL);
456
457 if (pItem->HasVideoInfoTag())
458 {
459 // Item has cached autogen image but no art entry. Save it to db.
460 CVideoInfoTag* info = pItem->GetVideoInfoTag();
461 if (info->m_iDbId > 0 && !info->m_type.empty())
462 m_videoDatabase->SetArtForItem(info->m_iDbId, info->m_type, "thumb", thumbURL);
463 }
464 }
465 else if (settings->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTTHUMB) &&
466 settings->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS) &&
467 settings->GetInt(CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL) !=
468 CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE)
469 {
470 CFileItem item(*pItem);
471 std::string path(item.GetPath());
472 if (URIUtils::IsInRAR(item.GetPath()))
473 SetupRarOptions(item,path);
474
475 CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
476 AddJob(extract);
477
478 m_videoDatabase->Close();
479 return true;
480 }
481 }
482
483 // flag extraction
484 if (settings->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS) &&
485 (!pItem->HasVideoInfoTag() ||
486 !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
487 {
488 CFileItem item(*pItem);
489 std::string path(item.GetPath());
490 if (URIUtils::IsInRAR(item.GetPath()))
491 SetupRarOptions(item,path);
492 CThumbExtractor* extract = new CThumbExtractor(item,path,false);
493 AddJob(extract);
494 }
495 }
496
497 m_videoDatabase->Close();
498 return true;
499 }
500
FillLibraryArt(CFileItem & item)501 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
502 {
503 CVideoInfoTag &tag = *item.GetVideoInfoTag();
504 std::map<std::string, std::string> artwork;
505 // Video item can be an album - either a
506 // a) search result with full details including music library album id, or
507 // b) musicvideo album that needs matching to a music album, storing id as well as fetch art.
508 if (tag.m_type == MediaTypeAlbum)
509 {
510 int idAlbum = -1;
511 if (item.HasMusicInfoTag()) // Album is a search result
512 idAlbum = item.GetMusicInfoTag()->GetAlbumId();
513 CMusicDatabase database;
514 database.Open();
515 if (idAlbum < 0 && !tag.m_strAlbum.empty() &&
516 item.GetProperty("musicvideomediatype") == MediaTypeAlbum)
517 {
518 // Musicvideo album - try to match album in music db on artist(s) and album name.
519 // Get review if available and save the matching music library album id.
520 std::string strArtist = StringUtils::Join(
521 tag.m_artist,
522 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
523 std::string strReview;
524 if (database.GetMatchingMusicVideoAlbum(
525 tag.m_strAlbum, strArtist, idAlbum, strReview))
526 {
527 item.SetProperty("album_musicid", idAlbum);
528 item.SetProperty("album_description", strReview);
529 }
530 }
531 // Get album art only (not related artist art)
532 if (database.GetArtForItem(idAlbum, MediaTypeAlbum, artwork))
533 item.SetArt(artwork);
534 database.Close();
535 }
536 else if (tag.m_type == "actor" && !tag.m_artist.empty() &&
537 item.GetProperty("musicvideomediatype") == MediaTypeArtist)
538 {
539 // Try to match artist in music db on name, get bio if available and fetch artist art
540 // Save the matching music library artist id.
541 CMusicDatabase database;
542 database.Open();
543 CArtist artist;
544 int idArtist = database.GetArtistByName(tag.m_artist[0]);
545 if (idArtist > 0)
546 {
547 database.GetArtist(idArtist, artist);
548 tag.m_strPlot = artist.strBiography;
549 item.SetProperty("artist_musicid", idArtist);
550 }
551 if (database.GetArtForItem(idArtist, MediaTypeArtist, artwork))
552 item.SetArt(artwork);
553 database.Close();
554 }
555
556 if (tag.m_iDbId > -1 && !tag.m_type.empty())
557 {
558 m_videoDatabase->Open();
559 if (m_videoDatabase->GetArtForItem(tag.m_iDbId, tag.m_type, artwork))
560 item.AppendArt(artwork);
561 else if (tag.m_type == "actor" && !tag.m_artist.empty() &&
562 item.GetProperty("musicvideomediatype") != MediaTypeArtist)
563 {
564 // Fallback to music library for actors without art
565 //! @todo Is m_artist set other than musicvideo? Remove this fallback if not.
566 CMusicDatabase database;
567 database.Open();
568 int idArtist = database.GetArtistByName(item.GetLabel());
569 if (database.GetArtForItem(idArtist, MediaTypeArtist, artwork))
570 item.SetArt(artwork);
571 database.Close();
572 }
573
574 if (tag.m_type == MediaTypeEpisode || tag.m_type == MediaTypeSeason)
575 {
576 // For episodes and seasons, we want to set fanart for that of the show
577 if (!item.HasArt("tvshow.fanart") && tag.m_iIdShow >= 0)
578 {
579 const ArtMap& artmap = GetArtFromCache(MediaTypeTvShow, tag.m_iIdShow);
580 if (!artmap.empty())
581 {
582 item.AppendArt(artmap, MediaTypeTvShow);
583 item.SetArtFallback("fanart", "tvshow.fanart");
584 item.SetArtFallback("tvshow.thumb", "tvshow.poster");
585 }
586 }
587
588 if (tag.m_type == MediaTypeEpisode && !item.HasArt("season.poster") && tag.m_iSeason > -1)
589 {
590 const ArtMap& artmap = GetArtFromCache(MediaTypeSeason, tag.m_iIdSeason);
591 if (!artmap.empty())
592 item.AppendArt(artmap, MediaTypeSeason);
593 }
594 }
595 else if (tag.m_type == MediaTypeMovie && tag.m_set.id >= 0 && !item.HasArt("set.fanart"))
596 {
597 const ArtMap& artmap = GetArtFromCache(MediaTypeVideoCollection, tag.m_set.id);
598 if (!artmap.empty())
599 item.AppendArt(artmap, MediaTypeVideoCollection);
600 }
601 m_videoDatabase->Close();
602 }
603 item.SetProperty("libraryartfilled", true);
604 return !item.GetArt().empty();
605 }
606
FillThumb(CFileItem & item)607 bool CVideoThumbLoader::FillThumb(CFileItem &item)
608 {
609 if (item.HasArt("thumb"))
610 return true;
611 std::string thumb = GetCachedImage(item, "thumb");
612 if (thumb.empty())
613 {
614 thumb = GetLocalArt(item, "thumb");
615 if (!thumb.empty())
616 SetCachedImage(item, "thumb", thumb);
617 }
618 if (!thumb.empty())
619 item.SetArt("thumb", thumb);
620 else
621 {
622 // If nothing was found, try embedded art
623 if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->m_coverArt.empty())
624 {
625 for (auto& it : item.GetVideoInfoTag()->m_coverArt)
626 {
627 if (it.m_type == "thumb")
628 {
629 thumb = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_" + it.m_type);
630 item.SetArt(it.m_type, thumb);
631 }
632 }
633 }
634 }
635
636 return !thumb.empty();
637 }
638
GetLocalArt(const CFileItem & item,const std::string & type,bool checkFolder)639 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
640 {
641 if (item.SkipLocalArt())
642 return "";
643
644 /* Cache directory for (sub) folders on streamed filesystems. We need to do this
645 else entering (new) directories from the app thread becomes much slower. This
646 is caused by the fact that Curl Stat/Exist() is really slow and that the
647 thumbloader thread accesses the streamed filesystem at the same time as the
648 App thread and the latter has to wait for it.
649 */
650 if (item.m_bIsFolder && (item.IsInternetStream(true) || CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheBufferMode == CACHE_BUFFER_MODE_ALL))
651 {
652 CFileItemList items; // Dummy list
653 CDirectory::GetDirectory(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
654 }
655
656 std::string art;
657 if (!type.empty())
658 {
659 art = item.FindLocalArt(type + ".jpg", checkFolder);
660 if (art.empty())
661 art = item.FindLocalArt(type + ".png", checkFolder);
662 }
663 if (art.empty() && (type.empty() || type == "thumb"))
664 { // backward compatibility
665 art = item.FindLocalArt("", false);
666 if (art.empty() && (checkFolder || (item.m_bIsFolder && !item.IsFileFolder()) || item.IsOpticalMediaFile()))
667 { // try movie.tbn
668 art = item.FindLocalArt("movie.tbn", true);
669 if (art.empty()) // try folder.jpg
670 art = item.FindLocalArt("folder.jpg", true);
671 }
672 }
673
674 return art;
675 }
676
GetEmbeddedThumbURL(const CFileItem & item)677 std::string CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
678 {
679 std::string path(item.GetPath());
680 if (item.IsVideoDb() && item.HasVideoInfoTag())
681 path = item.GetVideoInfoTag()->m_strFileNameAndPath;
682 if (URIUtils::IsStack(path))
683 path = CStackDirectory::GetFirstStackedFile(path);
684
685 return CTextureUtils::GetWrappedImageURL(path, "video");
686 }
687
GetEmbeddedThumb(const std::string & path,const std::string & type,EmbeddedArt & art)688 bool CVideoThumbLoader::GetEmbeddedThumb(const std::string& path,
689 const std::string& type, EmbeddedArt& art)
690 {
691 CFileItem item(path, false);
692 std::unique_ptr<IVideoInfoTagLoader> pLoader;
693 pLoader.reset(CVideoInfoTagLoaderFactory::CreateLoader(item,ADDON::ScraperPtr(),false));
694 CVideoInfoTag tag;
695 std::vector<EmbeddedArt> artv;
696 if (pLoader)
697 pLoader->Load(tag, false, &artv);
698
699 for (const EmbeddedArt& it : artv)
700 {
701 if (it.m_type == type)
702 {
703 art = it;
704 break;
705 }
706 }
707
708 return !art.Empty();
709 }
710
OnJobComplete(unsigned int jobID,bool success,CJob * job)711 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
712 {
713 if (success)
714 {
715 CThumbExtractor* loader = static_cast<CThumbExtractor*>(job);
716 loader->m_item.SetPath(loader->m_listpath);
717
718 if (m_pObserver)
719 m_pObserver->OnItemLoaded(&loader->m_item);
720 CFileItemPtr pItem(new CFileItem(loader->m_item));
721 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
722 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
723 }
724 CJobQueue::OnJobComplete(jobID, success, job);
725 }
726
DetectAndAddMissingItemData(CFileItem & item)727 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
728 {
729 if (item.m_bIsFolder) return;
730
731 if (item.HasVideoInfoTag())
732 {
733 CStreamDetails& details = item.GetVideoInfoTag()->m_streamDetails;
734
735 // add audio language properties
736 for (int i = 1; i <= details.GetAudioStreamCount(); i++)
737 {
738 std::string index = StringUtils::Format("%i", i);
739 item.SetProperty("AudioChannels." + index, details.GetAudioChannels(i));
740 item.SetProperty("AudioCodec." + index, details.GetAudioCodec(i).c_str());
741 item.SetProperty("AudioLanguage." + index, details.GetAudioLanguage(i).c_str());
742 }
743
744 // add subtitle language properties
745 for (int i = 1; i <= details.GetSubtitleStreamCount(); i++)
746 {
747 std::string index = StringUtils::Format("%i", i);
748 item.SetProperty("SubtitleLanguage." + index, details.GetSubtitleLanguage(i).c_str());
749 }
750 }
751
752 const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager();
753
754 std::string stereoMode;
755
756 // detect stereomode for videos
757 if (item.HasVideoInfoTag())
758 stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
759
760 if (stereoMode.empty())
761 {
762 std::string path = item.GetPath();
763 if (item.IsVideoDb() && item.HasVideoInfoTag())
764 path = item.GetVideoInfoTag()->GetPath();
765
766 // check for custom stereomode setting in video settings
767 CVideoSettings itemVideoSettings;
768 m_videoDatabase->Open();
769 if (m_videoDatabase->GetVideoSettings(item, itemVideoSettings) && itemVideoSettings.m_StereoMode != RENDER_STEREO_MODE_OFF)
770 {
771 stereoMode = CStereoscopicsManager::ConvertGuiStereoModeToString(static_cast<RENDER_STEREO_MODE>(itemVideoSettings.m_StereoMode));
772 }
773 m_videoDatabase->Close();
774
775 // still empty, try grabbing from filename
776 //! @todo in case of too many false positives due to using the full path, extract the filename only using string utils
777 if (stereoMode.empty())
778 stereoMode = stereoscopicsManager.DetectStereoModeByString(path);
779 }
780
781 if (!stereoMode.empty())
782 item.SetProperty("stereomode", CStereoscopicsManager::NormalizeStereoMode(stereoMode));
783 }
784
GetArtFromCache(const std::string & mediaType,const int id)785 const ArtMap& CVideoThumbLoader::GetArtFromCache(const std::string &mediaType, const int id)
786 {
787 std::pair<MediaType, int> key = std::make_pair(mediaType, id);
788 auto it = m_artCache.find(key);
789 if (it == m_artCache.end())
790 {
791 ArtMap newart;
792 m_videoDatabase->GetArtForItem(id, mediaType, newart);
793 it = m_artCache.insert(std::make_pair(key, std::move(newart))).first;
794 }
795 return it->second;
796 }
797