2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "VideoThumbLoader.h"
12 #include "ServiceBroker.h"
13 #include "TextureCache.h"
15 #include "cores/VideoPlayer/DVDFileInfo.h"
16 #include "cores/VideoSettings.h"
17 #include "filesystem/Directory.h"
18 #include "filesystem/File.h"
19 #include "filesystem/StackDirectory.h"
20 #include "guilib/GUIComponent.h"
21 #include "guilib/StereoscopicsManager.h"
22 #include "music/MusicDatabase.h"
23 #include "music/tags/MusicInfoTag.h"
24 #include "settings/AdvancedSettings.h"
25 #include "settings/SettingUtils.h"
26 #include "settings/Settings.h"
27 #include "settings/SettingsComponent.h"
28 #include "utils/StringUtils.h"
29 #include "utils/URIUtils.h"
30 #include "utils/log.h"
31 #include "video/VideoDatabase.h"
32 #include "video/VideoInfoTag.h"
38 using namespace XFILE
;
39 using namespace VIDEO
;
41 CVideoThumbLoader::CVideoThumbLoader() : CThumbLoader()
43 m_videoDatabase
= new CVideoDatabase();
46 CVideoThumbLoader::~CVideoThumbLoader()
49 delete m_videoDatabase
;
52 void CVideoThumbLoader::OnLoaderStart()
54 m_videoDatabase
->Open();
56 CThumbLoader::OnLoaderStart();
59 void CVideoThumbLoader::OnLoaderFinish()
61 m_videoDatabase
->Close();
63 CThumbLoader::OnLoaderFinish();
68 std::vector
<std::string
> GetSettingListAsString(const std::string
& settingID
)
70 std::vector
<CVariant
> values
=
71 CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(settingID
);
72 std::vector
<std::string
> result
;
73 std::transform(values
.begin(), values
.end(), std::back_inserter(result
),
74 [](const CVariant
& s
) { return s
.asString(); });
78 const std::map
<std::string
, std::vector
<std::string
>> artTypeDefaults
= {
79 {MediaTypeEpisode
, {"thumb"}},
80 {MediaTypeTvShow
, {"poster", "fanart", "banner"}},
81 {MediaTypeSeason
, {"poster", "fanart", "banner"}},
82 {MediaTypeMovie
, {"poster", "fanart"}},
83 {MediaTypeVideoCollection
, {"poster", "fanart"}},
84 {MediaTypeMusicVideo
, {"poster", "fanart"}},
85 {MediaTypeVideoVersion
, {"poster", "fanart", "banner", "thumb"}},
86 {MediaTypeNone
, {"poster", "fanart", "banner", "thumb"}},
89 const std::vector
<std::string
> artTypeDefaultsFallback
= {};
91 const std::vector
<std::string
>& GetArtTypeDefault(const std::string
& mediaType
)
93 auto defaults
= artTypeDefaults
.find(mediaType
);
94 if (defaults
!= artTypeDefaults
.end())
95 return defaults
->second
;
96 return artTypeDefaultsFallback
;
99 const std::map
<std::string
, std::string
> artTypeSettings
= {
100 {MediaTypeEpisode
, CSettings::SETTING_VIDEOLIBRARY_EPISODEART_WHITELIST
},
101 {MediaTypeTvShow
, CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST
},
102 {MediaTypeSeason
, CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST
},
103 {MediaTypeMovie
, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST
},
104 {MediaTypeVideoCollection
, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST
},
105 {MediaTypeMusicVideo
, CSettings::SETTING_VIDEOLIBRARY_MUSICVIDEOART_WHITELIST
},
106 {MediaTypeVideoVersion
, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST
},
110 std::vector
<std::string
> CVideoThumbLoader::GetArtTypes(const std::string
&type
)
112 int artworkLevel
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
113 CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL
);
114 if (artworkLevel
== CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE
)
119 std::vector
<std::string
> result
= GetArtTypeDefault(type
);
120 if (artworkLevel
!= CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_CUSTOM
)
125 auto settings
= artTypeSettings
.find(type
);
126 if (settings
== artTypeSettings
.end())
129 for (auto& artType
: GetSettingListAsString(settings
->second
))
131 if (find(result
.begin(), result
.end(), artType
) == result
.end())
132 result
.push_back(artType
);
138 bool CVideoThumbLoader::IsValidArtType(const std::string
& potentialArtType
)
140 return !potentialArtType
.empty() && potentialArtType
.length() <= 25 &&
142 potentialArtType
.begin(), potentialArtType
.end(),
143 StringUtils::isasciialphanum
144 ) == potentialArtType
.end();
147 bool CVideoThumbLoader::IsArtTypeInWhitelist(const std::string
& artType
, const std::vector
<std::string
>& whitelist
, bool exact
)
149 // whitelist contains art "families", 'fanart' also matches 'fanart1', 'fanart2', and so on
150 std::string compareArtType
= artType
;
152 StringUtils::TrimRight(compareArtType
, "0123456789");
154 return std::find(whitelist
.begin(), whitelist
.end(), compareArtType
) != whitelist
.end();
158 * Look for a thumbnail for pItem. If one does not exist, look for an autogenerated
159 * thumbnail. If that does not exist, attempt to autogenerate one. Finally, check
160 * for the existence of fanart and set properties accordingly.
161 * @return: true if pItem has been modified
163 bool CVideoThumbLoader::LoadItem(CFileItem
* pItem
)
165 bool result
= LoadItemCached(pItem
);
166 result
|= LoadItemLookup(pItem
);
171 bool CVideoThumbLoader::LoadItemCached(CFileItem
* pItem
)
173 if (pItem
->m_bIsShareOrDrive
174 || pItem
->IsParentFolder())
177 m_videoDatabase
->Open();
179 if (!pItem
->HasVideoInfoTag() || !pItem
->GetVideoInfoTag()->HasStreamDetails()) // no stream details
181 if ((pItem
->HasVideoInfoTag() && pItem
->GetVideoInfoTag()->m_iFileId
>= 0) // file (or maybe folder) is in the database
182 || (!pItem
->m_bIsFolder
&& pItem
->IsVideo())) // Some other video file for which we haven't yet got any database details
184 if (m_videoDatabase
->GetStreamDetails(*pItem
))
189 // video db items normally have info in the database
190 if (pItem
->HasVideoInfoTag() && !pItem
->GetProperty("libraryartfilled").asBoolean())
192 FillLibraryArt(*pItem
);
194 if (!pItem
->GetVideoInfoTag()->m_type
.empty() &&
195 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeMovie
&&
196 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeTvShow
&&
197 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeEpisode
&&
198 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeMusicVideo
&&
199 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeVideoVersion
)
201 m_videoDatabase
->Close();
202 return true; // nothing else to be done
206 // if we have no art, look for it all
207 std::map
<std::string
, std::string
> artwork
= pItem
->GetArt();
210 std::vector
<std::string
> artTypes
= GetArtTypes(pItem
->HasVideoInfoTag() ? pItem
->GetVideoInfoTag()->m_type
: "");
211 if (find(artTypes
.begin(), artTypes
.end(), "thumb") == artTypes
.end())
212 artTypes
.emplace_back("thumb"); // always look for "thumb" art for files
213 for (std::vector
<std::string
>::const_iterator i
= artTypes
.begin(); i
!= artTypes
.end(); ++i
)
215 std::string type
= *i
;
216 std::string art
= GetCachedImage(*pItem
, type
);
218 artwork
.insert(std::make_pair(type
, art
));
220 pItem
->AppendArt(artwork
);
223 m_videoDatabase
->Close();
228 bool CVideoThumbLoader::LoadItemLookup(CFileItem
* pItem
)
230 if (pItem
->m_bIsShareOrDrive
|| pItem
->IsParentFolder() || pItem
->GetPath() == "add")
233 if (pItem
->HasVideoInfoTag() && !pItem
->GetVideoInfoTag()->m_type
.empty() &&
234 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeMovie
&&
235 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeTvShow
&&
236 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeEpisode
&&
237 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeMusicVideo
&&
238 pItem
->GetVideoInfoTag()->m_type
!= MediaTypeVideoVersion
)
239 return false; // Nothing to do here
241 m_videoDatabase
->Open();
243 std::map
<std::string
, std::string
> artwork
= pItem
->GetArt();
244 std::vector
<std::string
> artTypes
= GetArtTypes(pItem
->HasVideoInfoTag() ? pItem
->GetVideoInfoTag()->m_type
: "");
245 if (find(artTypes
.begin(), artTypes
.end(), "thumb") == artTypes
.end())
246 artTypes
.emplace_back("thumb"); // always look for "thumb" art for files
247 for (std::vector
<std::string
>::const_iterator i
= artTypes
.begin(); i
!= artTypes
.end(); ++i
)
249 std::string type
= *i
;
250 if (!pItem
->HasArt(type
))
252 std::string art
= GetLocalArt(*pItem
, type
, type
=="fanart");
253 if (!art
.empty()) // cache it
255 SetCachedImage(*pItem
, type
, art
);
256 CServiceBroker::GetTextureCache()->BackgroundCacheImage(art
);
257 artwork
.insert(std::make_pair(type
, art
));
261 // If nothing was found, try embedded art
262 if (pItem
->HasVideoInfoTag() && !pItem
->GetVideoInfoTag()->m_coverArt
.empty())
264 for (auto& it
: pItem
->GetVideoInfoTag()->m_coverArt
)
266 if (it
.m_type
== type
)
268 art
= CTextureUtils::GetWrappedImageURL(pItem
->GetPath(), "video_" + type
);
269 artwork
.insert(std::make_pair(type
, art
));
276 pItem
->AppendArt(artwork
);
278 // We can only extract flags/thumbs for file-like items
279 if (!pItem
->m_bIsFolder
&& pItem
->IsVideo())
281 const std::shared_ptr
<CSettings
> settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
282 if (!pItem
->HasArt("thumb"))
284 std::string thumbURL
= GetEmbeddedThumbURL(*pItem
);
285 if (CDVDFileInfo::CanExtract(*pItem
) &&
286 settings
->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTTHUMB
) &&
287 settings
->GetInt(CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL
) !=
288 CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE
)
290 pItem
->SetArt("thumb", thumbURL
);
292 if (pItem
->HasVideoInfoTag())
294 CVideoInfoTag
* info
= pItem
->GetVideoInfoTag();
295 if (info
->m_iDbId
> 0 && !info
->m_type
.empty())
296 m_videoDatabase
->SetArtForItem(info
->m_iDbId
, info
->m_type
, "thumb", thumbURL
);
301 // flag extraction mostly for non-library items - should end up somewhere else,
302 // like a VideoInfoLoader if it existed
303 if (settings
->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS
) &&
304 CDVDFileInfo::CanExtract(*pItem
) &&
305 (!pItem
->HasVideoInfoTag() || !pItem
->GetVideoInfoTag()->HasStreamDetails()))
307 // No tag or no details set, so extract them
308 CLog::LogF(LOGDEBUG
, "trying to extract filestream details from video file {}",
309 CURL::GetRedacted(pItem
->GetPath()));
310 if (CDVDFileInfo::GetFileStreamDetails(pItem
))
312 CVideoInfoTag
* info
= pItem
->GetVideoInfoTag();
313 m_videoDatabase
->BeginTransaction();
315 if (info
->m_iFileId
< 0)
316 m_videoDatabase
->SetStreamDetailsForFile(
317 info
->m_streamDetails
,
318 !info
->m_strFileNameAndPath
.empty() ? info
->m_strFileNameAndPath
: pItem
->GetPath());
320 m_videoDatabase
->SetStreamDetailsForFileId(info
->m_streamDetails
, info
->m_iFileId
);
322 // overwrite the runtime value if the one from streamdetails is available
323 if (info
->m_iDbId
> 0 && info
->GetStaticDuration() != info
->GetDuration())
325 info
->SetDuration(info
->GetDuration());
327 // store the updated information in the database
328 m_videoDatabase
->SetDetailsForItem(info
->m_iDbId
, info
->m_type
, *info
, pItem
->GetArt());
331 m_videoDatabase
->CommitTransaction();
335 DetectAndAddMissingItemData(*pItem
);
337 m_videoDatabase
->Close();
341 bool CVideoThumbLoader::FillLibraryArt(CFileItem
&item
)
343 CVideoInfoTag
&tag
= *item
.GetVideoInfoTag();
344 std::map
<std::string
, std::string
> artwork
;
345 // Video item can be an album - either a
346 // a) search result with full details including music library album id, or
347 // b) musicvideo album that needs matching to a music album, storing id as well as fetch art.
348 if (tag
.m_type
== MediaTypeAlbum
)
351 if (item
.HasMusicInfoTag()) // Album is a search result
352 idAlbum
= item
.GetMusicInfoTag()->GetAlbumId();
353 CMusicDatabase database
;
355 if (idAlbum
< 0 && !tag
.m_strAlbum
.empty() &&
356 item
.GetProperty("musicvideomediatype") == MediaTypeAlbum
)
358 // Musicvideo album - try to match album in music db on artist(s) and album name.
359 // Get review if available and save the matching music library album id.
360 std::string strArtist
= StringUtils::Join(
362 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
363 std::string strReview
;
364 if (database
.GetMatchingMusicVideoAlbum(
365 tag
.m_strAlbum
, strArtist
, idAlbum
, strReview
))
367 item
.SetProperty("album_musicid", idAlbum
);
368 item
.SetProperty("album_description", strReview
);
371 // Get album art only (not related artist art)
372 if (database
.GetArtForItem(idAlbum
, MediaTypeAlbum
, artwork
))
373 item
.SetArt(artwork
);
376 else if (tag
.m_type
== "actor" && !tag
.m_artist
.empty() &&
377 item
.GetProperty("musicvideomediatype") == MediaTypeArtist
)
379 // Try to match artist in music db on name, get bio if available and fetch artist art
380 // Save the matching music library artist id.
381 CMusicDatabase database
;
384 int idArtist
= database
.GetArtistByName(tag
.m_artist
[0]);
387 database
.GetArtist(idArtist
, artist
);
388 tag
.m_strPlot
= artist
.strBiography
;
389 item
.SetProperty("artist_musicid", idArtist
);
391 if (database
.GetArtForItem(idArtist
, MediaTypeArtist
, artwork
))
392 item
.SetArt(artwork
);
396 if (tag
.m_iDbId
> -1 && !tag
.m_type
.empty())
398 m_videoDatabase
->Open();
400 if (tag
.GetAssetInfo().GetId() >= 0)
402 if (m_videoDatabase
->GetArtForAsset(tag
.m_iFileId
, tag
.m_iDbId
, tag
.m_type
, artwork
))
403 item
.AppendArt(artwork
);
405 else if (m_videoDatabase
->GetArtForItem(tag
.m_iDbId
, tag
.m_type
, artwork
))
407 item
.AppendArt(artwork
);
409 else if (tag
.m_type
== "actor" && !tag
.m_artist
.empty() &&
410 item
.GetProperty("musicvideomediatype") != MediaTypeArtist
)
412 // Fallback to music library for actors without art
413 //! @todo Is m_artist set other than musicvideo? Remove this fallback if not.
414 CMusicDatabase database
;
416 int idArtist
= database
.GetArtistByName(item
.GetLabel());
417 if (database
.GetArtForItem(idArtist
, MediaTypeArtist
, artwork
))
418 item
.SetArt(artwork
);
422 if (tag
.m_type
== MediaTypeEpisode
|| tag
.m_type
== MediaTypeSeason
)
424 // For episodes and seasons, we want to set fanart for that of the show
425 if (!item
.HasArt("tvshow.fanart") && tag
.m_iIdShow
>= 0)
427 const ArtMap
& artmap
= GetArtFromCache(MediaTypeTvShow
, tag
.m_iIdShow
);
430 item
.AppendArt(artmap
, MediaTypeTvShow
);
431 item
.SetArtFallback("fanart", "tvshow.fanart");
432 item
.SetArtFallback("tvshow.thumb", "tvshow.poster");
436 if (tag
.m_type
== MediaTypeEpisode
&& !item
.HasArt("season.poster") && tag
.m_iSeason
> -1)
438 const ArtMap
& artmap
= GetArtFromCache(MediaTypeSeason
, tag
.m_iIdSeason
);
440 item
.AppendArt(artmap
, MediaTypeSeason
);
443 else if (tag
.m_type
== MediaTypeMovie
&& tag
.m_set
.id
>= 0 && !item
.HasArt("set.fanart"))
445 const ArtMap
& artmap
= GetArtFromCache(MediaTypeVideoCollection
, tag
.m_set
.id
);
447 item
.AppendArt(artmap
, MediaTypeVideoCollection
);
449 m_videoDatabase
->Close();
451 item
.SetProperty("libraryartfilled", true);
452 return !item
.GetArt().empty();
455 bool CVideoThumbLoader::FillThumb(CFileItem
&item
)
457 if (item
.HasArt("thumb"))
459 std::string thumb
= GetCachedImage(item
, "thumb");
462 thumb
= GetLocalArt(item
, "thumb");
464 SetCachedImage(item
, "thumb", thumb
);
467 item
.SetArt("thumb", thumb
);
470 // If nothing was found, try embedded art
471 if (item
.HasVideoInfoTag() && !item
.GetVideoInfoTag()->m_coverArt
.empty())
473 for (auto& it
: item
.GetVideoInfoTag()->m_coverArt
)
475 if (it
.m_type
== "thumb")
477 thumb
= CTextureUtils::GetWrappedImageURL(item
.GetPath(), "video_" + it
.m_type
);
478 item
.SetArt(it
.m_type
, thumb
);
484 return !thumb
.empty();
487 std::string
CVideoThumbLoader::GetLocalArt(const CFileItem
&item
, const std::string
&type
, bool checkFolder
)
489 if (item
.SkipLocalArt())
492 /* Cache directory for (sub) folders with Curl("streamed") filesystems. We need to do this
493 else entering (new) directories from the app thread becomes much slower. This
494 is caused by the fact that Curl Stat/Exist() is really slow and that the
495 thumbloader thread accesses the streamed filesystem at the same time as the
496 app thread and the latter has to wait for it.
499 const auto settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
501 const bool cacheAll
=
502 settings
? settings
->GetInt(CSettings::SETTING_FILECACHE_BUFFERMODE
) == CACHE_BUFFER_MODE_ALL
505 if (item
.m_bIsFolder
&& (item
.IsStreamedFilesystem() || cacheAll
))
507 CFileItemList items
; // Dummy list
508 CDirectory::GetDirectory(item
.GetPath(), items
, "", DIR_FLAG_NO_FILE_DIRS
| DIR_FLAG_READ_CACHE
| DIR_FLAG_NO_FILE_INFO
);
514 art
= item
.FindLocalArt(type
+ ".jpg", checkFolder
);
516 art
= item
.FindLocalArt(type
+ ".png", checkFolder
);
518 if (art
.empty() && (type
.empty() || type
== "thumb"))
519 { // backward compatibility
520 art
= item
.FindLocalArt("", false);
521 if (art
.empty() && (checkFolder
|| (item
.m_bIsFolder
&& !item
.IsFileFolder()) || item
.IsOpticalMediaFile()))
523 art
= item
.FindLocalArt("movie.tbn", true);
524 if (art
.empty()) // try folder.jpg
525 art
= item
.FindLocalArt("folder.jpg", true);
532 std::string
CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem
&item
)
534 std::string
path(item
.GetPath());
535 if (item
.IsVideoDb() && item
.HasVideoInfoTag())
536 path
= item
.GetVideoInfoTag()->m_strFileNameAndPath
;
537 if (URIUtils::IsStack(path
))
538 path
= CStackDirectory::GetFirstStackedFile(path
);
540 return CTextureUtils::GetWrappedImageURL(path
, "video");
543 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem
&item
)
545 if (item
.m_bIsFolder
) return;
547 if (item
.HasVideoInfoTag())
549 CStreamDetails
& details
= item
.GetVideoInfoTag()->m_streamDetails
;
551 // add audio language properties
552 for (int i
= 1; i
<= details
.GetAudioStreamCount(); i
++)
554 std::string index
= std::to_string(i
);
555 item
.SetProperty("AudioChannels." + index
, details
.GetAudioChannels(i
));
556 item
.SetProperty("AudioCodec." + index
, details
.GetAudioCodec(i
).c_str());
557 item
.SetProperty("AudioLanguage." + index
, details
.GetAudioLanguage(i
).c_str());
560 // add subtitle language properties
561 for (int i
= 1; i
<= details
.GetSubtitleStreamCount(); i
++)
563 std::string index
= std::to_string(i
);
564 item
.SetProperty("SubtitleLanguage." + index
, details
.GetSubtitleLanguage(i
).c_str());
568 const CStereoscopicsManager
&stereoscopicsManager
= CServiceBroker::GetGUI()->GetStereoscopicsManager();
570 std::string stereoMode
;
572 // detect stereomode for videos
573 if (item
.HasVideoInfoTag())
574 stereoMode
= item
.GetVideoInfoTag()->m_streamDetails
.GetStereoMode();
576 if (stereoMode
.empty())
578 std::string path
= item
.GetPath();
579 if (item
.IsVideoDb() && item
.HasVideoInfoTag())
580 path
= item
.GetVideoInfoTag()->GetPath();
582 // check for custom stereomode setting in video settings
583 CVideoSettings itemVideoSettings
;
584 m_videoDatabase
->Open();
585 if (m_videoDatabase
->GetVideoSettings(item
, itemVideoSettings
) && itemVideoSettings
.m_StereoMode
!= RENDER_STEREO_MODE_OFF
)
587 stereoMode
= CStereoscopicsManager::ConvertGuiStereoModeToString(static_cast<RENDER_STEREO_MODE
>(itemVideoSettings
.m_StereoMode
));
589 m_videoDatabase
->Close();
591 // still empty, try grabbing from filename
592 //! @todo in case of too many false positives due to using the full path, extract the filename only using string utils
593 if (stereoMode
.empty())
594 stereoMode
= stereoscopicsManager
.DetectStereoModeByString(path
);
597 if (!stereoMode
.empty())
598 item
.SetProperty("stereomode", CStereoscopicsManager::NormalizeStereoMode(stereoMode
));
601 const ArtMap
& CVideoThumbLoader::GetArtFromCache(const std::string
&mediaType
, const int id
)
603 std::pair
<MediaType
, int> key
= std::make_pair(mediaType
, id
);
604 auto it
= m_artCache
.find(key
);
605 if (it
== m_artCache
.end())
608 m_videoDatabase
->GetArtForItem(id
, mediaType
, newart
);
609 it
= m_artCache
.insert(std::make_pair(key
, std::move(newart
))).first
;