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