Merge pull request #4222 from Montellese/jsonrpc_audiolibrary_fixes
[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 || pItem->IsParentFolder() || pItem->GetPath() == "add")
299     return false;
300
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
308
309   DetectAndAddMissingItemData(*pItem);
310
311   m_videoDatabase->Open();
312
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)
318   {
319     std::string type = *i;
320     if (!pItem->HasArt(type))
321     {
322       std::string art = GetLocalArt(*pItem, type, type=="fanart");
323       if (!art.empty()) // cache it
324       {
325         SetCachedImage(*pItem, type, art);
326         CTextureCache::Get().BackgroundCacheImage(art);
327         artwork.insert(make_pair(type, art));
328       }
329     }
330   }
331   SetArt(*pItem, artwork);
332
333   // We can only extract flags/thumbs for file-like items
334   if (!pItem->m_bIsFolder && pItem->IsVideo())
335   {
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", "");
340
341     if (!pItem->HasArt("thumb"))
342     {
343       // create unique thumb for auto generated thumbs
344       CStdString thumbURL = GetEmbeddedThumbURL(*pItem);
345       if (CTextureCache::Get().HasCachedImage(thumbURL))
346       {
347         CTextureCache::Get().BackgroundCacheImage(thumbURL);
348         pItem->SetProperty("HasAutoThumb", true);
349         pItem->SetProperty("AutoThumbImage", thumbURL);
350         pItem->SetArt("thumb", thumbURL);
351
352         if (pItem->HasVideoInfoTag())
353         {
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);
358         }
359       }
360       else if (CSettings::Get().GetBool("myvideos.extractthumb") &&
361                CSettings::Get().GetBool("myvideos.extractflags"))
362       {
363         CFileItem item(*pItem);
364         CStdString path(item.GetPath());
365         if (URIUtils::IsInRAR(item.GetPath()))
366           SetupRarOptions(item,path);
367
368         CThumbExtractor* extract = new CThumbExtractor(item, path, true, thumbURL);
369         AddJob(extract);
370
371         m_videoDatabase->Close();
372         return true;
373       }
374     }
375
376     // flag extraction
377     if (CSettings::Get().GetBool("myvideos.extractflags") &&
378        (!pItem->HasVideoInfoTag()                     ||
379         !pItem->GetVideoInfoTag()->HasStreamDetails() ) )
380     {
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);
386       AddJob(extract);
387     }
388   }
389
390   m_videoDatabase->Close();
391   return true;
392 }
393
394 void CVideoThumbLoader::SetArt(CFileItem &item, const map<string, string> &artwork)
395 {
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");
403   }
404 }
405
406 bool CVideoThumbLoader::FillLibraryArt(CFileItem &item)
407 {
408   CVideoInfoTag &tag = *item.GetVideoInfoTag();
409   if (tag.m_iDbId > -1 && !tag.m_type.empty())
410   {
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;
418       database.Open();
419       int idArtist = database.GetArtistByName(item.GetLabel());
420       if (database.GetArtForItem(idArtist, "artist", artwork))
421         item.SetArt(artwork);
422     }
423     else if (tag.m_type == "album")
424     { // we retrieve music video art from the music database (no backward compat)
425       CMusicDatabase database;
426       database.Open();
427       int idAlbum = database.GetAlbumByName(item.GetLabel(), tag.m_artist);
428       if (database.GetArtForItem(idAlbum, "album", artwork))
429         item.SetArt(artwork);
430     }
431     // For episodes and seasons, we want to set fanart for that of the show
432     if (!item.HasArt("fanart") && tag.m_iIdShow >= 0)
433     {
434       ArtCache::const_iterator i = m_showArt.find(tag.m_iIdShow);
435       if (i == m_showArt.end())
436       {
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;
440       }
441       if (i != m_showArt.end())
442       {
443         item.AppendArt(i->second, "tvshow");
444         item.SetArtFallback("fanart", "tvshow.fanart");
445         item.SetArtFallback("tvshow.thumb", "tvshow.poster");
446       }
447     }
448     m_videoDatabase->Close();
449   }
450   return !item.GetArt().empty();
451 }
452
453 bool CVideoThumbLoader::FillThumb(CFileItem &item)
454 {
455   if (item.HasArt("thumb"))
456     return true;
457   CStdString thumb = GetCachedImage(item, "thumb");
458   if (thumb.empty())
459   {
460     thumb = GetLocalArt(item, "thumb");
461     if (!thumb.empty())
462       SetCachedImage(item, "thumb", thumb);
463   }
464   item.SetArt("thumb", thumb);
465   return !thumb.empty();
466 }
467
468 std::string CVideoThumbLoader::GetLocalArt(const CFileItem &item, const std::string &type, bool checkFolder)
469 {
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.
475    */
476   if (item.m_bIsFolder && (item.IsInternetStream(true) || g_advancedSettings.m_networkBufferMode == 1))
477   {
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);
480   }
481
482   std::string art;
483   if (!type.empty())
484   {
485     art = item.FindLocalArt(type + ".jpg", checkFolder);
486     if (art.empty())
487       art = item.FindLocalArt(type + ".png", checkFolder);
488   }
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()))
493     { // try movie.tbn
494       art = item.FindLocalArt("movie.tbn", true);
495       if (art.empty()) // try folder.jpg
496         art = item.FindLocalArt("folder.jpg", true);
497     }
498   }
499   return art;
500 }
501
502 CStdString CVideoThumbLoader::GetEmbeddedThumbURL(const CFileItem &item)
503 {
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);
509
510   return CTextureUtils::GetWrappedImageURL(path, "video");
511 }
512
513 void CVideoThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
514 {
515   if (success)
516   {
517     CThumbExtractor* loader = (CThumbExtractor*)job;
518     loader->m_item.SetPath(loader->m_listpath);
519
520     if (m_pObserver)
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);
525   }
526   CJobQueue::OnJobComplete(jobID, success, job);
527 }
528
529 void CVideoThumbLoader::DetectAndAddMissingItemData(CFileItem &item)
530 {
531   if (item.m_bIsFolder) return;
532
533   std::string stereoMode;
534   // detect stereomode for videos
535   if (item.HasVideoInfoTag())
536     stereoMode = item.GetVideoInfoTag()->m_streamDetails.GetStereoMode();
537   if (stereoMode.empty())
538   {
539     std::string path = item.GetPath();
540     if (item.IsVideoDb() && item.HasVideoInfoTag())
541       path = item.GetVideoInfoTag()->GetPath();
542
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();
549
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 );
554   }
555   if (!stereoMode.empty())
556     item.SetProperty("stereomode", CStereoscopicsManager::Get().NormalizeStereoMode(stereoMode));
557 }