2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "VideoThumbLoader.h"
22 #include "filesystem/StackDirectory.h"
23 #include "utils/URIUtils.h"
25 #include "filesystem/File.h"
26 #include "filesystem/DirectoryCache.h"
28 #include "settings/Settings.h"
29 #include "settings/VideoSettings.h"
30 #include "GUIUserMessages.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "guilib/StereoscopicsManager.h"
33 #include "rendering/RenderSystem.h"
34 #include "TextureCache.h"
35 #include "utils/log.h"
36 #include "video/VideoInfoTag.h"
37 #include "video/VideoDatabase.h"
38 #include "cores/dvdplayer/DVDFileInfo.h"
39 #include "video/VideoInfoScanner.h"
40 #include "music/MusicDatabase.h"
41 #include "utils/StringUtils.h"
42 #include "settings/AdvancedSettings.h"
44 using namespace XFILE;
46 using namespace VIDEO;
48 CThumbExtractor::CThumbExtractor(const CFileItem& item, const CStdString& listpath, bool thumb, const CStdString& target)
50 m_listpath = listpath;
55 if (item.IsVideoDb() && item.HasVideoInfoTag())
56 m_item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath);
59 m_item.SetPath(CStackDirectory::GetFirstStackedFile(m_item.GetPath()));
62 CThumbExtractor::~CThumbExtractor()
66 bool CThumbExtractor::operator==(const CJob* job) const
68 if (strcmp(job->GetType(),GetType()) == 0)
70 const CThumbExtractor* jobExtract = dynamic_cast<const CThumbExtractor*>(job);
71 if (jobExtract && jobExtract->m_listpath == m_listpath)
77 bool CThumbExtractor::DoWork()
80 || URIUtils::IsUPnP(m_item.GetPath())
83 || m_item.IsDVDImage()
84 || m_item.IsDVDFile(false, true)
85 || m_item.IsInternetStream()
86 || m_item.IsDiscStub()
87 || m_item.IsPlayList())
90 if (URIUtils::IsRemote(m_item.GetPath()) && !URIUtils::IsOnLAN(m_item.GetPath()))
92 // A quasi internet filesystem like webdav is generally fast enough for extracting stuff
93 if (!URIUtils::IsDAV(m_item.GetPath()))
100 CLog::Log(LOGDEBUG,"%s - trying to extract thumb from video file %s", __FUNCTION__, CURL::GetRedacted(m_item.GetPath()).c_str());
101 // construct the thumb cache file
102 CTextureDetails details;
103 details.file = CTextureCache::GetCacheFile(m_target) + ".jpg";
104 result = CDVDFileInfo::ExtractThumb(m_item.GetPath(), details, &m_item.GetVideoInfoTag()->m_streamDetails);
107 CTextureCache::Get().AddCachedTexture(m_target, details);
108 m_item.SetProperty("HasAutoThumb", true);
109 m_item.SetProperty("AutoThumbImage", m_target);
110 m_item.SetArt("thumb", m_target);
112 CVideoInfoTag* info = m_item.GetVideoInfoTag();
113 if (info->m_iDbId > 0 && !info->m_type.empty())
118 db.SetArtForItem(info->m_iDbId, info->m_type, "thumb", m_item.GetArt("thumb"));
124 else if (!m_item.HasVideoInfoTag() || !m_item.GetVideoInfoTag()->HasStreamDetails())
126 // No tag or no details set, so extract them
127 CLog::Log(LOGDEBUG,"%s - trying to extract filestream details from video file %s", __FUNCTION__, CURL::GetRedacted(m_item.GetPath()).c_str());
128 result = CDVDFileInfo::GetFileStreamDetails(&m_item);
133 CVideoInfoTag* info = m_item.GetVideoInfoTag();
137 if (URIUtils::IsStack(m_listpath))
138 m_item.GetVideoInfoTag()->m_streamDetails.SetVideoDuration(0, 0); // Don't know the total time of the stack, so set duration to zero to avoid confusion
140 if (info->m_iFileId < 0)
141 db.SetStreamDetailsForFile(info->m_streamDetails, !info->m_strFileNameAndPath.empty() ? info->m_strFileNameAndPath : m_item.GetPath());
143 db.SetStreamDetailsForFileId(info->m_streamDetails, info->m_iFileId);
153 CVideoThumbLoader::CVideoThumbLoader() :
154 CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
156 m_videoDatabase = new CVideoDatabase();
159 CVideoThumbLoader::~CVideoThumbLoader()
162 delete m_videoDatabase;
165 void CVideoThumbLoader::OnLoaderStart()
167 m_videoDatabase->Open();
169 CThumbLoader::OnLoaderStart();
172 void CVideoThumbLoader::OnLoaderFinish()
174 m_videoDatabase->Close();
176 CThumbLoader::OnLoaderFinish();
179 static void SetupRarOptions(CFileItem& item, const CStdString& path)
181 CStdString path2(path);
182 if (item.IsVideoDb() && item.HasVideoInfoTag())
183 path2 = item.GetVideoInfoTag()->m_strFileNameAndPath;
185 CStdString opts = url.GetOptions();
186 if (opts.find("flags") != std::string::npos)
192 url.SetOptions(opts);
193 if (item.IsVideoDb() && item.HasVideoInfoTag())
194 item.GetVideoInfoTag()->m_strFileNameAndPath = url.Get();
196 item.SetPath(url.Get());
197 g_directoryCache.ClearDirectory(url.GetWithoutFilename());
200 vector<string> CVideoThumbLoader::GetArtTypes(const string &type)
203 if (type == "episode")
204 ret.push_back("thumb");
205 else if (type == "tvshow" || type == "season")
207 ret.push_back("banner");
208 ret.push_back("poster");
209 ret.push_back("fanart");
211 else if (type == "movie" || type == "musicvideo" || type == "set")
213 ret.push_back("poster");
214 ret.push_back("fanart");
216 else if (type.empty()) // unknown - just throw everything in
218 ret.push_back("poster");
219 ret.push_back("banner");
220 ret.push_back("thumb");
221 ret.push_back("fanart");
227 * Look for a thumbnail for pItem. If one does not exist, look for an autogenerated
228 * thumbnail. If that does not exist, attempt to autogenerate one. Finally, check
229 * for the existance of fanart and set properties accordingly.
230 * @return: true if pItem has been modified
232 bool CVideoThumbLoader::LoadItem(CFileItem* pItem)
234 bool result = LoadItemCached(pItem);
235 result |= LoadItemLookup(pItem);
240 bool CVideoThumbLoader::LoadItemCached(CFileItem* pItem)
242 if (pItem->m_bIsShareOrDrive
243 || pItem->IsParentFolder())
246 m_videoDatabase->Open();
248 if (!pItem->HasVideoInfoTag() || !pItem->GetVideoInfoTag()->HasStreamDetails()) // no stream details
250 if ((pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_iFileId >= 0) // file (or maybe folder) is in the database
251 || (!pItem->m_bIsFolder && pItem->IsVideo())) // Some other video file for which we haven't yet got any database details
253 if (m_videoDatabase->GetStreamDetails(*pItem))
258 // video db items normally have info in the database
259 if (pItem->HasVideoInfoTag() && !pItem->HasArt("thumb"))
261 FillLibraryArt(*pItem);
263 if (!pItem->GetVideoInfoTag()->m_type.empty() &&
264 pItem->GetVideoInfoTag()->m_type != "movie" &&
265 pItem->GetVideoInfoTag()->m_type != "tvshow" &&
266 pItem->GetVideoInfoTag()->m_type != "episode" &&
267 pItem->GetVideoInfoTag()->m_type != "musicvideo")
269 m_videoDatabase->Close();
270 return true; // nothing else to be done
274 // if we have no art, look for it all
275 map<string, string> artwork = pItem->GetArt();
278 vector<string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
279 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
280 artTypes.push_back("thumb"); // always look for "thumb" art for files
281 for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
283 std::string type = *i;
284 std::string art = GetCachedImage(*pItem, type);
286 artwork.insert(make_pair(type, art));
288 SetArt(*pItem, artwork);
291 m_videoDatabase->Close();
296 bool CVideoThumbLoader::LoadItemLookup(CFileItem* pItem)
298 if (pItem->m_bIsShareOrDrive
299 || pItem->IsParentFolder())
302 if (pItem->HasVideoInfoTag() &&
303 !pItem->GetVideoInfoTag()->m_type.empty() &&
304 pItem->GetVideoInfoTag()->m_type != "movie" &&
305 pItem->GetVideoInfoTag()->m_type != "tvshow" &&
306 pItem->GetVideoInfoTag()->m_type != "episode" &&
307 pItem->GetVideoInfoTag()->m_type != "musicvideo")
308 return false; // Nothing to do here
310 DetectAndAddMissingItemData(*pItem);
312 m_videoDatabase->Open();
314 map<string, string> artwork = pItem->GetArt();
315 vector<string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
316 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
317 artTypes.push_back("thumb"); // always look for "thumb" art for files
318 for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
320 std::string type = *i;
321 if (!pItem->HasArt(type))
323 std::string art = GetLocalArt(*pItem, type, type=="fanart");
324 if (!art.empty()) // cache it
326 SetCachedImage(*pItem, type, art);
327 CTextureCache::Get().BackgroundCacheImage(art);
328 artwork.insert(make_pair(type, art));
332 SetArt(*pItem, artwork);
334 // We can only extract flags/thumbs for file-like items
335 if (!pItem->m_bIsFolder && pItem->IsVideo())
337 // An auto-generated thumb may have been cached on a different device - check we have it here
338 CStdString url = pItem->GetArt("thumb");
339 if (StringUtils::StartsWith(url, "image://video@") && !CTextureCache::Get().HasCachedImage(url))
340 pItem->SetArt("thumb", "");
342 if (!pItem->HasArt("thumb"))
344 // create unique thumb for auto generated thumbs
345 CStdString thumbURL = GetEmbeddedThumbURL(*pItem);
346 if (CTextureCache::Get().HasCachedImage(thumbURL))
348 CTextureCache::Get().BackgroundCacheImage(thumbURL);
349 pItem->SetProperty("HasAutoThumb", true);
350 pItem->SetProperty("AutoThumbImage", thumbURL);
351 pItem->SetArt("thumb", thumbURL);
353 if (pItem->HasVideoInfoTag())
355 // Item has cached autogen image but no art entry. Save it to db.
356 CVideoInfoTag* info = pItem->GetVideoInfoTag();
357 if (info->m_iDbId > 0 && !info->m_type.empty())
358 m_videoDatabase->SetArtForItem(info->m_iDbId, info->m_type, "thumb", thumbURL);
361 else if (CSettings::Get().GetBool("myvideos.extractthumb") &&
362 CSettings::Get().GetBool("myvideos.extractflags"))
364 CFileItem item(*pItem);
365 CStdString path(item.GetPath());
366 if (URIUtils::IsInRAR(item.GetPath()))
367 SetupRarOptions(item,path);
369 CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
372 m_videoDatabase->Close();
378 if (CSettings::Get().GetBool("myvideos.extractflags") &&
379 (!pItem->HasVideoInfoTag() ||
380 !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
382 CFileItem item(*pItem);
383 CStdString path(item.GetPath());
384 if (URIUtils::IsInRAR(item.GetPath()))
385 SetupRarOptions(item,path);
386 CThumbExtractor* extract = new CThumbExtractor(item,path,false);
391 m_videoDatabase->Close();
395 void CVideoThumbLoader::SetArt(CFileItem &item, const map<string, string> &artwork)
397 item.SetArt(artwork);
398 if (artwork.find("thumb") == artwork.end())
399 { // set fallback for "thumb"
400 if (artwork.find("poster") != artwork.end())
401 item.SetArtFallback("thumb", "poster");
402 else if (artwork.find("banner") != artwork.end())
403 item.SetArtFallback("thumb", "banner");
407 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
409 CVideoInfoTag &tag = *item.GetVideoInfoTag();
410 if (tag.m_iDbId > -1 && !tag.m_type.empty())
412 map<string, string> artwork;
413 m_videoDatabase->Open();
414 if (m_videoDatabase->GetArtForItem(tag.m_iDbId, tag.m_type, artwork))
415 SetArt(item, artwork);
416 else if (tag.m_type == "artist")
417 { // we retrieve music video art from the music database (no backward compat)
418 CMusicDatabase database;
420 int idArtist = database.GetArtistByName(item.GetLabel());
421 if (database.GetArtForItem(idArtist, "artist", artwork))
422 item.SetArt(artwork);
424 else if (tag.m_type == "album")
425 { // we retrieve music video art from the music database (no backward compat)
426 CMusicDatabase database;
428 int idAlbum = database.GetAlbumByName(item.GetLabel(), tag.m_artist);
429 if (database.GetArtForItem(idAlbum, "album", artwork))
430 item.SetArt(artwork);
432 // For episodes and seasons, we want to set fanart for that of the show
433 if (!item.HasArt("fanart") && tag.m_iIdShow >= 0)
435 ArtCache::const_iterator i = m_showArt.find(tag.m_iIdShow);
436 if (i == m_showArt.end())
438 map<string, string> showArt;
439 m_videoDatabase->GetArtForItem(tag.m_iIdShow, "tvshow", showArt);
440 i = m_showArt.insert(make_pair(tag.m_iIdShow, showArt)).first;
442 if (i != m_showArt.end())
444 item.AppendArt(i->second, "tvshow");
445 item.SetArtFallback("fanart", "tvshow.fanart");
446 item.SetArtFallback("tvshow.thumb", "tvshow.poster");
449 m_videoDatabase->Close();
451 return !item.GetArt().empty();
454 bool CVideoThumbLoader::FillThumb(CFileItem &item)
456 if (item.HasArt("thumb"))
458 CStdString thumb = GetCachedImage(item, "thumb");
461 thumb = GetLocalArt(item, "thumb");
463 SetCachedImage(item, "thumb", thumb);
465 item.SetArt("thumb", thumb);
466 return !thumb.empty();
469 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
471 /* Cache directory for (sub) folders on streamed filesystems. We need to do this
472 else entering (new) directories from the app thread becomes much slower. This
473 is caused by the fact that Curl Stat/Exist() is really slow and that the
474 thumbloader thread accesses the streamed filesystem at the same time as the
475 App thread and the latter has to wait for it.
477 if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
479 CFileItemList items; // Dummy list
480 CDirectory::GetDirectory(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
486 art = item.FindLocalArt(type + ".jpg", checkFolder);
488 art = item.FindLocalArt(type + ".png", checkFolder);
490 if (art.empty() && (type.empty() || type == "thumb"))
491 { // backward compatibility
492 art = item.FindLocalArt("", false);
493 if (art.empty() && (checkFolder || (item.m_bIsFolder && !item.IsFileFolder()) || item.IsOpticalMediaFile()))
495 art = item.FindLocalArt("movie.tbn", true);
496 if (art.empty()) // try folder.jpg
497 art = item.FindLocalArt("folder.jpg", true);
503 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
505 CStdString path(item.GetPath());
506 if (item.IsVideoDb() && item.HasVideoInfoTag())
507 path = item.GetVideoInfoTag()->m_strFileNameAndPath;
508 if (URIUtils::IsStack(path))
509 path = CStackDirectory::GetFirstStackedFile(path);
511 return CTextureUtils::GetWrappedImageURL(path, "video");
514 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
518 CThumbExtractor* loader = (CThumbExtractor*)job;
519 loader->m_item.SetPath(loader->m_listpath);
522 m_pObserver->OnItemLoaded(&loader->m_item);
523 CFileItemPtr pItem(new CFileItem(loader->m_item));
524 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
525 g_windowManager.SendThreadMessage(msg);
527 CJobQueue::OnJobComplete(jobID, success, job);
530 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
532 if (item.m_bIsFolder) return;
534 std::string stereoMode;
535 // detect stereomode for videos
536 if (item.HasVideoInfoTag())
537 stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
538 if (stereoMode.empty())
540 std::string path = item.GetPath();
541 if (item.IsVideoDb() && item.HasVideoInfoTag())
542 path = item.GetVideoInfoTag()->GetPath();
544 // check for custom stereomode setting in video settings
545 CVideoSettings itemVideoSettings;
546 m_videoDatabase->Open();
547 if (m_videoDatabase->GetVideoSettings(path, itemVideoSettings) && itemVideoSettings.m_StereoMode != RENDER_STEREO_MODE_OFF)
548 stereoMode = CStereoscopicsManager::Get().ConvertGuiStereoModeToString( (RENDER_STEREO_MODE) itemVideoSettings.m_StereoMode );
549 m_videoDatabase->Close();
551 // still empty, try grabbing from filename
552 // TODO: in case of too many false positives due to using the full path, extract the filename only using string utils
553 if (stereoMode.empty())
554 stereoMode = CStereoscopicsManager::Get().DetectStereoModeByString( path );
556 if (!stereoMode.empty())
557 item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));