changed: Add logic to properly handle subtitles for stacked files
[vuplus_xbmc] / xbmc / video / VideoThumbLoader.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "VideoThumbLoader.h"
22 #include "filesystem/StackDirectory.h"
23 #include "utils/URIUtils.h"
24 #include "URL.h"
25 #include "filesystem/File.h"
26 #include "filesystem/DirectoryCache.h"
27 #include "FileItem.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"
43
44 using namespace XFILE;
45 using namespace std;
46 using namespace VIDEO;
47
48 CThumbExtractor::CThumbExtractor(const CFileItem& item, const CStdString& listpath, bool thumb, const CStdString& target)
49 {
50   m_listpath = listpath;
51   m_target = target;
52   m_thumb = thumb;
53   m_item = item;
54
55   if (item.IsVideoDb() && item.HasVideoInfoTag())
56     m_item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath);
57
58   if (m_item.IsStack())
59     m_item.SetPath(CStackDirectory::GetFirstStackedFile(m_item.GetPath()));
60 }
61
62 CThumbExtractor::~CThumbExtractor()
63 {
64 }
65
66 bool CThumbExtractor::operator==(const CJob* job) const
67 {
68   if (strcmp(job->GetType(),GetType()) == 0)
69   {
70     const CThumbExtractor* jobExtract = dynamic_cast<const CThumbExtractor*>(job);
71     if (jobExtract && jobExtract->m_listpath == m_listpath)
72       return true;
73   }
74   return false;
75 }
76
77 bool CThumbExtractor::DoWork()
78 {
79   if (m_item.IsLiveTV()
80   ||  URIUtils::IsUPnP(m_item.GetPath())
81   ||  m_item.IsDAAP()
82   ||  m_item.IsDVD()
83   ||  m_item.IsDVDImage()
84   ||  m_item.IsDVDFile(false, true)
85   ||  m_item.IsInternetStream()
86   ||  m_item.IsDiscStub()
87   ||  m_item.IsPlayList())
88     return false;
89
90   if (URIUtils::IsRemote(m_item.GetPath()) && !URIUtils::IsOnLAN(m_item.GetPath()))
91   {
92     // A quasi internet filesystem like webdav is generally fast enough for extracting stuff
93     if (!URIUtils::IsDAV(m_item.GetPath()))
94       return false;
95   }
96
97   bool result=false;
98   if (m_thumb)
99   {
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);
105     if(result)
106     {
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);
111
112       CVideoInfoTag* info = m_item.GetVideoInfoTag();
113       if (info->m_iDbId > 0 && !info->m_type.empty())
114       {
115         CVideoDatabase db;
116         if (db.Open())
117         {
118           db.SetArtForItem(info->m_iDbId, info->m_type, "thumb", m_item.GetArt("thumb"));
119           db.Close();
120         }
121       }
122     }
123   }
124   else if (!m_item.HasVideoInfoTag() || !m_item.GetVideoInfoTag()->HasStreamDetails())
125   {
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);
129   }
130
131   if (result)
132   {
133     CVideoInfoTag* info = m_item.GetVideoInfoTag();
134     CVideoDatabase db;
135     if (db.Open())
136     {
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
139
140       if (info->m_iFileId < 0)
141         db.SetStreamDetailsForFile(info->m_streamDetails, !info->m_strFileNameAndPath.empty() ? info->m_strFileNameAndPath : m_item.GetPath());
142       else
143         db.SetStreamDetailsForFileId(info->m_streamDetails, info->m_iFileId);
144
145       db.Close();
146     }
147     return true;
148   }
149
150   return false;
151 }
152
153 CVideoThumbLoader::CVideoThumbLoader() :
154   CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
155 {
156   m_videoDatabase = new CVideoDatabase();
157 }
158
159 CVideoThumbLoader::~CVideoThumbLoader()
160 {
161   StopThread();
162   delete m_videoDatabase;
163 }
164
165 void CVideoThumbLoader::OnLoaderStart()
166 {
167   m_videoDatabase->Open();
168   m_showArt.clear();
169   CThumbLoader::OnLoaderStart();
170 }
171
172 void CVideoThumbLoader::OnLoaderFinish()
173 {
174   m_videoDatabase->Close();
175   m_showArt.clear();
176   CThumbLoader::OnLoaderFinish();
177 }
178
179 static void SetupRarOptions(CFileItem& item, const CStdString& path)
180 {
181   CStdString path2(path);
182   if (item.IsVideoDb() && item.HasVideoInfoTag())
183     path2 = item.GetVideoInfoTag()->m_strFileNameAndPath;
184   CURL url(path2);
185   CStdString opts = url.GetOptions();
186   if (opts.find("flags") != std::string::npos)
187     return;
188   if (opts.size())
189     opts += "&flags=8";
190   else
191     opts = "?flags=8";
192   url.SetOptions(opts);
193   if (item.IsVideoDb() && item.HasVideoInfoTag())
194     item.GetVideoInfoTag()->m_strFileNameAndPath = url.Get();
195   else
196     item.SetPath(url.Get());
197   g_directoryCache.ClearDirectory(url.GetWithoutFilename());
198 }
199
200 vector<string> CVideoThumbLoader::GetArtTypes(const string &type)
201 {
202   vector<string> ret;
203   if (type == "episode")
204     ret.push_back("thumb");
205   else if (type == "tvshow" || type == "season")
206   {
207     ret.push_back("banner");
208     ret.push_back("poster");
209     ret.push_back("fanart");
210   }
211   else if (type == "movie" || type == "musicvideo" || type == "set")
212   {
213     ret.push_back("poster");
214     ret.push_back("fanart");
215   }
216   else if (type.empty()) // unknown - just throw everything in
217   {
218     ret.push_back("poster");
219     ret.push_back("banner");
220     ret.push_back("thumb");
221     ret.push_back("fanart");
222   }
223   return ret;
224 }
225
226 /**
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
231  */
232 bool CVideoThumbLoader::LoadItem(CFileItem* pItem)
233 {
234   bool result  = LoadItemCached(pItem);
235        result |= LoadItemLookup(pItem);
236
237   return result;
238 }
239
240 bool CVideoThumbLoader::LoadItemCached(CFileItem* pItem)
241 {
242   if (pItem->m_bIsShareOrDrive
243   ||  pItem->IsParentFolder())
244     return false;
245
246   m_videoDatabase->Open();
247
248   if (!pItem->HasVideoInfoTag() || !pItem->GetVideoInfoTag()->HasStreamDetails()) // no stream details
249   {
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
252     {
253       if (m_videoDatabase->GetStreamDetails(*pItem))
254         pItem->SetInvalid();
255     }
256   }
257
258   // video db items normally have info in the database
259   if (pItem->HasVideoInfoTag() && !pItem->HasArt("thumb"))
260   {
261     FillLibraryArt(*pItem);
262
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")
268     {
269       m_videoDatabase->Close();
270       return true; // nothing else to be done
271     }
272   }
273
274   // if we have no art, look for it all
275   map<string, string> artwork = pItem->GetArt();
276   if (artwork.empty())
277   {
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)
282     {
283       std::string type = *i;
284       std::string art = GetCachedImage(*pItem, type);
285       if (!art.empty())
286         artwork.insert(make_pair(type, art));
287     }
288     SetArt(*pItem, artwork);
289   }
290
291   m_videoDatabase->Close();
292
293   return true;
294 }
295
296 bool CVideoThumbLoader::LoadItemLookup(CFileItem* pItem)
297 {
298   if (pItem->m_bIsShareOrDrive
299   ||  pItem->IsParentFolder())
300     return false;
301
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
309
310   DetectAndAddMissingItemData(*pItem);
311
312   m_videoDatabase->Open();
313
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)
319   {
320     std::string type = *i;
321     if (!pItem->HasArt(type))
322     {
323       std::string art = GetLocalArt(*pItem, type, type=="fanart");
324       if (!art.empty()) // cache it
325       {
326         SetCachedImage(*pItem, type, art);
327         CTextureCache::Get().BackgroundCacheImage(art);
328         artwork.insert(make_pair(type, art));
329       }
330     }
331   }
332   SetArt(*pItem, artwork);
333
334   // We can only extract flags/thumbs for file-like items
335   if (!pItem->m_bIsFolder && pItem->IsVideo())
336   {
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", "");
341
342     if (!pItem->HasArt("thumb"))
343     {
344       // create unique thumb for auto generated thumbs
345       CStdString thumbURL = GetEmbeddedThumbURL(*pItem);
346       if (CTextureCache::Get().HasCachedImage(thumbURL))
347       {
348         CTextureCache::Get().BackgroundCacheImage(thumbURL);
349         pItem->SetProperty("HasAutoThumb", true);
350         pItem->SetProperty("AutoThumbImage", thumbURL);
351         pItem->SetArt("thumb", thumbURL);
352
353         if (pItem->HasVideoInfoTag())
354         {
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);
359         }
360       }
361       else if (CSettings::Get().GetBool("myvideos.extractthumb") &&
362                CSettings::Get().GetBool("myvideos.extractflags"))
363       {
364         CFileItem item(*pItem);
365         CStdString path(item.GetPath());
366         if (URIUtils::IsInRAR(item.GetPath()))
367           SetupRarOptions(item,path);
368
369         CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
370         AddJob(extract);
371
372         m_videoDatabase->Close();
373         return true;
374       }
375     }
376
377     // flag extraction
378     if (CSettings::Get().GetBool("myvideos.extractflags") &&
379        (!pItem->HasVideoInfoTag()                     ||
380         !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
381     {
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);
387       AddJob(extract);
388     }
389   }
390
391   m_videoDatabase->Close();
392   return true;
393 }
394
395 void CVideoThumbLoader::SetArt(CFileItem &item, const map<string, string> &artwork)
396 {
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");
404   }
405 }
406
407 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
408 {
409   CVideoInfoTag &tag = *item.GetVideoInfoTag();
410   if (tag.m_iDbId > -1 && !tag.m_type.empty())
411   {
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;
419       database.Open();
420       int idArtist = database.GetArtistByName(item.GetLabel());
421       if (database.GetArtForItem(idArtist, "artist", artwork))
422         item.SetArt(artwork);
423     }
424     else if (tag.m_type == "album")
425     { // we retrieve music video art from the music database (no backward compat)
426       CMusicDatabase database;
427       database.Open();
428       int idAlbum = database.GetAlbumByName(item.GetLabel(), tag.m_artist);
429       if (database.GetArtForItem(idAlbum, "album", artwork))
430         item.SetArt(artwork);
431     }
432     // For episodes and seasons, we want to set fanart for that of the show
433     if (!item.HasArt("fanart") && tag.m_iIdShow >= 0)
434     {
435       ArtCache::const_iterator i = m_showArt.find(tag.m_iIdShow);
436       if (i == m_showArt.end())
437       {
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;
441       }
442       if (i != m_showArt.end())
443       {
444         item.AppendArt(i->second, "tvshow");
445         item.SetArtFallback("fanart", "tvshow.fanart");
446         item.SetArtFallback("tvshow.thumb", "tvshow.poster");
447       }
448     }
449     m_videoDatabase->Close();
450   }
451   return !item.GetArt().empty();
452 }
453
454 bool CVideoThumbLoader::FillThumb(CFileItem &item)
455 {
456   if (item.HasArt("thumb"))
457     return true;
458   CStdString thumb = GetCachedImage(item, "thumb");
459   if (thumb.empty())
460   {
461     thumb = GetLocalArt(item, "thumb");
462     if (!thumb.empty())
463       SetCachedImage(item, "thumb", thumb);
464   }
465   item.SetArt("thumb", thumb);
466   return !thumb.empty();
467 }
468
469 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
470 {
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.
476    */
477   if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
478   {
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);
481   }
482
483   std::string art;
484   if (!type.empty())
485   {
486     art = item.FindLocalArt(type + ".jpg", checkFolder);
487     if (art.empty())
488       art = item.FindLocalArt(type + ".png", checkFolder);
489   }
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()))
494     { // try movie.tbn
495       art = item.FindLocalArt("movie.tbn", true);
496       if (art.empty()) // try folder.jpg
497         art = item.FindLocalArt("folder.jpg", true);
498     }
499   }
500   return art;
501 }
502
503 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
504 {
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);
510
511   return CTextureUtils::GetWrappedImageURL(path, "video");
512 }
513
514 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
515 {
516   if (success)
517   {
518     CThumbExtractor* loader = (CThumbExtractor*)job;
519     loader->m_item.SetPath(loader->m_listpath);
520
521     if (m_pObserver)
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);
526   }
527   CJobQueue::OnJobComplete(jobID, success, job);
528 }
529
530 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
531 {
532   if (item.m_bIsFolder) return;
533
534   std::string stereoMode;
535   // detect stereomode for videos
536   if (item.HasVideoInfoTag())
537     stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
538   if (stereoMode.empty())
539   {
540     std::string path = item.GetPath();
541     if (item.IsVideoDb() && item.HasVideoInfoTag())
542       path = item.GetVideoInfoTag()->GetPath();
543
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();
550
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 );
555   }
556   if (!stereoMode.empty())
557     item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));
558 }