Fix keymap.
[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   if (!thumb.empty())
471     item.SetArt("thumb", thumb);
472   return !thumb.empty();
473 }
474
475 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
476 {
477   if (item.SkipLocalArt())
478     return "";
479
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.
485    */
486   if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
487   {
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);
490   }
491
492   std::string art;
493   if (!type.empty())
494   {
495     art = item.FindLocalArt(type + ".jpg", checkFolder);
496     if (art.empty())
497       art = item.FindLocalArt(type + ".png", checkFolder);
498   }
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()))
503     { // try movie.tbn
504       art = item.FindLocalArt("movie.tbn", true);
505       if (art.empty()) // try folder.jpg
506         art = item.FindLocalArt("folder.jpg", true);
507     }
508   }
509   return art;
510 }
511
512 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
513 {
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);
519
520   return CTextureUtils::GetWrappedImageURL(path, "video");
521 }
522
523 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
524 {
525   if (success)
526   {
527     CThumbExtractor* loader = (CThumbExtractor*)job;
528     loader->m_item.SetPath(loader->m_listpath);
529
530     if (m_pObserver)
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);
535   }
536   CJobQueue::OnJobComplete(jobID, success, job);
537 }
538
539 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
540 {
541   if (item.m_bIsFolder) return;
542
543   std::string stereoMode;
544   // detect stereomode for videos
545   if (item.HasVideoInfoTag())
546     stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
547   if (stereoMode.empty())
548   {
549     std::string path = item.GetPath();
550     if (item.IsVideoDb() && item.HasVideoInfoTag())
551       path = item.GetVideoInfoTag()->GetPath();
552
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();
559
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 );
564   }
565   if (!stereoMode.empty())
566     item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));
567 }