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))
139 // Don't know the total time of the stack, so set duration to zero to avoid confusion
140 m_item.GetVideoInfoTag()->m_streamDetails.SetVideoDuration(0, 0);
142 // Restore original stack path
143 m_item.SetPath(m_listpath);
146 if (info->m_iFileId < 0)
147 db.SetStreamDetailsForFile(info->m_streamDetails, !info->m_strFileNameAndPath.empty() ? info->m_strFileNameAndPath : m_item.GetPath());
149 db.SetStreamDetailsForFileId(info->m_streamDetails, info->m_iFileId);
159 CVideoThumbLoader::CVideoThumbLoader() :
160 CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
162 m_videoDatabase = new CVideoDatabase();
165 CVideoThumbLoader::~CVideoThumbLoader()
168 delete m_videoDatabase;
171 void CVideoThumbLoader::OnLoaderStart()
173 m_videoDatabase->Open();
175 CThumbLoader::OnLoaderStart();
178 void CVideoThumbLoader::OnLoaderFinish()
180 m_videoDatabase->Close();
182 CThumbLoader::OnLoaderFinish();
185 static void SetupRarOptions(CFileItem& item, const CStdString& path)
187 CStdString path2(path);
188 if (item.IsVideoDb() && item.HasVideoInfoTag())
189 path2 = item.GetVideoInfoTag()->m_strFileNameAndPath;
191 CStdString opts = url.GetOptions();
192 if (opts.find("flags") != std::string::npos)
198 url.SetOptions(opts);
199 if (item.IsVideoDb() && item.HasVideoInfoTag())
200 item.GetVideoInfoTag()->m_strFileNameAndPath = url.Get();
202 item.SetPath(url.Get());
203 g_directoryCache.ClearDirectory(url.GetWithoutFilename());
206 vector<string> CVideoThumbLoader::GetArtTypes(const string &type)
209 if (type == "episode")
210 ret.push_back("thumb");
211 else if (type == "tvshow" || type == "season")
213 ret.push_back("banner");
214 ret.push_back("poster");
215 ret.push_back("fanart");
217 else if (type == "movie" || type == "musicvideo" || type == "set")
219 ret.push_back("poster");
220 ret.push_back("fanart");
222 else if (type.empty()) // unknown - just throw everything in
224 ret.push_back("poster");
225 ret.push_back("banner");
226 ret.push_back("thumb");
227 ret.push_back("fanart");
233 * Look for a thumbnail for pItem. If one does not exist, look for an autogenerated
234 * thumbnail. If that does not exist, attempt to autogenerate one. Finally, check
235 * for the existance of fanart and set properties accordingly.
236 * @return: true if pItem has been modified
238 bool CVideoThumbLoader::LoadItem(CFileItem* pItem)
240 bool result = LoadItemCached(pItem);
241 result |= LoadItemLookup(pItem);
246 bool CVideoThumbLoader::LoadItemCached(CFileItem* pItem)
248 if (pItem->m_bIsShareOrDrive
249 || pItem->IsParentFolder())
252 m_videoDatabase->Open();
254 if (!pItem->HasVideoInfoTag() || !pItem->GetVideoInfoTag()->HasStreamDetails()) // no stream details
256 if ((pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_iFileId >= 0) // file (or maybe folder) is in the database
257 || (!pItem->m_bIsFolder && pItem->IsVideo())) // Some other video file for which we haven't yet got any database details
259 if (m_videoDatabase->GetStreamDetails(*pItem))
264 // video db items normally have info in the database
265 if (pItem->HasVideoInfoTag() && !pItem->HasArt("thumb"))
267 FillLibraryArt(*pItem);
269 if (!pItem->GetVideoInfoTag()->m_type.empty() &&
270 pItem->GetVideoInfoTag()->m_type != "movie" &&
271 pItem->GetVideoInfoTag()->m_type != "tvshow" &&
272 pItem->GetVideoInfoTag()->m_type != "episode" &&
273 pItem->GetVideoInfoTag()->m_type != "musicvideo")
275 m_videoDatabase->Close();
276 return true; // nothing else to be done
280 // if we have no art, look for it all
281 map<string, string> artwork = pItem->GetArt();
284 vector<string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
285 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
286 artTypes.push_back("thumb"); // always look for "thumb" art for files
287 for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
289 std::string type = *i;
290 std::string art = GetCachedImage(*pItem, type);
292 artwork.insert(make_pair(type, art));
294 SetArt(*pItem, artwork);
297 m_videoDatabase->Close();
302 bool CVideoThumbLoader::LoadItemLookup(CFileItem* pItem)
304 if (pItem->m_bIsShareOrDrive || pItem->IsParentFolder() || pItem->GetPath() == "add")
307 if (pItem->HasVideoInfoTag() &&
308 !pItem->GetVideoInfoTag()->m_type.empty() &&
309 pItem->GetVideoInfoTag()->m_type != "movie" &&
310 pItem->GetVideoInfoTag()->m_type != "tvshow" &&
311 pItem->GetVideoInfoTag()->m_type != "episode" &&
312 pItem->GetVideoInfoTag()->m_type != "musicvideo")
313 return false; // Nothing to do here
315 DetectAndAddMissingItemData(*pItem);
317 m_videoDatabase->Open();
319 map<string, string> artwork = pItem->GetArt();
320 vector<string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
321 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
322 artTypes.push_back("thumb"); // always look for "thumb" art for files
323 for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
325 std::string type = *i;
326 if (!pItem->HasArt(type))
328 std::string art = GetLocalArt(*pItem, type, type=="fanart");
329 if (!art.empty()) // cache it
331 SetCachedImage(*pItem, type, art);
332 CTextureCache::Get().BackgroundCacheImage(art);
333 artwork.insert(make_pair(type, art));
337 SetArt(*pItem, artwork);
339 // We can only extract flags/thumbs for file-like items
340 if (!pItem->m_bIsFolder && pItem->IsVideo())
342 // An auto-generated thumb may have been cached on a different device - check we have it here
343 CStdString url = pItem->GetArt("thumb");
344 if (StringUtils::StartsWith(url, "image://video@") && !CTextureCache::Get().HasCachedImage(url))
345 pItem->SetArt("thumb", "");
347 if (!pItem->HasArt("thumb"))
349 // create unique thumb for auto generated thumbs
350 CStdString thumbURL = GetEmbeddedThumbURL(*pItem);
351 if (CTextureCache::Get().HasCachedImage(thumbURL))
353 CTextureCache::Get().BackgroundCacheImage(thumbURL);
354 pItem->SetProperty("HasAutoThumb", true);
355 pItem->SetProperty("AutoThumbImage", thumbURL);
356 pItem->SetArt("thumb", thumbURL);
358 if (pItem->HasVideoInfoTag())
360 // Item has cached autogen image but no art entry. Save it to db.
361 CVideoInfoTag* info = pItem->GetVideoInfoTag();
362 if (info->m_iDbId > 0 && !info->m_type.empty())
363 m_videoDatabase->SetArtForItem(info->m_iDbId, info->m_type, "thumb", thumbURL);
366 else if (CSettings::Get().GetBool("myvideos.extractthumb") &&
367 CSettings::Get().GetBool("myvideos.extractflags"))
369 CFileItem item(*pItem);
370 CStdString path(item.GetPath());
371 if (URIUtils::IsInRAR(item.GetPath()))
372 SetupRarOptions(item,path);
374 CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
377 m_videoDatabase->Close();
383 if (CSettings::Get().GetBool("myvideos.extractflags") &&
384 (!pItem->HasVideoInfoTag() ||
385 !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
387 CFileItem item(*pItem);
388 CStdString path(item.GetPath());
389 if (URIUtils::IsInRAR(item.GetPath()))
390 SetupRarOptions(item,path);
391 CThumbExtractor* extract = new CThumbExtractor(item,path,false);
396 m_videoDatabase->Close();
400 void CVideoThumbLoader::SetArt(CFileItem &item, const map<string, string> &artwork)
402 item.SetArt(artwork);
403 if (artwork.find("thumb") == artwork.end())
404 { // set fallback for "thumb"
405 if (artwork.find("poster") != artwork.end())
406 item.SetArtFallback("thumb", "poster");
407 else if (artwork.find("banner") != artwork.end())
408 item.SetArtFallback("thumb", "banner");
412 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
414 CVideoInfoTag &tag = *item.GetVideoInfoTag();
415 if (tag.m_iDbId > -1 && !tag.m_type.empty())
417 map<string, string> artwork;
418 m_videoDatabase->Open();
419 if (m_videoDatabase->GetArtForItem(tag.m_iDbId, tag.m_type, artwork))
420 SetArt(item, artwork);
421 else if (tag.m_type == "artist")
422 { // we retrieve music video art from the music database (no backward compat)
423 CMusicDatabase database;
425 int idArtist = database.GetArtistByName(item.GetLabel());
426 if (database.GetArtForItem(idArtist, "artist", artwork))
427 item.SetArt(artwork);
429 else if (tag.m_type == "album")
430 { // we retrieve music video art from the music database (no backward compat)
431 CMusicDatabase database;
433 int idAlbum = database.GetAlbumByName(item.GetLabel(), tag.m_artist);
434 if (database.GetArtForItem(idAlbum, "album", artwork))
435 item.SetArt(artwork);
437 // For episodes and seasons, we want to set fanart for that of the show
438 if (!item.HasArt("fanart") && tag.m_iIdShow >= 0)
440 ArtCache::const_iterator i = m_showArt.find(tag.m_iIdShow);
441 if (i == m_showArt.end())
443 map<string, string> showArt;
444 m_videoDatabase->GetArtForItem(tag.m_iIdShow, "tvshow", showArt);
445 i = m_showArt.insert(make_pair(tag.m_iIdShow, showArt)).first;
447 if (i != m_showArt.end())
449 item.AppendArt(i->second, "tvshow");
450 item.SetArtFallback("fanart", "tvshow.fanart");
451 item.SetArtFallback("tvshow.thumb", "tvshow.poster");
454 m_videoDatabase->Close();
456 return !item.GetArt().empty();
459 bool CVideoThumbLoader::FillThumb(CFileItem &item)
461 if (item.HasArt("thumb"))
463 CStdString thumb = GetCachedImage(item, "thumb");
466 thumb = GetLocalArt(item, "thumb");
468 SetCachedImage(item, "thumb", thumb);
471 item.SetArt("thumb", thumb);
472 return !thumb.empty();
475 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
477 if (item.SkipLocalArt())
480 /* Cache directory for (sub) folders on streamed filesystems. We need to do this
481 else entering (new) directories from the app thread becomes much slower. This
482 is caused by the fact that Curl Stat/Exist() is really slow and that the
483 thumbloader thread accesses the streamed filesystem at the same time as the
484 App thread and the latter has to wait for it.
486 if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
488 CFileItemList items; // Dummy list
489 CDirectory::GetDirectory(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
495 art = item.FindLocalArt(type + ".jpg", checkFolder);
497 art = item.FindLocalArt(type + ".png", checkFolder);
499 if (art.empty() && (type.empty() || type == "thumb"))
500 { // backward compatibility
501 art = item.FindLocalArt("", false);
502 if (art.empty() && (checkFolder || (item.m_bIsFolder && !item.IsFileFolder()) || item.IsOpticalMediaFile()))
504 art = item.FindLocalArt("movie.tbn", true);
505 if (art.empty()) // try folder.jpg
506 art = item.FindLocalArt("folder.jpg", true);
512 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
514 CStdString path(item.GetPath());
515 if (item.IsVideoDb() && item.HasVideoInfoTag())
516 path = item.GetVideoInfoTag()->m_strFileNameAndPath;
517 if (URIUtils::IsStack(path))
518 path = CStackDirectory::GetFirstStackedFile(path);
520 return CTextureUtils::GetWrappedImageURL(path, "video");
523 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
527 CThumbExtractor* loader = (CThumbExtractor*)job;
528 loader->m_item.SetPath(loader->m_listpath);
531 m_pObserver->OnItemLoaded(&loader->m_item);
532 CFileItemPtr pItem(new CFileItem(loader->m_item));
533 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
534 g_windowManager.SendThreadMessage(msg);
536 CJobQueue::OnJobComplete(jobID, success, job);
539 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
541 if (item.m_bIsFolder) return;
543 std::string stereoMode;
544 // detect stereomode for videos
545 if (item.HasVideoInfoTag())
546 stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
547 if (stereoMode.empty())
549 std::string path = item.GetPath();
550 if (item.IsVideoDb() && item.HasVideoInfoTag())
551 path = item.GetVideoInfoTag()->GetPath();
553 // check for custom stereomode setting in video settings
554 CVideoSettings itemVideoSettings;
555 m_videoDatabase->Open();
556 if (m_videoDatabase->GetVideoSettings(path, itemVideoSettings) && itemVideoSettings.m_StereoMode != RENDER_STEREO_MODE_OFF)
557 stereoMode = CStereoscopicsManager::Get().ConvertGuiStereoModeToString( (RENDER_STEREO_MODE) itemVideoSettings.m_StereoMode );
558 m_videoDatabase->Close();
560 // still empty, try grabbing from filename
561 // TODO: in case of too many false positives due to using the full path, extract the filename only using string utils
562 if (stereoMode.empty())
563 stereoMode = CStereoscopicsManager::Get().DetectStereoModeByString( path );
565 if (!stereoMode.empty())
566 item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));