Merge pull request #2948 from ace20022/blu_lang_fix
[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       EPISODE backupkey(file->iSeason, file->iEpisode, 0);
1397       bool bFound = false;
1398       EPISODELIST::iterator guide = episodes.begin();;
1399       EPISODELIST matches;
1400
1401       for (; guide != episodes.end(); ++guide )
1402       {
1403         if ((file->iEpisode!=-1) && (file->iSeason!=-1))
1404         {
1405           if (key==*guide)
1406           {
1407             bFound = true;
1408             break;
1409           }
1410           else if ((file->iSubepisode!=0) && (backupkey==*guide))
1411           {
1412             matches.push_back(*guide);
1413             continue;
1414           }
1415         }
1416         if (file->cDate.IsValid() && guide->cDate.IsValid() && file->cDate==guide->cDate)
1417         {
1418           matches.push_back(*guide);
1419           continue;
1420         }
1421         if (!guide->cScraperUrl.strTitle.IsEmpty() && guide->cScraperUrl.strTitle.CompareNoCase(file->strTitle.c_str()) == 0)
1422         {
1423           bFound = true;
1424           break;
1425         }
1426       }
1427
1428       if (!bFound)
1429       {
1430         /*
1431          * If there is only one match or there are matches but no title to compare with to help
1432          * identify the best match, then pick the first match as the best possible candidate.
1433          *
1434          * Otherwise, use the title to further refine the best match.
1435          */
1436         if (matches.size() == 1 || (file->strTitle.empty() && matches.size() > 1))
1437         {
1438           guide = matches.begin();
1439           bFound = true;
1440         }
1441         else if (!file->strTitle.empty())
1442         {
1443           double minscore = 0; // Default minimum score is 0 to find whatever is the best match.
1444
1445           EPISODELIST *candidates;
1446           if (matches.empty()) // No matches found using earlier criteria. Use fuzzy match on titles across all episodes.
1447           {
1448             minscore = 0.8; // 80% should ensure a good match.
1449             candidates = &episodes;
1450           }
1451           else // Multiple matches found. Use fuzzy match on the title with already matched episodes to pick the best.
1452             candidates = &matches;
1453
1454           CStdStringArray titles;
1455           for (guide = candidates->begin(); guide != candidates->end(); ++guide)
1456             titles.push_back(guide->cScraperUrl.strTitle.ToLower());
1457
1458           double matchscore;
1459           std::string loweredTitle(file->strTitle);
1460           StringUtils::ToLower(loweredTitle);
1461           int index = StringUtils::FindBestMatch(loweredTitle, titles, matchscore);
1462           if (matchscore >= minscore)
1463           {
1464             guide = candidates->begin() + index;
1465             bFound = true;
1466             CLog::Log(LOGDEBUG,"%s fuzzy title match for show: '%s', title: '%s', match: '%s', score: %f >= %f",
1467                       __FUNCTION__, showInfo.m_strTitle.c_str(), file->strTitle.c_str(), titles[index].c_str(), matchscore, minscore);
1468           }
1469         }
1470       }
1471
1472       if (bFound)
1473       {
1474         CVideoInfoDownloader imdb(scraper);
1475         CFileItem item;
1476         item.SetPath(file->strPath);
1477         if (!imdb.GetEpisodeDetails(guide->cScraperUrl, *item.GetVideoInfoTag(), pDlgProgress))
1478           return INFO_NOT_FOUND; // TODO: should we just skip to the next episode?
1479           
1480         // Only set season/epnum from filename when it is not already set by a scraper
1481         if (item.GetVideoInfoTag()->m_iSeason == -1)
1482           item.GetVideoInfoTag()->m_iSeason = guide->iSeason;
1483         if (item.GetVideoInfoTag()->m_iEpisode == -1)
1484           item.GetVideoInfoTag()->m_iEpisode = guide->iEpisode;
1485           
1486         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, useLocal, &showInfo) < 0)
1487           return INFO_ERROR;
1488       }
1489       else
1490       {
1491         CLog::Log(LOGDEBUG,"%s - no match for show: '%s', season: %d, episode: %d.%d, airdate: '%s', title: '%s'",
1492                   __FUNCTION__, showInfo.m_strTitle.c_str(), file->iSeason, file->iEpisode, file->iSubepisode,
1493                   file->cDate.GetAsLocalizedDate().c_str(), file->strTitle.c_str());
1494       }
1495     }
1496     return INFO_ADDED;
1497   }
1498
1499   CStdString CVideoInfoScanner::GetnfoFile(CFileItem *item, bool bGrabAny) const
1500   {
1501     CStdString nfoFile;
1502     // Find a matching .nfo file
1503     if (!item->m_bIsFolder)
1504     {
1505       if (URIUtils::IsInRAR(item->GetPath())) // we have a rarred item - we want to check outside the rars
1506       {
1507         CFileItem item2(*item);
1508         CURL url(item->GetPath());
1509         CStdString strPath;
1510         URIUtils::GetDirectory(url.GetHostName(), strPath);
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;
1517       URIUtils::GetDirectory(item->GetPath(), strPath);
1518
1519       if (bGrabAny && !item->IsStack())
1520       { // looking up by folder name - movie.nfo takes priority - but not for stacked items (handled below)
1521         nfoFile = URIUtils::AddFileToFolder(strPath, "movie.nfo");
1522         if (CFile::Exists(nfoFile))
1523           return nfoFile;
1524       }
1525
1526       // try looking for .nfo file for a stacked item
1527       if (item->IsStack())
1528       {
1529         // first try .nfo file matching first file in stack
1530         CStackDirectory dir;
1531         CStdString firstFile = dir.GetFirstStackedFile(item->GetPath());
1532         CFileItem item2;
1533         item2.SetPath(firstFile);
1534         nfoFile = GetnfoFile(&item2, bGrabAny);
1535         // else try .nfo file matching stacked title
1536         if (nfoFile.IsEmpty())
1537         {
1538           CStdString stackedTitlePath = dir.GetStackedTitlePath(item->GetPath());
1539           item2.SetPath(stackedTitlePath);
1540           nfoFile = GetnfoFile(&item2, bGrabAny);
1541         }
1542       }
1543       else
1544       {
1545         // already an .nfo file?
1546         if (URIUtils::HasExtension(item->GetPath(), ".nfo"))
1547           nfoFile = item->GetPath();
1548         // no, create .nfo file
1549         else
1550           nfoFile = URIUtils::ReplaceExtension(item->GetPath(), ".nfo");
1551       }
1552
1553       // test file existence
1554       if (!nfoFile.IsEmpty() && !CFile::Exists(nfoFile))
1555         nfoFile.Empty();
1556
1557       if (nfoFile.IsEmpty()) // final attempt - strip off any cd1 folders
1558       {
1559         URIUtils::RemoveSlashAtEnd(strPath); // need no slash for the check that follows
1560         CFileItem item2;
1561         if (strPath.Mid(strPath.size()-3).Equals("cd1"))
1562         {
1563           strPath = strPath.Mid(0,strPath.size()-3);
1564           item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1565           return GetnfoFile(&item2, bGrabAny);
1566         }
1567       }
1568
1569       if (nfoFile.IsEmpty() && item->IsOpticalMediaFile())
1570       {
1571         CFileItem parentDirectory(item->GetLocalMetadataPath(), true);
1572         nfoFile = GetnfoFile(&parentDirectory, true);
1573       }
1574     }
1575     // folders (or stacked dvds) can take any nfo file if there's a unique one
1576     if (item->m_bIsFolder || item->IsOpticalMediaFile() || (bGrabAny && nfoFile.IsEmpty()))
1577     {
1578       // see if there is a unique nfo file in this folder, and if so, use that
1579       CFileItemList items;
1580       CDirectory dir;
1581       CStdString strPath = item->GetPath();
1582       if (!item->m_bIsFolder)
1583         URIUtils::GetDirectory(item->GetPath(), strPath);
1584       if (dir.GetDirectory(strPath, items, ".nfo") && items.Size())
1585       {
1586         int numNFO = -1;
1587         for (int i = 0; i < items.Size(); i++)
1588         {
1589           if (items[i]->IsNFO())
1590           {
1591             if (numNFO == -1)
1592               numNFO = i;
1593             else
1594             {
1595               numNFO = -1;
1596               break;
1597             }
1598           }
1599         }
1600         if (numNFO > -1)
1601           return items[numNFO]->GetPath();
1602       }
1603     }
1604
1605     return nfoFile;
1606   }
1607
1608   bool CVideoInfoScanner::GetDetails(CFileItem *pItem, CScraperUrl &url, const ScraperPtr& scraper, CNfoFile *nfoFile, CGUIDialogProgress* pDialog /* = NULL */)
1609   {
1610     CVideoInfoTag movieDetails;
1611
1612     if (m_handle && !url.strTitle.IsEmpty())
1613       m_handle->SetText(url.strTitle);
1614
1615     CVideoInfoDownloader imdb(scraper);
1616     bool ret = imdb.GetDetails(url, movieDetails, pDialog);
1617
1618     if (ret)
1619     {
1620       if (nfoFile)
1621         nfoFile->GetDetails(movieDetails,NULL,true);
1622
1623       if (m_handle && url.strTitle.IsEmpty())
1624         m_handle->SetText(movieDetails.m_strTitle);
1625
1626       if (pDialog)
1627       {
1628         pDialog->SetLine(1, movieDetails.m_strTitle);
1629         pDialog->Progress();
1630       }
1631
1632       *pItem->GetVideoInfoTag() = movieDetails;
1633       return true;
1634     }
1635     return false; // no info found, or cancelled
1636   }
1637
1638   void CVideoInfoScanner::ApplyThumbToFolder(const CStdString &folder, const CStdString &imdbThumb)
1639   {
1640     // copy icon to folder also;
1641     if (!imdbThumb.IsEmpty())
1642     {
1643       CFileItem folderItem(folder, true);
1644       CThumbLoader loader;
1645       loader.SetCachedImage(folderItem, "thumb", imdbThumb);
1646     }
1647   }
1648
1649   int CVideoInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
1650   {
1651     // Create a hash based on the filenames, filesize and filedate.  Also count the number of files
1652     if (0 == items.Size()) return 0;
1653     XBMC::XBMC_MD5 md5state;
1654     int count = 0;
1655     for (int i = 0; i < items.Size(); ++i)
1656     {
1657       const CFileItemPtr pItem = items[i];
1658       md5state.append(pItem->GetPath());
1659       md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1660       FILETIME time = pItem->m_dateTime;
1661       md5state.append((unsigned char *)&time, sizeof(FILETIME));
1662       if (pItem->IsVideo() && !pItem->IsPlayList() && !pItem->IsNFO())
1663         count++;
1664     }
1665     md5state.getDigest(hash);
1666     return count;
1667   }
1668
1669   bool CVideoInfoScanner::CanFastHash(const CFileItemList &items) const
1670   {
1671     // TODO: Probably should account for excluded folders here (eg samples), though that then
1672     //       introduces possible problems if the user then changes the exclude regexps and
1673     //       expects excluded folders that are inside a fast-hashed folder to then be picked
1674     //       up. The chances that the user has a folder which contains only excluded folders
1675     //       where some of those folders should be scanned recursively is pretty small.
1676     return items.GetFolderCount() == 0;
1677   }
1678
1679   CStdString CVideoInfoScanner::GetFastHash(const CStdString &directory) const
1680   {
1681     struct __stat64 buffer;
1682     if (XFILE::CFile::Stat(directory, &buffer) == 0)
1683     {
1684       int64_t time = buffer.st_mtime;
1685       if (!time)
1686         time = buffer.st_ctime;
1687       if (time)
1688       {
1689         CStdString hash;
1690         hash.Format("fast%"PRId64, time);
1691         return hash;
1692       }
1693     }
1694     return "";
1695   }
1696
1697   void CVideoInfoScanner::GetSeasonThumbs(const CVideoInfoTag &show, map<int, map<string, string> > &seasonArt, const vector<string> &artTypes, bool useLocal)
1698   {
1699     bool lookForThumb = find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end();
1700
1701     // find the maximum number of seasons we have thumbs for (local + remote)
1702     int maxSeasons = show.m_strPictureURL.GetMaxSeasonThumb();
1703
1704     CFileItemList items;
1705     CDirectory::GetDirectory(show.m_strPath, items, ".png|.jpg|.tbn", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
1706     CRegExp reg;
1707     if (items.Size() && reg.RegComp("season([0-9]+)(-[a-z]+)?\\.(tbn|jpg|png)"))
1708     {
1709       for (int i = 0; i < items.Size(); i++)
1710       {
1711         CStdString name = URIUtils::GetFileName(items[i]->GetPath());
1712         if (reg.RegFind(name) > -1)
1713         {
1714           int season = atoi(reg.GetReplaceString("\\1").c_str());
1715           if (season > maxSeasons)
1716             maxSeasons = season;
1717         }
1718       }
1719     }
1720     for (int season = -1; season <= maxSeasons; season++)
1721     {
1722       map<string, string> art;
1723       if (useLocal)
1724       {
1725         string basePath;
1726         if (season == -1)
1727           basePath = "season-all";
1728         else if (season == 0)
1729           basePath = "season-specials";
1730         else
1731           basePath = StringUtils::Format("season%02i", season);
1732         CFileItem artItem(URIUtils::AddFileToFolder(show.m_strPath, basePath), false);
1733
1734         for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1735         {
1736           std::string image = CVideoThumbLoader::GetLocalArt(artItem, *i, false);
1737           if (!image.empty())
1738             art.insert(make_pair(*i, image));
1739         }
1740         // find and classify the local thumb (backcompat) if available
1741         if (lookForThumb)
1742         {
1743           std::string image = CVideoThumbLoader::GetLocalArt(artItem, "thumb", false);
1744           if (!image.empty())
1745           { // cache the image and determine sizing
1746             CTextureDetails details;
1747             if (CTextureCache::Get().CacheImage(image, details))
1748             {
1749               std::string type = GetArtTypeFromSize(details.width, details.height);
1750               if (art.find(type) == art.end())
1751                 art.insert(make_pair(type, image));
1752             }
1753           }
1754         }
1755       }
1756
1757       // find online art
1758       for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1759       {
1760         if (art.find(*i) == art.end())
1761         {
1762           string image = CScraperUrl::GetThumbURL(show.m_strPictureURL.GetSeasonThumb(season, *i));
1763           if (!image.empty())
1764             art.insert(make_pair(*i, image));
1765         }
1766       }
1767       // use the first piece of online art as the first art type if no thumb type is available yet
1768       if (art.empty() && lookForThumb)
1769       {
1770         string image = CScraperUrl::GetThumbURL(show.m_strPictureURL.GetSeasonThumb(season, "thumb"));
1771         if (!image.empty())
1772           art.insert(make_pair(artTypes.front(), image));
1773       }
1774
1775       seasonArt.insert(make_pair(season, art));
1776     }
1777   }
1778
1779   void CVideoInfoScanner::FetchActorThumbs(vector<SActorInfo>& actors, const CStdString& strPath)
1780   {
1781     CFileItemList items;
1782     CStdString actorsDir = URIUtils::AddFileToFolder(strPath, ".actors");
1783     if (CDirectory::Exists(actorsDir))
1784       CDirectory::GetDirectory(actorsDir, items, ".png|.jpg|.tbn", DIR_FLAG_NO_FILE_DIRS |
1785                                DIR_FLAG_NO_FILE_INFO);
1786     for (vector<SActorInfo>::iterator i = actors.begin(); i != actors.end(); ++i)
1787     {
1788       if (i->thumb.IsEmpty())
1789       {
1790         CStdString thumbFile = i->strName;
1791         thumbFile.Replace(" ","_");
1792         for (int j = 0; j < items.Size(); j++)
1793         {
1794           CStdString compare = URIUtils::GetFileName(items[j]->GetPath());
1795           URIUtils::RemoveExtension(compare);
1796           if (!items[j]->m_bIsFolder && compare == thumbFile)
1797           {
1798             i->thumb = items[j]->GetPath();
1799             break;
1800           }
1801         }
1802         if (i->thumb.IsEmpty() && !i->thumbUrl.GetFirstThumb().m_url.IsEmpty())
1803           i->thumb = CScraperUrl::GetThumbURL(i->thumbUrl.GetFirstThumb());
1804         if (!i->thumb.IsEmpty())
1805           CTextureCache::Get().BackgroundCacheImage(i->thumb);
1806       }
1807     }
1808   }
1809
1810   CNfoFile::NFOResult CVideoInfoScanner::CheckForNFOFile(CFileItem* pItem, bool bGrabAny, ScraperPtr& info, CScraperUrl& scrUrl)
1811   {
1812     CStdString strNfoFile;
1813     if (info->Content() == CONTENT_MOVIES || info->Content() == CONTENT_MUSICVIDEOS
1814         || (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder))
1815       strNfoFile = GetnfoFile(pItem, bGrabAny);
1816     if (info->Content() == CONTENT_TVSHOWS && pItem->m_bIsFolder)
1817       strNfoFile = URIUtils::AddFileToFolder(pItem->GetPath(), "tvshow.nfo");
1818
1819     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1820     if (!strNfoFile.IsEmpty() && CFile::Exists(strNfoFile))
1821     {
1822       if (info->Content() == CONTENT_TVSHOWS && !pItem->m_bIsFolder)
1823         result = m_nfoReader.Create(strNfoFile,info,pItem->GetVideoInfoTag()->m_iEpisode);
1824       else
1825         result = m_nfoReader.Create(strNfoFile,info);
1826
1827       CStdString type;
1828       switch(result)
1829       {
1830         case CNfoFile::COMBINED_NFO:
1831           type = "Mixed";
1832           break;
1833         case CNfoFile::FULL_NFO:
1834           type = "Full";
1835           break;
1836         case CNfoFile::URL_NFO:
1837           type = "URL";
1838           break;
1839         case CNfoFile::NO_NFO:
1840           type = "";
1841           break;
1842         default:
1843           type = "malformed";
1844       }
1845       if (result != CNfoFile::NO_NFO)
1846         CLog::Log(LOGDEBUG, "VideoInfoScanner: Found matching %s NFO file: %s", type.c_str(), strNfoFile.c_str());
1847       if (result == CNfoFile::FULL_NFO)
1848       {
1849         if (info->Content() == CONTENT_TVSHOWS)
1850           info = m_nfoReader.GetScraperInfo();
1851       }
1852       else if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
1853       {
1854         scrUrl = m_nfoReader.ScraperUrl();
1855         info = m_nfoReader.GetScraperInfo();
1856
1857         CLog::Log(LOGDEBUG, "VideoInfoScanner: Fetching url '%s' using %s scraper (content: '%s')",
1858           scrUrl.m_url[0].m_url.c_str(), info->Name().c_str(), TranslateContent(info->Content()).c_str());
1859
1860         if (result == CNfoFile::COMBINED_NFO)
1861           m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
1862       }
1863     }
1864     else
1865       CLog::Log(LOGDEBUG, "VideoInfoScanner: No NFO file found. Using title search for '%s'", pItem->GetPath().c_str());
1866
1867     return result;
1868   }
1869
1870   bool CVideoInfoScanner::DownloadFailed(CGUIDialogProgress* pDialog)
1871   {
1872     if (g_advancedSettings.m_bVideoScannerIgnoreErrors)
1873       return true;
1874
1875     if (pDialog)
1876     {
1877       CGUIDialogOK::ShowAndGetInput(20448,20449,20022,20022);
1878       return false;
1879     }
1880     return CGUIDialogYesNo::ShowAndGetInput(20448,20449,20450,20022);
1881   }
1882
1883   bool CVideoInfoScanner::ProgressCancelled(CGUIDialogProgress* progress, int heading, const CStdString &line1)
1884   {
1885     if (progress)
1886     {
1887       progress->SetHeading(heading);
1888       progress->SetLine(0, line1);
1889       progress->SetLine(2, "");
1890       progress->Progress();
1891       return progress->IsCanceled();
1892     }
1893     return m_bStop;
1894   }
1895
1896   int CVideoInfoScanner::FindVideo(const CStdString &videoName, const ScraperPtr &scraper, CScraperUrl &url, CGUIDialogProgress *progress)
1897   {
1898     MOVIELIST movielist;
1899     CVideoInfoDownloader imdb(scraper);
1900     int returncode = imdb.FindMovie(videoName, movielist, progress);
1901     if (returncode < 0 || (returncode == 0 && (m_bStop || !DownloadFailed(progress))))
1902     { // scraper reported an error, or we had an error and user wants to cancel the scan
1903       m_bStop = true;
1904       return -1; // cancelled
1905     }
1906     if (returncode > 0 && movielist.size())
1907     {
1908       url = movielist[0];
1909       return 1;  // found a movie
1910     }
1911     return 0;    // didn't find anything
1912   }
1913
1914   CStdString CVideoInfoScanner::GetParentDir(const CFileItem &item) const
1915   {
1916     CStdString strCheck = item.GetPath();
1917     if (item.IsStack())
1918       strCheck = CStackDirectory::GetFirstStackedFile(item.GetPath());
1919
1920     CStdString strDirectory;
1921     URIUtils::GetDirectory(strCheck, strDirectory);
1922     if (URIUtils::IsInRAR(strCheck))
1923     {
1924       CStdString strPath=strDirectory;
1925       URIUtils::GetParentPath(strPath, strDirectory);
1926     }
1927     if (item.IsStack())
1928     {
1929       strCheck = strDirectory;
1930       URIUtils::RemoveSlashAtEnd(strCheck);
1931       if (URIUtils::GetFileName(strCheck).size() == 3 && URIUtils::GetFileName(strCheck).Left(2).Equals("cd"))
1932         URIUtils::GetDirectory(strCheck, strDirectory);
1933     }
1934     return strDirectory;
1935   }
1936
1937 }