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);
470 item.SetArt("thumb", thumb);
471 return !thumb.empty();
474 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
476 /* Cache directory for (sub) folders on streamed filesystems. We need to do this
477 else entering (new) directories from the app thread becomes much slower. This
478 is caused by the fact that Curl Stat/Exist() is really slow and that the
479 thumbloader thread accesses the streamed filesystem at the same time as the
480 App thread and the latter has to wait for it.
482 if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
484 CFileItemList items; // Dummy list
485 CDirectory::GetDirectory(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
491 art = item.FindLocalArt(type + ".jpg", checkFolder);
493 art = item.FindLocalArt(type + ".png", checkFolder);
495 if (art.empty() && (type.empty() || type == "thumb"))
496 { // backward compatibility
497 art = item.FindLocalArt("", false);
498 if (art.empty() && (checkFolder || (item.m_bIsFolder && !item.IsFileFolder()) || item.IsOpticalMediaFile()))
500 art = item.FindLocalArt("movie.tbn", true);
501 if (art.empty()) // try folder.jpg
502 art = item.FindLocalArt("folder.jpg", true);
508 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
510 CStdString path(item.GetPath());
511 if (item.IsVideoDb() && item.HasVideoInfoTag())
512 path = item.GetVideoInfoTag()->m_strFileNameAndPath;
513 if (URIUtils::IsStack(path))
514 path = CStackDirectory::GetFirstStackedFile(path);
516 return CTextureUtils::GetWrappedImageURL(path, "video");
519 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
523 CThumbExtractor* loader = (CThumbExtractor*)job;
524 loader->m_item.SetPath(loader->m_listpath);
527 m_pObserver->OnItemLoaded(&loader->m_item);
528 CFileItemPtr pItem(new CFileItem(loader->m_item));
529 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
530 g_windowManager.SendThreadMessage(msg);
532 CJobQueue::OnJobComplete(jobID, success, job);
535 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
537 if (item.m_bIsFolder) return;
539 std::string stereoMode;
540 // detect stereomode for videos
541 if (item.HasVideoInfoTag())
542 stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
543 if (stereoMode.empty())
545 std::string path = item.GetPath();
546 if (item.IsVideoDb() && item.HasVideoInfoTag())
547 path = item.GetVideoInfoTag()->GetPath();
549 // check for custom stereomode setting in video settings
550 CVideoSettings itemVideoSettings;
551 m_videoDatabase->Open();
552 if (m_videoDatabase->GetVideoSettings(path, itemVideoSettings) && itemVideoSettings.m_StereoMode != RENDER_STEREO_MODE_OFF)
553 stereoMode = CStereoscopicsManager::Get().ConvertGuiStereoModeToString( (RENDER_STEREO_MODE) itemVideoSettings.m_StereoMode );
554 m_videoDatabase->Close();
556 // still empty, try grabbing from filename
557 // TODO: in case of too many false positives due to using the full path, extract the filename only using string utils
558 if (stereoMode.empty())
559 stereoMode = CStereoscopicsManager::Get().DetectStereoModeByString( path );
561 if (!stereoMode.empty())
562 item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));