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 || pItem->IsParentFolder() || pItem->GetPath() == "add")
301 if (pItem->HasVideoInfoTag() &&
302 !pItem->GetVideoInfoTag()->m_type.empty() &&
303 pItem->GetVideoInfoTag()->m_type != "movie" &&
304 pItem->GetVideoInfoTag()->m_type != "tvshow" &&
305 pItem->GetVideoInfoTag()->m_type != "episode" &&
306 pItem->GetVideoInfoTag()->m_type != "musicvideo")
307 return false; // Nothing to do here
309 DetectAndAddMissingItemData(*pItem);
311 m_videoDatabase->Open();
313 map<string, string> artwork = pItem->GetArt();
314 vector<string> artTypes = GetArtTypes(pItem->HasVideoInfoTag() ? pItem->GetVideoInfoTag()->m_type : "");
315 if (find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end())
316 artTypes.push_back("thumb"); // always look for "thumb" art for files
317 for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
319 std::string type = *i;
320 if (!pItem->HasArt(type))
322 std::string art = GetLocalArt(*pItem, type, type=="fanart");
323 if (!art.empty()) // cache it
325 SetCachedImage(*pItem, type, art);
326 CTextureCache::Get().BackgroundCacheImage(art);
327 artwork.insert(make_pair(type, art));
331 SetArt(*pItem, artwork);
333 // We can only extract flags/thumbs for file-like items
334 if (!pItem->m_bIsFolder && pItem->IsVideo())
336 // An auto-generated thumb may have been cached on a different device - check we have it here
337 CStdString url = pItem->GetArt("thumb");
338 if (StringUtils::StartsWith(url, "image://video@") && !CTextureCache::Get().HasCachedImage(url))
339 pItem->SetArt("thumb", "");
341 if (!pItem->HasArt("thumb"))
343 // create unique thumb for auto generated thumbs
344 CStdString thumbURL = GetEmbeddedThumbURL(*pItem);
345 if (CTextureCache::Get().HasCachedImage(thumbURL))
347 CTextureCache::Get().BackgroundCacheImage(thumbURL);
348 pItem->SetProperty("HasAutoThumb", true);
349 pItem->SetProperty("AutoThumbImage", thumbURL);
350 pItem->SetArt("thumb", thumbURL);
352 if (pItem->HasVideoInfoTag())
354 // Item has cached autogen image but no art entry. Save it to db.
355 CVideoInfoTag* info = pItem->GetVideoInfoTag();
356 if (info->m_iDbId > 0 && !info->m_type.empty())
357 m_videoDatabase->SetArtForItem(info->m_iDbId, info->m_type, "thumb", thumbURL);
360 else if (CSettings::Get().GetBool("myvideos.extractthumb") &&
361 CSettings::Get().GetBool("myvideos.extractflags"))
363 CFileItem item(*pItem);
364 CStdString path(item.GetPath());
365 if (URIUtils::IsInRAR(item.GetPath()))
366 SetupRarOptions(item,path);
368 CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
371 m_videoDatabase->Close();
377 if (CSettings::Get().GetBool("myvideos.extractflags") &&
378 (!pItem->HasVideoInfoTag() ||
379 !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
381 CFileItem item(*pItem);
382 CStdString path(item.GetPath());
383 if (URIUtils::IsInRAR(item.GetPath()))
384 SetupRarOptions(item,path);
385 CThumbExtractor* extract = new CThumbExtractor(item,path,false);
390 m_videoDatabase->Close();
394 void CVideoThumbLoader::SetArt(CFileItem &item, const map<string, string> &artwork)
396 item.SetArt(artwork);
397 if (artwork.find("thumb") == artwork.end())
398 { // set fallback for "thumb"
399 if (artwork.find("poster") != artwork.end())
400 item.SetArtFallback("thumb", "poster");
401 else if (artwork.find("banner") != artwork.end())
402 item.SetArtFallback("thumb", "banner");
406 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
408 CVideoInfoTag &tag = *item.GetVideoInfoTag();
409 if (tag.m_iDbId > -1 && !tag.m_type.empty())
411 map<string, string> artwork;
412 m_videoDatabase->Open();
413 if (m_videoDatabase->GetArtForItem(tag.m_iDbId, tag.m_type, artwork))
414 SetArt(item, artwork);
415 else if (tag.m_type == "artist")
416 { // we retrieve music video art from the music database (no backward compat)
417 CMusicDatabase database;
419 int idArtist = database.GetArtistByName(item.GetLabel());
420 if (database.GetArtForItem(idArtist, "artist", artwork))
421 item.SetArt(artwork);
423 else if (tag.m_type == "album")
424 { // we retrieve music video art from the music database (no backward compat)
425 CMusicDatabase database;
427 int idAlbum = database.GetAlbumByName(item.GetLabel(), tag.m_artist);
428 if (database.GetArtForItem(idAlbum, "album", artwork))
429 item.SetArt(artwork);
431 // For episodes and seasons, we want to set fanart for that of the show
432 if (!item.HasArt("fanart") && tag.m_iIdShow >= 0)
434 ArtCache::const_iterator i = m_showArt.find(tag.m_iIdShow);
435 if (i == m_showArt.end())
437 map<string, string> showArt;
438 m_videoDatabase->GetArtForItem(tag.m_iIdShow, "tvshow", showArt);
439 i = m_showArt.insert(make_pair(tag.m_iIdShow, showArt)).first;
441 if (i != m_showArt.end())
443 item.AppendArt(i->second, "tvshow");
444 item.SetArtFallback("fanart", "tvshow.fanart");
445 item.SetArtFallback("tvshow.thumb", "tvshow.poster");
448 m_videoDatabase->Close();
450 return !item.GetArt().empty();
453 bool CVideoThumbLoader::FillThumb(CFileItem &item)
455 if (item.HasArt("thumb"))
457 CStdString thumb = GetCachedImage(item, "thumb");
460 thumb = GetLocalArt(item, "thumb");
462 SetCachedImage(item, "thumb", thumb);
464 item.SetArt("thumb", thumb);
465 return !thumb.empty();
468 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
470 /* Cache directory for (sub) folders on streamed filesystems. We need to do this
471 else entering (new) directories from the app thread becomes much slower. This
472 is caused by the fact that Curl Stat/Exist() is really slow and that the
473 thumbloader thread accesses the streamed filesystem at the same time as the
474 App thread and the latter has to wait for it.
476 if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
478 CFileItemList items; // Dummy list
479 CDirectory::GetDirectory(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
485 art = item.FindLocalArt(type + ".jpg", checkFolder);
487 art = item.FindLocalArt(type + ".png", checkFolder);
489 if (art.empty() && (type.empty() || type == "thumb"))
490 { // backward compatibility
491 art = item.FindLocalArt("", false);
492 if (art.empty() && (checkFolder || (item.m_bIsFolder && !item.IsFileFolder()) || item.IsOpticalMediaFile()))
494 art = item.FindLocalArt("movie.tbn", true);
495 if (art.empty()) // try folder.jpg
496 art = item.FindLocalArt("folder.jpg", true);
502 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
504 CStdString path(item.GetPath());
505 if (item.IsVideoDb() && item.HasVideoInfoTag())
506 path = item.GetVideoInfoTag()->m_strFileNameAndPath;
507 if (URIUtils::IsStack(path))
508 path = CStackDirectory::GetFirstStackedFile(path);
510 return CTextureUtils::GetWrappedImageURL(path, "video");
513 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
517 CThumbExtractor* loader = (CThumbExtractor*)job;
518 loader->m_item.SetPath(loader->m_listpath);
521 m_pObserver->OnItemLoaded(&loader->m_item);
522 CFileItemPtr pItem(new CFileItem(loader->m_item));
523 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
524 g_windowManager.SendThreadMessage(msg);
526 CJobQueue::OnJobComplete(jobID, success, job);
529 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
531 if (item.m_bIsFolder) return;
533 std::string stereoMode;
534 // detect stereomode for videos
535 if (item.HasVideoInfoTag())
536 stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
537 if (stereoMode.empty())
539 std::string path = item.GetPath();
540 if (item.IsVideoDb() && item.HasVideoInfoTag())
541 path = item.GetVideoInfoTag()->GetPath();
543 // check for custom stereomode setting in video settings
544 CVideoSettings itemVideoSettings;
545 m_videoDatabase->Open();
546 if (m_videoDatabase->GetVideoSettings(path, itemVideoSettings) && itemVideoSettings.m_StereoMode != RENDER_STEREO_MODE_OFF)
547 stereoMode = CStereoscopicsManager::Get().ConvertGuiStereoModeToString( (RENDER_STEREO_MODE) itemVideoSettings.m_StereoMode );
548 m_videoDatabase->Close();
550 // still empty, try grabbing from filename
551 // TODO: in case of too many false positives due to using the full path, extract the filename only using string utils
552 if (stereoMode.empty())
553 stereoMode = CStereoscopicsManager::Get().DetectStereoModeByString( path );
555 if (!stereoMode.empty())
556 item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));