[bluray] Fix stream info/language retrieval for blurays in non-nav mode.
[vuplus_xbmc] / xbmc / video / VideoInfoScanner.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://www.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.IsEmpty())
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.IsEmpty() && fastHash == dbHash)
268       { // fast hashes match - no need to process anything
269         CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' due to no change (fasthash)", 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.IsEmpty())
280         {
281           if (dbHash.IsEmpty())
282             CLog::Log(LOGDEBUG, "VideoInfoScanner: Scanning dir '%s' as not in the database", strDirectory.c_str());
283           else
284             CLog::Log(LOGDEBUG, "VideoInfoScanner: Rescanning dir '%s' due to change (%s != %s)", 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.IsEmpty() && !dbHash.IsEmpty())
289           {
290             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' as it's empty or doesn't exist - adding to clean list", 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", 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.IsEmpty())
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", 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", 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(), 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         CLog::Log(LOGWARNING, "No information found for item '%s', it won't be added to the library.", pItem->GetPath().c_str());
443
444       pURL = NULL;
445
446       // Keep track of directories we've seen
447       if (pItem->m_bIsFolder)
448         seenPaths.push_back(m_database.GetPathId(pItem->GetPath()));
449     }
450
451     if (content == CONTENT_TVSHOWS && ! seenPaths.empty())
452     {
453       vector< pair<int,string> > libPaths;
454       m_database.GetSubPaths(items.GetPath(), libPaths);
455       for (vector< pair<int,string> >::iterator i = libPaths.begin(); i < libPaths.end(); ++i)
456       {
457         if (find(seenPaths.begin(), seenPaths.end(), i->first) == seenPaths.end())
458           m_pathsToClean.insert(i->first);
459       }
460     }
461     if(pDlgProgress)
462       pDlgProgress->ShowProgressBar(false);
463
464     g_infoManager.ResetLibraryBools();
465     m_database.Close();
466     return FoundSomeInfo;
467   }
468
469   INFO_RET CVideoInfoScanner::RetrieveInfoForTvShow(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
470   {
471     long idTvShow = -1;
472     if (pItem->m_bIsFolder)
473       idTvShow = m_database.GetTvShowId(pItem->GetPath());
474     else
475     {
476       CStdString strPath;
477       URIUtils::GetDirectory(pItem->GetPath(),strPath);
478       idTvShow = m_database.GetTvShowId(strPath);
479     }
480     if (idTvShow > -1 && (fetchEpisodes || !pItem->m_bIsFolder))
481     {
482       INFO_RET ret = RetrieveInfoForEpisodes(pItem, idTvShow, info2, useLocal, pDlgProgress);
483       if (ret == INFO_ADDED)
484         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
485       return ret;
486     }
487
488     if (ProgressCancelled(pDlgProgress, pItem->m_bIsFolder ? 20353 : 20361, pItem->GetLabel()))
489       return INFO_CANCELLED;
490
491     if (m_handle)
492       m_handle->SetText(pItem->GetMovieName(bDirNames));
493
494     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
495     CScraperUrl scrUrl;
496     // handle .nfo files
497     if (useLocal)
498       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
499     if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
500     { // check for preconfigured scraper; if found, overwrite with interpreted scraper (from Nfofile)
501       // but keep current scan settings
502       SScanSettings settings;
503       if (m_database.GetScraperForPath(pItem->GetPath(), settings))
504         m_database.SetScraperForPath(pItem->GetPath(), info2, settings);
505     }
506     if (result == CNfoFile::FULL_NFO)
507     {
508       pItem->GetVideoInfoTag()->Reset();
509       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
510
511       long lResult = AddVideo(pItem, info2->Content(), bDirNames, useLocal);
512       if (lResult < 0)
513         return INFO_ERROR;
514       if (fetchEpisodes)
515       {
516         INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
517         if (ret == INFO_ADDED)
518           m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
519         return ret;
520       }
521       return INFO_ADDED;
522     }
523     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
524       pURL = &scrUrl;
525
526     CScraperUrl url;
527     int retVal = 0;
528     if (pURL)
529       url = *pURL;
530     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
531       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
532
533     long lResult=-1;
534     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
535     {
536       if ((lResult = AddVideo(pItem, info2->Content(), false, useLocal)) < 0)
537         return INFO_ERROR;
538     }
539     if (fetchEpisodes)
540     {
541       INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
542       if (ret == INFO_ADDED)
543         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
544     }
545     return INFO_ADDED;
546   }
547
548   INFO_RET CVideoInfoScanner::RetrieveInfoForMovie(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
549   {
550     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
551        (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm")))
552       return INFO_NOT_NEEDED;
553
554     if (ProgressCancelled(pDlgProgress, 198, pItem->GetLabel()))
555       return INFO_CANCELLED;
556
557     if (m_database.HasMovieInfo(pItem->GetPath()))
558       return INFO_HAVE_ALREADY;
559
560     if (m_handle)
561       m_handle->SetText(pItem->GetMovieName(bDirNames));
562
563     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
564     CScraperUrl scrUrl;
565     // handle .nfo files
566     if (useLocal)
567       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
568     if (result == CNfoFile::FULL_NFO)
569     {
570       pItem->GetVideoInfoTag()->Reset();
571       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
572
573       if (AddVideo(pItem, info2->Content(), bDirNames, true) < 0)
574         return INFO_ERROR;
575       return INFO_ADDED;
576     }
577     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
578       pURL = &scrUrl;
579
580     CScraperUrl url;
581     int retVal = 0;
582     if (pURL)
583       url = *pURL;
584     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
585       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
586
587     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
588     {
589       if (AddVideo(pItem, info2->Content(), bDirNames, useLocal) < 0)
590         return INFO_ERROR;
591       return INFO_ADDED;
592     }
593     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
594     return INFO_NOT_FOUND;
595   }
596
597   INFO_RET CVideoInfoScanner::RetrieveInfoForMusicVideo(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
598   {
599     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
600        (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm")))
601       return INFO_NOT_NEEDED;
602
603     if (ProgressCancelled(pDlgProgress, 20394, pItem->GetLabel()))
604       return INFO_CANCELLED;
605
606     if (m_database.HasMusicVideoInfo(pItem->GetPath()))
607       return INFO_HAVE_ALREADY;
608
609     if (m_handle)
610       m_handle->SetText(pItem->GetMovieName(bDirNames));
611
612     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
613     CScraperUrl scrUrl;
614     // handle .nfo files
615     if (useLocal)
616       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
617     if (result == CNfoFile::FULL_NFO)
618     {
619       pItem->GetVideoInfoTag()->Reset();
620       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
621
622       if (AddVideo(pItem, info2->Content(), bDirNames, true) < 0)
623         return INFO_ERROR;
624       return INFO_ADDED;
625     }
626     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
627       pURL = &scrUrl;
628
629     CScraperUrl url;
630     int retVal = 0;
631     if (pURL)
632       url = *pURL;
633     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
634       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
635
636     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
637     {
638       if (AddVideo(pItem, info2->Content(), bDirNames, useLocal) < 0)
639         return INFO_ERROR;
640       return INFO_ADDED;
641     }
642     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
643     return INFO_NOT_FOUND;
644   }
645
646   INFO_RET CVideoInfoScanner::RetrieveInfoForEpisodes(CFileItem *item, long showID, const ADDON::ScraperPtr &scraper, bool useLocal, CGUIDialogProgress *progress)
647   {
648     // enumerate episodes
649     EPISODELIST files;
650     EnumerateSeriesFolder(item, files);
651     if (files.size() == 0) // no update or no files
652       return INFO_NOT_NEEDED;
653
654     if (m_bStop || (progress && progress->IsCanceled()))
655       return INFO_CANCELLED;
656
657     CVideoInfoTag showInfo;
658     m_database.GetTvShowInfo("", showInfo, showID);
659     return OnProcessSeriesFolder(files, scraper, useLocal, showInfo, progress);
660   }
661
662   void CVideoInfoScanner::EnumerateSeriesFolder(CFileItem* item, EPISODELIST& episodeList)
663   {
664     CFileItemList items;
665
666     if (item->m_bIsFolder)
667     {
668       CUtil::GetRecursiveListing(item->GetPath(), items, g_advancedSettings.m_videoExtensions, true);
669       CStdString hash, dbHash;
670       int numFilesInFolder = GetPathHash(items, hash);
671
672       if (m_database.GetPathHash(item->GetPath(), dbHash) && dbHash == hash)
673       {
674         m_currentItem += numFilesInFolder;
675
676         // update our dialog with our progress
677         if (m_handle)
678         {
679           if (m_itemCount>0)
680             m_handle->SetPercentage(m_currentItem*100.f/m_itemCount);
681
682           OnDirectoryScanned(item->GetPath());
683         }
684         return;
685       }
686       m_pathsToClean.insert(m_database.GetPathId(item->GetPath()));
687       m_database.GetPathsForTvShow(m_database.GetTvShowId(item->GetPath()), m_pathsToClean);
688       item->SetProperty("hash", hash);
689     }
690     else
691     {
692       CFileItemPtr newItem(new CFileItem(*item));
693       items.Add(newItem);
694     }
695
696     /*
697     stack down any dvd folders
698     need to sort using the full path since this is a collapsed recursive listing of all subdirs
699     video_ts.ifo files should sort at the top of a dvd folder in ascending order
700
701     /foo/bar/video_ts.ifo
702     /foo/bar/vts_x_y.ifo
703     /foo/bar/vts_x_y.vob
704     */
705
706     // since we're doing this now anyway, should other items be stacked?
707     items.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
708     int x = 0;
709     while (x < items.Size())
710     {
711       if (items[x]->m_bIsFolder)
712         continue;
713
714
715       CStdString strPathX, strFileX;
716       URIUtils::Split(items[x]->GetPath(), strPathX, strFileX);
717       //CLog::Log(LOGDEBUG,"%i:%s:%s", x, strPathX.c_str(), strFileX.c_str());
718
719       int y = x + 1;
720       if (strFileX.Equals("VIDEO_TS.IFO"))
721       {
722         while (y < items.Size())
723         {
724           CStdString strPathY, strFileY;
725           URIUtils::Split(items[y]->GetPath(), strPathY, strFileY);
726           //CLog::Log(LOGDEBUG," %i:%s:%s", y, strPathY.c_str(), strFileY.c_str());
727
728           if (strPathY.Equals(strPathX))
729             /*
730             remove everything sorted below the video_ts.ifo file in the same path.
731             understandbly this wont stack correctly if there are other files in the the dvd folder.
732             this should be unlikely and thus is being ignored for now but we can monitor the
733             where the path changes and potentially remove the items above the video_ts.ifo file.
734             */
735             items.Remove(y);
736           else
737             break;
738         }
739       }
740       x = y;
741     }
742
743     // enumerate
744     CStdStringArray regexps = g_advancedSettings.m_tvshowExcludeFromScanRegExps;
745
746     for (int i=0;i<items.Size();++i)
747     {
748       if (items[i]->m_bIsFolder)
749         continue;
750       CStdString strPath;
751       URIUtils::GetDirectory(items[i]->GetPath(), strPath);
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       {
771         CStdString decode(items[i]->GetPath());
772         CURL::Decode(decode);
773         CLog::Log(LOGDEBUG, "VideoInfoScanner: Could not enumerate file %s", decode.c_str());
774       }
775     }
776   }
777
778   bool CVideoInfoScanner::ProcessItemByVideoInfoTag(const CFileItem *item, EPISODELIST &episodeList)
779   {
780     if (!item->HasVideoInfoTag())
781       return false;
782
783     const CVideoInfoTag* tag = item->GetVideoInfoTag();
784     /*
785      * First check the season and episode number. This takes precedence over the original air
786      * date and episode title. Must be a valid season and episode number combination.
787      */
788     if (tag->m_iSeason > -1 && tag->m_iEpisode > 0)
789     {
790       EPISODE episode;
791       episode.strPath = item->GetPath();
792       episode.iSeason = tag->m_iSeason;
793       episode.iEpisode = tag->m_iEpisode;
794       episode.isFolder = false;
795       episodeList.push_back(episode);
796       CLog::Log(LOGDEBUG, "%s - found match for: %s. Season %d, Episode %d", __FUNCTION__,
797                 episode.strPath.c_str(), episode.iSeason, episode.iEpisode);
798       return true;
799     }
800
801     /*
802      * Next preference is the first aired date. If it exists use that for matching the TV Show
803      * information. Also set the title in case there are multiple matches for the first aired date.
804      */
805     if (tag->m_firstAired.IsValid())
806     {
807       EPISODE episode;
808       episode.strPath = item->GetPath();
809       episode.strTitle = tag->m_strTitle;
810       episode.isFolder = false;
811       /*
812        * Set season and episode to -1 to indicate to use the aired date.
813        */
814       episode.iSeason = -1;
815       episode.iEpisode = -1;
816       /*
817        * The first aired date string must be parseable.
818        */
819       episode.cDate = item->GetVideoInfoTag()->m_firstAired;
820       episodeList.push_back(episode);
821       CLog::Log(LOGDEBUG, "%s - found match for: '%s', firstAired: '%s' = '%s', title: '%s'",
822         __FUNCTION__, episode.strPath.c_str(), tag->m_firstAired.GetAsDBDateTime().c_str(),
823                 episode.cDate.GetAsLocalizedDate().c_str(), episode.strTitle.c_str());
824       return true;
825     }
826
827     /*
828      * Next preference is the episode title. If it exists use that for matching the TV Show
829      * information.
830      */
831     if (!tag->m_strTitle.IsEmpty())
832     {
833       EPISODE episode;
834       episode.strPath = item->GetPath();
835       episode.strTitle = tag->m_strTitle;
836       episode.isFolder = false;
837       /*
838        * Set season and episode to -1 to indicate to use the title.
839        */
840       episode.iSeason = -1;
841       episode.iEpisode = -1;
842       episodeList.push_back(episode);
843       CLog::Log(LOGDEBUG,"%s - found match for: '%s', title: '%s'", __FUNCTION__,
844                 episode.strPath.c_str(), episode.strTitle.c_str());
845       return true;
846     }
847
848     /*
849      * There is no further episode information available if both the season and episode number have
850      * been set to 0. Return the match as true so no further matching is attempted, but don't add it
851      * to the episode list.
852      */
853     if (tag->m_iSeason == 0 && tag->m_iEpisode == 0)
854     {
855       CLog::Log(LOGDEBUG,"%s - found exclusion match for: %s. Both Season and Episode are 0. Item will be ignored for scanning.",
856                 __FUNCTION__, item->GetPath().c_str());
857       return true;
858     }
859
860     return false;
861   }
862
863   bool CVideoInfoScanner::EnumerateEpisodeItem(const CFileItem *item, EPISODELIST& episodeList)
864   {
865     SETTINGS_TVSHOWLIST expression = g_advancedSettings.m_tvshowEnumRegExps;
866
867     CStdString strLabel=item->GetPath();
868     // URLDecode in case an episode is on a http/https/dav/davs:// source and URL-encoded like foo%201x01%20bar.avi
869     CURL::Decode(strLabel);
870     strLabel.MakeLower();
871
872     for (unsigned int i=0;i<expression.size();++i)
873     {
874       CRegExp reg(true);
875       if (!reg.RegComp(expression[i].regexp))
876         continue;
877
878       int regexppos, regexp2pos;
879       //CLog::Log(LOGDEBUG,"running expression %s on %s",expression[i].regexp.c_str(),strLabel.c_str());
880       if ((regexppos = reg.RegFind(strLabel.c_str())) < 0)
881         continue;
882
883       EPISODE episode;
884       episode.strPath = item->GetPath();
885       episode.iSeason = -1;
886       episode.iEpisode = -1;
887       episode.cDate.SetValid(false);
888       episode.isFolder = false;
889
890       bool byDate = expression[i].byDate ? true : false;
891       int defaultSeason = expression[i].defaultSeason;
892
893       if (byDate)
894       {
895         if (!GetAirDateFromRegExp(reg, episode))
896           continue;
897
898         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found date based match %s (%s) [%s]", strLabel.c_str(),
899                   episode.cDate.GetAsLocalizedDate().c_str(), expression[i].regexp.c_str());
900       }
901       else
902       {
903         if (!GetEpisodeAndSeasonFromRegExp(reg, episode, defaultSeason))
904           continue;
905
906         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found episode match %s (s%ie%i) [%s]", strLabel.c_str(),
907                   episode.iSeason, episode.iEpisode, expression[i].regexp.c_str());
908       }
909
910       // Grab the remainder from first regexp run
911       // as second run might modify or empty it.
912       std::string remainder = reg.GetReplaceString("\\3");
913
914       /*
915        * Check if the files base path is a dedicated folder that contains
916        * only this single episode. If season and episode match with the
917        * actual media file, we set episode.isFolder to true.
918        */
919       CStdString strBasePath = item->GetBaseMoviePath(true);
920       URIUtils::RemoveSlashAtEnd(strBasePath);
921       strBasePath = URIUtils::GetFileName(strBasePath);
922
923       if (reg.RegFind(strBasePath.c_str()) > -1)
924       {
925         EPISODE parent;
926         if (byDate)
927         {
928           GetAirDateFromRegExp(reg, parent);
929           if (episode.cDate == parent.cDate)
930             episode.isFolder = true;
931         }
932         else
933         {
934           GetEpisodeAndSeasonFromRegExp(reg, parent, defaultSeason);
935           if (episode.iSeason == parent.iSeason && episode.iEpisode == parent.iEpisode)
936             episode.isFolder = true;
937         }
938       }
939
940       // add what we found by now
941       episodeList.push_back(episode);
942
943       CRegExp reg2(true);
944       // check the remainder of the string for any further episodes.
945       if (!byDate && reg2.RegComp(g_advancedSettings.m_tvshowMultiPartEnumRegExp))
946       {
947         int offset = 0;
948
949         // we want "long circuit" OR below so that both offsets are evaluated
950         while (((regexp2pos = reg2.RegFind(remainder.c_str() + offset)) > -1) | ((regexppos = reg.RegFind(remainder.c_str() + offset)) > -1))
951         {
952           if (((regexppos <= regexp2pos) && regexppos != -1) ||
953              (regexppos >= 0 && regexp2pos == -1))
954           {
955             GetEpisodeAndSeasonFromRegExp(reg, episode, defaultSeason);
956
957             CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new season %u, multipart episode %u [%s]",
958                       episode.iSeason, episode.iEpisode,
959                       g_advancedSettings.m_tvshowMultiPartEnumRegExp.c_str());
960
961             episodeList.push_back(episode);
962             remainder = reg.GetReplaceString("\\3");
963             offset = 0;
964           }
965           else if (((regexp2pos < regexppos) && regexp2pos != -1) ||
966                    (regexp2pos >= 0 && regexppos == -1))
967           {
968             episode.iEpisode = atoi(reg2.GetReplaceString("\\1").c_str());
969             CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding multipart episode %u [%s]",
970                       episode.iEpisode, g_advancedSettings.m_tvshowMultiPartEnumRegExp.c_str());
971             episodeList.push_back(episode);
972             offset += regexp2pos + reg2.GetFindLen();
973           }
974         }
975       }
976       return true;
977     }
978     return false;
979   }
980
981   bool CVideoInfoScanner::GetEpisodeAndSeasonFromRegExp(CRegExp &reg, EPISODE &episodeInfo, int defaultSeason)
982   {
983     std::string season = reg.GetReplaceString("\\1");
984     std::string episode = reg.GetReplaceString("\\2");
985
986     if (!season.empty() || !episode.empty())
987     {
988       char* endptr = NULL;
989       if (season.empty() && !episode.empty())
990       { // no season specified -> assume defaultSeason
991         episodeInfo.iSeason = defaultSeason;
992         if ((episodeInfo.iEpisode = CUtil::TranslateRomanNumeral(episode.c_str())) == -1)
993           episodeInfo.iEpisode = strtol(episode.c_str(), &endptr, 10);
994       }
995       else if (!season.empty() && episode.empty())
996       { // no episode specification -> assume defaultSeason
997         episodeInfo.iSeason = defaultSeason;
998         if ((episodeInfo.iEpisode = CUtil::TranslateRomanNumeral(season.c_str())) == -1)
999           episodeInfo.iEpisode = atoi(season.c_str());
1000       }
1001       else
1002       { // season and episode specified
1003         episodeInfo.iSeason = atoi(season.c_str());
1004         episodeInfo.iEpisode = strtol(episode.c_str(), &endptr, 10);
1005       }
1006       if (endptr)
1007       {
1008         if (isalpha(*endptr))
1009           episodeInfo.iSubepisode = *endptr - (islower(*endptr) ? 'a' : 'A') + 1;
1010         else if (*endptr == '.')
1011           episodeInfo.iSubepisode = atoi(endptr+1);
1012       }
1013       return true;
1014     }
1015     return false;
1016   }
1017
1018   bool CVideoInfoScanner::GetAirDateFromRegExp(CRegExp &reg, EPISODE &episodeInfo)
1019   {
1020     std::string param1 = reg.GetReplaceString("\\1");
1021     std::string param2 = reg.GetReplaceString("\\2");
1022     std::string param3 = reg.GetReplaceString("\\3");
1023
1024     if (!param1.empty() && !param2.empty() && !param3.empty())
1025     {
1026       // regular expression by date
1027       int len1 = param1.size();
1028       int len2 = param2.size();
1029       int len3 = param3.size();
1030
1031       if (len1==4 && len2==2 && len3==2)
1032       {
1033         // yyyy mm dd format
1034         episodeInfo.cDate.SetDate(atoi(param1.c_str()), atoi(param2.c_str()), atoi(param3.c_str()));
1035       }
1036       else if (len1==2 && len2==2 && len3==4)
1037       {
1038         // mm dd yyyy format
1039         episodeInfo.cDate.SetDate(atoi(param3.c_str()), atoi(param1.c_str()), atoi(param2.c_str()));
1040       }
1041     }
1042     return episodeInfo.cDate.IsValid();
1043   }
1044
1045   long CVideoInfoScanner::AddVideo(CFileItem *pItem, const CONTENT_TYPE &content, bool videoFolder /* = false */, bool useLocal /* = true */, const CVideoInfoTag *showInfo /* = NULL */, bool libraryImport /* = false */)
1046   {
1047     // ensure our database is open (this can get called via other classes)
1048     if (!m_database.Open())
1049       return -1;
1050
1051     if (!libraryImport)
1052       GetArtwork(pItem, content, videoFolder, useLocal, showInfo ? showInfo->m_strPath : "");
1053
1054     // ensure the art map isn't completely empty by specifying an empty thumb
1055     map<string, string> art = pItem->GetArt();
1056     if (art.empty())
1057       art["thumb"] = "";
1058
1059     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1060     if (movieDetails.m_basePath.IsEmpty())
1061       movieDetails.m_basePath = pItem->GetBaseMoviePath(videoFolder);
1062     movieDetails.m_parentPathID = m_database.AddPath(URIUtils::GetParentPath(movieDetails.m_basePath));
1063
1064     movieDetails.m_strFileNameAndPath = pItem->GetPath();
1065
1066     if (pItem->m_bIsFolder)
1067       movieDetails.m_strPath = pItem->GetPath();
1068
1069     CStdString strTitle(movieDetails.m_strTitle);
1070
1071     if (showInfo && content == CONTENT_TVSHOWS)
1072     {
1073       strTitle.Format("%s - %ix%i - %s", showInfo->m_strTitle.c_str(), movieDetails.m_iSeason, movieDetails.m_iEpisode, strTitle.c_str());
1074     }
1075
1076     CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new item to %s:%s", TranslateContent(content).c_str(), pItem->GetPath().c_str());
1077     long lResult = -1;
1078
1079     if (content == CONTENT_MOVIES)
1080     {
1081       // find local trailer first
1082       CStdString strTrailer = pItem->FindTrailer();
1083       if (!strTrailer.IsEmpty())
1084         movieDetails.m_strTrailer = strTrailer;
1085
1086       lResult = m_database.SetDetailsForMovie(pItem->GetPath(), movieDetails, art);
1087       movieDetails.m_iDbId = lResult;
1088       movieDetails.m_type = "movie";
1089
1090       // setup links to shows if the linked shows are in the db
1091       for (unsigned int i=0; i < movieDetails.m_showLink.size(); ++i)
1092       {
1093         CFileItemList items;
1094         m_database.GetTvShowsByName(movieDetails.m_showLink[i], items);
1095         if (items.Size())
1096           m_database.LinkMovieToTvshow(lResult, items[0]->GetVideoInfoTag()->m_iDbId, false);
1097         else
1098           CLog::Log(LOGDEBUG, "VideoInfoScanner: Failed to link movie %s to show %s", movieDetails.m_strTitle.c_str(), movieDetails.m_showLink[i].c_str());
1099       }
1100     }
1101     else if (content == CONTENT_TVSHOWS)
1102     {
1103       if (pItem->m_bIsFolder)
1104       {
1105         map<int, map<string, string> > seasonArt;
1106         if (!libraryImport)
1107         { // get and cache season thumbs
1108           GetSeasonThumbs(movieDetails, seasonArt, CVideoThumbLoader::GetArtTypes("season"), useLocal);
1109           for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
1110             for (map<string, string>::iterator j = i->second.begin(); j != i->second.end(); ++j)
1111               CTextureCache::Get().BackgroundCacheImage(j->second);
1112         }
1113         lResult = m_database.SetDetailsForTvShow(pItem->GetPath(), movieDetails, art, seasonArt);
1114         movieDetails.m_iDbId = lResult;
1115         movieDetails.m_type = "tvshow";
1116       }
1117       else
1118       {
1119         // we add episode then set details, as otherwise set details will delete the
1120         // episode then add, which breaks multi-episode files.
1121         int idShow = showInfo ? showInfo->m_iDbId : -1;
1122         int idEpisode = m_database.AddEpisode(idShow, pItem->GetPath());
1123         lResult = m_database.SetDetailsForEpisode(pItem->GetPath(), movieDetails, art, idShow, idEpisode);
1124         movieDetails.m_iDbId = lResult;
1125         movieDetails.m_type = "episode";
1126         movieDetails.m_strShowTitle = showInfo ? showInfo->m_strTitle : "";
1127         if (movieDetails.m_fEpBookmark > 0)
1128         {
1129           movieDetails.m_strFileNameAndPath = pItem->GetPath();
1130           CBookmark bookmark;
1131           bookmark.timeInSeconds = movieDetails.m_fEpBookmark;
1132           bookmark.seasonNumber = movieDetails.m_iSeason;
1133           bookmark.episodeNumber = movieDetails.m_iEpisode;
1134           m_database.AddBookMarkForEpisode(movieDetails, bookmark);
1135         }
1136       }
1137     }
1138     else if (content == CONTENT_MUSICVIDEOS)
1139     {
1140       lResult = m_database.SetDetailsForMusicVideo(pItem->GetPath(), movieDetails, art);
1141       movieDetails.m_iDbId = lResult;
1142       movieDetails.m_type = "musicvideo";
1143     }
1144
1145     if (g_advancedSettings.m_bVideoLibraryImportWatchedState || libraryImport)
1146       m_database.SetPlayCount(*pItem, movieDetails.m_playCount, movieDetails.m_lastPlayed);
1147
1148     if ((g_advancedSettings.m_bVideoLibraryImportResumePoint || libraryImport) &&
1149         movieDetails.m_resumePoint.IsSet())
1150       m_database.AddBookMarkToFile(pItem->GetPath(), movieDetails.m_resumePoint, CBookmark::RESUME);
1151
1152     m_database.Close();
1153
1154     CFileItemPtr itemCopy = CFileItemPtr(new CFileItem(*pItem));
1155     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", itemCopy);
1156     return lResult;
1157   }
1158
1159   string ContentToMediaType(CONTENT_TYPE content, bool folder)
1160   {
1161     switch (content)
1162     {
1163       case CONTENT_MOVIES:
1164         return "movie";
1165       case CONTENT_MUSICVIDEOS:
1166         return "musicvideo";
1167       case CONTENT_TVSHOWS:
1168         return folder ? "tvshow" : "episode";
1169       default:
1170         return "";
1171     }
1172   }
1173
1174   std::string CVideoInfoScanner::GetArtTypeFromSize(unsigned int width, unsigned int height)
1175   {
1176     std::string type = "thumb";
1177     if (width*5 < height*4)
1178       type = "poster";
1179     else if (width*1 > height*4)
1180       type = "banner";
1181     return type;
1182   }
1183
1184   void CVideoInfoScanner::GetArtwork(CFileItem *pItem, const CONTENT_TYPE &content, bool bApplyToDir, bool useLocal, const std::string &actorArtPath)
1185   {
1186     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1187     movieDetails.m_fanart.Unpack();
1188     movieDetails.m_strPictureURL.Parse();
1189
1190     CGUIListItem::ArtMap art = pItem->GetArt();
1191
1192     // get and cache thumb images
1193     vector<string> artTypes = CVideoThumbLoader::GetArtTypes(ContentToMediaType(content, pItem->m_bIsFolder));
1194     vector<string>::iterator i = find(artTypes.begin(), artTypes.end(), "fanart");
1195     if (i != artTypes.end())
1196       artTypes.erase(i); // fanart is handled below
1197     bool lookForThumb = find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end() &&
1198                         art.find("thumb") == art.end();
1199     // find local art
1200     if (useLocal)
1201     {
1202       for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1203       {
1204         if (art.find(*i) == art.end())
1205         {
1206           std::string image = CVideoThumbLoader::GetLocalArt(*pItem, *i, bApplyToDir);
1207           if (!image.empty())
1208             art.insert(make_pair(*i, image));
1209         }
1210       }
1211       // find and classify the local thumb (backcompat) if available
1212       if (lookForThumb)
1213       {
1214         std::string image = CVideoThumbLoader::GetLocalArt(*pItem, "thumb", bApplyToDir);
1215         if (!image.empty())
1216         { // cache the image and determine sizing
1217           CTextureDetails details;
1218           if (CTextureCache::Get().CacheImage(image, details))
1219           {
1220             std::string type = GetArtTypeFromSize(details.width, details.height);
1221             if (art.find(type) == art.end())
1222               art.insert(make_pair(type, image));
1223           }
1224         }
1225       }
1226     }
1227
1228     // find online art
1229     for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1230     {
1231       if (art.find(*i) == art.end())
1232       {
1233         std::string image = GetImage(pItem, false, bApplyToDir, *i);
1234         if (!image.empty())
1235           art.insert(make_pair(*i, image));
1236       }
1237     }
1238
1239     // use the first piece of online art as the first art type if no thumb type is available yet
1240     if (art.empty() && lookForThumb)
1241     {
1242       std::string image = GetImage(pItem, false, bApplyToDir, "thumb");
1243       if (!image.empty())
1244         art.insert(make_pair(artTypes.front(), image));
1245     }
1246
1247     // get & save fanart image (treated separately due to it being stored in m_fanart)
1248     bool isEpisode = (content == CONTENT_TVSHOWS && !pItem->m_bIsFolder);
1249     if (!isEpisode && art.find("fanart") == art.end())
1250     {
1251       string fanart = GetFanart(pItem, useLocal);
1252       if (!fanart.empty())
1253         art.insert(make_pair("fanart", fanart));
1254     }
1255
1256     for (CGUIListItem::ArtMap::const_iterator i = art.begin(); i != art.end(); ++i)
1257       CTextureCache::Get().BackgroundCacheImage(i->second);
1258
1259     pItem->SetArt(art);
1260
1261     // parent folder to apply the thumb to and to search for local actor thumbs
1262     CStdString parentDir = GetParentDir(*pItem);
1263     if (CSettings::Get().GetBool("videolibrary.actorthumbs"))
1264       FetchActorThumbs(movieDetails.m_cast, actorArtPath.empty() ? parentDir : actorArtPath);
1265     if (bApplyToDir)
1266       ApplyThumbToFolder(parentDir, art["thumb"]);
1267   }
1268
1269   std::string CVideoInfoScanner::GetImage(CFileItem *pItem, bool useLocal, bool bApplyToDir, const std::string &type)
1270   {
1271     std::string thumb;
1272     if (useLocal)
1273       thumb = CVideoThumbLoader::GetLocalArt(*pItem, type, bApplyToDir);
1274
1275     if (thumb.empty())
1276     {
1277       thumb = CScraperUrl::GetThumbURL(pItem->GetVideoInfoTag()->m_strPictureURL.GetFirstThumb(type));
1278       if (!thumb.empty())
1279       {
1280         if (thumb.find("http://") == string::npos &&
1281             thumb.find("/") == string::npos &&
1282             thumb.find("\\") == string::npos)
1283         {
1284           CStdString strPath;
1285           URIUtils::GetDirectory(pItem->GetPath(), strPath);
1286           thumb = URIUtils::AddFileToFolder(strPath, thumb);
1287         }
1288       }
1289     }
1290     return thumb;
1291   }
1292
1293   std::string CVideoInfoScanner::GetFanart(CFileItem *pItem, bool useLocal)
1294   {
1295     std::string fanart = pItem->GetArt("fanart");
1296     if (fanart.empty() && useLocal)
1297       fanart = pItem->FindLocalArt("fanart.jpg", true);
1298     if (fanart.empty())
1299       fanart = pItem->GetVideoInfoTag()->m_fanart.GetImageURL();
1300     return fanart;
1301   }
1302
1303   INFO_RET CVideoInfoScanner::OnProcessSeriesFolder(EPISODELIST& files, const ADDON::ScraperPtr &scraper, bool useLocal, const CVideoInfoTag& showInfo, CGUIDialogProgress* pDlgProgress /* = NULL */)
1304   {
1305     if (pDlgProgress)
1306     {
1307       pDlgProgress->SetLine(1, showInfo.m_strTitle);
1308       pDlgProgress->SetLine(2, 20361);
1309       pDlgProgress->SetPercentage(0);
1310       pDlgProgress->ShowProgressBar(true);
1311       pDlgProgress->Progress();
1312     }
1313
1314     EPISODELIST episodes;
1315     bool hasEpisodeGuide = false;
1316
1317     int iMax = files.size();
1318     int iCurr = 1;
1319     for (EPISODELIST::iterator file = files.begin(); file != files.end(); ++file)
1320     {
1321       m_nfoReader.Close();
1322       if (pDlgProgress)
1323       {
1324         pDlgProgress->SetLine(2, 20361);
1325         pDlgProgress->SetPercentage((int)((float)(iCurr++)/iMax*100));
1326         pDlgProgress->Progress();
1327       }
1328       if (m_handle)
1329         m_handle->SetPercentage(100.f*iCurr++/iMax);
1330
1331       if ((pDlgProgress && pDlgProgress->IsCanceled()) || m_bStop)
1332         return INFO_CANCELLED;
1333
1334       if (m_database.GetEpisodeId(file->strPath, file->iEpisode, file->iSeason) > -1)
1335       {
1336         if (m_handle)
1337           m_handle->SetText(g_localizeStrings.Get(20415));
1338         continue;
1339       }
1340
1341       CFileItem item;
1342       item.SetPath(file->strPath);
1343
1344       // handle .nfo files
1345       CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1346       CScraperUrl scrUrl;
1347       ScraperPtr info(scraper);
1348       item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1349       if (useLocal)
1350         result = CheckForNFOFile(&item, false, info,scrUrl);
1351       if (result == CNfoFile::FULL_NFO)
1352       {
1353         m_nfoReader.GetDetails(*item.GetVideoInfoTag());
1354         // override with episode and season number from file if available
1355         if (file->iEpisode > -1)
1356         {
1357           item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1358           item.GetVideoInfoTag()->m_iSeason = file->iSeason;
1359         }
1360         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, true, &showInfo) < 0)
1361           return INFO_ERROR;
1362         continue;
1363       }
1364
1365       if (!hasEpisodeGuide)
1366       {
1367         // fetch episode guide
1368         if (!showInfo.m_strEpisodeGuide.IsEmpty())
1369         {
1370           CScraperUrl url;
1371           url.ParseEpisodeGuide(showInfo.m_strEpisodeGuide);
1372
1373           if (pDlgProgress)
1374           {
1375             pDlgProgress->SetLine(2, 20354);
1376             pDlgProgress->Progress();
1377           }
1378
1379           CVideoInfoDownloader imdb(scraper);
1380           if (!imdb.GetEpisodeList(url, episodes))
1381             return INFO_NOT_FOUND;
1382
1383           hasEpisodeGuide = true;
1384         }
1385       }
1386
1387       if (episodes.empty())
1388       {
1389         CLog::Log(LOGERROR, "VideoInfoScanner: Asked to lookup episode %s"
1390                             " online, but we have no episode guide. Check your tvshow.nfo and make"
1391                             " sure the <episodeguide> tag is in place.", file->strPath.c_str());
1392         continue;
1393       }
1394
1395       EPISODE key(file->iSeason, file->iEpisode, file->iSubepisode);
1396       bool bFound = false;
1397       EPISODELIST::iterator guide = episodes.begin();;
1398       EPISODELIST matches;
1399
1400       for (; guide != episodes.end(); ++guide )
1401       {
1402         if ((file->iEpisode!=-1) && (file->iSeason!=-1) && (key==*guide))
1403         {
1404           bFound = true;
1405           break;
1406         }
1407         if (file->cDate.IsValid() && guide->cDate.IsValid() && file->cDate==guide->cDate)
1408         {
1409           matches.push_back(*guide);
1410           continue;
1411         }
1412         if (!guide->cScraperUrl.strTitle.IsEmpty() && guide->cScraperUrl.strTitle.CompareNoCase(file->strTitle.c_str()) == 0)
1413         {
1414           bFound = true;
1415           break;
1416         }
1417       }
1418
1419       if (!bFound)
1420       {
1421         /*
1422          * If there is only one match or there are matches but no title to compare with to help
1423          * identify the best match, then pick the first match as the best possible candidate.
1424          *
1425          * Otherwise, use the title to further refine the best match.
1426          */
1427         if (matches.size() == 1 || (file->strTitle.empty() && matches.size() > 1))
1428         {
1429           guide = matches.begin();
1430           bFound = true;
1431         }
1432         else if (!file->strTitle.empty())
1433         {
1434           double minscore = 0; // Default minimum score is 0 to find whatever is the best match.
1435
1436           EPISODELIST *candidates;
1437           if (matches.empty()) // No matches found using earlier criteria. Use fuzzy match on titles across all episodes.
1438           {
1439             minscore = 0.8; // 80% should ensure a good match.
1440             candidates = &episodes;
1441           }
1442           else // Multiple matches found. Use fuzzy match on the title with already matched episodes to pick the best.
1443             candidates = &matches;
1444
1445           CStdStringArray titles;
1446           for (guide = candidates->begin(); guide != candidates->end(); ++guide)
1447             titles.push_back(guide->cScraperUrl.strTitle.ToLower());
1448
1449           double matchscore;
1450           std::string loweredTitle(file->strTitle);
1451           StringUtils::ToLower(loweredTitle);
1452           int index = StringUtils::FindBestMatch(loweredTitle, titles, matchscore);
1453           if (matchscore >= minscore)
1454           {
1455             guide = candidates->begin() + index;
1456             bFound = true;
1457             CLog::Log(LOGDEBUG,"%s fuzzy title match for show: '%s', title: '%s', match: '%s', score: %f >= %f",
1458                       __FUNCTION__, showInfo.m_strTitle.c_str(), file->strTitle.c_str(), titles[index].c_str(), matchscore, minscore);
1459           }
1460         }
1461       }
1462
1463       if (bFound)
1464       {
1465         CVideoInfoDownloader imdb(scraper);
1466         CFileItem item;
1467         item.SetPath(file->strPath);
1468         if (!imdb.GetEpisodeDetails(guide->cScraperUrl, *item.GetVideoInfoTag(), pDlgProgress))
1469           return INFO_NOT_FOUND; // TODO: should we just skip to the next episode?
1470           
1471         // Only set season/epnum from filename when it is not already set by a scraper
1472         if (item.GetVideoInfoTag()->m_iSeason == -1)
1473           item.GetVideoInfoTag()->m_iSeason = guide->iSeason;
1474         if (item.GetVideoInfoTag()->m_iEpisode == -1)
1475           item.GetVideoInfoTag()->m_iEpisode = guide->iEpisode;
1476           
1477         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, useLocal, &showInfo) < 0)
1478           return INFO_ERROR;
1479       }
1480       else
1481       {
1482         CLog::Log(LOGDEBUG,"%s - no match for show: '%s', season: %d, episode: %d.%d, airdate: '%s', title: '%s'",
1483                   __FUNCTION__, showInfo.m_strTitle.c_str(), file->iSeason, file->iEpisode, file->iSubepisode,
1484                   file->cDate.GetAsLocalizedDate().c_str(), file->strTitle.c_str());
1485       }
1486     }
1487     return INFO_ADDED;
1488   }
1489
1490   CStdString CVideoInfoScanner::GetnfoFile(CFileItem *item, bool bGrabAny) const
1491   {
1492     CStdString nfoFile;
1493     // Find a matching .nfo file
1494     if (!item->m_bIsFolder)
1495     {
1496       if (URIUtils::IsInRAR(item->GetPath())) // we have a rarred item - we want to check outside the rars
1497       {
1498         CFileItem item2(*item);
1499         CURL url(item->GetPath());
1500         CStdString strPath;
1501         URIUtils::GetDirectory(url.GetHostName(), strPath);
1502         item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1503         return GetnfoFile(&item2, bGrabAny);
1504       }
1505
1506       // grab the folder path
1507       CStdString strPath;
1508       URIUtils::GetDirectory(item->GetPath(), strPath);
1509
1510       if (bGrabAny && !item->IsStack())
1511       { // looking up by folder name - movie.nfo takes priority - but not for stacked items (handled below)
1512         nfoFile = URIUtils::AddFileToFolder(strPath, "movie.nfo");
1513         if (CFile::Exists(nfoFile))
1514           return nfoFile;
1515       }
1516
1517       // try looking for .nfo file for a stacked item
1518       if (item->IsStack())
1519       {
1520         // first try .nfo file matching first file in stack
1521         CStackDirectory dir;
1522         CStdString firstFile = dir.GetFirstStackedFile(item->GetPath());
1523         CFileItem item2;
1524         item2.SetPath(firstFile);
1525         nfoFile = GetnfoFile(&item2, bGrabAny);
1526         // else try .nfo file matching stacked title
1527         if (nfoFile.IsEmpty())
1528         {
1529           CStdString stackedTitlePath = dir.GetStackedTitlePath(item->GetPath());
1530           item2.SetPath(stackedTitlePath);
1531           nfoFile = GetnfoFile(&item2, bGrabAny);
1532         }
1533       }
1534       else
1535       {
1536         // already an .nfo file?
1537         if (URIUtils::HasExtension(item->GetPath(), ".nfo"))
1538           nfoFile = item->GetPath();
1539         // no, create .nfo file
1540         else
1541           nfoFile = URIUtils::ReplaceExtension(item->GetPath(), ".nfo");
1542       }
1543
1544       // test file existence
1545       if (!nfoFile.IsEmpty() && !CFile::Exists(nfoFile))
1546         nfoFile.Empty();
1547
1548       if (nfoFile.IsEmpty()) // final attempt - strip off any cd1 folders
1549       {
1550         URIUtils::RemoveSlashAtEnd(strPath); // need no slash for the check that follows
1551         CFileItem item2;
1552         if (strPath.Mid(strPath.size()-3).Equals("cd1"))
1553         {
1554           strPath = strPath.Mid(0,strPath.size()-3);
1555           item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1556           return GetnfoFile(&item2, bGrabAny);
1557         }
1558       }
1559
1560       if (nfoFile.IsEmpty() && item->IsOpticalMediaFile())
1561       {
1562         CFileItem parentDirectory(item->GetLocalMetadataPath(), true);
1563         nfoFile = GetnfoFile(&parentDirectory, true);
1564       }
1565     }
1566     // folders (or stacked dvds) can take any nfo file if there's a unique one
1567     if (item->m_bIsFolder || item->IsOpticalMediaFile() || (bGrabAny && nfoFile.IsEmpty()))
1568     {
1569       // see if there is a unique nfo file in this folder, and if so, use that
1570       CFileItemList items;
1571       CDirectory dir;
1572       CStdString strPath = item->GetPath();
1573       if (!item->m_bIsFolder)
1574         URIUtils::GetDirectory(item->GetPath(), strPath);
1575       if (dir.GetDirectory(strPath, items, ".nfo") && items.Size())
1576       {
1577         int numNFO = -1;
1578         for (int i = 0; i < items.Size(); i++)
1579         {
1580           if (items[i]->IsNFO())
1581           {
1582             if (numNFO == -1)
1583               numNFO = i;
1584             else
1585             {
1586               numNFO = -1;
1587               break;
1588             }
1589           }
1590         }
1591         if (numNFO > -1)
1592           return items[numNFO]->GetPath();
1593       }
1594     }
1595
1596     return nfoFile;
1597   }
1598
1599   bool CVideoInfoScanner::GetDetails(CFileItem *pItem, CScraperUrl &url, const ScraperPtr& scraper, CNfoFile *nfoFile, CGUIDialogProgress* pDialog /* = NULL */)
1600   {
1601     CVideoInfoTag movieDetails;
1602
1603     if (m_handle && !url.strTitle.IsEmpty())
1604       m_handle->SetText(url.strTitle);
1605
1606     CVideoInfoDownloader imdb(scraper);
1607     bool ret = imdb.GetDetails(url, movieDetails, pDialog);
1608
1609     if (ret)
1610     {
1611       if (nfoFile)
1612         nfoFile->GetDetails(movieDetails,NULL,true);
1613
1614       if (m_handle && url.strTitle.IsEmpty())
1615         m_handle->SetText(movieDetails.m_strTitle);
1616
1617       if (pDialog)
1618       {
1619         pDialog->SetLine(1, movieDetails.m_strTitle);
1620         pDialog->Progress();
1621       }
1622
1623       *pItem->GetVideoInfoTag() = movieDetails;
1624       return true;
1625     }
1626     return false; // no info found, or cancelled
1627   }
1628
1629   void CVideoInfoScanner::ApplyThumbToFolder(const CStdString &folder, const CStdString &imdbThumb)
1630   {
1631     // copy icon to folder also;
1632     if (!imdbThumb.IsEmpty())
1633     {
1634       CFileItem folderItem(folder, true);
1635       CThumbLoader loader;
1636       loader.SetCachedImage(folderItem, "thumb", imdbThumb);
1637     }
1638   }
1639
1640   int CVideoInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
1641   {
1642     // Create a hash based on the filenames, filesize and filedate.  Also count the number of files
1643     if (0 == items.Size()) return 0;
1644     XBMC::XBMC_MD5 md5state;
1645     int count = 0;
1646     for (int i = 0; i < items.Size(); ++i)
1647     {
1648       const CFileItemPtr pItem = items[i];
1649       md5state.append(pItem->GetPath());
1650       md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1651       FILETIME time = pItem->m_dateTime;
1652       md5state.append((unsigned char *)&time, sizeof(FILETIME));
1653       if (pItem->IsVideo() && !pItem->IsPlayList() && !pItem->IsNFO())
1654         count++;
1655     }
1656     md5state.getDigest(hash);
1657     return count;
1658   }
1659
1660   bool CVideoInfoScanner::CanFastHash(const CFileItemList &items) const
1661   {
1662     // TODO: Probably should account for excluded folders here (eg samples), though that then
1663     //       introduces possible problems if the user then changes the exclude regexps and
1664     //       expects excluded folders that are inside a fast-hashed folder to then be picked
1665     //       up. The chances that the user has a folder which contains only excluded folders
1666     //       where some of those folders should be scanned recursively is pretty small.
1667     return items.GetFolderCount() == 0;
1668   }
1669
1670   CStdString CVideoInfoScanner::GetFastHash(const CStdString &directory) const
1671   {
1672     struct __stat64 buffer;
1673     if (XFILE::CFile::Stat(directory, &buffer) == 0)
1674     {
1675       int64_t time = buffer.st_mtime;
1676       if (!time)
1677         time = buffer.st_ctime;
1678       if (time)
1679       {
1680         CStdString hash;
1681         hash.Format("fast%"PRId64, time);
1682         return hash;
1683       }
1684     }
1685     return "";
1686   }
1687
1688   void CVideoInfoScanner::GetSeasonThumbs(const CVideoInfoTag &show, map<int, map<string, string> > &seasonArt, const vector<string> &artTypes, bool useLocal)
1689   {
1690     bool lookForThumb = find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end();
1691
1692     // find the maximum number of seasons we have thumbs for (local + remote)
1693     int maxSeasons = show.m_strPictureURL.GetMaxSeasonThumb();
1694
1695     CFileItemList items;
1696     CDirectory::GetDirectory(show.m_strPath, items, ".png|.jpg|.tbn", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
1697     CRegExp reg;
1698     if (items.Size() && reg.RegComp("season([0-9]+)(-[a-z]+)?\\.(tbn|jpg|png)"))
1699     {
1700       for (int i = 0; i < items.Size(); i++)
1701       {
1702         CStdString name = URIUtils::GetFileName(items[i]->GetPath());
1703         if (reg.RegFind(name) > -1)
1704         {
1705           int season = atoi(reg.GetReplaceString("\\1").c_str());
1706           if (season > maxSeasons)
1707             maxSeasons = season;
1708         }
1709       }
1710     }
1711     for (int season = -1; season <= maxSeasons; season++)
1712     {
1713       map<string, string> art;
1714       if (useLocal)
1715       {
1716         string basePath;
1717         if (season == -1)
1718           basePath = "season-all";
1719         else if (season == 0)
1720           basePath = "season-specials";
1721         else
1722           basePath = StringUtils::Format("season%02i", season);
1723         CFileItem artItem(URIUtils::AddFileToFolder(show.m_strPath, basePath), false);
1724
1725         for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1726         {
1727           std::string image = CVideoThumbLoader::GetLocalArt(artItem, *i, false);
1728           if (!image.empty())
1729             art.insert(make_pair(*i, image));
1730         }
1731         // find and classify the local thumb (backcompat) if available
1732         if (lookForThumb)
1733         {
1734           std::string image = CVideoThumbLoader::GetLocalArt(artItem, "thumb", false);
1735           if (!image.empty())
1736           { // cache the image and determine sizing
1737             CTextureDetails details;
1738             if (CTextureCache::Get().CacheImage(image, details))
1739             {
1740               std::string type = GetArtTypeFromSize(details.width, details.height);
1741               if (art.find(type) == art.end())
1742                 art.insert(make_pair(type, image));
1743             }
1744           }
1745         }
1746       }
1747
1748       // find online art
1749       for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1750       {
1751         if (art.find(*i) == art.end())
1752         {
1753           string image = CScraperUrl::GetThumbURL(show.m_strPictureURL.GetSeasonThumb(season, *i));
1754           if (!image.empty())
1755             art.insert(make_pair(*i, image));
1756         }
1757       }
1758       // use the first piece of online art as the first art type if no thumb type is available yet
1759       if (art.empty() && lookForThumb)
1760       {
1761         string image = CScraperUrl::GetThumbURL(show.m_strPictureURL.GetSeasonThumb(season, "thumb"));
1762         if (!image.empty())
1763           art.insert(make_pair(artTypes.front(), image));
1764       }
1765
1766       seasonArt.insert(make_pair(season, art));
1767     }
1768   }
1769
1770   void CVideoInfoScanner::FetchActorThumbs(vector<SActorInfo>& actors, const CStdString& strPath)
1771   {
1772     CFileItemList items;
1773     CStdString actorsDir = URIUtils::AddFileToFolder(strPath, ".actors");
1774     if (CDirectory::Exists(actorsDir))
1775       CDirectory::GetDirectory(actorsDir, items, ".png|.jpg|.tbn", DIR_FLAG_NO_FILE_DIRS |
1776                                DIR_FLAG_NO_FILE_INFO);
1777     for (vector<SActorInfo>::iterator i = actors.begin(); i != actors.end(); ++i)
1778     {
1779       if (i->thumb.IsEmpty())
1780       {
1781         CStdString thumbFile = i->strName;
1782         thumbFile.Replace(" ","_");
1783         for (int j = 0; j < items.Size(); j++)
1784         {
1785           CStdString compare = URIUtils::GetFileName(items[j]->GetPath());
1786           URIUtils::RemoveExtension(compare);
1787           if (!items[j]->m_bIsFolder && compare == thumbFile)
1788           {
1789             i->thumb = items[j]->GetPath();
1790             break;
1791           }
1792         }
1793         if (i->thumb.IsEmpty() && !i->thumbUrl.GetFirstThumb().m_url.IsEmpty())
1794           i->thumb = CScraperUrl::GetThumbURL(i->thumbUrl.GetFirstThumb());
1795         if (!i->thumb.IsEmpty())
1796           CTextureCache::Get().BackgroundCacheImage(i->thumb);
1797       }
1798     }
1799   }
1800
1801   CNfoFile::NFOResult CVideoInfoScanner::CheckForNFOFile(CFileItem* pItem, bool bGrabAny, ScraperPtr& info, CScraperUrl& scrUrl)
1802   {
1803     CStdString strNfoFile;
1804     if (info->Content() == CONTENT_MOVIES || info->Content() == CONTENT_MUSICVIDEOS
1805         || (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder))
1806       strNfoFile = GetnfoFile(pItem, bGrabAny);
1807     if (info->Content() == CONTENT_TVSHOWS && pItem->m_bIsFolder)
1808       strNfoFile = URIUtils::AddFileToFolder(pItem->GetPath(), "tvshow.nfo");
1809
1810     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1811     if (!strNfoFile.IsEmpty() && CFile::Exists(strNfoFile))
1812     {
1813       if (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder)
1814         result = m_nfoReader.Create(strNfoFile,info,pItem->GetVideoInfoTag()->m_iEpisode);
1815       else
1816         result = m_nfoReader.Create(strNfoFile,info);
1817
1818       CStdString type;
1819       switch(result)
1820       {
1821         case CNfoFile::COMBINED_NFO:
1822           type = "Mixed";
1823           break;
1824         case CNfoFile::FULL_NFO:
1825           type = "Full";
1826           break;
1827         case CNfoFile::URL_NFO:
1828           type = "URL";
1829           break;
1830         case CNfoFile::NO_NFO:
1831           type = "";
1832           break;
1833         default:
1834           type = "malformed";
1835       }
1836       if (result != CNfoFile::NO_NFO)
1837         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found matching %s NFO file: %s", type.c_str(), strNfoFile.c_str());
1838       if (result == CNfoFile::FULL_NFO)
1839       {
1840         if (info->Content() == CONTENT_TVSHOWS)
1841           info = m_nfoReader.GetScraperInfo();
1842       }
1843       else if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
1844       {
1845         scrUrl = m_nfoReader.ScraperUrl();
1846         info = m_nfoReader.GetScraperInfo();
1847
1848         CLog::Log(LOGDEBUG, "VideoInfoScanner: Fetching url '%s' using %s scraper (content: '%s')",
1849           scrUrl.m_url[0].m_url.c_str(), info->Name().c_str(), TranslateContent(info->Content()).c_str());
1850
1851         if (result == CNfoFile::COMBINED_NFO)
1852           m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
1853       }
1854     }
1855     else
1856       CLog::Log(LOGDEBUG, "VideoInfoScanner: No NFO file found. Using title search for '%s'", pItem->GetPath().c_str());
1857
1858     return result;
1859   }
1860
1861   bool CVideoInfoScanner::DownloadFailed(CGUIDialogProgress* pDialog)
1862   {
1863     if (g_advancedSettings.m_bVideoScannerIgnoreErrors)
1864       return true;
1865
1866     if (pDialog)
1867     {
1868       CGUIDialogOK::ShowAndGetInput(20448,20449,20022,20022);
1869       return false;
1870     }
1871     return CGUIDialogYesNo::ShowAndGetInput(20448,20449,20450,20022);
1872   }
1873
1874   bool CVideoInfoScanner::ProgressCancelled(CGUIDialogProgress* progress, int heading, const CStdString &line1)
1875   {
1876     if (progress)
1877     {
1878       progress->SetHeading(heading);
1879       progress->SetLine(0, line1);
1880       progress->SetLine(2, "");
1881       progress->Progress();
1882       return progress->IsCanceled();
1883     }
1884     return m_bStop;
1885   }
1886
1887   int CVideoInfoScanner::FindVideo(const CStdString &videoName, const ScraperPtr &scraper, CScraperUrl &url, CGUIDialogProgress *progress)
1888   {
1889     MOVIELIST movielist;
1890     CVideoInfoDownloader imdb(scraper);
1891     int returncode = imdb.FindMovie(videoName, movielist, progress);
1892     if (returncode < 0 || (returncode == 0 && (m_bStop || !DownloadFailed(progress))))
1893     { // scraper reported an error, or we had an error and user wants to cancel the scan
1894       m_bStop = true;
1895       return -1; // cancelled
1896     }
1897     if (returncode > 0 && movielist.size())
1898     {
1899       url = movielist[0];
1900       return 1;  // found a movie
1901     }
1902     return 0;    // didn't find anything
1903   }
1904
1905   CStdString CVideoInfoScanner::GetParentDir(const CFileItem &item) const
1906   {
1907     CStdString strCheck = item.GetPath();
1908     if (item.IsStack())
1909       strCheck = CStackDirectory::GetFirstStackedFile(item.GetPath());
1910
1911     CStdString strDirectory;
1912     URIUtils::GetDirectory(strCheck, strDirectory);
1913     if (URIUtils::IsInRAR(strCheck))
1914     {
1915       CStdString strPath=strDirectory;
1916       URIUtils::GetParentPath(strPath, strDirectory);
1917     }
1918     if (item.IsStack())
1919     {
1920       strCheck = strDirectory;
1921       URIUtils::RemoveSlashAtEnd(strCheck);
1922       if (URIUtils::GetFileName(strCheck).size() == 3 && URIUtils::GetFileName(strCheck).Left(2).Equals("cd"))
1923         URIUtils::GetDirectory(strCheck, strDirectory);
1924     }
1925     return strDirectory;
1926   }
1927
1928 }