summaryrefslogtreecommitdiffstats
path: root/xbmc/video/VideoThumbLoader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/video/VideoThumbLoader.cpp')
-rw-r--r--xbmc/video/VideoThumbLoader.cpp803
1 files changed, 803 insertions, 0 deletions
diff --git a/xbmc/video/VideoThumbLoader.cpp b/xbmc/video/VideoThumbLoader.cpp
new file mode 100644
index 0000000..31ebe3e
--- /dev/null
+++ b/xbmc/video/VideoThumbLoader.cpp
@@ -0,0 +1,803 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VideoThumbLoader.h"
+
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "URL.h"
+#include "cores/VideoPlayer/DVDFileInfo.h"
+#include "cores/VideoSettings.h"
+#include "filesystem/Directory.h"
+#include "filesystem/DirectoryCache.h"
+#include "filesystem/StackDirectory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/StereoscopicsManager.h"
+#include "music/MusicDatabase.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingUtils.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/EmbeddedArt.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoInfoTag.h"
+#include "video/tags/VideoInfoTagLoaderFactory.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <utility>
+
+using namespace XFILE;
+using namespace VIDEO;
+
+CThumbExtractor::CThumbExtractor(const CFileItem& item,
+ const std::string& listpath,
+ bool thumb,
+ const std::string& target,
+ int64_t pos,
+ bool fillStreamDetails)
+ : m_target(target), m_listpath(listpath), m_item(item)
+{
+ m_thumb = thumb;
+ m_pos = pos;
+ m_fillStreamDetails = fillStreamDetails;
+
+ if (item.IsVideoDb() && item.HasVideoInfoTag())
+ m_item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath);
+
+ if (m_item.IsStack())
+ m_item.SetPath(CStackDirectory::GetFirstStackedFile(m_item.GetPath()));
+}
+
+CThumbExtractor::~CThumbExtractor() = default;
+
+bool CThumbExtractor::operator==(const CJob* job) const
+{
+ if (strcmp(job->GetType(),GetType()) == 0)
+ {
+ const CThumbExtractor* jobExtract = dynamic_cast<const CThumbExtractor*>(job);
+ if (jobExtract && jobExtract->m_listpath == m_listpath
+ && jobExtract->m_target == m_target)
+ return true;
+ }
+ return false;
+}
+
+bool CThumbExtractor::DoWork()
+{
+ if (m_item.IsLiveTV()
+ // Due to a pvr addon api design flaw (no support for multiple concurrent streams
+ // per addon instance), pvr recording thumbnail extraction does not work (reliably).
+ || URIUtils::IsPVRRecording(m_item.GetDynPath())
+ || URIUtils::IsUPnP(m_item.GetPath())
+ || URIUtils::IsBluray(m_item.GetPath())
+ || URIUtils::IsPlugin(m_item.GetDynPath()) // plugin path not fully resolved
+ || m_item.IsBDFile()
+ || m_item.IsDVD()
+ || m_item.IsDiscImage()
+ || m_item.IsDVDFile(false, true)
+ || m_item.IsInternetStream()
+ || m_item.IsDiscStub()
+ || m_item.IsPlayList())
+ return false;
+
+ // For HTTP/FTP we only allow extraction when on a LAN
+ if (URIUtils::IsRemote(m_item.GetPath()) &&
+ !URIUtils::IsOnLAN(m_item.GetPath()) &&
+ (URIUtils::IsFTP(m_item.GetPath()) ||
+ URIUtils::IsHTTP(m_item.GetPath())))
+ return false;
+
+ bool result=false;
+ if (m_thumb)
+ {
+ CLog::Log(LOGDEBUG, "{} - trying to extract thumb from video file {}", __FUNCTION__,
+ CURL::GetRedacted(m_item.GetPath()));
+ // construct the thumb cache file
+ CTextureDetails details;
+ details.file = CTextureCache::GetCacheFile(m_target) + ".jpg";
+ result = CDVDFileInfo::ExtractThumb(m_item, details, m_fillStreamDetails ? &m_item.GetVideoInfoTag()->m_streamDetails : nullptr, m_pos);
+ if (result)
+ {
+ CServiceBroker::GetTextureCache()->AddCachedTexture(m_target, details);
+ m_item.SetProperty("HasAutoThumb", true);
+ m_item.SetProperty("AutoThumbImage", m_target);
+ m_item.SetArt("thumb", m_target);
+
+ CVideoInfoTag* info = m_item.GetVideoInfoTag();
+ if (info->m_iDbId > 0 && !info->m_type.empty())
+ {
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ db.SetArtForItem(info->m_iDbId, info->m_type, "thumb", m_item.GetArt("thumb"));
+ db.Close();
+ }
+ }
+ }
+ }
+ else if (!m_item.IsPlugin() &&
+ (!m_item.HasVideoInfoTag() ||
+ !m_item.GetVideoInfoTag()->HasStreamDetails()))
+ {
+ // No tag or no details set, so extract them
+ CLog::Log(LOGDEBUG, "{} - trying to extract filestream details from video file {}",
+ __FUNCTION__, CURL::GetRedacted(m_item.GetPath()));
+ result = CDVDFileInfo::GetFileStreamDetails(&m_item);
+ }
+
+ if (result)
+ {
+ CVideoInfoTag* info = m_item.GetVideoInfoTag();
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ if (URIUtils::IsStack(m_listpath))
+ {
+ // Don't know the total time of the stack, so set duration to zero to avoid confusion
+ info->m_streamDetails.SetVideoDuration(0, 0);
+
+ // Restore original stack path
+ m_item.SetPath(m_listpath);
+ }
+
+ db.BeginTransaction();
+
+ if (info->m_iFileId < 0)
+ db.SetStreamDetailsForFile(info->m_streamDetails, !info->m_strFileNameAndPath.empty() ? info->m_strFileNameAndPath : m_item.GetPath());
+ else
+ db.SetStreamDetailsForFileId(info->m_streamDetails, info->m_iFileId);
+
+ // overwrite the runtime value if the one from streamdetails is available
+ if (info->m_iDbId > 0
+ && info->GetStaticDuration() != info->GetDuration())
+ {
+ info->SetDuration(info->GetDuration());
+
+ // store the updated information in the database
+ db.SetDetailsForItem(info->m_iDbId, info->m_type, *info, m_item.GetArt());
+ }
+
+ db.CommitTransaction();
+ db.Close();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+CVideoThumbLoader::CVideoThumbLoader() :
+ CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
+{
+ m_videoDatabase = new CVideoDatabase();
+}
+
+CVideoThumbLoader::~CVideoThumbLoader()
+{
+ StopThread();
+ delete m_videoDatabase;
+}
+
+void CVideoThumbLoader::OnLoaderStart()
+{
+ m_videoDatabase->Open();
+ m_artCache.clear();
+ CThumbLoader::OnLoaderStart();
+}
+
+void CVideoThumbLoader::OnLoaderFinish()
+{
+ m_videoDatabase->Close();
+ m_artCache.clear();
+ CThumbLoader::OnLoaderFinish();
+}
+
+static void SetupRarOptions(CFileItem& item, const std::string& path)
+{
+ std::string path2(path);
+ if (item.IsVideoDb() && item.HasVideoInfoTag())
+ path2 = item.GetVideoInfoTag()->m_strFileNameAndPath;
+ CURL url(path2);
+ std::string opts = url.GetOptions();
+ if (opts.find("flags") != std::string::npos)
+ return;
+ if (opts.size())
+ opts += "&flags=8";
+ else
+ opts = "?flags=8";
+ url.SetOptions(opts);
+ if (item.IsVideoDb() && item.HasVideoInfoTag())
+ item.GetVideoInfoTag()->m_strFileNameAndPath = url.Get();
+ else
+ item.SetPath(url.Get());
+ g_directoryCache.ClearDirectory(url.GetWithoutFilename());
+}
+
+namespace
+{
+std::vector<std::string> GetSettingListAsString(const std::string& settingID)
+{
+ std::vector<CVariant> values =
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(settingID);
+ std::vector<std::string> result;
+ std::transform(values.begin(), values.end(), std::back_inserter(result),
+ [](const CVariant& s) { return s.asString(); });
+ return result;
+}
+
+const std::map<std::string, std::vector<std::string>> artTypeDefaults = {
+ {MediaTypeEpisode, {"thumb"}},
+ {MediaTypeTvShow, {"poster", "fanart", "banner"}},
+ {MediaTypeSeason, {"poster", "fanart", "banner"}},
+ {MediaTypeMovie, {"poster", "fanart"}},
+ {MediaTypeVideoCollection, {"poster", "fanart"}},
+ {MediaTypeMusicVideo, {"poster", "fanart"}},
+ {MediaTypeNone, { "poster", "fanart", "banner", "thumb" }},
+};
+
+const std::vector<std::string> artTypeDefaultsFallback = {};
+
+const std::vector<std::string>& GetArtTypeDefault(const std::string& mediaType)
+{
+ auto defaults = artTypeDefaults.find(mediaType);
+ if (defaults != artTypeDefaults.end())
+ return defaults->second;
+ return artTypeDefaultsFallback;
+}
+
+const std::map<std::string, std::string> artTypeSettings = {
+ {MediaTypeEpisode, CSettings::SETTING_VIDEOLIBRARY_EPISODEART_WHITELIST},
+ {MediaTypeTvShow, CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST},
+ {MediaTypeSeason, CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST},
+ {MediaTypeMovie, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST},
+ {MediaTypeVideoCollection, CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST},
+ {MediaTypeMusicVideo, CSettings::SETTING_VIDEOLIBRARY_MUSICVIDEOART_WHITELIST},
+};
+} // namespace
+
+std::vector<std::string> CVideoThumbLoader::GetArtTypes(const std::string &type)
+{
+ int artworkLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL);
+ if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE)
+ {
+ return {};
+ }
+
+ std::vector<std::string> result = GetArtTypeDefault(type);
+ if (artworkLevel != CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_CUSTOM)
+ {
+ return result;
+ }
+
+ auto settings = artTypeSettings.find(type);
+ if (settings == artTypeSettings.end())
+ return result;
+
+ for (auto& artType : GetSettingListAsString(settings->second))
+ {
+ if (find(result.begin(), result.end(), artType) == result.end())
+ result.push_back(artType);
+ }
+
+ return result;
+}
+
+bool CVideoThumbLoader::IsValidArtType(const std::string& potentialArtType)
+{
+ return !potentialArtType.empty() && potentialArtType.length() <= 25 &&
+ std::find_if_not(
+ potentialArtType.begin(), potentialArtType.end(),
+ StringUtils::isasciialphanum
+ ) == potentialArtType.end();
+}
+
+bool CVideoThumbLoader::IsArtTypeInWhitelist(const std::string& artType, const std::vector<std::string>& whitelist, bool exact)
+{
+ // whitelist contains art "families", 'fanart' also matches 'fanart1', 'fanart2', and so on
+ std::string compareArtType = artType;
+ if (!exact)
+ StringUtils::TrimRight(compareArtType, "0123456789");
+
+ return std::find(whitelist.begin(), whitelist.end(), compareArtType) != whitelist.end();
+}
+
+/**
+ * Look for a thumbnail for pItem. If one does not exist, look for an autogenerated
+ * thumbnail. If that does not exist, attempt to autogenerate one. Finally, check
+ * for the existence of fanart and set properties accordingly.
+ * @return: true if pItem has been modified
+ */
+bool CVideoThumbLoader::LoadItem(CFileItem* pItem)
+{
+ bool result = LoadItemCached(pItem);
+ result |= LoadItemLookup(pItem);
+
+ return result;
+}
+
+bool CVideoThumbLoader::LoadItemCached(CFileItem* pItem)
+{
+ if (pItem->m_bIsShareOrDrive
+ || pItem->IsParentFolder())
+ return false;
+
+ m_videoDatabase->Open();
+
+ if (!pItem->HasVideoInfoTag() || !pItem->GetVideoInfoTag()->HasStreamDetails()) // no stream details
+ {
+ if ((pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_iFileId >= 0) // file (or maybe folder) is in the database
+ || (!pItem->m_bIsFolder && pItem->IsVideo())) // Some other video file for which we haven't yet got any database details
+ {
+ if (m_videoDatabase->GetStreamDetails(*pItem))
+ pItem->SetInvalid();
+ }
+ }
+
+ // video db items normally have info in the database
+ if (pItem->HasVideoInfoTag() && !pItem->GetProperty("libraryartfilled").asBoolean())
+ {
+ FillLibraryArt(*pItem);
+
+ if (!pItem->GetVideoInfoTag()->m_type.empty() &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeMovie &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeTvShow &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeEpisode &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeMusicVideo)
+ {
+ m_videoDatabase->Close();
+ return true; // nothing else to be done
+ }
+ }
+
+ // if we have no art, look for it all
+ std::map<std::string, std::string> artwork = pItem->GetArt();
+ if (artwork.empty())
+ {
+ std::vector<std::string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
+ if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
+ artTypes.emplace_back("thumb"); // always look for "thumb" art for files
+ for (std::vector<std::string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
+ {
+ std::string type = *i;
+ std::string art = GetCachedImage(*pItem, type);
+ if (!art.empty())
+ artwork.insert(std::make_pair(type, art));
+ }
+ pItem->AppendArt(artwork);
+ }
+
+ m_videoDatabase->Close();
+
+ return true;
+}
+
+bool CVideoThumbLoader::LoadItemLookup(CFileItem* pItem)
+{
+ if (pItem->m_bIsShareOrDrive || pItem->IsParentFolder() || pItem->GetPath() == "add")
+ return false;
+
+ if (pItem->HasVideoInfoTag() &&
+ !pItem->GetVideoInfoTag()->m_type.empty() &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeMovie &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeTvShow &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeEpisode &&
+ pItem->GetVideoInfoTag()->m_type != MediaTypeMusicVideo)
+ return false; // Nothing to do here
+
+ DetectAndAddMissingItemData(*pItem);
+
+ m_videoDatabase->Open();
+
+ std::map<std::string, std::string> artwork = pItem->GetArt();
+ std::vector<std::string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
+ if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
+ artTypes.emplace_back("thumb"); // always look for "thumb" art for files
+ for (std::vector<std::string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
+ {
+ std::string type = *i;
+ if (!pItem->HasArt(type))
+ {
+ std::string art = GetLocalArt(*pItem, type, type=="fanart");
+ if (!art.empty()) // cache it
+ {
+ SetCachedImage(*pItem, type, art);
+ CServiceBroker::GetTextureCache()->BackgroundCacheImage(art);
+ artwork.insert(std::make_pair(type, art));
+ }
+ else
+ {
+ // If nothing was found, try embedded art
+ if (pItem->HasVideoInfoTag() && !pItem->GetVideoInfoTag()->m_coverArt.empty())
+ {
+ for (auto& it : pItem->GetVideoInfoTag()->m_coverArt)
+ {
+ if (it.m_type == type)
+ {
+ art = CTextureUtils::GetWrappedImageURL(pItem->GetPath(), "video_" + type);
+ artwork.insert(std::make_pair(type, art));
+ }
+ }
+ }
+ }
+ }
+ }
+ pItem->AppendArt(artwork);
+
+ // We can only extract flags/thumbs for file-like items
+ if (!pItem->m_bIsFolder && pItem->IsVideo())
+ {
+ // An auto-generated thumb may have been cached on a different device - check we have it here
+ std::string url = pItem->GetArt("thumb");
+ if (StringUtils::StartsWith(url, "image://video@") &&
+ !CServiceBroker::GetTextureCache()->HasCachedImage(url))
+ pItem->SetArt("thumb", "");
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ if (!pItem->HasArt("thumb"))
+ {
+ // create unique thumb for auto generated thumbs
+ std::string thumbURL = GetEmbeddedThumbURL(*pItem);
+ if (CServiceBroker::GetTextureCache()->HasCachedImage(thumbURL))
+ {
+ CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumbURL);
+ pItem->SetProperty("HasAutoThumb", true);
+ pItem->SetProperty("AutoThumbImage", thumbURL);
+ pItem->SetArt("thumb", thumbURL);
+
+ if (pItem->HasVideoInfoTag())
+ {
+ // Item has cached autogen image but no art entry. Save it to db.
+ CVideoInfoTag* info = pItem->GetVideoInfoTag();
+ if (info->m_iDbId > 0 && !info->m_type.empty())
+ m_videoDatabase->SetArtForItem(info->m_iDbId, info->m_type, "thumb", thumbURL);
+ }
+ }
+ else if (settings->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTTHUMB) &&
+ settings->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS) &&
+ settings->GetInt(CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL) !=
+ CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE)
+ {
+ CFileItem item(*pItem);
+ std::string path(item.GetPath());
+ if (URIUtils::IsInRAR(item.GetPath()))
+ SetupRarOptions(item,path);
+
+ CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
+ AddJob(extract);
+
+ m_videoDatabase->Close();
+ return true;
+ }
+ }
+
+ // flag extraction
+ if (settings->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS) &&
+ (!pItem->HasVideoInfoTag() ||
+ !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
+ {
+ CFileItem item(*pItem);
+ std::string path(item.GetPath());
+ if (URIUtils::IsInRAR(item.GetPath()))
+ SetupRarOptions(item,path);
+ CThumbExtractor* extract = new CThumbExtractor(item,path,false);
+ AddJob(extract);
+ }
+ }
+
+ m_videoDatabase->Close();
+ return true;
+}
+
+bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
+{
+ CVideoInfoTag &tag = *item.GetVideoInfoTag();
+ std::map<std::string, std::string> artwork;
+ // Video item can be an album - either a
+ // a) search result with full details including music library album id, or
+ // b) musicvideo album that needs matching to a music album, storing id as well as fetch art.
+ if (tag.m_type == MediaTypeAlbum)
+ {
+ int idAlbum = -1;
+ if (item.HasMusicInfoTag()) // Album is a search result
+ idAlbum = item.GetMusicInfoTag()->GetAlbumId();
+ CMusicDatabase database;
+ database.Open();
+ if (idAlbum < 0 && !tag.m_strAlbum.empty() &&
+ item.GetProperty("musicvideomediatype") == MediaTypeAlbum)
+ {
+ // Musicvideo album - try to match album in music db on artist(s) and album name.
+ // Get review if available and save the matching music library album id.
+ std::string strArtist = StringUtils::Join(
+ tag.m_artist,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ std::string strReview;
+ if (database.GetMatchingMusicVideoAlbum(
+ tag.m_strAlbum, strArtist, idAlbum, strReview))
+ {
+ item.SetProperty("album_musicid", idAlbum);
+ item.SetProperty("album_description", strReview);
+ }
+ }
+ // Get album art only (not related artist art)
+ if (database.GetArtForItem(idAlbum, MediaTypeAlbum, artwork))
+ item.SetArt(artwork);
+ database.Close();
+ }
+ else if (tag.m_type == "actor" && !tag.m_artist.empty() &&
+ item.GetProperty("musicvideomediatype") == MediaTypeArtist)
+ {
+ // Try to match artist in music db on name, get bio if available and fetch artist art
+ // Save the matching music library artist id.
+ CMusicDatabase database;
+ database.Open();
+ CArtist artist;
+ int idArtist = database.GetArtistByName(tag.m_artist[0]);
+ if (idArtist > 0)
+ {
+ database.GetArtist(idArtist, artist);
+ tag.m_strPlot = artist.strBiography;
+ item.SetProperty("artist_musicid", idArtist);
+ }
+ if (database.GetArtForItem(idArtist, MediaTypeArtist, artwork))
+ item.SetArt(artwork);
+ database.Close();
+ }
+
+ if (tag.m_iDbId > -1 && !tag.m_type.empty())
+ {
+ m_videoDatabase->Open();
+ if (m_videoDatabase->GetArtForItem(tag.m_iDbId, tag.m_type, artwork))
+ item.AppendArt(artwork);
+ else if (tag.m_type == "actor" && !tag.m_artist.empty() &&
+ item.GetProperty("musicvideomediatype") != MediaTypeArtist)
+ {
+ // Fallback to music library for actors without art
+ //! @todo Is m_artist set other than musicvideo? Remove this fallback if not.
+ CMusicDatabase database;
+ database.Open();
+ int idArtist = database.GetArtistByName(item.GetLabel());
+ if (database.GetArtForItem(idArtist, MediaTypeArtist, artwork))
+ item.SetArt(artwork);
+ database.Close();
+ }
+
+ if (tag.m_type == MediaTypeEpisode || tag.m_type == MediaTypeSeason)
+ {
+ // For episodes and seasons, we want to set fanart for that of the show
+ if (!item.HasArt("tvshow.fanart") && tag.m_iIdShow >= 0)
+ {
+ const ArtMap& artmap = GetArtFromCache(MediaTypeTvShow, tag.m_iIdShow);
+ if (!artmap.empty())
+ {
+ item.AppendArt(artmap, MediaTypeTvShow);
+ item.SetArtFallback("fanart", "tvshow.fanart");
+ item.SetArtFallback("tvshow.thumb", "tvshow.poster");
+ }
+ }
+
+ if (tag.m_type == MediaTypeEpisode && !item.HasArt("season.poster") && tag.m_iSeason > -1)
+ {
+ const ArtMap& artmap = GetArtFromCache(MediaTypeSeason, tag.m_iIdSeason);
+ if (!artmap.empty())
+ item.AppendArt(artmap, MediaTypeSeason);
+ }
+ }
+ else if (tag.m_type == MediaTypeMovie && tag.m_set.id >= 0 && !item.HasArt("set.fanart"))
+ {
+ const ArtMap& artmap = GetArtFromCache(MediaTypeVideoCollection, tag.m_set.id);
+ if (!artmap.empty())
+ item.AppendArt(artmap, MediaTypeVideoCollection);
+ }
+ m_videoDatabase->Close();
+ }
+ item.SetProperty("libraryartfilled", true);
+ return !item.GetArt().empty();
+}
+
+bool CVideoThumbLoader::FillThumb(CFileItem &item)
+{
+ if (item.HasArt("thumb"))
+ return true;
+ std::string thumb = GetCachedImage(item, "thumb");
+ if (thumb.empty())
+ {
+ thumb = GetLocalArt(item, "thumb");
+ if (!thumb.empty())
+ SetCachedImage(item, "thumb", thumb);
+ }
+ if (!thumb.empty())
+ item.SetArt("thumb", thumb);
+ else
+ {
+ // If nothing was found, try embedded art
+ if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->m_coverArt.empty())
+ {
+ for (auto& it : item.GetVideoInfoTag()->m_coverArt)
+ {
+ if (it.m_type == "thumb")
+ {
+ thumb = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_" + it.m_type);
+ item.SetArt(it.m_type, thumb);
+ }
+ }
+ }
+ }
+
+ return !thumb.empty();
+}
+
+std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
+{
+ if (item.SkipLocalArt())
+ return "";
+
+ /* Cache directory for (sub) folders with Curl("streamed") filesystems. We need to do this
+ else entering (new) directories from the app thread becomes much slower. This
+ is caused by the fact that Curl Stat/Exist() is really slow and that the
+ thumbloader thread accesses the streamed filesystem at the same time as the
+ app thread and the latter has to wait for it.
+ */
+ if (item.m_bIsFolder &&
+ (item.IsStreamedFilesystem() ||
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheBufferMode ==
+ CACHE_BUFFER_MODE_ALL))
+ {
+ CFileItemList items; // Dummy list
+ CDirectory::GetDirectory(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
+ }
+
+ std::string art;
+ if (!type.empty())
+ {
+ art = item.FindLocalArt(type + ".jpg", checkFolder);
+ if (art.empty())
+ art = item.FindLocalArt(type + ".png", checkFolder);
+ }
+ if (art.empty() && (type.empty() || type == "thumb"))
+ { // backward compatibility
+ art = item.FindLocalArt("", false);
+ if (art.empty() && (checkFolder || (item.m_bIsFolder && !item.IsFileFolder()) || item.IsOpticalMediaFile()))
+ { // try movie.tbn
+ art = item.FindLocalArt("movie.tbn", true);
+ if (art.empty()) // try folder.jpg
+ art = item.FindLocalArt("folder.jpg", true);
+ }
+ }
+
+ return art;
+}
+
+std::string CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
+{
+ std::string path(item.GetPath());
+ if (item.IsVideoDb() && item.HasVideoInfoTag())
+ path = item.GetVideoInfoTag()->m_strFileNameAndPath;
+ if (URIUtils::IsStack(path))
+ path = CStackDirectory::GetFirstStackedFile(path);
+
+ return CTextureUtils::GetWrappedImageURL(path, "video");
+}
+
+bool CVideoThumbLoader::GetEmbeddedThumb(const std::string& path,
+ const std::string& type, EmbeddedArt& art)
+{
+ CFileItem item(path, false);
+ std::unique_ptr<IVideoInfoTagLoader> pLoader;
+ pLoader.reset(CVideoInfoTagLoaderFactory::CreateLoader(item,ADDON::ScraperPtr(),false));
+ CVideoInfoTag tag;
+ std::vector<EmbeddedArt> artv;
+ if (pLoader)
+ pLoader->Load(tag, false, &artv);
+
+ for (const EmbeddedArt& it : artv)
+ {
+ if (it.m_type == type)
+ {
+ art = it;
+ break;
+ }
+ }
+
+ return !art.Empty();
+}
+
+void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
+{
+ if (success)
+ {
+ CThumbExtractor* loader = static_cast<CThumbExtractor*>(job);
+ loader->m_item.SetPath(loader->m_listpath);
+
+ if (m_pObserver)
+ m_pObserver->OnItemLoaded(&loader->m_item);
+ CFileItemPtr pItem(new CFileItem(loader->m_item));
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ }
+ CJobQueue::OnJobComplete(jobID, success, job);
+}
+
+void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
+{
+ if (item.m_bIsFolder) return;
+
+ if (item.HasVideoInfoTag())
+ {
+ CStreamDetails& details = item.GetVideoInfoTag()->m_streamDetails;
+
+ // add audio language properties
+ for (int i = 1; i <= details.GetAudioStreamCount(); i++)
+ {
+ std::string index = std::to_string(i);
+ item.SetProperty("AudioChannels." + index, details.GetAudioChannels(i));
+ item.SetProperty("AudioCodec." + index, details.GetAudioCodec(i).c_str());
+ item.SetProperty("AudioLanguage." + index, details.GetAudioLanguage(i).c_str());
+ }
+
+ // add subtitle language properties
+ for (int i = 1; i <= details.GetSubtitleStreamCount(); i++)
+ {
+ std::string index = std::to_string(i);
+ item.SetProperty("SubtitleLanguage." + index, details.GetSubtitleLanguage(i).c_str());
+ }
+ }
+
+ const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager();
+
+ std::string stereoMode;
+
+ // detect stereomode for videos
+ if (item.HasVideoInfoTag())
+ stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
+
+ if (stereoMode.empty())
+ {
+ std::string path = item.GetPath();
+ if (item.IsVideoDb() && item.HasVideoInfoTag())
+ path = item.GetVideoInfoTag()->GetPath();
+
+ // check for custom stereomode setting in video settings
+ CVideoSettings itemVideoSettings;
+ m_videoDatabase->Open();
+ if (m_videoDatabase->GetVideoSettings(item, itemVideoSettings) && itemVideoSettings.m_StereoMode != RENDER_STEREO_MODE_OFF)
+ {
+ stereoMode = CStereoscopicsManager::ConvertGuiStereoModeToString(static_cast<RENDER_STEREO_MODE>(itemVideoSettings.m_StereoMode));
+ }
+ m_videoDatabase->Close();
+
+ // still empty, try grabbing from filename
+ //! @todo in case of too many false positives due to using the full path, extract the filename only using string utils
+ if (stereoMode.empty())
+ stereoMode = stereoscopicsManager.DetectStereoModeByString(path);
+ }
+
+ if (!stereoMode.empty())
+ item.SetProperty("stereomode", CStereoscopicsManager::NormalizeStereoMode(stereoMode));
+}
+
+const ArtMap& CVideoThumbLoader::GetArtFromCache(const std::string &mediaType, const int id)
+{
+ std::pair<MediaType, int> key = std::make_pair(mediaType, id);
+ auto it = m_artCache.find(key);
+ if (it == m_artCache.end())
+ {
+ ArtMap newart;
+ m_videoDatabase->GetArtForItem(id, mediaType, newart);
+ it = m_artCache.insert(std::make_pair(key, std::move(newart))).first;
+ }
+ return it->second;
+}