Merge pull request #4314 from MartijnKaijser/beta1
[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       {
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);
141
142         // Restore original stack path
143         m_item.SetPath(m_listpath);
144       }
145
146       if (info->m_iFileId < 0)
147         db.SetStreamDetailsForFile(info->m_streamDetails, !info->m_strFileNameAndPath.empty() ? info->m_strFileNameAndPath : m_item.GetPath());
148       else
149         db.SetStreamDetailsForFileId(info->m_streamDetails, info->m_iFileId);
150
151       db.Close();
152     }
153     return true;
154   }
155
156   return false;
157 }
158
159 CVideoThumbLoader::CVideoThumbLoader() :
160   CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
161 {
162   m_videoDatabase = new CVideoDatabase();
163 }
164
165 CVideoThumbLoader::~CVideoThumbLoader()
166 {
167   StopThread();
168   delete m_videoDatabase;
169 }
170
171 void CVideoThumbLoader::OnLoaderStart()
172 {
173   m_videoDatabase->Open();
174   m_showArt.clear();
175   CThumbLoader::OnLoaderStart();
176 }
177
178 void CVideoThumbLoader::OnLoaderFinish()
179 {
180   m_videoDatabase->Close();
181   m_showArt.clear();
182   CThumbLoader::OnLoaderFinish();
183 }
184
185 static void SetupRarOptions(CFileItem& item, const CStdString& path)
186 {
187   CStdString path2(path);
188   if (item.IsVideoDb() && item.HasVideoInfoTag())
189     path2 = item.GetVideoInfoTag()->m_strFileNameAndPath;
190   CURL url(path2);
191   CStdString opts = url.GetOptions();
192   if (opts.find("flags") != std::string::npos)
193     return;
194   if (opts.size())
195     opts += "&flags=8";
196   else
197     opts = "?flags=8";
198   url.SetOptions(opts);
199   if (item.IsVideoDb() && item.HasVideoInfoTag())
200     item.GetVideoInfoTag()->m_strFileNameAndPath = url.Get();
201   else
202     item.SetPath(url.Get());
203   g_directoryCache.ClearDirectory(url.GetWithoutFilename());
204 }
205
206 vector<string> CVideoThumbLoader::GetArtTypes(const string &type)
207 {
208   vector<string> ret;
209   if (type == "episode")
210     ret.push_back("thumb");
211   else if (type == "tvshow" || type == "season")
212   {
213     ret.push_back("banner");
214     ret.push_back("poster");
215     ret.push_back("fanart");
216   }
217   else if (type == "movie" || type == "musicvideo" || type == "set")
218   {
219     ret.push_back("poster");
220     ret.push_back("fanart");
221   }
222   else if (type.empty()) // unknown - just throw everything in
223   {
224     ret.push_back("poster");
225     ret.push_back("banner");
226     ret.push_back("thumb");
227     ret.push_back("fanart");
228   }
229   return ret;
230 }
231
232 /**
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
237  */
238 bool CVideoThumbLoader::LoadItem(CFileItem* pItem)
239 {
240   bool result  = LoadItemCached(pItem);
241        result |= LoadItemLookup(pItem);
242
243   return result;
244 }
245
246 bool CVideoThumbLoader::LoadItemCached(CFileItem* pItem)
247 {
248   if (pItem->m_bIsShareOrDrive
249   ||  pItem->IsParentFolder())
250     return false;
251
252   m_videoDatabase->Open();
253
254   if (!pItem->HasVideoInfoTag() || !pItem->GetVideoInfoTag()->HasStreamDetails()) // no stream details
255   {
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
258     {
259       if (m_videoDatabase->GetStreamDetails(*pItem))
260         pItem->SetInvalid();
261     }
262   }
263
264   // video db items normally have info in the database
265   if (pItem->HasVideoInfoTag() && !pItem->HasArt("thumb"))
266   {
267     FillLibraryArt(*pItem);
268
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")
274     {
275       m_videoDatabase->Close();
276       return true; // nothing else to be done
277     }
278   }
279
280   // if we have no art, look for it all
281   map<string, string> artwork = pItem->GetArt();
282   if (artwork.empty())
283   {
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)
288     {
289       std::string type = *i;
290       std::string art = GetCachedImage(*pItem, type);
291       if (!art.empty())
292         artwork.insert(make_pair(type, art));
293     }
294     SetArt(*pItem, artwork);
295   }
296
297   m_videoDatabase->Close();
298
299   return true;
300 }
301
302 bool CVideoThumbLoader::LoadItemLookup(CFileItem* pItem)
303 {
304   if (pItem->m_bIsShareOrDrive || pItem->IsParentFolder() || pItem->GetPath() == "add")
305     return false;
306
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
314
315   DetectAndAddMissingItemData(*pItem);
316
317   m_videoDatabase->Open();
318
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)
324   {
325     std::string type = *i;
326     if (!pItem->HasArt(type))
327     {
328       std::string art = GetLocalArt(*pItem, type, type=="fanart");
329       if (!art.empty()) // cache it
330       {
331         SetCachedImage(*pItem, type, art);
332         CTextureCache::Get().BackgroundCacheImage(art);
333         artwork.insert(make_pair(type, art));
334       }
335     }
336   }
337   SetArt(*pItem, artwork);
338
339   // We can only extract flags/thumbs for file-like items
340   if (!pItem->m_bIsFolder && pItem->IsVideo())
341   {
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", "");
346
347     if (!pItem->HasArt("thumb"))
348     {
349       // create unique thumb for auto generated thumbs
350       CStdString thumbURL = GetEmbeddedThumbURL(*pItem);
351       if (CTextureCache::Get().HasCachedImage(thumbURL))
352       {
353         CTextureCache::Get().BackgroundCacheImage(thumbURL);
354         pItem->SetProperty("HasAutoThumb", true);
355         pItem->SetProperty("AutoThumbImage", thumbURL);
356         pItem->SetArt("thumb", thumbURL);
357
358         if (pItem->HasVideoInfoTag())
359         {
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);
364         }
365       }
366       else if (CSettings::Get().GetBool("myvideos.extractthumb") &&
367                CSettings::Get().GetBool("myvideos.extractflags"))
368       {
369         CFileItem item(*pItem);
370         CStdString path(item.GetPath());
371         if (URIUtils::IsInRAR(item.GetPath()))
372           SetupRarOptions(item,path);
373
374         CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
375         AddJob(extract);
376
377         m_videoDatabase->Close();
378         return true;
379       }
380     }
381
382     // flag extraction
383     if (CSettings::Get().GetBool("myvideos.extractflags") &&
384        (!pItem->HasVideoInfoTag()                     ||
385         !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
386     {
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);
392       AddJob(extract);
393     }
394   }
395
396   m_videoDatabase->Close();
397   return true;
398 }
399
400 void CVideoThumbLoader::SetArt(CFileItem &item, const map<string, string> &artwork)
401 {
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");
409   }
410 }
411
412 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
413 {
414   CVideoInfoTag &tag = *item.GetVideoInfoTag();
415   if (tag.m_iDbId > -1 && !tag.m_type.empty())
416   {
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;
424       database.Open();
425       int idArtist = database.GetArtistByName(item.GetLabel());
426       if (database.GetArtForItem(idArtist, "artist", artwork))
427         item.SetArt(artwork);
428     }
429     else if (tag.m_type == "album")
430     { // we retrieve music video art from the music database (no backward compat)
431       CMusicDatabase database;
432       database.Open();
433       int idAlbum = database.GetAlbumByName(item.GetLabel(), tag.m_artist);
434       if (database.GetArtForItem(idAlbum, "album", artwork))
435         item.SetArt(artwork);
436     }
437     // For episodes and seasons, we want to set fanart for that of the show
438     if (!item.HasArt("fanart") && tag.m_iIdShow >= 0)
439     {
440       ArtCache::const_iterator i = m_showArt.find(tag.m_iIdShow);
441       if (i == m_showArt.end())
442       {
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;
446       }
447       if (i != m_showArt.end())
448       {
449         item.AppendArt(i->second, "tvshow");
450         item.SetArtFallback("fanart", "tvshow.fanart");
451         item.SetArtFallback("tvshow.thumb", "tvshow.poster");
452       }
453     }
454     m_videoDatabase->Close();
455   }
456   return !item.GetArt().empty();
457 }
458
459 bool CVideoThumbLoader::FillThumb(CFileItem &item)
460 {
461   if (item.HasArt("thumb"))
462     return true;
463   CStdString thumb = GetCachedImage(item, "thumb");
464   if (thumb.empty())
465   {
466     thumb = GetLocalArt(item, "thumb");
467     if (!thumb.empty())
468       SetCachedImage(item, "thumb", thumb);
469   }
470   item.SetArt("thumb", thumb);
471   return !thumb.empty();
472 }
473
474 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
475 {
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.
481    */
482   if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
483   {
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);
486   }
487
488   std::string art;
489   if (!type.empty())
490   {
491     art = item.FindLocalArt(type + ".jpg", checkFolder);
492     if (art.empty())
493       art = item.FindLocalArt(type + ".png", checkFolder);
494   }
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()))
499     { // try movie.tbn
500       art = item.FindLocalArt("movie.tbn", true);
501       if (art.empty()) // try folder.jpg
502         art = item.FindLocalArt("folder.jpg", true);
503     }
504   }
505   return art;
506 }
507
508 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
509 {
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);
515
516   return CTextureUtils::GetWrappedImageURL(path, "video");
517 }
518
519 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
520 {
521   if (success)
522   {
523     CThumbExtractor* loader = (CThumbExtractor*)job;
524     loader->m_item.SetPath(loader->m_listpath);
525
526     if (m_pObserver)
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);
531   }
532   CJobQueue::OnJobComplete(jobID, success, job);
533 }
534
535 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
536 {
537   if (item.m_bIsFolder) return;
538
539   std::string stereoMode;
540   // detect stereomode for videos
541   if (item.HasVideoInfoTag())
542     stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
543   if (stereoMode.empty())
544   {
545     std::string path = item.GetPath();
546     if (item.IsVideoDb() && item.HasVideoInfoTag())
547       path = item.GetVideoInfoTag()->GetPath();
548
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();
555
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 );
560   }
561   if (!stereoMode.empty())
562     item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));
563 }