Merge pull request #4735 from cg110/fix_web_server_mem_leak
[vuplus_xbmc] / xbmc / video / VideoInfoScanner.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 "threads/SystemClock.h"
22 #include "FileItem.h"
23 #include "VideoInfoScanner.h"
24 #include "addons/AddonManager.h"
25 #include "filesystem/DirectoryCache.h"
26 #include "Util.h"
27 #include "NfoFile.h"
28 #include "utils/RegExp.h"
29 #include "utils/md5.h"
30 #include "filesystem/StackDirectory.h"
31 #include "VideoInfoDownloader.h"
32 #include "GUIInfoManager.h"
33 #include "filesystem/File.h"
34 #include "dialogs/GUIDialogExtendedProgressBar.h"
35 #include "dialogs/GUIDialogProgress.h"
36 #include "dialogs/GUIDialogYesNo.h"
37 #include "dialogs/GUIDialogOK.h"
38 #include "interfaces/AnnouncementManager.h"
39 #include "settings/AdvancedSettings.h"
40 #include "settings/Settings.h"
41 #include "utils/StringUtils.h"
42 #include "guilib/LocalizeStrings.h"
43 #include "guilib/GUIWindowManager.h"
44 #include "utils/TimeUtils.h"
45 #include "utils/log.h"
46 #include "utils/URIUtils.h"
47 #include "utils/Variant.h"
48 #include "video/VideoThumbLoader.h"
49 #include "TextureCache.h"
50 #include "GUIUserMessages.h"
51 #include "URL.h"
52
53 using namespace std;
54 using namespace XFILE;
55 using namespace ADDON;
56
57 namespace VIDEO
58 {
59
60   CVideoInfoScanner::CVideoInfoScanner() : CThread("VideoInfoScanner")
61   {
62     m_bRunning = false;
63     m_handle = NULL;
64     m_showDialog = false;
65     m_bCanInterrupt = false;
66     m_currentItem = 0;
67     m_itemCount = 0;
68     m_bClean = false;
69     m_scanAll = false;
70   }
71
72   CVideoInfoScanner::~CVideoInfoScanner()
73   {
74   }
75
76   void CVideoInfoScanner::Process()
77   {
78     try
79     {
80       unsigned int tick = XbmcThreads::SystemClockMillis();
81
82       m_database.Open();
83
84       if (m_showDialog && !CSettings::Get().GetBool("videolibrary.backgroundupdate"))
85       {
86         CGUIDialogExtendedProgressBar* dialog =
87           (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
88         if (dialog)
89            m_handle = dialog->GetHandle(g_localizeStrings.Get(314));
90       }
91
92       m_bCanInterrupt = true;
93
94       CLog::Log(LOGNOTICE, "VideoInfoScanner: Starting scan ..");
95       ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnScanStarted");
96
97       // Reset progress vars
98       m_currentItem = 0;
99       m_itemCount = -1;
100
101       SetPriority(GetMinPriority());
102
103       // Database operations should not be canceled
104       // using Interupt() while scanning as it could
105       // result in unexpected behaviour.
106       m_bCanInterrupt = false;
107
108       bool bCancelled = false;
109       while (!bCancelled && m_pathsToScan.size())
110       {
111         /*
112          * A copy of the directory path is used because the path supplied is
113          * immediately removed from the m_pathsToScan set in DoScan(). If the
114          * reference points to the entry in the set a null reference error
115          * occurs.
116          */
117         CStdString directory = *m_pathsToScan.begin();
118         if (!CDirectory::Exists(directory))
119         {
120           /*
121            * Note that this will skip clean (if m_bClean is enabled) if the directory really
122            * doesn't exist rather than a NAS being switched off.  A manual clean from settings
123            * will still pick up and remove it though.
124            */
125           CLog::Log(LOGWARNING, "%s directory '%s' does not exist - skipping scan%s.", __FUNCTION__, directory.c_str(), m_bClean ? " and clean" : "");
126           m_pathsToScan.erase(m_pathsToScan.begin());
127         }
128         else if (!DoScan(directory))
129           bCancelled = true;
130       }
131
132       if (!bCancelled)
133       {
134         if (m_bClean)
135           CleanDatabase(m_handle,&m_pathsToClean, false);
136         else
137         {
138           if (m_handle)
139             m_handle->SetTitle(g_localizeStrings.Get(331));
140           m_database.Compress(false);
141         }
142       }
143
144       m_database.Close();
145
146       tick = XbmcThreads::SystemClockMillis() - tick;
147       CLog::Log(LOGNOTICE, "VideoInfoScanner: Finished scan. Scanning for video info took %s", StringUtils::SecondsToTimeString(tick / 1000).c_str());
148     }
149     catch (...)
150     {
151       CLog::Log(LOGERROR, "VideoInfoScanner: Exception while scanning.");
152     }
153     
154     m_bRunning = false;
155     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnScanFinished");
156
157     // we need to clear the videodb cache and update any active lists
158     CUtil::DeleteVideoDatabaseDirectoryCache();
159     CGUIMessage msg(GUI_MSG_SCAN_FINISHED, 0, 0, 0);
160     g_windowManager.SendThreadMessage(msg);
161     
162     if (m_handle)
163       m_handle->MarkFinished();
164     m_handle = NULL;
165   }
166
167   void CVideoInfoScanner::Start(const CStdString& strDirectory, bool scanAll)
168   {
169     m_strStartDir = strDirectory;
170     m_scanAll = scanAll;
171     m_pathsToScan.clear();
172     m_pathsToClean.clear();
173
174     if (strDirectory.empty())
175     { // scan all paths in the database.  We do this by scanning all paths in the db, and crossing them off the list as
176       // we go.
177       m_database.Open();
178       m_database.GetPaths(m_pathsToScan);
179       m_database.Close();
180     }
181     else
182     {
183       m_pathsToScan.insert(strDirectory);
184     }
185     m_bClean = g_advancedSettings.m_bVideoLibraryCleanOnUpdate;
186
187     StopThread();
188     Create();
189     m_bRunning = true;
190   }
191
192   bool CVideoInfoScanner::IsScanning()
193   {
194     return m_bRunning;
195   }
196
197   void CVideoInfoScanner::Stop()
198   {
199     if (m_bCanInterrupt)
200       m_database.Interupt();
201
202     StopThread(false);
203   }
204
205   void CVideoInfoScanner::CleanDatabase(CGUIDialogProgressBarHandle* handle /*= NULL */, const set<int>* paths /*= NULL */, bool showProgress /*= true */)
206   {
207     m_bRunning = true;
208     m_database.Open();
209     m_database.CleanDatabase(handle, paths, showProgress);
210     m_database.Close();
211     m_bRunning = false;
212   }
213
214   static void OnDirectoryScanned(const CStdString& strDirectory)
215   {
216     CGUIMessage msg(GUI_MSG_DIRECTORY_SCANNED, 0, 0, 0);
217     msg.SetStringParam(strDirectory);
218     g_windowManager.SendThreadMessage(msg);
219   }
220
221   bool CVideoInfoScanner::DoScan(const CStdString& strDirectory)
222   {
223     if (m_handle)
224     {
225       m_handle->SetText(g_localizeStrings.Get(20415));
226     }
227
228     /*
229      * Remove this path from the list we're processing. This must be done prior to
230      * the check for file or folder exclusion to prevent an infinite while loop
231      * in Process().
232      */
233     set<CStdString>::iterator it = m_pathsToScan.find(strDirectory);
234     if (it != m_pathsToScan.end())
235       m_pathsToScan.erase(it);
236
237     // load subfolder
238     CFileItemList items;
239     bool foundDirectly = false;
240     bool bSkip = false;
241
242     SScanSettings settings;
243     ScraperPtr info = m_database.GetScraperForPath(strDirectory, settings, foundDirectly);
244     CONTENT_TYPE content = info ? info->Content() : CONTENT_NONE;
245
246     // exclude folders that match our exclude regexps
247     CStdStringArray regexps = content == CONTENT_TVSHOWS ? g_advancedSettings.m_tvshowExcludeFromScanRegExps
248                                                          : g_advancedSettings.m_moviesExcludeFromScanRegExps;
249
250     if (CUtil::ExcludeFileOrFolder(strDirectory, regexps))
251       return true;
252
253     bool ignoreFolder = !m_scanAll && settings.noupdate;
254     if (content == CONTENT_NONE || ignoreFolder)
255       return true;
256
257     CStdString hash, dbHash;
258     if (content == CONTENT_MOVIES ||content == CONTENT_MUSICVIDEOS)
259     {
260       if (m_handle)
261       {
262         int str = content == CONTENT_MOVIES ? 20317:20318;
263         m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(str), info->Name().c_str()));
264       }
265
266       CStdString fastHash = GetFastHash(strDirectory);
267       if (m_database.GetPathHash(strDirectory, dbHash) && !fastHash.empty() && fastHash == dbHash)
268       { // fast hashes match - no need to process anything
269         CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' due to no change (fasthash)", CURL::GetRedacted(strDirectory).c_str());
270         hash = fastHash;
271         bSkip = true;
272       }
273       if (!bSkip)
274       { // need to fetch the folder
275         CDirectory::GetDirectory(strDirectory, items, g_advancedSettings.m_videoExtensions);
276         items.Stack();
277         // compute hash
278         GetPathHash(items, hash);
279         if (hash != dbHash && !hash.empty())
280         {
281           if (dbHash.empty())
282             CLog::Log(LOGDEBUG, "VideoInfoScanner: Scanning dir '%s' as not in the database", CURL::GetRedacted(strDirectory).c_str());
283           else
284             CLog::Log(LOGDEBUG, "VideoInfoScanner: Rescanning dir '%s' due to change (%s != %s)", CURL::GetRedacted(strDirectory).c_str(), dbHash.c_str(), hash.c_str());
285         }
286         else
287         { // they're the same or the hash is empty (dir empty/dir not retrievable)
288           if (hash.empty() && !dbHash.empty())
289           {
290             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' as it's empty or doesn't exist - adding to clean list", CURL::GetRedacted(strDirectory).c_str());
291             m_pathsToClean.insert(m_database.GetPathId(strDirectory));
292           }
293           else
294             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' due to no change", CURL::GetRedacted(strDirectory).c_str());
295           bSkip = true;
296           if (m_handle)
297             OnDirectoryScanned(strDirectory);
298         }
299         // update the hash to a fast hash if needed
300         if (CanFastHash(items) && !fastHash.empty())
301           hash = fastHash;
302       }
303     }
304     else if (content == CONTENT_TVSHOWS)
305     {
306       if (m_handle)
307         m_handle->SetTitle(StringUtils::Format(g_localizeStrings.Get(20319), info->Name().c_str()));
308
309       if (foundDirectly && !settings.parent_name_root)
310       {
311         CDirectory::GetDirectory(strDirectory, items, g_advancedSettings.m_videoExtensions);
312         items.SetPath(strDirectory);
313         GetPathHash(items, hash);
314         bSkip = true;
315         if (!m_database.GetPathHash(strDirectory, dbHash) || dbHash != hash)
316         {
317           m_database.SetPathHash(strDirectory, hash);
318           bSkip = false;
319         }
320         else
321           items.Clear();
322       }
323       else
324       {
325         CFileItemPtr item(new CFileItem(URIUtils::GetFileName(strDirectory)));
326         item->SetPath(strDirectory);
327         item->m_bIsFolder = true;
328         items.Add(item);
329         items.SetPath(URIUtils::GetParentPath(item->GetPath()));
330       }
331     }
332
333     if (!bSkip)
334     {
335       if (RetrieveVideoInfo(items, settings.parent_name_root, content))
336       {
337         if (!m_bStop && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS))
338         {
339           m_database.SetPathHash(strDirectory, hash);
340           m_pathsToClean.insert(m_database.GetPathId(strDirectory));
341           CLog::Log(LOGDEBUG, "VideoInfoScanner: Finished adding information from dir %s", CURL::GetRedacted(strDirectory).c_str());
342         }
343       }
344       else
345       {
346         m_pathsToClean.insert(m_database.GetPathId(strDirectory));
347         CLog::Log(LOGDEBUG, "VideoInfoScanner: No (new) information was found in dir %s", CURL::GetRedacted(strDirectory).c_str());
348       }
349     }
350     else if (hash != dbHash && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS))
351     { // update the hash either way - we may have changed the hash to a fast version
352       m_database.SetPathHash(strDirectory, hash);
353     }
354
355     if (m_handle)
356       OnDirectoryScanned(strDirectory);
357
358     for (int i = 0; i < items.Size(); ++i)
359     {
360       CFileItemPtr pItem = items[i];
361
362       if (m_bStop)
363         break;
364
365       // if we have a directory item (non-playlist) we then recurse into that folder
366       // do not recurse for tv shows - we have already looked recursively for episodes
367       if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList() && settings.recurse > 0 && content != CONTENT_TVSHOWS)
368       {
369         if (!DoScan(pItem->GetPath()))
370         {
371           m_bStop = true;
372         }
373       }
374     }
375     return !m_bStop;
376   }
377
378   bool CVideoInfoScanner::RetrieveVideoInfo(CFileItemList& items, bool bDirNames, CONTENT_TYPE content, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
379   {
380     if (pDlgProgress)
381     {
382       if (items.Size() > 1 || (items[0]->m_bIsFolder && fetchEpisodes))
383       {
384         pDlgProgress->ShowProgressBar(true);
385         pDlgProgress->SetPercentage(0);
386       }
387       else
388         pDlgProgress->ShowProgressBar(false);
389
390       pDlgProgress->Progress();
391     }
392
393     m_database.Open();
394
395     bool FoundSomeInfo = false;
396     vector<int> seenPaths;
397     for (int i = 0; i < (int)items.Size(); ++i)
398     {
399       m_nfoReader.Close();
400       CFileItemPtr pItem = items[i];
401
402       // we do this since we may have a override per dir
403       ScraperPtr info2 = m_database.GetScraperForPath(pItem->m_bIsFolder ? pItem->GetPath() : items.GetPath());
404       if (!info2) // skip
405         continue;
406
407       // Discard all exclude files defined by regExExclude
408       if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), (content == CONTENT_TVSHOWS) ? g_advancedSettings.m_tvshowExcludeFromScanRegExps
409                                                                                     : g_advancedSettings.m_moviesExcludeFromScanRegExps))
410         continue;
411
412       if (info2->Content() == CONTENT_MOVIES || info2->Content() == CONTENT_MUSICVIDEOS)
413       {
414         if (m_handle)
415           m_handle->SetPercentage(i*100.f/items.Size());
416       }
417
418       // clear our scraper cache
419       info2->ClearCache();
420
421       INFO_RET ret = INFO_CANCELLED;
422       if (info2->Content() == CONTENT_TVSHOWS)
423         ret = RetrieveInfoForTvShow(pItem.get(), bDirNames, info2, useLocal, pURL, fetchEpisodes, pDlgProgress);
424       else if (info2->Content() == CONTENT_MOVIES)
425         ret = RetrieveInfoForMovie(pItem.get(), bDirNames, info2, useLocal, pURL, pDlgProgress);
426       else if (info2->Content() == CONTENT_MUSICVIDEOS)
427         ret = RetrieveInfoForMusicVideo(pItem.get(), bDirNames, info2, useLocal, pURL, pDlgProgress);
428       else
429       {
430         CLog::Log(LOGERROR, "VideoInfoScanner: Unknown content type %d (%s)", info2->Content(), CURL::GetRedacted(pItem->GetPath()).c_str());
431         FoundSomeInfo = false;
432         break;
433       }
434       if (ret == INFO_CANCELLED || ret == INFO_ERROR)
435       {
436         FoundSomeInfo = false;
437         break;
438       }
439       if (ret == INFO_ADDED || ret == INFO_HAVE_ALREADY)
440         FoundSomeInfo = true;
441       else if (ret == INFO_NOT_FOUND)
442       {
443         CLog::Log(LOGWARNING, "No information found for item '%s', it won't be added to the library.", CURL::GetRedacted(pItem->GetPath()).c_str());
444       }
445
446       pURL = NULL;
447
448       // Keep track of directories we've seen
449       if (pItem->m_bIsFolder)
450         seenPaths.push_back(m_database.GetPathId(pItem->GetPath()));
451     }
452
453     if (content == CONTENT_TVSHOWS && ! seenPaths.empty())
454     {
455       vector< pair<int,string> > libPaths;
456       m_database.GetSubPaths(items.GetPath(), libPaths);
457       for (vector< pair<int,string> >::iterator i = libPaths.begin(); i < libPaths.end(); ++i)
458       {
459         if (find(seenPaths.begin(), seenPaths.end(), i->first) == seenPaths.end())
460           m_pathsToClean.insert(i->first);
461       }
462     }
463     if(pDlgProgress)
464       pDlgProgress->ShowProgressBar(false);
465
466     g_infoManager.ResetLibraryBools();
467     m_database.Close();
468     return FoundSomeInfo;
469   }
470
471   INFO_RET CVideoInfoScanner::RetrieveInfoForTvShow(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
472   {
473     long idTvShow = -1;
474     if (pItem->m_bIsFolder)
475       idTvShow = m_database.GetTvShowId(pItem->GetPath());
476     else
477     {
478       CStdString strPath = URIUtils::GetDirectory(pItem->GetPath());
479       idTvShow = m_database.GetTvShowId(strPath);
480     }
481     if (idTvShow > -1 && (fetchEpisodes || !pItem->m_bIsFolder))
482     {
483       INFO_RET ret = RetrieveInfoForEpisodes(pItem, idTvShow, info2, useLocal, pDlgProgress);
484       if (ret == INFO_ADDED)
485         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
486       return ret;
487     }
488
489     if (ProgressCancelled(pDlgProgress, pItem->m_bIsFolder ? 20353 : 20361, pItem->GetLabel()))
490       return INFO_CANCELLED;
491
492     if (m_handle)
493       m_handle->SetText(pItem->GetMovieName(bDirNames));
494
495     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
496     CScraperUrl scrUrl;
497     // handle .nfo files
498     if (useLocal)
499       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
500     if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
501     { // check for preconfigured scraper; if found, overwrite with interpreted scraper (from Nfofile)
502       // but keep current scan settings
503       SScanSettings settings;
504       if (m_database.GetScraperForPath(pItem->GetPath(), settings))
505         m_database.SetScraperForPath(pItem->GetPath(), info2, settings);
506     }
507     if (result == CNfoFile::FULL_NFO)
508     {
509       pItem->GetVideoInfoTag()->Reset();
510       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
511
512       long lResult = AddVideo(pItem, info2->Content(), bDirNames, useLocal);
513       if (lResult < 0)
514         return INFO_ERROR;
515       if (fetchEpisodes)
516       {
517         INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
518         if (ret == INFO_ADDED)
519           m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
520         return ret;
521       }
522       return INFO_ADDED;
523     }
524     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
525       pURL = &scrUrl;
526
527     CScraperUrl url;
528     int retVal = 0;
529     if (pURL)
530       url = *pURL;
531     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
532       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
533
534     long lResult=-1;
535     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
536     {
537       if ((lResult = AddVideo(pItem, info2->Content(), false, useLocal)) < 0)
538         return INFO_ERROR;
539     }
540     if (fetchEpisodes)
541     {
542       INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
543       if (ret == INFO_ADDED)
544         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
545     }
546     return INFO_ADDED;
547   }
548
549   INFO_RET CVideoInfoScanner::RetrieveInfoForMovie(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
550   {
551     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
552        (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm")))
553       return INFO_NOT_NEEDED;
554
555     if (ProgressCancelled(pDlgProgress, 198, pItem->GetLabel()))
556       return INFO_CANCELLED;
557
558     if (m_database.HasMovieInfo(pItem->GetPath()))
559       return INFO_HAVE_ALREADY;
560
561     if (m_handle)
562       m_handle->SetText(pItem->GetMovieName(bDirNames));
563
564     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
565     CScraperUrl scrUrl;
566     // handle .nfo files
567     if (useLocal)
568       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
569     if (result == CNfoFile::FULL_NFO)
570     {
571       pItem->GetVideoInfoTag()->Reset();
572       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
573
574       if (AddVideo(pItem, info2->Content(), bDirNames, true) < 0)
575         return INFO_ERROR;
576       return INFO_ADDED;
577     }
578     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
579       pURL = &scrUrl;
580
581     CScraperUrl url;
582     int retVal = 0;
583     if (pURL)
584       url = *pURL;
585     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
586       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
587
588     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
589     {
590       if (AddVideo(pItem, info2->Content(), bDirNames, useLocal) < 0)
591         return INFO_ERROR;
592       return INFO_ADDED;
593     }
594     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
595     return INFO_NOT_FOUND;
596   }
597
598   INFO_RET CVideoInfoScanner::RetrieveInfoForMusicVideo(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
599   {
600     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
601        (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm")))
602       return INFO_NOT_NEEDED;
603
604     if (ProgressCancelled(pDlgProgress, 20394, pItem->GetLabel()))
605       return INFO_CANCELLED;
606
607     if (m_database.HasMusicVideoInfo(pItem->GetPath()))
608       return INFO_HAVE_ALREADY;
609
610     if (m_handle)
611       m_handle->SetText(pItem->GetMovieName(bDirNames));
612
613     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
614     CScraperUrl scrUrl;
615     // handle .nfo files
616     if (useLocal)
617       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
618     if (result == CNfoFile::FULL_NFO)
619     {
620       pItem->GetVideoInfoTag()->Reset();
621       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
622
623       if (AddVideo(pItem, info2->Content(), bDirNames, true) < 0)
624         return INFO_ERROR;
625       return INFO_ADDED;
626     }
627     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
628       pURL = &scrUrl;
629
630     CScraperUrl url;
631     int retVal = 0;
632     if (pURL)
633       url = *pURL;
634     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
635       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
636
637     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
638     {
639       if (AddVideo(pItem, info2->Content(), bDirNames, useLocal) < 0)
640         return INFO_ERROR;
641       return INFO_ADDED;
642     }
643     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
644     return INFO_NOT_FOUND;
645   }
646
647   INFO_RET CVideoInfoScanner::RetrieveInfoForEpisodes(CFileItem *item, long showID, const ADDON::ScraperPtr &scraper, bool useLocal, CGUIDialogProgress *progress)
648   {
649     // enumerate episodes
650     EPISODELIST files;
651     EnumerateSeriesFolder(item, files);
652     if (files.size() == 0) // no update or no files
653       return INFO_NOT_NEEDED;
654
655     if (m_bStop || (progress && progress->IsCanceled()))
656       return INFO_CANCELLED;
657
658     CVideoInfoTag showInfo;
659     m_database.GetTvShowInfo("", showInfo, showID);
660     return OnProcessSeriesFolder(files, scraper, useLocal, showInfo, progress);
661   }
662
663   void CVideoInfoScanner::EnumerateSeriesFolder(CFileItem* item, EPISODELIST& episodeList)
664   {
665     CFileItemList items;
666
667     if (item->m_bIsFolder)
668     {
669       CUtil::GetRecursiveListing(item->GetPath(), items, g_advancedSettings.m_videoExtensions, true);
670       CStdString hash, dbHash;
671       int numFilesInFolder = GetPathHash(items, hash);
672
673       if (m_database.GetPathHash(item->GetPath(), dbHash) && dbHash == hash)
674       {
675         m_currentItem += numFilesInFolder;
676
677         // update our dialog with our progress
678         if (m_handle)
679         {
680           if (m_itemCount>0)
681             m_handle->SetPercentage(m_currentItem*100.f/m_itemCount);
682
683           OnDirectoryScanned(item->GetPath());
684         }
685         return;
686       }
687       m_pathsToClean.insert(m_database.GetPathId(item->GetPath()));
688       m_database.GetPathsForTvShow(m_database.GetTvShowId(item->GetPath()), m_pathsToClean);
689       item->SetProperty("hash", hash);
690     }
691     else
692     {
693       CFileItemPtr newItem(new CFileItem(*item));
694       items.Add(newItem);
695     }
696
697     /*
698     stack down any dvd folders
699     need to sort using the full path since this is a collapsed recursive listing of all subdirs
700     video_ts.ifo files should sort at the top of a dvd folder in ascending order
701
702     /foo/bar/video_ts.ifo
703     /foo/bar/vts_x_y.ifo
704     /foo/bar/vts_x_y.vob
705     */
706
707     // since we're doing this now anyway, should other items be stacked?
708     items.Sort(SortByPath, SortOrderAscending);
709     int x = 0;
710     while (x < items.Size())
711     {
712       if (items[x]->m_bIsFolder)
713         continue;
714
715
716       CStdString strPathX, strFileX;
717       URIUtils::Split(items[x]->GetPath(), strPathX, strFileX);
718       //CLog::Log(LOGDEBUG,"%i:%s:%s", x, strPathX.c_str(), strFileX.c_str());
719
720       int y = x + 1;
721       if (strFileX.Equals("VIDEO_TS.IFO"))
722       {
723         while (y < items.Size())
724         {
725           CStdString strPathY, strFileY;
726           URIUtils::Split(items[y]->GetPath(), strPathY, strFileY);
727           //CLog::Log(LOGDEBUG," %i:%s:%s", y, strPathY.c_str(), strFileY.c_str());
728
729           if (strPathY.Equals(strPathX))
730             /*
731             remove everything sorted below the video_ts.ifo file in the same path.
732             understandbly this wont stack correctly if there are other files in the the dvd folder.
733             this should be unlikely and thus is being ignored for now but we can monitor the
734             where the path changes and potentially remove the items above the video_ts.ifo file.
735             */
736             items.Remove(y);
737           else
738             break;
739         }
740       }
741       x = y;
742     }
743
744     // enumerate
745     CStdStringArray regexps = g_advancedSettings.m_tvshowExcludeFromScanRegExps;
746
747     for (int i=0;i<items.Size();++i)
748     {
749       if (items[i]->m_bIsFolder)
750         continue;
751       CStdString strPath = URIUtils::GetDirectory(items[i]->GetPath());
752       URIUtils::RemoveSlashAtEnd(strPath); // want no slash for the test that follows
753
754       if (URIUtils::GetFileName(strPath).Equals("sample"))
755         continue;
756
757       // Discard all exclude files defined by regExExcludes
758       if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
759         continue;
760
761       /*
762        * Check if the media source has already set the season and episode or original air date in
763        * the VideoInfoTag. If it has, do not try to parse any of them from the file path to avoid
764        * any false positive matches.
765        */
766       if (ProcessItemByVideoInfoTag(items[i].get(), episodeList))
767         continue;
768
769       if (!EnumerateEpisodeItem(items[i].get(), episodeList))
770         CLog::Log(LOGDEBUG, "VideoInfoScanner: Could not enumerate file %s", CURL::GetRedacted(CURL::Decode(items[i]->GetPath())).c_str());
771     }
772   }
773
774   bool CVideoInfoScanner::ProcessItemByVideoInfoTag(const CFileItem *item, EPISODELIST &episodeList)
775   {
776     if (!item->HasVideoInfoTag())
777       return false;
778
779     const CVideoInfoTag* tag = item->GetVideoInfoTag();
780     /*
781      * First check the season and episode number. This takes precedence over the original air
782      * date and episode title. Must be a valid season and episode number combination.
783      */
784     if (tag->m_iSeason > -1 && tag->m_iEpisode > 0)
785     {
786       EPISODE episode;
787       episode.strPath = item->GetPath();
788       episode.iSeason = tag->m_iSeason;
789       episode.iEpisode = tag->m_iEpisode;
790       episode.isFolder = false;
791       episodeList.push_back(episode);
792       CLog::Log(LOGDEBUG, "%s - found match for: %s. Season %d, Episode %d", __FUNCTION__,
793                 episode.strPath.c_str(), episode.iSeason, episode.iEpisode);
794       return true;
795     }
796
797     /*
798      * Next preference is the first aired date. If it exists use that for matching the TV Show
799      * information. Also set the title in case there are multiple matches for the first aired date.
800      */
801     if (tag->m_firstAired.IsValid())
802     {
803       EPISODE episode;
804       episode.strPath = item->GetPath();
805       episode.strTitle = tag->m_strTitle;
806       episode.isFolder = false;
807       /*
808        * Set season and episode to -1 to indicate to use the aired date.
809        */
810       episode.iSeason = -1;
811       episode.iEpisode = -1;
812       /*
813        * The first aired date string must be parseable.
814        */
815       episode.cDate = item->GetVideoInfoTag()->m_firstAired;
816       episodeList.push_back(episode);
817       CLog::Log(LOGDEBUG, "%s - found match for: '%s', firstAired: '%s' = '%s', title: '%s'",
818         __FUNCTION__, episode.strPath.c_str(), tag->m_firstAired.GetAsDBDateTime().c_str(),
819                 episode.cDate.GetAsLocalizedDate().c_str(), episode.strTitle.c_str());
820       return true;
821     }
822
823     /*
824      * Next preference is the episode title. If it exists use that for matching the TV Show
825      * information.
826      */
827     if (!tag->m_strTitle.empty())
828     {
829       EPISODE episode;
830       episode.strPath = item->GetPath();
831       episode.strTitle = tag->m_strTitle;
832       episode.isFolder = false;
833       /*
834        * Set season and episode to -1 to indicate to use the title.
835        */
836       episode.iSeason = -1;
837       episode.iEpisode = -1;
838       episodeList.push_back(episode);
839       CLog::Log(LOGDEBUG,"%s - found match for: '%s', title: '%s'", __FUNCTION__,
840                 episode.strPath.c_str(), episode.strTitle.c_str());
841       return true;
842     }
843
844     /*
845      * There is no further episode information available if both the season and episode number have
846      * been set to 0. Return the match as true so no further matching is attempted, but don't add it
847      * to the episode list.
848      */
849     if (tag->m_iSeason == 0 && tag->m_iEpisode == 0)
850     {
851       CLog::Log(LOGDEBUG,"%s - found exclusion match for: %s. Both Season and Episode are 0. Item will be ignored for scanning.",
852                 __FUNCTION__, item->GetPath().c_str());
853       return true;
854     }
855
856     return false;
857   }
858
859   bool CVideoInfoScanner::EnumerateEpisodeItem(const CFileItem *item, EPISODELIST& episodeList)
860   {
861     SETTINGS_TVSHOWLIST expression = g_advancedSettings.m_tvshowEnumRegExps;
862
863     CStdString strLabel=item->GetPath();
864     // URLDecode in case an episode is on a http/https/dav/davs:// source and URL-encoded like foo%201x01%20bar.avi
865     strLabel = CURL::Decode(strLabel);
866
867     for (unsigned int i=0;i<expression.size();++i)
868     {
869       CRegExp reg(true, CRegExp::autoUtf8);
870       if (!reg.RegComp(expression[i].regexp))
871         continue;
872
873       int regexppos, regexp2pos;
874       //CLog::Log(LOGDEBUG,"running expression %s on %s",expression[i].regexp.c_str(),strLabel.c_str());
875       if ((regexppos = reg.RegFind(strLabel.c_str())) < 0)
876         continue;
877
878       EPISODE episode;
879       episode.strPath = item->GetPath();
880       episode.iSeason = -1;
881       episode.iEpisode = -1;
882       episode.cDate.SetValid(false);
883       episode.isFolder = false;
884
885       bool byDate = expression[i].byDate ? true : false;
886       int defaultSeason = expression[i].defaultSeason;
887
888       if (byDate)
889       {
890         if (!GetAirDateFromRegExp(reg, episode))
891           continue;
892
893         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found date based match %s (%s) [%s]", strLabel.c_str(),
894                   episode.cDate.GetAsLocalizedDate().c_str(), expression[i].regexp.c_str());
895       }
896       else
897       {
898         if (!GetEpisodeAndSeasonFromRegExp(reg, episode, defaultSeason))
899           continue;
900
901         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found episode match %s (s%ie%i) [%s]", strLabel.c_str(),
902                   episode.iSeason, episode.iEpisode, expression[i].regexp.c_str());
903       }
904
905       // Grab the remainder from first regexp run
906       // as second run might modify or empty it.
907       std::string remainder(reg.GetMatch(3));
908
909       /*
910        * Check if the files base path is a dedicated folder that contains
911        * only this single episode. If season and episode match with the
912        * actual media file, we set episode.isFolder to true.
913        */
914       CStdString strBasePath = item->GetBaseMoviePath(true);
915       URIUtils::RemoveSlashAtEnd(strBasePath);
916       strBasePath = URIUtils::GetFileName(strBasePath);
917
918       if (reg.RegFind(strBasePath.c_str()) > -1)
919       {
920         EPISODE parent;
921         if (byDate)
922         {
923           GetAirDateFromRegExp(reg, parent);
924           if (episode.cDate == parent.cDate)
925             episode.isFolder = true;
926         }
927         else
928         {
929           GetEpisodeAndSeasonFromRegExp(reg, parent, defaultSeason);
930           if (episode.iSeason == parent.iSeason && episode.iEpisode == parent.iEpisode)
931             episode.isFolder = true;
932         }
933       }
934
935       // add what we found by now
936       episodeList.push_back(episode);
937
938       CRegExp reg2(true, CRegExp::autoUtf8);
939       // check the remainder of the string for any further episodes.
940       if (!byDate && reg2.RegComp(g_advancedSettings.m_tvshowMultiPartEnumRegExp))
941       {
942         int offset = 0;
943
944         // we want "long circuit" OR below so that both offsets are evaluated
945         while (((regexp2pos = reg2.RegFind(remainder.c_str() + offset)) > -1) | ((regexppos = reg.RegFind(remainder.c_str() + offset)) > -1))
946         {
947           if (((regexppos <= regexp2pos) && regexppos != -1) ||
948              (regexppos >= 0 && regexp2pos == -1))
949           {
950             GetEpisodeAndSeasonFromRegExp(reg, episode, defaultSeason);
951
952             CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new season %u, multipart episode %u [%s]",
953                       episode.iSeason, episode.iEpisode,
954                       g_advancedSettings.m_tvshowMultiPartEnumRegExp.c_str());
955
956             episodeList.push_back(episode);
957             remainder = reg.GetMatch(3);
958             offset = 0;
959           }
960           else if (((regexp2pos < regexppos) && regexp2pos != -1) ||
961                    (regexp2pos >= 0 && regexppos == -1))
962           {
963             episode.iEpisode = atoi(reg2.GetMatch(1).c_str());
964             CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding multipart episode %u [%s]",
965                       episode.iEpisode, g_advancedSettings.m_tvshowMultiPartEnumRegExp.c_str());
966             episodeList.push_back(episode);
967             offset += regexp2pos + reg2.GetFindLen();
968           }
969         }
970       }
971       return true;
972     }
973     return false;
974   }
975
976   bool CVideoInfoScanner::GetEpisodeAndSeasonFromRegExp(CRegExp &reg, EPISODE &episodeInfo, int defaultSeason)
977   {
978     std::string season(reg.GetMatch(1));
979     std::string episode(reg.GetMatch(2));
980
981     if (!season.empty() || !episode.empty())
982     {
983       char* endptr = NULL;
984       if (season.empty() && !episode.empty())
985       { // no season specified -> assume defaultSeason
986         episodeInfo.iSeason = defaultSeason;
987         if ((episodeInfo.iEpisode = CUtil::TranslateRomanNumeral(episode.c_str())) == -1)
988           episodeInfo.iEpisode = strtol(episode.c_str(), &endptr, 10);
989       }
990       else if (!season.empty() && episode.empty())
991       { // no episode specification -> assume defaultSeason
992         episodeInfo.iSeason = defaultSeason;
993         if ((episodeInfo.iEpisode = CUtil::TranslateRomanNumeral(season.c_str())) == -1)
994           episodeInfo.iEpisode = atoi(season.c_str());
995       }
996       else
997       { // season and episode specified
998         episodeInfo.iSeason = atoi(season.c_str());
999         episodeInfo.iEpisode = strtol(episode.c_str(), &endptr, 10);
1000       }
1001       if (endptr)
1002       {
1003         if (isalpha(*endptr))
1004           episodeInfo.iSubepisode = *endptr - (islower(*endptr) ? 'a' : 'A') + 1;
1005         else if (*endptr == '.')
1006           episodeInfo.iSubepisode = atoi(endptr+1);
1007       }
1008       return true;
1009     }
1010     return false;
1011   }
1012
1013   bool CVideoInfoScanner::GetAirDateFromRegExp(CRegExp &reg, EPISODE &episodeInfo)
1014   {
1015     std::string param1(reg.GetMatch(1));
1016     std::string param2(reg.GetMatch(2));
1017     std::string param3(reg.GetMatch(3));
1018
1019     if (!param1.empty() && !param2.empty() && !param3.empty())
1020     {
1021       // regular expression by date
1022       int len1 = param1.size();
1023       int len2 = param2.size();
1024       int len3 = param3.size();
1025
1026       if (len1==4 && len2==2 && len3==2)
1027       {
1028         // yyyy mm dd format
1029         episodeInfo.cDate.SetDate(atoi(param1.c_str()), atoi(param2.c_str()), atoi(param3.c_str()));
1030       }
1031       else if (len1==2 && len2==2 && len3==4)
1032       {
1033         // mm dd yyyy format
1034         episodeInfo.cDate.SetDate(atoi(param3.c_str()), atoi(param1.c_str()), atoi(param2.c_str()));
1035       }
1036     }
1037     return episodeInfo.cDate.IsValid();
1038   }
1039
1040   long CVideoInfoScanner::AddVideo(CFileItem *pItem, const CONTENT_TYPE &content, bool videoFolder /* = false */, bool useLocal /* = true */, const CVideoInfoTag *showInfo /* = NULL */, bool libraryImport /* = false */)
1041   {
1042     // ensure our database is open (this can get called via other classes)
1043     if (!m_database.Open())
1044       return -1;
1045
1046     if (!libraryImport)
1047       GetArtwork(pItem, content, videoFolder, useLocal, showInfo ? showInfo->m_strPath : "");
1048
1049     // ensure the art map isn't completely empty by specifying an empty thumb
1050     map<string, string> art = pItem->GetArt();
1051     if (art.empty())
1052       art["thumb"] = "";
1053
1054     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1055     if (movieDetails.m_basePath.empty())
1056       movieDetails.m_basePath = pItem->GetBaseMoviePath(videoFolder);
1057     movieDetails.m_parentPathID = m_database.AddPath(URIUtils::GetParentPath(movieDetails.m_basePath));
1058
1059     movieDetails.m_strFileNameAndPath = pItem->GetPath();
1060
1061     if (pItem->m_bIsFolder)
1062       movieDetails.m_strPath = pItem->GetPath();
1063
1064     CStdString strTitle(movieDetails.m_strTitle);
1065
1066     if (showInfo && content == CONTENT_TVSHOWS)
1067     {
1068       strTitle = StringUtils::Format("%s - %ix%i - %s", showInfo->m_strTitle.c_str(), movieDetails.m_iSeason, movieDetails.m_iEpisode, strTitle.c_str());
1069     }
1070
1071     std::string redactPath(CURL::GetRedacted(CURL::Decode(pItem->GetPath())));
1072
1073     CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new item to %s:%s", TranslateContent(content).c_str(), redactPath.c_str());
1074     long lResult = -1;
1075
1076     if (content == CONTENT_MOVIES)
1077     {
1078       // find local trailer first
1079       CStdString strTrailer = pItem->FindTrailer();
1080       if (!strTrailer.empty())
1081         movieDetails.m_strTrailer = strTrailer;
1082
1083       lResult = m_database.SetDetailsForMovie(pItem->GetPath(), movieDetails, art);
1084       movieDetails.m_iDbId = lResult;
1085       movieDetails.m_type = "movie";
1086
1087       // setup links to shows if the linked shows are in the db
1088       for (unsigned int i=0; i < movieDetails.m_showLink.size(); ++i)
1089       {
1090         CFileItemList items;
1091         m_database.GetTvShowsByName(movieDetails.m_showLink[i], items);
1092         if (items.Size())
1093           m_database.LinkMovieToTvshow(lResult, items[0]->GetVideoInfoTag()->m_iDbId, false);
1094         else
1095           CLog::Log(LOGDEBUG, "VideoInfoScanner: Failed to link movie %s to show %s", movieDetails.m_strTitle.c_str(), movieDetails.m_showLink[i].c_str());
1096       }
1097     }
1098     else if (content == CONTENT_TVSHOWS)
1099     {
1100       if (pItem->m_bIsFolder)
1101       {
1102         map<int, map<string, string> > seasonArt;
1103         if (!libraryImport)
1104         { // get and cache season thumbs
1105           GetSeasonThumbs(movieDetails, seasonArt, CVideoThumbLoader::GetArtTypes("season"), useLocal);
1106           for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
1107             for (map<string, string>::iterator j = i->second.begin(); j != i->second.end(); ++j)
1108               CTextureCache::Get().BackgroundCacheImage(j->second);
1109         }
1110         lResult = m_database.SetDetailsForTvShow(pItem->GetPath(), movieDetails, art, seasonArt);
1111         movieDetails.m_iDbId = lResult;
1112         movieDetails.m_type = "tvshow";
1113       }
1114       else
1115       {
1116         // we add episode then set details, as otherwise set details will delete the
1117         // episode then add, which breaks multi-episode files.
1118         int idShow = showInfo ? showInfo->m_iDbId : -1;
1119         int idEpisode = m_database.AddEpisode(idShow, pItem->GetPath());
1120         lResult = m_database.SetDetailsForEpisode(pItem->GetPath(), movieDetails, art, idShow, idEpisode);
1121         movieDetails.m_iDbId = lResult;
1122         movieDetails.m_type = "episode";
1123         movieDetails.m_strShowTitle = showInfo ? showInfo->m_strTitle : "";
1124         if (movieDetails.m_fEpBookmark > 0)
1125         {
1126           movieDetails.m_strFileNameAndPath = pItem->GetPath();
1127           CBookmark bookmark;
1128           bookmark.timeInSeconds = movieDetails.m_fEpBookmark;
1129           bookmark.seasonNumber = movieDetails.m_iSeason;
1130           bookmark.episodeNumber = movieDetails.m_iEpisode;
1131           m_database.AddBookMarkForEpisode(movieDetails, bookmark);
1132         }
1133       }
1134     }
1135     else if (content == CONTENT_MUSICVIDEOS)
1136     {
1137       lResult = m_database.SetDetailsForMusicVideo(pItem->GetPath(), movieDetails, art);
1138       movieDetails.m_iDbId = lResult;
1139       movieDetails.m_type = "musicvideo";
1140     }
1141
1142     if (g_advancedSettings.m_bVideoLibraryImportWatchedState || libraryImport)
1143       m_database.SetPlayCount(*pItem, movieDetails.m_playCount, movieDetails.m_lastPlayed);
1144
1145     if ((g_advancedSettings.m_bVideoLibraryImportResumePoint || libraryImport) &&
1146         movieDetails.m_resumePoint.IsSet())
1147       m_database.AddBookMarkToFile(pItem->GetPath(), movieDetails.m_resumePoint, CBookmark::RESUME);
1148
1149     m_database.Close();
1150
1151     CFileItemPtr itemCopy = CFileItemPtr(new CFileItem(*pItem));
1152     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", itemCopy);
1153     return lResult;
1154   }
1155
1156   string ContentToMediaType(CONTENT_TYPE content, bool folder)
1157   {
1158     switch (content)
1159     {
1160       case CONTENT_MOVIES:
1161         return "movie";
1162       case CONTENT_MUSICVIDEOS:
1163         return "musicvideo";
1164       case CONTENT_TVSHOWS:
1165         return folder ? "tvshow" : "episode";
1166       default:
1167         return "";
1168     }
1169   }
1170
1171   std::string CVideoInfoScanner::GetArtTypeFromSize(unsigned int width, unsigned int height)
1172   {
1173     std::string type = "thumb";
1174     if (width*5 < height*4)
1175       type = "poster";
1176     else if (width*1 > height*4)
1177       type = "banner";
1178     return type;
1179   }
1180
1181   void CVideoInfoScanner::GetArtwork(CFileItem *pItem, const CONTENT_TYPE &content, bool bApplyToDir, bool useLocal, const std::string &actorArtPath)
1182   {
1183     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1184     movieDetails.m_fanart.Unpack();
1185     movieDetails.m_strPictureURL.Parse();
1186
1187     CGUIListItem::ArtMap art = pItem->GetArt();
1188
1189     // get and cache thumb images
1190     vector<string> artTypes = CVideoThumbLoader::GetArtTypes(ContentToMediaType(content, pItem->m_bIsFolder));
1191     vector<string>::iterator i = find(artTypes.begin(), artTypes.end(), "fanart");
1192     if (i != artTypes.end())
1193       artTypes.erase(i); // fanart is handled below
1194     bool lookForThumb = find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end() &&
1195                         art.find("thumb") == art.end();
1196     // find local art
1197     if (useLocal)
1198     {
1199       for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1200       {
1201         if (art.find(*i) == art.end())
1202         {
1203           std::string image = CVideoThumbLoader::GetLocalArt(*pItem, *i, bApplyToDir);
1204           if (!image.empty())
1205             art.insert(make_pair(*i, image));
1206         }
1207       }
1208       // find and classify the local thumb (backcompat) if available
1209       if (lookForThumb)
1210       {
1211         std::string image = CVideoThumbLoader::GetLocalArt(*pItem, "thumb", bApplyToDir);
1212         if (!image.empty())
1213         { // cache the image and determine sizing
1214           CTextureDetails details;
1215           if (CTextureCache::Get().CacheImage(image, details))
1216           {
1217             std::string type = GetArtTypeFromSize(details.width, details.height);
1218             if (art.find(type) == art.end())
1219               art.insert(make_pair(type, image));
1220           }
1221         }
1222       }
1223     }
1224
1225     // find online art
1226     for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1227     {
1228       if (art.find(*i) == art.end())
1229       {
1230         std::string image = GetImage(pItem, false, bApplyToDir, *i);
1231         if (!image.empty())
1232           art.insert(make_pair(*i, image));
1233       }
1234     }
1235
1236     // use the first piece of online art as the first art type if no thumb type is available yet
1237     if (art.empty() && lookForThumb)
1238     {
1239       std::string image = GetImage(pItem, false, bApplyToDir, "thumb");
1240       if (!image.empty())
1241         art.insert(make_pair(artTypes.front(), image));
1242     }
1243
1244     // get & save fanart image (treated separately due to it being stored in m_fanart)
1245     bool isEpisode = (content == CONTENT_TVSHOWS && !pItem->m_bIsFolder);
1246     if (!isEpisode && art.find("fanart") == art.end())
1247     {
1248       string fanart = GetFanart(pItem, useLocal);
1249       if (!fanart.empty())
1250         art.insert(make_pair("fanart", fanart));
1251     }
1252
1253     for (CGUIListItem::ArtMap::const_iterator i = art.begin(); i != art.end(); ++i)
1254       CTextureCache::Get().BackgroundCacheImage(i->second);
1255
1256     pItem->SetArt(art);
1257
1258     // parent folder to apply the thumb to and to search for local actor thumbs
1259     CStdString parentDir = GetParentDir(*pItem);
1260     if (CSettings::Get().GetBool("videolibrary.actorthumbs"))
1261       FetchActorThumbs(movieDetails.m_cast, actorArtPath.empty() ? parentDir : actorArtPath);
1262     if (bApplyToDir)
1263       ApplyThumbToFolder(parentDir, art["thumb"]);
1264   }
1265
1266   std::string CVideoInfoScanner::GetImage(CFileItem *pItem, bool useLocal, bool bApplyToDir, const std::string &type)
1267   {
1268     std::string thumb;
1269     if (useLocal)
1270       thumb = CVideoThumbLoader::GetLocalArt(*pItem, type, bApplyToDir);
1271
1272     if (thumb.empty())
1273     {
1274       thumb = CScraperUrl::GetThumbURL(pItem->GetVideoInfoTag()->m_strPictureURL.GetFirstThumb(type));
1275       if (!thumb.empty())
1276       {
1277         if (thumb.find("http://") == string::npos &&
1278             thumb.find("/") == string::npos &&
1279             thumb.find("\\") == string::npos)
1280         {
1281           CStdString strPath = URIUtils::GetDirectory(pItem->GetPath());
1282           thumb = URIUtils::AddFileToFolder(strPath, thumb);
1283         }
1284       }
1285     }
1286     return thumb;
1287   }
1288
1289   std::string CVideoInfoScanner::GetFanart(CFileItem *pItem, bool useLocal)
1290   {
1291     if (!pItem)
1292       return "";
1293     std::string fanart = pItem->GetArt("fanart");
1294     if (fanart.empty() && useLocal)
1295       fanart = pItem->FindLocalArt("fanart.jpg", true);
1296     if (fanart.empty())
1297       fanart = pItem->GetVideoInfoTag()->m_fanart.GetImageURL();
1298     return fanart;
1299   }
1300
1301   INFO_RET CVideoInfoScanner::OnProcessSeriesFolder(EPISODELIST& files, const ADDON::ScraperPtr &scraper, bool useLocal, const CVideoInfoTag& showInfo, CGUIDialogProgress* pDlgProgress /* = NULL */)
1302   {
1303     if (pDlgProgress)
1304     {
1305       pDlgProgress->SetLine(1, showInfo.m_strTitle);
1306       pDlgProgress->SetLine(2, 20361);
1307       pDlgProgress->SetPercentage(0);
1308       pDlgProgress->ShowProgressBar(true);
1309       pDlgProgress->Progress();
1310     }
1311
1312     EPISODELIST episodes;
1313     bool hasEpisodeGuide = false;
1314
1315     int iMax = files.size();
1316     int iCurr = 1;
1317     for (EPISODELIST::iterator file = files.begin(); file != files.end(); ++file)
1318     {
1319       m_nfoReader.Close();
1320       if (pDlgProgress)
1321       {
1322         pDlgProgress->SetLine(2, 20361);
1323         pDlgProgress->SetPercentage((int)((float)(iCurr++)/iMax*100));
1324         pDlgProgress->Progress();
1325       }
1326       if (m_handle)
1327         m_handle->SetPercentage(100.f*iCurr++/iMax);
1328
1329       if ((pDlgProgress && pDlgProgress->IsCanceled()) || m_bStop)
1330         return INFO_CANCELLED;
1331
1332       if (m_database.GetEpisodeId(file->strPath, file->iEpisode, file->iSeason) > -1)
1333       {
1334         if (m_handle)
1335           m_handle->SetText(g_localizeStrings.Get(20415));
1336         continue;
1337       }
1338
1339       CFileItem item;
1340       item.SetPath(file->strPath);
1341
1342       // handle .nfo files
1343       CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1344       CScraperUrl scrUrl;
1345       ScraperPtr info(scraper);
1346       item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1347       if (useLocal)
1348         result = CheckForNFOFile(&item, false, info,scrUrl);
1349       if (result == CNfoFile::FULL_NFO)
1350       {
1351         m_nfoReader.GetDetails(*item.GetVideoInfoTag());
1352         // override with episode and season number from file if available
1353         if (file->iEpisode > -1)
1354         {
1355           item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1356           item.GetVideoInfoTag()->m_iSeason = file->iSeason;
1357         }
1358         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, true, &showInfo) < 0)
1359           return INFO_ERROR;
1360         continue;
1361       }
1362
1363       if (!hasEpisodeGuide)
1364       {
1365         // fetch episode guide
1366         if (!showInfo.m_strEpisodeGuide.empty())
1367         {
1368           CScraperUrl url;
1369           url.ParseEpisodeGuide(showInfo.m_strEpisodeGuide);
1370
1371           if (pDlgProgress)
1372           {
1373             pDlgProgress->SetLine(2, 20354);
1374             pDlgProgress->Progress();
1375           }
1376
1377           CVideoInfoDownloader imdb(scraper);
1378           if (!imdb.GetEpisodeList(url, episodes))
1379             return INFO_NOT_FOUND;
1380
1381           hasEpisodeGuide = true;
1382         }
1383       }
1384
1385       if (episodes.empty())
1386       {
1387         CLog::Log(LOGERROR, "VideoInfoScanner: Asked to lookup episode %s"
1388                             " online, but we have no episode guide. Check your tvshow.nfo and make"
1389                             " sure the <episodeguide> tag is in place.", file->strPath.c_str());
1390         continue;
1391       }
1392
1393       EPISODE key(file->iSeason, file->iEpisode, file->iSubepisode);
1394       EPISODE backupkey(file->iSeason, file->iEpisode, 0);
1395       bool bFound = false;
1396       EPISODELIST::iterator guide = episodes.begin();;
1397       EPISODELIST matches;
1398
1399       for (; guide != episodes.end(); ++guide )
1400       {
1401         if ((file->iEpisode!=-1) && (file->iSeason!=-1))
1402         {
1403           if (key==*guide)
1404           {
1405             bFound = true;
1406             break;
1407           }
1408           else if ((file->iSubepisode!=0) && (backupkey==*guide))
1409           {
1410             matches.push_back(*guide);
1411             continue;
1412           }
1413         }
1414         if (file->cDate.IsValid() && guide->cDate.IsValid() && file->cDate==guide->cDate)
1415         {
1416           matches.push_back(*guide);
1417           continue;
1418         }
1419         if (!guide->cScraperUrl.strTitle.empty() && StringUtils::EqualsNoCase(guide->cScraperUrl.strTitle, file->strTitle))
1420         {
1421           bFound = true;
1422           break;
1423         }
1424       }
1425
1426       if (!bFound)
1427       {
1428         /*
1429          * If there is only one match or there are matches but no title to compare with to help
1430          * identify the best match, then pick the first match as the best possible candidate.
1431          *
1432          * Otherwise, use the title to further refine the best match.
1433          */
1434         if (matches.size() == 1 || (file->strTitle.empty() && matches.size() > 1))
1435         {
1436           guide = matches.begin();
1437           bFound = true;
1438         }
1439         else if (!file->strTitle.empty())
1440         {
1441           double minscore = 0; // Default minimum score is 0 to find whatever is the best match.
1442
1443           EPISODELIST *candidates;
1444           if (matches.empty()) // No matches found using earlier criteria. Use fuzzy match on titles across all episodes.
1445           {
1446             minscore = 0.8; // 80% should ensure a good match.
1447             candidates = &episodes;
1448           }
1449           else // Multiple matches found. Use fuzzy match on the title with already matched episodes to pick the best.
1450             candidates = &matches;
1451
1452           CStdStringArray titles;
1453           for (guide = candidates->begin(); guide != candidates->end(); ++guide)
1454           {
1455             StringUtils::ToLower(guide->cScraperUrl.strTitle);
1456             titles.push_back(guide->cScraperUrl.strTitle);
1457           }
1458
1459           double matchscore;
1460           std::string loweredTitle(file->strTitle);
1461           StringUtils::ToLower(loweredTitle);
1462           int index = StringUtils::FindBestMatch(loweredTitle, titles, matchscore);
1463           if (matchscore >= minscore)
1464           {
1465             guide = candidates->begin() + index;
1466             bFound = true;
1467             CLog::Log(LOGDEBUG,"%s fuzzy title match for show: '%s', title: '%s', match: '%s', score: %f >= %f",
1468                       __FUNCTION__, showInfo.m_strTitle.c_str(), file->strTitle.c_str(), titles[index].c_str(), matchscore, minscore);
1469           }
1470         }
1471       }
1472
1473       if (bFound)
1474       {
1475         CVideoInfoDownloader imdb(scraper);
1476         CFileItem item;
1477         item.SetPath(file->strPath);
1478         if (!imdb.GetEpisodeDetails(guide->cScraperUrl, *item.GetVideoInfoTag(), pDlgProgress))
1479           return INFO_NOT_FOUND; // TODO: should we just skip to the next episode?
1480           
1481         // Only set season/epnum from filename when it is not already set by a scraper
1482         if (item.GetVideoInfoTag()->m_iSeason == -1)
1483           item.GetVideoInfoTag()->m_iSeason = guide->iSeason;
1484         if (item.GetVideoInfoTag()->m_iEpisode == -1)
1485           item.GetVideoInfoTag()->m_iEpisode = guide->iEpisode;
1486           
1487         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, useLocal, &showInfo) < 0)
1488           return INFO_ERROR;
1489       }
1490       else
1491       {
1492         CLog::Log(LOGDEBUG,"%s - no match for show: '%s', season: %d, episode: %d.%d, airdate: '%s', title: '%s'",
1493                   __FUNCTION__, showInfo.m_strTitle.c_str(), file->iSeason, file->iEpisode, file->iSubepisode,
1494                   file->cDate.GetAsLocalizedDate().c_str(), file->strTitle.c_str());
1495       }
1496     }
1497     return INFO_ADDED;
1498   }
1499
1500   CStdString CVideoInfoScanner::GetnfoFile(CFileItem *item, bool bGrabAny) const
1501   {
1502     CStdString nfoFile;
1503     // Find a matching .nfo file
1504     if (!item->m_bIsFolder)
1505     {
1506       if (URIUtils::IsInRAR(item->GetPath())) // we have a rarred item - we want to check outside the rars
1507       {
1508         CFileItem item2(*item);
1509         CURL url(item->GetPath());
1510         CStdString strPath = URIUtils::GetDirectory(url.GetHostName());
1511         item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1512         return GetnfoFile(&item2, bGrabAny);
1513       }
1514
1515       // grab the folder path
1516       CStdString strPath = URIUtils::GetDirectory(item->GetPath());
1517
1518       if (bGrabAny && !item->IsStack())
1519       { // looking up by folder name - movie.nfo takes priority - but not for stacked items (handled below)
1520         nfoFile = URIUtils::AddFileToFolder(strPath, "movie.nfo");
1521         if (CFile::Exists(nfoFile))
1522           return nfoFile;
1523       }
1524
1525       // try looking for .nfo file for a stacked item
1526       if (item->IsStack())
1527       {
1528         // first try .nfo file matching first file in stack
1529         CStackDirectory dir;
1530         CStdString firstFile = dir.GetFirstStackedFile(item->GetPath());
1531         CFileItem item2;
1532         item2.SetPath(firstFile);
1533         nfoFile = GetnfoFile(&item2, bGrabAny);
1534         // else try .nfo file matching stacked title
1535         if (nfoFile.empty())
1536         {
1537           CStdString stackedTitlePath = dir.GetStackedTitlePath(item->GetPath());
1538           item2.SetPath(stackedTitlePath);
1539           nfoFile = GetnfoFile(&item2, bGrabAny);
1540         }
1541       }
1542       else
1543       {
1544         // already an .nfo file?
1545         if (URIUtils::HasExtension(item->GetPath(), ".nfo"))
1546           nfoFile = item->GetPath();
1547         // no, create .nfo file
1548         else
1549           nfoFile = URIUtils::ReplaceExtension(item->GetPath(), ".nfo");
1550       }
1551
1552       // test file existence
1553       if (!nfoFile.empty() && !CFile::Exists(nfoFile))
1554         nfoFile.clear();
1555
1556       if (nfoFile.empty()) // final attempt - strip off any cd1 folders
1557       {
1558         URIUtils::RemoveSlashAtEnd(strPath); // need no slash for the check that follows
1559         CFileItem item2;
1560         if (StringUtils::EndsWithNoCase(strPath, "cd1"))
1561         {
1562           strPath.erase(strPath.size() - 3);
1563           item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1564           return GetnfoFile(&item2, bGrabAny);
1565         }
1566       }
1567
1568       if (nfoFile.empty() && item->IsOpticalMediaFile())
1569       {
1570         CFileItem parentDirectory(item->GetLocalMetadataPath(), true);
1571         nfoFile = GetnfoFile(&parentDirectory, true);
1572       }
1573     }
1574     // folders (or stacked dvds) can take any nfo file if there's a unique one
1575     if (item->m_bIsFolder || item->IsOpticalMediaFile() || (bGrabAny && nfoFile.empty()))
1576     {
1577       // see if there is a unique nfo file in this folder, and if so, use that
1578       CFileItemList items;
1579       CDirectory dir;
1580       CStdString strPath;
1581       if (item->m_bIsFolder)
1582         strPath = item->GetPath();
1583       else
1584         strPath = URIUtils::GetDirectory(item->GetPath());
1585
1586       if (dir.GetDirectory(strPath, items, ".nfo") && items.Size())
1587       {
1588         int numNFO = -1;
1589         for (int i = 0; i < items.Size(); i++)
1590         {
1591           if (items[i]->IsNFO())
1592           {
1593             if (numNFO == -1)
1594               numNFO = i;
1595             else
1596             {
1597               numNFO = -1;
1598               break;
1599             }
1600           }
1601         }
1602         if (numNFO > -1)
1603           return items[numNFO]->GetPath();
1604       }
1605     }
1606
1607     return nfoFile;
1608   }
1609
1610   bool CVideoInfoScanner::GetDetails(CFileItem *pItem, CScraperUrl &url, const ScraperPtr& scraper, CNfoFile *nfoFile, CGUIDialogProgress* pDialog /* = NULL */)
1611   {
1612     CVideoInfoTag movieDetails;
1613
1614     if (m_handle && !url.strTitle.empty())
1615       m_handle->SetText(url.strTitle);
1616
1617     CVideoInfoDownloader imdb(scraper);
1618     bool ret = imdb.GetDetails(url, movieDetails, pDialog);
1619
1620     if (ret)
1621     {
1622       if (nfoFile)
1623         nfoFile->GetDetails(movieDetails,NULL,true);
1624
1625       if (m_handle && url.strTitle.empty())
1626         m_handle->SetText(movieDetails.m_strTitle);
1627
1628       if (pDialog)
1629       {
1630         pDialog->SetLine(1, movieDetails.m_strTitle);
1631         pDialog->Progress();
1632       }
1633
1634       *pItem->GetVideoInfoTag() = movieDetails;
1635       return true;
1636     }
1637     return false; // no info found, or cancelled
1638   }
1639
1640   void CVideoInfoScanner::ApplyThumbToFolder(const CStdString &folder, const CStdString &imdbThumb)
1641   {
1642     // copy icon to folder also;
1643     if (!imdbThumb.empty())
1644     {
1645       CFileItem folderItem(folder, true);
1646       CThumbLoader loader;
1647       loader.SetCachedImage(folderItem, "thumb", imdbThumb);
1648     }
1649   }
1650
1651   int CVideoInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
1652   {
1653     // Create a hash based on the filenames, filesize and filedate.  Also count the number of files
1654     if (0 == items.Size()) return 0;
1655     XBMC::XBMC_MD5 md5state;
1656     int count = 0;
1657     for (int i = 0; i < items.Size(); ++i)
1658     {
1659       const CFileItemPtr pItem = items[i];
1660       md5state.append(pItem->GetPath());
1661       md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1662       FILETIME time = pItem->m_dateTime;
1663       md5state.append((unsigned char *)&time, sizeof(FILETIME));
1664       if (pItem->IsVideo() && !pItem->IsPlayList() && !pItem->IsNFO())
1665         count++;
1666     }
1667     md5state.getDigest(hash);
1668     return count;
1669   }
1670
1671   bool CVideoInfoScanner::CanFastHash(const CFileItemList &items) const
1672   {
1673     // TODO: Probably should account for excluded folders here (eg samples), though that then
1674     //       introduces possible problems if the user then changes the exclude regexps and
1675     //       expects excluded folders that are inside a fast-hashed folder to then be picked
1676     //       up. The chances that the user has a folder which contains only excluded folders
1677     //       where some of those folders should be scanned recursively is pretty small.
1678     return items.GetFolderCount() == 0;
1679   }
1680
1681   CStdString CVideoInfoScanner::GetFastHash(const CStdString &directory) const
1682   {
1683     struct __stat64 buffer;
1684     if (XFILE::CFile::Stat(directory, &buffer) == 0)
1685     {
1686       int64_t time = buffer.st_mtime;
1687       if (!time)
1688         time = buffer.st_ctime;
1689       if (time)
1690         return StringUtils::Format("fast%"PRId64, time);
1691     }
1692     return "";
1693   }
1694
1695   void CVideoInfoScanner::GetSeasonThumbs(const CVideoInfoTag &show, map<int, map<string, string> > &seasonArt, const vector<string> &artTypes, bool useLocal)
1696   {
1697     bool lookForThumb = find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end();
1698
1699     // find the maximum number of seasons we have thumbs for (local + remote)
1700     int maxSeasons = show.m_strPictureURL.GetMaxSeasonThumb();
1701
1702     CFileItemList items;
1703     CDirectory::GetDirectory(show.m_strPath, items, ".png|.jpg|.tbn", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
1704     CRegExp reg;
1705     if (items.Size() && reg.RegComp("season([0-9]+)(-[a-z]+)?\\.(tbn|jpg|png)"))
1706     {
1707       for (int i = 0; i < items.Size(); i++)
1708       {
1709         CStdString name = URIUtils::GetFileName(items[i]->GetPath());
1710         if (reg.RegFind(name) > -1)
1711         {
1712           int season = atoi(reg.GetMatch(1).c_str());
1713           if (season > maxSeasons)
1714             maxSeasons = season;
1715         }
1716       }
1717     }
1718     for (int season = -1; season <= maxSeasons; season++)
1719     {
1720       map<string, string> art;
1721       if (useLocal)
1722       {
1723         string basePath;
1724         if (season == -1)
1725           basePath = "season-all";
1726         else if (season == 0)
1727           basePath = "season-specials";
1728         else
1729           basePath = StringUtils::Format("season%02i", season);
1730         CFileItem artItem(URIUtils::AddFileToFolder(show.m_strPath, basePath), false);
1731
1732         for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1733         {
1734           std::string image = CVideoThumbLoader::GetLocalArt(artItem, *i, false);
1735           if (!image.empty())
1736             art.insert(make_pair(*i, image));
1737         }
1738         // find and classify the local thumb (backcompat) if available
1739         if (lookForThumb)
1740         {
1741           std::string image = CVideoThumbLoader::GetLocalArt(artItem, "thumb", false);
1742           if (!image.empty())
1743           { // cache the image and determine sizing
1744             CTextureDetails details;
1745             if (CTextureCache::Get().CacheImage(image, details))
1746             {
1747               std::string type = GetArtTypeFromSize(details.width, details.height);
1748               if (art.find(type) == art.end())
1749                 art.insert(make_pair(type, image));
1750             }
1751           }
1752         }
1753       }
1754
1755       // find online art
1756       for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1757       {
1758         if (art.find(*i) == art.end())
1759         {
1760           string image = CScraperUrl::GetThumbURL(show.m_strPictureURL.GetSeasonThumb(season, *i));
1761           if (!image.empty())
1762             art.insert(make_pair(*i, image));
1763         }
1764       }
1765       // use the first piece of online art as the first art type if no thumb type is available yet
1766       if (art.empty() && lookForThumb)
1767       {
1768         string image = CScraperUrl::GetThumbURL(show.m_strPictureURL.GetSeasonThumb(season, "thumb"));
1769         if (!image.empty())
1770           art.insert(make_pair(artTypes.front(), image));
1771       }
1772
1773       seasonArt.insert(make_pair(season, art));
1774     }
1775   }
1776
1777   void CVideoInfoScanner::FetchActorThumbs(vector<SActorInfo>& actors, const CStdString& strPath)
1778   {
1779     CFileItemList items;
1780     CStdString actorsDir = URIUtils::AddFileToFolder(strPath, ".actors");
1781     if (CDirectory::Exists(actorsDir))
1782       CDirectory::GetDirectory(actorsDir, items, ".png|.jpg|.tbn", DIR_FLAG_NO_FILE_DIRS |
1783                                DIR_FLAG_NO_FILE_INFO);
1784     for (vector<SActorInfo>::iterator i = actors.begin(); i != actors.end(); ++i)
1785     {
1786       if (i->thumb.empty())
1787       {
1788         CStdString thumbFile = i->strName;
1789         StringUtils::Replace(thumbFile, ' ', '_');
1790         for (int j = 0; j < items.Size(); j++)
1791         {
1792           CStdString compare = URIUtils::GetFileName(items[j]->GetPath());
1793           URIUtils::RemoveExtension(compare);
1794           if (!items[j]->m_bIsFolder && compare == thumbFile)
1795           {
1796             i->thumb = items[j]->GetPath();
1797             break;
1798           }
1799         }
1800         if (i->thumb.empty() && !i->thumbUrl.GetFirstThumb().m_url.empty())
1801           i->thumb = CScraperUrl::GetThumbURL(i->thumbUrl.GetFirstThumb());
1802         if (!i->thumb.empty())
1803           CTextureCache::Get().BackgroundCacheImage(i->thumb);
1804       }
1805     }
1806   }
1807
1808   CNfoFile::NFOResult CVideoInfoScanner::CheckForNFOFile(CFileItem* pItem, bool bGrabAny, ScraperPtr& info, CScraperUrl& scrUrl)
1809   {
1810     CStdString strNfoFile;
1811     if (info->Content() == CONTENT_MOVIES || info->Content() == CONTENT_MUSICVIDEOS
1812         || (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder))
1813       strNfoFile = GetnfoFile(pItem, bGrabAny);
1814     if (info->Content() == CONTENT_TVSHOWS && pItem->m_bIsFolder)
1815       strNfoFile = URIUtils::AddFileToFolder(pItem->GetPath(), "tvshow.nfo");
1816
1817     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1818     if (!strNfoFile.empty() && CFile::Exists(strNfoFile))
1819     {
1820       if (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder)
1821         result = m_nfoReader.Create(strNfoFile,info,pItem->GetVideoInfoTag()->m_iEpisode);
1822       else
1823         result = m_nfoReader.Create(strNfoFile,info);
1824
1825       CStdString type;
1826       switch(result)
1827       {
1828         case CNfoFile::COMBINED_NFO:
1829           type = "Mixed";
1830           break;
1831         case CNfoFile::FULL_NFO:
1832           type = "Full";
1833           break;
1834         case CNfoFile::URL_NFO:
1835           type = "URL";
1836           break;
1837         case CNfoFile::NO_NFO:
1838           type = "";
1839           break;
1840         default:
1841           type = "malformed";
1842       }
1843       if (result != CNfoFile::NO_NFO)
1844         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found matching %s NFO file: %s", type.c_str(), CURL::GetRedacted(strNfoFile).c_str());
1845       if (result == CNfoFile::FULL_NFO)
1846       {
1847         if (info->Content() == CONTENT_TVSHOWS)
1848           info = m_nfoReader.GetScraperInfo();
1849       }
1850       else if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
1851       {
1852         scrUrl = m_nfoReader.ScraperUrl();
1853         info = m_nfoReader.GetScraperInfo();
1854
1855         CLog::Log(LOGDEBUG, "VideoInfoScanner: Fetching url '%s' using %s scraper (content: '%s')",
1856           scrUrl.m_url[0].m_url.c_str(), info->Name().c_str(), TranslateContent(info->Content()).c_str());
1857
1858         if (result == CNfoFile::COMBINED_NFO)
1859           m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
1860       }
1861     }
1862     else
1863       CLog::Log(LOGDEBUG, "VideoInfoScanner: No NFO file found. Using title search for '%s'", CURL::GetRedacted(pItem->GetPath()).c_str());
1864
1865     return result;
1866   }
1867
1868   bool CVideoInfoScanner::DownloadFailed(CGUIDialogProgress* pDialog)
1869   {
1870     if (g_advancedSettings.m_bVideoScannerIgnoreErrors)
1871       return true;
1872
1873     if (pDialog)
1874     {
1875       CGUIDialogOK::ShowAndGetInput(20448,20449,20022,20022);
1876       return false;
1877     }
1878     return CGUIDialogYesNo::ShowAndGetInput(20448,20449,20450,20022);
1879   }
1880
1881   bool CVideoInfoScanner::ProgressCancelled(CGUIDialogProgress* progress, int heading, const CStdString &line1)
1882   {
1883     if (progress)
1884     {
1885       progress->SetHeading(heading);
1886       progress->SetLine(0, line1);
1887       progress->SetLine(2, "");
1888       progress->Progress();
1889       return progress->IsCanceled();
1890     }
1891     return m_bStop;
1892   }
1893
1894   int CVideoInfoScanner::FindVideo(const CStdString &videoName, const ScraperPtr &scraper, CScraperUrl &url, CGUIDialogProgress *progress)
1895   {
1896     MOVIELIST movielist;
1897     CVideoInfoDownloader imdb(scraper);
1898     int returncode = imdb.FindMovie(videoName, movielist, progress);
1899     if (returncode < 0 || (returncode == 0 && (m_bStop || !DownloadFailed(progress))))
1900     { // scraper reported an error, or we had an error and user wants to cancel the scan
1901       m_bStop = true;
1902       return -1; // cancelled
1903     }
1904     if (returncode > 0 && movielist.size())
1905     {
1906       url = movielist[0];
1907       return 1;  // found a movie
1908     }
1909     return 0;    // didn't find anything
1910   }
1911
1912   CStdString CVideoInfoScanner::GetParentDir(const CFileItem &item) const
1913   {
1914     CStdString strCheck = item.GetPath();
1915     if (item.IsStack())
1916       strCheck = CStackDirectory::GetFirstStackedFile(item.GetPath());
1917
1918     CStdString strDirectory = URIUtils::GetDirectory(strCheck);
1919     if (URIUtils::IsInRAR(strCheck))
1920     {
1921       CStdString strPath=strDirectory;
1922       URIUtils::GetParentPath(strPath, strDirectory);
1923     }
1924     if (item.IsStack())
1925     {
1926       strCheck = strDirectory;
1927       URIUtils::RemoveSlashAtEnd(strCheck);
1928       if (URIUtils::GetFileName(strCheck).size() == 3 && StringUtils::StartsWithNoCase(URIUtils::GetFileName(strCheck), "cd"))
1929         strDirectory = URIUtils::GetDirectory(strCheck);
1930     }
1931     return strDirectory;
1932   }
1933
1934 }