[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / video / VideoInfoScanner.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "FileItem.h"
23 #include "VideoInfoScanner.h"
24 #include "addons/AddonManager.h"
25 #include "filesystem/DirectoryCache.h"
26 #include "Util.h"
27 #include "NfoFile.h"
28 #include "utils/RegExp.h"
29 #include "utils/md5.h"
30 #include "filesystem/StackDirectory.h"
31 #include "VideoInfoDownloader.h"
32 #include "GUIInfoManager.h"
33 #include "filesystem/File.h"
34 #include "dialogs/GUIDialogExtendedProgressBar.h"
35 #include "dialogs/GUIDialogProgress.h"
36 #include "dialogs/GUIDialogYesNo.h"
37 #include "dialogs/GUIDialogOK.h"
38 #include "interfaces/AnnouncementManager.h"
39 #include "settings/AdvancedSettings.h"
40 #include "settings/Settings.h"
41 #include "utils/StringUtils.h"
42 #include "guilib/LocalizeStrings.h"
43 #include "guilib/GUIWindowManager.h"
44 #include "utils/TimeUtils.h"
45 #include "utils/log.h"
46 #include "utils/URIUtils.h"
47 #include "utils/Variant.h"
48 #include "video/VideoThumbLoader.h"
49 #include "TextureCache.h"
50 #include "GUIUserMessages.h"
51 #include "URL.h"
52
53 using namespace std;
54 using namespace XFILE;
55 using namespace ADDON;
56
57 namespace VIDEO
58 {
59
60   CVideoInfoScanner::CVideoInfoScanner() : CThread("VideoInfoScanner")
61   {
62     m_bRunning = false;
63     m_handle = NULL;
64     m_showDialog = false;
65     m_bCanInterrupt = false;
66     m_currentItem = 0;
67     m_itemCount = 0;
68     m_bClean = false;
69     m_scanAll = false;
70   }
71
72   CVideoInfoScanner::~CVideoInfoScanner()
73   {
74   }
75
76   void CVideoInfoScanner::Process()
77   {
78     try
79     {
80       unsigned int tick = XbmcThreads::SystemClockMillis();
81
82       m_database.Open();
83
84       if (m_showDialog && !CSettings::Get().GetBool("videolibrary.backgroundupdate"))
85       {
86         CGUIDialogExtendedProgressBar* dialog =
87           (CGUIDialogExtendedProgressBar*)g_windowManager.GetWindow(WINDOW_DIALOG_EXT_PROGRESS);
88         if (dialog)
89            m_handle = dialog->GetHandle(g_localizeStrings.Get(314));
90       }
91
92       m_bCanInterrupt = true;
93
94       CLog::Log(LOGNOTICE, "VideoInfoScanner: Starting scan ..");
95       ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnScanStarted");
96
97       // Reset progress vars
98       m_currentItem = 0;
99       m_itemCount = -1;
100
101       SetPriority(GetMinPriority());
102
103       // Database operations should not be canceled
104       // using Interupt() while scanning as it could
105       // result in unexpected behaviour.
106       m_bCanInterrupt = false;
107
108       bool bCancelled = false;
109       while (!bCancelled && m_pathsToScan.size())
110       {
111         /*
112          * A copy of the directory path is used because the path supplied is
113          * immediately removed from the m_pathsToScan set in DoScan(). If the
114          * reference points to the entry in the set a null reference error
115          * occurs.
116          */
117         CStdString directory = *m_pathsToScan.begin();
118         if (!CDirectory::Exists(directory))
119         {
120           /*
121            * Note that this will skip clean (if m_bClean is enabled) if the directory really
122            * doesn't exist rather than a NAS being switched off.  A manual clean from settings
123            * will still pick up and remove it though.
124            */
125           CLog::Log(LOGWARNING, "%s directory '%s' does not exist - skipping scan%s.", __FUNCTION__, directory.c_str(), m_bClean ? " and clean" : "");
126           m_pathsToScan.erase(m_pathsToScan.begin());
127         }
128         else if (!DoScan(directory))
129           bCancelled = true;
130       }
131
132       if (!bCancelled)
133       {
134         if (m_bClean)
135           CleanDatabase(m_handle,&m_pathsToClean, false);
136         else
137         {
138           if (m_handle)
139             m_handle->SetTitle(g_localizeStrings.Get(331));
140           m_database.Compress(false);
141         }
142       }
143
144       m_database.Close();
145
146       tick = XbmcThreads::SystemClockMillis() - tick;
147       CLog::Log(LOGNOTICE, "VideoInfoScanner: Finished scan. Scanning for video info took %s", StringUtils::SecondsToTimeString(tick / 1000).c_str());
148     }
149     catch (...)
150     {
151       CLog::Log(LOGERROR, "VideoInfoScanner: Exception while scanning.");
152     }
153     
154     m_bRunning = false;
155     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnScanFinished");
156
157     // we need to clear the videodb cache and update any active lists
158     CUtil::DeleteVideoDatabaseDirectoryCache();
159     CGUIMessage msg(GUI_MSG_SCAN_FINISHED, 0, 0, 0);
160     g_windowManager.SendThreadMessage(msg);
161     
162     if (m_handle)
163       m_handle->MarkFinished();
164     m_handle = NULL;
165   }
166
167   void CVideoInfoScanner::Start(const CStdString& strDirectory, bool scanAll)
168   {
169     m_strStartDir = strDirectory;
170     m_scanAll = scanAll;
171     m_pathsToScan.clear();
172     m_pathsToClean.clear();
173
174     if (strDirectory.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)", CURL::GetRedacted(strDirectory).c_str());
270         hash = fastHash;
271         bSkip = true;
272       }
273       if (!bSkip)
274       { // need to fetch the folder
275         CDirectory::GetDirectory(strDirectory, items, g_advancedSettings.m_videoExtensions);
276         items.Stack();
277         // compute hash
278         GetPathHash(items, hash);
279         if (hash != dbHash && !hash.IsEmpty())
280         {
281           if (dbHash.IsEmpty())
282             CLog::Log(LOGDEBUG, "VideoInfoScanner: Scanning dir '%s' as not in the database", CURL::GetRedacted(strDirectory).c_str());
283           else
284             CLog::Log(LOGDEBUG, "VideoInfoScanner: Rescanning dir '%s' due to change (%s != %s)", CURL::GetRedacted(strDirectory).c_str(), dbHash.c_str(), hash.c_str());
285         }
286         else
287         { // they're the same or the hash is empty (dir empty/dir not retrievable)
288           if (hash.IsEmpty() && !dbHash.IsEmpty())
289           {
290             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' as it's empty or doesn't exist - adding to clean list", CURL::GetRedacted(strDirectory).c_str());
291             m_pathsToClean.insert(m_database.GetPathId(strDirectory));
292           }
293           else
294             CLog::Log(LOGDEBUG, "VideoInfoScanner: Skipping dir '%s' due to no change", CURL::GetRedacted(strDirectory).c_str());
295           bSkip = true;
296           if (m_handle)
297             OnDirectoryScanned(strDirectory);
298         }
299         // update the hash to a fast hash if needed
300         if (CanFastHash(items) && !fastHash.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", CURL::GetRedacted(strDirectory).c_str());
342         }
343       }
344       else
345       {
346         m_pathsToClean.insert(m_database.GetPathId(strDirectory));
347         CLog::Log(LOGDEBUG, "VideoInfoScanner: No (new) information was found in dir %s", CURL::GetRedacted(strDirectory).c_str());
348       }
349     }
350     else if (hash != dbHash && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS))
351     { // update the hash either way - we may have changed the hash to a fast version
352       m_database.SetPathHash(strDirectory, hash);
353     }
354
355     if (m_handle)
356       OnDirectoryScanned(strDirectory);
357
358     for (int i = 0; i < items.Size(); ++i)
359     {
360       CFileItemPtr pItem = items[i];
361
362       if (m_bStop)
363         break;
364
365       // if we have a directory item (non-playlist) we then recurse into that folder
366       // do not recurse for tv shows - we have already looked recursively for episodes
367       if (pItem->m_bIsFolder && !pItem->IsParentFolder() && !pItem->IsPlayList() && settings.recurse > 0 && content != CONTENT_TVSHOWS)
368       {
369         if (!DoScan(pItem->GetPath()))
370         {
371           m_bStop = true;
372         }
373       }
374     }
375     return !m_bStop;
376   }
377
378   bool CVideoInfoScanner::RetrieveVideoInfo(CFileItemList& items, bool bDirNames, CONTENT_TYPE content, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
379   {
380     if (pDlgProgress)
381     {
382       if (items.Size() > 1 || (items[0]->m_bIsFolder && fetchEpisodes))
383       {
384         pDlgProgress->ShowProgressBar(true);
385         pDlgProgress->SetPercentage(0);
386       }
387       else
388         pDlgProgress->ShowProgressBar(false);
389
390       pDlgProgress->Progress();
391     }
392
393     m_database.Open();
394
395     bool FoundSomeInfo = false;
396     vector<int> seenPaths;
397     for (int i = 0; i < (int)items.Size(); ++i)
398     {
399       m_nfoReader.Close();
400       CFileItemPtr pItem = items[i];
401
402       // we do this since we may have a override per dir
403       ScraperPtr info2 = m_database.GetScraperForPath(pItem->m_bIsFolder ? pItem->GetPath() : items.GetPath());
404       if (!info2) // skip
405         continue;
406
407       // Discard all exclude files defined by regExExclude
408       if (CUtil::ExcludeFileOrFolder(pItem->GetPath(), (content == CONTENT_TVSHOWS) ? g_advancedSettings.m_tvshowExcludeFromScanRegExps
409                                                                                     : g_advancedSettings.m_moviesExcludeFromScanRegExps))
410         continue;
411
412       if (info2->Content() == CONTENT_MOVIES || info2->Content() == CONTENT_MUSICVIDEOS)
413       {
414         if (m_handle)
415           m_handle->SetPercentage(i*100.f/items.Size());
416       }
417
418       // clear our scraper cache
419       info2->ClearCache();
420
421       INFO_RET ret = INFO_CANCELLED;
422       if (info2->Content() == CONTENT_TVSHOWS)
423         ret = RetrieveInfoForTvShow(pItem.get(), bDirNames, info2, useLocal, pURL, fetchEpisodes, pDlgProgress);
424       else if (info2->Content() == CONTENT_MOVIES)
425         ret = RetrieveInfoForMovie(pItem.get(), bDirNames, info2, useLocal, pURL, pDlgProgress);
426       else if (info2->Content() == CONTENT_MUSICVIDEOS)
427         ret = RetrieveInfoForMusicVideo(pItem.get(), bDirNames, info2, useLocal, pURL, pDlgProgress);
428       else
429       {
430         CLog::Log(LOGERROR, "VideoInfoScanner: Unknown content type %d (%s)", info2->Content(), CURL::GetRedacted(pItem->GetPath()).c_str());
431         FoundSomeInfo = false;
432         break;
433       }
434       if (ret == INFO_CANCELLED || ret == INFO_ERROR)
435       {
436         FoundSomeInfo = false;
437         break;
438       }
439       if (ret == INFO_ADDED || ret == INFO_HAVE_ALREADY)
440         FoundSomeInfo = true;
441       else if (ret == INFO_NOT_FOUND)
442       {
443         CLog::Log(LOGWARNING, "No information found for item '%s', it won't be added to the library.", CURL::GetRedacted(pItem->GetPath()).c_str());
444       }
445
446       pURL = NULL;
447
448       // Keep track of directories we've seen
449       if (pItem->m_bIsFolder)
450         seenPaths.push_back(m_database.GetPathId(pItem->GetPath()));
451     }
452
453     if (content == CONTENT_TVSHOWS && ! seenPaths.empty())
454     {
455       vector< pair<int,string> > libPaths;
456       m_database.GetSubPaths(items.GetPath(), libPaths);
457       for (vector< pair<int,string> >::iterator i = libPaths.begin(); i < libPaths.end(); ++i)
458       {
459         if (find(seenPaths.begin(), seenPaths.end(), i->first) == seenPaths.end())
460           m_pathsToClean.insert(i->first);
461       }
462     }
463     if(pDlgProgress)
464       pDlgProgress->ShowProgressBar(false);
465
466     g_infoManager.ResetLibraryBools();
467     m_database.Close();
468     return FoundSomeInfo;
469   }
470
471   INFO_RET CVideoInfoScanner::RetrieveInfoForTvShow(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, bool fetchEpisodes, CGUIDialogProgress* pDlgProgress)
472   {
473     long idTvShow = -1;
474     if (pItem->m_bIsFolder)
475       idTvShow = m_database.GetTvShowId(pItem->GetPath());
476     else
477     {
478       CStdString strPath = URIUtils::GetDirectory(pItem->GetPath());
479       idTvShow = m_database.GetTvShowId(strPath);
480     }
481     if (idTvShow > -1 && (fetchEpisodes || !pItem->m_bIsFolder))
482     {
483       INFO_RET ret = RetrieveInfoForEpisodes(pItem, idTvShow, info2, useLocal, pDlgProgress);
484       if (ret == INFO_ADDED)
485         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
486       return ret;
487     }
488
489     if (ProgressCancelled(pDlgProgress, pItem->m_bIsFolder ? 20353 : 20361, pItem->GetLabel()))
490       return INFO_CANCELLED;
491
492     if (m_handle)
493       m_handle->SetText(pItem->GetMovieName(bDirNames));
494
495     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
496     CScraperUrl scrUrl;
497     // handle .nfo files
498     if (useLocal)
499       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
500     if (result != CNfoFile::NO_NFO && result != CNfoFile::ERROR_NFO)
501     { // check for preconfigured scraper; if found, overwrite with interpreted scraper (from Nfofile)
502       // but keep current scan settings
503       SScanSettings settings;
504       if (m_database.GetScraperForPath(pItem->GetPath(), settings))
505         m_database.SetScraperForPath(pItem->GetPath(), info2, settings);
506     }
507     if (result == CNfoFile::FULL_NFO)
508     {
509       pItem->GetVideoInfoTag()->Reset();
510       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
511
512       long lResult = AddVideo(pItem, info2->Content(), bDirNames, useLocal);
513       if (lResult < 0)
514         return INFO_ERROR;
515       if (fetchEpisodes)
516       {
517         INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
518         if (ret == INFO_ADDED)
519           m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
520         return ret;
521       }
522       return INFO_ADDED;
523     }
524     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
525       pURL = &scrUrl;
526
527     CScraperUrl url;
528     int retVal = 0;
529     if (pURL)
530       url = *pURL;
531     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
532       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
533
534     long lResult=-1;
535     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
536     {
537       if ((lResult = AddVideo(pItem, info2->Content(), false, useLocal)) < 0)
538         return INFO_ERROR;
539     }
540     if (fetchEpisodes)
541     {
542       INFO_RET ret = RetrieveInfoForEpisodes(pItem, lResult, info2, useLocal, pDlgProgress);
543       if (ret == INFO_ADDED)
544         m_database.SetPathHash(pItem->GetPath(), pItem->GetProperty("hash").asString());
545     }
546     return INFO_ADDED;
547   }
548
549   INFO_RET CVideoInfoScanner::RetrieveInfoForMovie(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
550   {
551     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
552        (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm")))
553       return INFO_NOT_NEEDED;
554
555     if (ProgressCancelled(pDlgProgress, 198, pItem->GetLabel()))
556       return INFO_CANCELLED;
557
558     if (m_database.HasMovieInfo(pItem->GetPath()))
559       return INFO_HAVE_ALREADY;
560
561     if (m_handle)
562       m_handle->SetText(pItem->GetMovieName(bDirNames));
563
564     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
565     CScraperUrl scrUrl;
566     // handle .nfo files
567     if (useLocal)
568       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
569     if (result == CNfoFile::FULL_NFO)
570     {
571       pItem->GetVideoInfoTag()->Reset();
572       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
573
574       if (AddVideo(pItem, info2->Content(), bDirNames, true) < 0)
575         return INFO_ERROR;
576       return INFO_ADDED;
577     }
578     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
579       pURL = &scrUrl;
580
581     CScraperUrl url;
582     int retVal = 0;
583     if (pURL)
584       url = *pURL;
585     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
586       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
587
588     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
589     {
590       if (AddVideo(pItem, info2->Content(), bDirNames, useLocal) < 0)
591         return INFO_ERROR;
592       return INFO_ADDED;
593     }
594     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
595     return INFO_NOT_FOUND;
596   }
597
598   INFO_RET CVideoInfoScanner::RetrieveInfoForMusicVideo(CFileItem *pItem, bool bDirNames, ScraperPtr &info2, bool useLocal, CScraperUrl* pURL, CGUIDialogProgress* pDlgProgress)
599   {
600     if (pItem->m_bIsFolder || !pItem->IsVideo() || pItem->IsNFO() ||
601        (pItem->IsPlayList() && !URIUtils::HasExtension(pItem->GetPath(), ".strm")))
602       return INFO_NOT_NEEDED;
603
604     if (ProgressCancelled(pDlgProgress, 20394, pItem->GetLabel()))
605       return INFO_CANCELLED;
606
607     if (m_database.HasMusicVideoInfo(pItem->GetPath()))
608       return INFO_HAVE_ALREADY;
609
610     if (m_handle)
611       m_handle->SetText(pItem->GetMovieName(bDirNames));
612
613     CNfoFile::NFOResult result=CNfoFile::NO_NFO;
614     CScraperUrl scrUrl;
615     // handle .nfo files
616     if (useLocal)
617       result = CheckForNFOFile(pItem, bDirNames, info2, scrUrl);
618     if (result == CNfoFile::FULL_NFO)
619     {
620       pItem->GetVideoInfoTag()->Reset();
621       m_nfoReader.GetDetails(*pItem->GetVideoInfoTag());
622
623       if (AddVideo(pItem, info2->Content(), bDirNames, true) < 0)
624         return INFO_ERROR;
625       return INFO_ADDED;
626     }
627     if (result == CNfoFile::URL_NFO || result == CNfoFile::COMBINED_NFO)
628       pURL = &scrUrl;
629
630     CScraperUrl url;
631     int retVal = 0;
632     if (pURL)
633       url = *pURL;
634     else if ((retVal = FindVideo(pItem->GetMovieName(bDirNames), info2, url, pDlgProgress)) <= 0)
635       return retVal < 0 ? INFO_CANCELLED : INFO_NOT_FOUND;
636
637     if (GetDetails(pItem, url, info2, result == CNfoFile::COMBINED_NFO ? &m_nfoReader : NULL, pDlgProgress))
638     {
639       if (AddVideo(pItem, info2->Content(), bDirNames, useLocal) < 0)
640         return INFO_ERROR;
641       return INFO_ADDED;
642     }
643     // TODO: This is not strictly correct as we could fail to download information here or error, or be cancelled
644     return INFO_NOT_FOUND;
645   }
646
647   INFO_RET CVideoInfoScanner::RetrieveInfoForEpisodes(CFileItem *item, long showID, const ADDON::ScraperPtr &scraper, bool useLocal, CGUIDialogProgress *progress)
648   {
649     // enumerate episodes
650     EPISODELIST files;
651     EnumerateSeriesFolder(item, files);
652     if (files.size() == 0) // no update or no files
653       return INFO_NOT_NEEDED;
654
655     if (m_bStop || (progress && progress->IsCanceled()))
656       return INFO_CANCELLED;
657
658     CVideoInfoTag showInfo;
659     m_database.GetTvShowInfo("", showInfo, showID);
660     return OnProcessSeriesFolder(files, scraper, useLocal, showInfo, progress);
661   }
662
663   void CVideoInfoScanner::EnumerateSeriesFolder(CFileItem* item, EPISODELIST& episodeList)
664   {
665     CFileItemList items;
666
667     if (item->m_bIsFolder)
668     {
669       CUtil::GetRecursiveListing(item->GetPath(), items, g_advancedSettings.m_videoExtensions, true);
670       CStdString hash, dbHash;
671       int numFilesInFolder = GetPathHash(items, hash);
672
673       if (m_database.GetPathHash(item->GetPath(), dbHash) && dbHash == hash)
674       {
675         m_currentItem += numFilesInFolder;
676
677         // update our dialog with our progress
678         if (m_handle)
679         {
680           if (m_itemCount>0)
681             m_handle->SetPercentage(m_currentItem*100.f/m_itemCount);
682
683           OnDirectoryScanned(item->GetPath());
684         }
685         return;
686       }
687       m_pathsToClean.insert(m_database.GetPathId(item->GetPath()));
688       m_database.GetPathsForTvShow(m_database.GetTvShowId(item->GetPath()), m_pathsToClean);
689       item->SetProperty("hash", hash);
690     }
691     else
692     {
693       CFileItemPtr newItem(new CFileItem(*item));
694       items.Add(newItem);
695     }
696
697     /*
698     stack down any dvd folders
699     need to sort using the full path since this is a collapsed recursive listing of all subdirs
700     video_ts.ifo files should sort at the top of a dvd folder in ascending order
701
702     /foo/bar/video_ts.ifo
703     /foo/bar/vts_x_y.ifo
704     /foo/bar/vts_x_y.vob
705     */
706
707     // since we're doing this now anyway, should other items be stacked?
708     items.Sort(SortByPath, SortOrderAscending);
709     int x = 0;
710     while (x < items.Size())
711     {
712       if (items[x]->m_bIsFolder)
713         continue;
714
715
716       CStdString strPathX, strFileX;
717       URIUtils::Split(items[x]->GetPath(), strPathX, strFileX);
718       //CLog::Log(LOGDEBUG,"%i:%s:%s", x, strPathX.c_str(), strFileX.c_str());
719
720       int y = x + 1;
721       if (strFileX.Equals("VIDEO_TS.IFO"))
722       {
723         while (y < items.Size())
724         {
725           CStdString strPathY, strFileY;
726           URIUtils::Split(items[y]->GetPath(), strPathY, strFileY);
727           //CLog::Log(LOGDEBUG," %i:%s:%s", y, strPathY.c_str(), strFileY.c_str());
728
729           if (strPathY.Equals(strPathX))
730             /*
731             remove everything sorted below the video_ts.ifo file in the same path.
732             understandbly this wont stack correctly if there are other files in the the dvd folder.
733             this should be unlikely and thus is being ignored for now but we can monitor the
734             where the path changes and potentially remove the items above the video_ts.ifo file.
735             */
736             items.Remove(y);
737           else
738             break;
739         }
740       }
741       x = y;
742     }
743
744     // enumerate
745     CStdStringArray regexps = g_advancedSettings.m_tvshowExcludeFromScanRegExps;
746
747     for (int i=0;i<items.Size();++i)
748     {
749       if (items[i]->m_bIsFolder)
750         continue;
751       CStdString strPath = URIUtils::GetDirectory(items[i]->GetPath());
752       URIUtils::RemoveSlashAtEnd(strPath); // want no slash for the test that follows
753
754       if (URIUtils::GetFileName(strPath).Equals("sample"))
755         continue;
756
757       // Discard all exclude files defined by regExExcludes
758       if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
759         continue;
760
761       /*
762        * Check if the media source has already set the season and episode or original air date in
763        * the VideoInfoTag. If it has, do not try to parse any of them from the file path to avoid
764        * any false positive matches.
765        */
766       if (ProcessItemByVideoInfoTag(items[i].get(), episodeList))
767         continue;
768
769       if (!EnumerateEpisodeItem(items[i].get(), episodeList))
770       {
771         CStdString decode(items[i]->GetPath());
772         CURL::Decode(decode);
773         CLog::Log(LOGDEBUG, "VideoInfoScanner: Could not enumerate file %s", CURL::GetRedacted(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, 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.GetMatch(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, 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.GetMatch(3);
963             offset = 0;
964           }
965           else if (((regexp2pos < regexppos) && regexp2pos != -1) ||
966                    (regexp2pos >= 0 && regexppos == -1))
967           {
968             episode.iEpisode = atoi(reg2.GetMatch(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.GetMatch(1));
984     std::string episode(reg.GetMatch(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.GetMatch(1));
1021     std::string param2(reg.GetMatch(2));
1022     std::string param3(reg.GetMatch(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 = StringUtils::Format("%s - %ix%i - %s", showInfo->m_strTitle.c_str(), movieDetails.m_iSeason, movieDetails.m_iEpisode, strTitle.c_str());
1074     }
1075
1076     std::string redactPath = pItem->GetPath();
1077     CURL::Decode(redactPath);
1078     redactPath = CURL::GetRedacted(redactPath);
1079
1080     CLog::Log(LOGDEBUG, "VideoInfoScanner: Adding new item to %s:%s", TranslateContent(content).c_str(), redactPath.c_str());
1081     long lResult = -1;
1082
1083     if (content == CONTENT_MOVIES)
1084     {
1085       // find local trailer first
1086       CStdString strTrailer = pItem->FindTrailer();
1087       if (!strTrailer.IsEmpty())
1088         movieDetails.m_strTrailer = strTrailer;
1089
1090       lResult = m_database.SetDetailsForMovie(pItem->GetPath(), movieDetails, art);
1091       movieDetails.m_iDbId = lResult;
1092       movieDetails.m_type = "movie";
1093
1094       // setup links to shows if the linked shows are in the db
1095       for (unsigned int i=0; i < movieDetails.m_showLink.size(); ++i)
1096       {
1097         CFileItemList items;
1098         m_database.GetTvShowsByName(movieDetails.m_showLink[i], items);
1099         if (items.Size())
1100           m_database.LinkMovieToTvshow(lResult, items[0]->GetVideoInfoTag()->m_iDbId, false);
1101         else
1102           CLog::Log(LOGDEBUG, "VideoInfoScanner: Failed to link movie %s to show %s", movieDetails.m_strTitle.c_str(), movieDetails.m_showLink[i].c_str());
1103       }
1104     }
1105     else if (content == CONTENT_TVSHOWS)
1106     {
1107       if (pItem->m_bIsFolder)
1108       {
1109         map<int, map<string, string> > seasonArt;
1110         if (!libraryImport)
1111         { // get and cache season thumbs
1112           GetSeasonThumbs(movieDetails, seasonArt, CVideoThumbLoader::GetArtTypes("season"), useLocal);
1113           for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
1114             for (map<string, string>::iterator j = i->second.begin(); j != i->second.end(); ++j)
1115               CTextureCache::Get().BackgroundCacheImage(j->second);
1116         }
1117         lResult = m_database.SetDetailsForTvShow(pItem->GetPath(), movieDetails, art, seasonArt);
1118         movieDetails.m_iDbId = lResult;
1119         movieDetails.m_type = "tvshow";
1120       }
1121       else
1122       {
1123         // we add episode then set details, as otherwise set details will delete the
1124         // episode then add, which breaks multi-episode files.
1125         int idShow = showInfo ? showInfo->m_iDbId : -1;
1126         int idEpisode = m_database.AddEpisode(idShow, pItem->GetPath());
1127         lResult = m_database.SetDetailsForEpisode(pItem->GetPath(), movieDetails, art, idShow, idEpisode);
1128         movieDetails.m_iDbId = lResult;
1129         movieDetails.m_type = "episode";
1130         movieDetails.m_strShowTitle = showInfo ? showInfo->m_strTitle : "";
1131         if (movieDetails.m_fEpBookmark > 0)
1132         {
1133           movieDetails.m_strFileNameAndPath = pItem->GetPath();
1134           CBookmark bookmark;
1135           bookmark.timeInSeconds = movieDetails.m_fEpBookmark;
1136           bookmark.seasonNumber = movieDetails.m_iSeason;
1137           bookmark.episodeNumber = movieDetails.m_iEpisode;
1138           m_database.AddBookMarkForEpisode(movieDetails, bookmark);
1139         }
1140       }
1141     }
1142     else if (content == CONTENT_MUSICVIDEOS)
1143     {
1144       lResult = m_database.SetDetailsForMusicVideo(pItem->GetPath(), movieDetails, art);
1145       movieDetails.m_iDbId = lResult;
1146       movieDetails.m_type = "musicvideo";
1147     }
1148
1149     if (g_advancedSettings.m_bVideoLibraryImportWatchedState || libraryImport)
1150       m_database.SetPlayCount(*pItem, movieDetails.m_playCount, movieDetails.m_lastPlayed);
1151
1152     if ((g_advancedSettings.m_bVideoLibraryImportResumePoint || libraryImport) &&
1153         movieDetails.m_resumePoint.IsSet())
1154       m_database.AddBookMarkToFile(pItem->GetPath(), movieDetails.m_resumePoint, CBookmark::RESUME);
1155
1156     m_database.Close();
1157
1158     CFileItemPtr itemCopy = CFileItemPtr(new CFileItem(*pItem));
1159     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", itemCopy);
1160     return lResult;
1161   }
1162
1163   string ContentToMediaType(CONTENT_TYPE content, bool folder)
1164   {
1165     switch (content)
1166     {
1167       case CONTENT_MOVIES:
1168         return "movie";
1169       case CONTENT_MUSICVIDEOS:
1170         return "musicvideo";
1171       case CONTENT_TVSHOWS:
1172         return folder ? "tvshow" : "episode";
1173       default:
1174         return "";
1175     }
1176   }
1177
1178   std::string CVideoInfoScanner::GetArtTypeFromSize(unsigned int width, unsigned int height)
1179   {
1180     std::string type = "thumb";
1181     if (width*5 < height*4)
1182       type = "poster";
1183     else if (width*1 > height*4)
1184       type = "banner";
1185     return type;
1186   }
1187
1188   void CVideoInfoScanner::GetArtwork(CFileItem *pItem, const CONTENT_TYPE &content, bool bApplyToDir, bool useLocal, const std::string &actorArtPath)
1189   {
1190     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
1191     movieDetails.m_fanart.Unpack();
1192     movieDetails.m_strPictureURL.Parse();
1193
1194     CGUIListItem::ArtMap art = pItem->GetArt();
1195
1196     // get and cache thumb images
1197     vector<string> artTypes = CVideoThumbLoader::GetArtTypes(ContentToMediaType(content, pItem->m_bIsFolder));
1198     vector<string>::iterator i = find(artTypes.begin(), artTypes.end(), "fanart");
1199     if (i != artTypes.end())
1200       artTypes.erase(i); // fanart is handled below
1201     bool lookForThumb = find(artTypes.begin(), artTypes.end(), "thumb") == artTypes.end() &&
1202                         art.find("thumb") == art.end();
1203     // find local art
1204     if (useLocal)
1205     {
1206       for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1207       {
1208         if (art.find(*i) == art.end())
1209         {
1210           std::string image = CVideoThumbLoader::GetLocalArt(*pItem, *i, bApplyToDir);
1211           if (!image.empty())
1212             art.insert(make_pair(*i, image));
1213         }
1214       }
1215       // find and classify the local thumb (backcompat) if available
1216       if (lookForThumb)
1217       {
1218         std::string image = CVideoThumbLoader::GetLocalArt(*pItem, "thumb", bApplyToDir);
1219         if (!image.empty())
1220         { // cache the image and determine sizing
1221           CTextureDetails details;
1222           if (CTextureCache::Get().CacheImage(image, details))
1223           {
1224             std::string type = GetArtTypeFromSize(details.width, details.height);
1225             if (art.find(type) == art.end())
1226               art.insert(make_pair(type, image));
1227           }
1228         }
1229       }
1230     }
1231
1232     // find online art
1233     for (vector<string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
1234     {
1235       if (art.find(*i) == art.end())
1236       {
1237         std::string image = GetImage(pItem, false, bApplyToDir, *i);
1238         if (!image.empty())
1239           art.insert(make_pair(*i, image));
1240       }
1241     }
1242
1243     // use the first piece of online art as the first art type if no thumb type is available yet
1244     if (art.empty() && lookForThumb)
1245     {
1246       std::string image = GetImage(pItem, false, bApplyToDir, "thumb");
1247       if (!image.empty())
1248         art.insert(make_pair(artTypes.front(), image));
1249     }
1250
1251     // get & save fanart image (treated separately due to it being stored in m_fanart)
1252     bool isEpisode = (content == CONTENT_TVSHOWS && !pItem->m_bIsFolder);
1253     if (!isEpisode && art.find("fanart") == art.end())
1254     {
1255       string fanart = GetFanart(pItem, useLocal);
1256       if (!fanart.empty())
1257         art.insert(make_pair("fanart", fanart));
1258     }
1259
1260     for (CGUIListItem::ArtMap::const_iterator i = art.begin(); i != art.end(); ++i)
1261       CTextureCache::Get().BackgroundCacheImage(i->second);
1262
1263     pItem->SetArt(art);
1264
1265     // parent folder to apply the thumb to and to search for local actor thumbs
1266     CStdString parentDir = GetParentDir(*pItem);
1267     if (CSettings::Get().GetBool("videolibrary.actorthumbs"))
1268       FetchActorThumbs(movieDetails.m_cast, actorArtPath.empty() ? parentDir : actorArtPath);
1269     if (bApplyToDir)
1270       ApplyThumbToFolder(parentDir, art["thumb"]);
1271   }
1272
1273   std::string CVideoInfoScanner::GetImage(CFileItem *pItem, bool useLocal, bool bApplyToDir, const std::string &type)
1274   {
1275     std::string thumb;
1276     if (useLocal)
1277       thumb = CVideoThumbLoader::GetLocalArt(*pItem, type, bApplyToDir);
1278
1279     if (thumb.empty())
1280     {
1281       thumb = CScraperUrl::GetThumbURL(pItem->GetVideoInfoTag()->m_strPictureURL.GetFirstThumb(type));
1282       if (!thumb.empty())
1283       {
1284         if (thumb.find("http://") == string::npos &&
1285             thumb.find("/") == string::npos &&
1286             thumb.find("\\") == string::npos)
1287         {
1288           CStdString strPath = URIUtils::GetDirectory(pItem->GetPath());
1289           thumb = URIUtils::AddFileToFolder(strPath, thumb);
1290         }
1291       }
1292     }
1293     return thumb;
1294   }
1295
1296   std::string CVideoInfoScanner::GetFanart(CFileItem *pItem, bool useLocal)
1297   {
1298     std::string fanart = pItem->GetArt("fanart");
1299     if (fanart.empty() && useLocal)
1300       fanart = pItem->FindLocalArt("fanart.jpg", true);
1301     if (fanart.empty())
1302       fanart = pItem->GetVideoInfoTag()->m_fanart.GetImageURL();
1303     return fanart;
1304   }
1305
1306   INFO_RET CVideoInfoScanner::OnProcessSeriesFolder(EPISODELIST& files, const ADDON::ScraperPtr &scraper, bool useLocal, const CVideoInfoTag& showInfo, CGUIDialogProgress* pDlgProgress /* = NULL */)
1307   {
1308     if (pDlgProgress)
1309     {
1310       pDlgProgress->SetLine(1, showInfo.m_strTitle);
1311       pDlgProgress->SetLine(2, 20361);
1312       pDlgProgress->SetPercentage(0);
1313       pDlgProgress->ShowProgressBar(true);
1314       pDlgProgress->Progress();
1315     }
1316
1317     EPISODELIST episodes;
1318     bool hasEpisodeGuide = false;
1319
1320     int iMax = files.size();
1321     int iCurr = 1;
1322     for (EPISODELIST::iterator file = files.begin(); file != files.end(); ++file)
1323     {
1324       m_nfoReader.Close();
1325       if (pDlgProgress)
1326       {
1327         pDlgProgress->SetLine(2, 20361);
1328         pDlgProgress->SetPercentage((int)((float)(iCurr++)/iMax*100));
1329         pDlgProgress->Progress();
1330       }
1331       if (m_handle)
1332         m_handle->SetPercentage(100.f*iCurr++/iMax);
1333
1334       if ((pDlgProgress && pDlgProgress->IsCanceled()) || m_bStop)
1335         return INFO_CANCELLED;
1336
1337       if (m_database.GetEpisodeId(file->strPath, file->iEpisode, file->iSeason) > -1)
1338       {
1339         if (m_handle)
1340           m_handle->SetText(g_localizeStrings.Get(20415));
1341         continue;
1342       }
1343
1344       CFileItem item;
1345       item.SetPath(file->strPath);
1346
1347       // handle .nfo files
1348       CNfoFile::NFOResult result=CNfoFile::NO_NFO;
1349       CScraperUrl scrUrl;
1350       ScraperPtr info(scraper);
1351       item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1352       if (useLocal)
1353         result = CheckForNFOFile(&item, false, info,scrUrl);
1354       if (result == CNfoFile::FULL_NFO)
1355       {
1356         m_nfoReader.GetDetails(*item.GetVideoInfoTag());
1357         // override with episode and season number from file if available
1358         if (file->iEpisode > -1)
1359         {
1360           item.GetVideoInfoTag()->m_iEpisode = file->iEpisode;
1361           item.GetVideoInfoTag()->m_iSeason = file->iSeason;
1362         }
1363         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, true, &showInfo) < 0)
1364           return INFO_ERROR;
1365         continue;
1366       }
1367
1368       if (!hasEpisodeGuide)
1369       {
1370         // fetch episode guide
1371         if (!showInfo.m_strEpisodeGuide.IsEmpty())
1372         {
1373           CScraperUrl url;
1374           url.ParseEpisodeGuide(showInfo.m_strEpisodeGuide);
1375
1376           if (pDlgProgress)
1377           {
1378             pDlgProgress->SetLine(2, 20354);
1379             pDlgProgress->Progress();
1380           }
1381
1382           CVideoInfoDownloader imdb(scraper);
1383           if (!imdb.GetEpisodeList(url, episodes))
1384             return INFO_NOT_FOUND;
1385
1386           hasEpisodeGuide = true;
1387         }
1388       }
1389
1390       if (episodes.empty())
1391       {
1392         CLog::Log(LOGERROR, "VideoInfoScanner: Asked to lookup episode %s"
1393                             " online, but we have no episode guide. Check your tvshow.nfo and make"
1394                             " sure the <episodeguide> tag is in place.", file->strPath.c_str());
1395         continue;
1396       }
1397
1398       EPISODE key(file->iSeason, file->iEpisode, file->iSubepisode);
1399       EPISODE backupkey(file->iSeason, file->iEpisode, 0);
1400       bool bFound = false;
1401       EPISODELIST::iterator guide = episodes.begin();;
1402       EPISODELIST matches;
1403
1404       for (; guide != episodes.end(); ++guide )
1405       {
1406         if ((file->iEpisode!=-1) && (file->iSeason!=-1))
1407         {
1408           if (key==*guide)
1409           {
1410             bFound = true;
1411             break;
1412           }
1413           else if ((file->iSubepisode!=0) && (backupkey==*guide))
1414           {
1415             matches.push_back(*guide);
1416             continue;
1417           }
1418         }
1419         if (file->cDate.IsValid() && guide->cDate.IsValid() && file->cDate==guide->cDate)
1420         {
1421           matches.push_back(*guide);
1422           continue;
1423         }
1424         if (!guide->cScraperUrl.strTitle.IsEmpty() && guide->cScraperUrl.strTitle.CompareNoCase(file->strTitle.c_str()) == 0)
1425         {
1426           bFound = true;
1427           break;
1428         }
1429       }
1430
1431       if (!bFound)
1432       {
1433         /*
1434          * If there is only one match or there are matches but no title to compare with to help
1435          * identify the best match, then pick the first match as the best possible candidate.
1436          *
1437          * Otherwise, use the title to further refine the best match.
1438          */
1439         if (matches.size() == 1 || (file->strTitle.empty() && matches.size() > 1))
1440         {
1441           guide = matches.begin();
1442           bFound = true;
1443         }
1444         else if (!file->strTitle.empty())
1445         {
1446           double minscore = 0; // Default minimum score is 0 to find whatever is the best match.
1447
1448           EPISODELIST *candidates;
1449           if (matches.empty()) // No matches found using earlier criteria. Use fuzzy match on titles across all episodes.
1450           {
1451             minscore = 0.8; // 80% should ensure a good match.
1452             candidates = &episodes;
1453           }
1454           else // Multiple matches found. Use fuzzy match on the title with already matched episodes to pick the best.
1455             candidates = &matches;
1456
1457           CStdStringArray titles;
1458           for (guide = candidates->begin(); guide != candidates->end(); ++guide)
1459             titles.push_back(guide->cScraperUrl.strTitle.ToLower());
1460
1461           double matchscore;
1462           std::string loweredTitle(file->strTitle);
1463           StringUtils::ToLower(loweredTitle);
1464           int index = StringUtils::FindBestMatch(loweredTitle, titles, matchscore);
1465           if (matchscore >= minscore)
1466           {
1467             guide = candidates->begin() + index;
1468             bFound = true;
1469             CLog::Log(LOGDEBUG,"%s fuzzy title match for show: '%s', title: '%s', match: '%s', score: %f >= %f",
1470                       __FUNCTION__, showInfo.m_strTitle.c_str(), file->strTitle.c_str(), titles[index].c_str(), matchscore, minscore);
1471           }
1472         }
1473       }
1474
1475       if (bFound)
1476       {
1477         CVideoInfoDownloader imdb(scraper);
1478         CFileItem item;
1479         item.SetPath(file->strPath);
1480         if (!imdb.GetEpisodeDetails(guide->cScraperUrl, *item.GetVideoInfoTag(), pDlgProgress))
1481           return INFO_NOT_FOUND; // TODO: should we just skip to the next episode?
1482           
1483         // Only set season/epnum from filename when it is not already set by a scraper
1484         if (item.GetVideoInfoTag()->m_iSeason == -1)
1485           item.GetVideoInfoTag()->m_iSeason = guide->iSeason;
1486         if (item.GetVideoInfoTag()->m_iEpisode == -1)
1487           item.GetVideoInfoTag()->m_iEpisode = guide->iEpisode;
1488           
1489         if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, useLocal, &showInfo) < 0)
1490           return INFO_ERROR;
1491       }
1492       else
1493       {
1494         CLog::Log(LOGDEBUG,"%s - no match for show: '%s', season: %d, episode: %d.%d, airdate: '%s', title: '%s'",
1495                   __FUNCTION__, showInfo.m_strTitle.c_str(), file->iSeason, file->iEpisode, file->iSubepisode,
1496                   file->cDate.GetAsLocalizedDate().c_str(), file->strTitle.c_str());
1497       }
1498     }
1499     return INFO_ADDED;
1500   }
1501
1502   CStdString CVideoInfoScanner::GetnfoFile(CFileItem *item, bool bGrabAny) const
1503   {
1504     CStdString nfoFile;
1505     // Find a matching .nfo file
1506     if (!item->m_bIsFolder)
1507     {
1508       if (URIUtils::IsInRAR(item->GetPath())) // we have a rarred item - we want to check outside the rars
1509       {
1510         CFileItem item2(*item);
1511         CURL url(item->GetPath());
1512         CStdString strPath = URIUtils::GetDirectory(url.GetHostName());
1513         item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1514         return GetnfoFile(&item2, bGrabAny);
1515       }
1516
1517       // grab the folder path
1518       CStdString strPath = URIUtils::GetDirectory(item->GetPath());
1519
1520       if (bGrabAny && !item->IsStack())
1521       { // looking up by folder name - movie.nfo takes priority - but not for stacked items (handled below)
1522         nfoFile = URIUtils::AddFileToFolder(strPath, "movie.nfo");
1523         if (CFile::Exists(nfoFile))
1524           return nfoFile;
1525       }
1526
1527       // try looking for .nfo file for a stacked item
1528       if (item->IsStack())
1529       {
1530         // first try .nfo file matching first file in stack
1531         CStackDirectory dir;
1532         CStdString firstFile = dir.GetFirstStackedFile(item->GetPath());
1533         CFileItem item2;
1534         item2.SetPath(firstFile);
1535         nfoFile = GetnfoFile(&item2, bGrabAny);
1536         // else try .nfo file matching stacked title
1537         if (nfoFile.IsEmpty())
1538         {
1539           CStdString stackedTitlePath = dir.GetStackedTitlePath(item->GetPath());
1540           item2.SetPath(stackedTitlePath);
1541           nfoFile = GetnfoFile(&item2, bGrabAny);
1542         }
1543       }
1544       else
1545       {
1546         // already an .nfo file?
1547         if (URIUtils::HasExtension(item->GetPath(), ".nfo"))
1548           nfoFile = item->GetPath();
1549         // no, create .nfo file
1550         else
1551           nfoFile = URIUtils::ReplaceExtension(item->GetPath(), ".nfo");
1552       }
1553
1554       // test file existence
1555       if (!nfoFile.IsEmpty() && !CFile::Exists(nfoFile))
1556         nfoFile.Empty();
1557
1558       if (nfoFile.IsEmpty()) // final attempt - strip off any cd1 folders
1559       {
1560         URIUtils::RemoveSlashAtEnd(strPath); // need no slash for the check that follows
1561         CFileItem item2;
1562         if (strPath.Mid(strPath.size()-3).Equals("cd1"))
1563         {
1564           strPath = strPath.Mid(0,strPath.size()-3);
1565           item2.SetPath(URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(item->GetPath())));
1566           return GetnfoFile(&item2, bGrabAny);
1567         }
1568       }
1569
1570       if (nfoFile.IsEmpty() && item->IsOpticalMediaFile())
1571       {
1572         CFileItem parentDirectory(item->GetLocalMetadataPath(), true);
1573         nfoFile = GetnfoFile(&parentDirectory, true);
1574       }
1575     }
1576     // folders (or stacked dvds) can take any nfo file if there's a unique one
1577     if (item->m_bIsFolder || item->IsOpticalMediaFile() || (bGrabAny && nfoFile.IsEmpty()))
1578     {
1579       // see if there is a unique nfo file in this folder, and if so, use that
1580       CFileItemList items;
1581       CDirectory dir;
1582       CStdString strPath;
1583       if (item->m_bIsFolder)
1584         strPath = item->GetPath();
1585       else
1586         strPath = URIUtils::GetDirectory(item->GetPath());
1587
1588       if (dir.GetDirectory(strPath, items, ".nfo") && items.Size())
1589       {
1590         int numNFO = -1;
1591         for (int i = 0; i < items.Size(); i++)
1592         {
1593           if (items[i]->IsNFO())
1594           {
1595             if (numNFO == -1)
1596               numNFO = i;
1597             else
1598             {
1599               numNFO = -1;
1600               break;
1601             }
1602           }
1603         }
1604         if (numNFO > -1)
1605           return items[numNFO]->GetPath();
1606       }
1607     }
1608
1609     return nfoFile;
1610   }
1611
1612   bool CVideoInfoScanner::GetDetails(CFileItem *pItem, CScraperUrl &url, const ScraperPtr& scraper, CNfoFile *nfoFile, CGUIDialogProgress* pDialog /* = NULL */)
1613   {
1614     CVideoInfoTag movieDetails;
1615
1616     if (m_handle && !url.strTitle.IsEmpty())
1617       m_handle->SetText(url.strTitle);
1618
1619     CVideoInfoDownloader imdb(scraper);
1620     bool ret = imdb.GetDetails(url, movieDetails, pDialog);
1621
1622     if (ret)
1623     {
1624       if (nfoFile)
1625         nfoFile->GetDetails(movieDetails,NULL,true);
1626
1627       if (m_handle && url.strTitle.IsEmpty())
1628         m_handle->SetText(movieDetails.m_strTitle);
1629
1630       if (pDialog)
1631       {
1632         pDialog->SetLine(1, movieDetails.m_strTitle);
1633         pDialog->Progress();
1634       }
1635
1636       *pItem->GetVideoInfoTag() = movieDetails;
1637       return true;
1638     }
1639     return false; // no info found, or cancelled
1640   }
1641
1642   void CVideoInfoScanner::ApplyThumbToFolder(const CStdString &folder, const CStdString &imdbThumb)
1643   {
1644     // copy icon to folder also;
1645     if (!imdbThumb.IsEmpty())
1646     {
1647       CFileItem folderItem(folder, true);
1648       CThumbLoader loader;
1649       loader.SetCachedImage(folderItem, "thumb", imdbThumb);
1650     }
1651   }
1652
1653   int CVideoInfoScanner::GetPathHash(const CFileItemList &items, CStdString &hash)
1654   {
1655     // Create a hash based on the filenames, filesize and filedate.  Also count the number of files
1656     if (0 == items.Size()) return 0;
1657     XBMC::XBMC_MD5 md5state;
1658     int count = 0;
1659     for (int i = 0; i < items.Size(); ++i)
1660     {
1661       const CFileItemPtr pItem = items[i];
1662       md5state.append(pItem->GetPath());
1663       md5state.append((unsigned char *)&pItem->m_dwSize, sizeof(pItem->m_dwSize));
1664       FILETIME time = pItem->m_dateTime;
1665       md5state.append((unsigned char *)&time, sizeof(FILETIME));
1666       if (pItem->IsVideo() && !pItem->IsPlayList() && !pItem->IsNFO())
1667         count++;
1668     }
1669     md5state.getDigest(hash);
1670     return count;
1671   }
1672
1673   bool CVideoInfoScanner::CanFastHash(const CFileItemList &items) const
1674   {
1675     // TODO: Probably should account for excluded folders here (eg samples), though that then
1676     //       introduces possible problems if the user then changes the exclude regexps and
1677     //       expects excluded folders that are inside a fast-hashed folder to then be picked
1678     //       up. The chances that the user has a folder which contains only excluded folders
1679     //       where some of those folders should be scanned recursively is pretty small.
1680     return items.GetFolderCount() == 0;
1681   }
1682
1683   CStdString CVideoInfoScanner::GetFastHash(const CStdString &directory) const
1684   {
1685     struct __stat64 buffer;
1686     if (XFILE::CFile::Stat(directory, &buffer) == 0)
1687     {
1688       int64_t time = buffer.st_mtime;
1689       if (!time)
1690         time = buffer.st_ctime;
1691       if (time)
1692         return StringUtils::Format("fast%"PRId64, time);
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.GetMatch(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(), CURL::GetRedacted(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'", CURL::GetRedacted(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 = URIUtils::GetDirectory(strCheck);
1921     if (URIUtils::IsInRAR(strCheck))
1922     {
1923       CStdString strPath=strDirectory;
1924       URIUtils::GetParentPath(strPath, strDirectory);
1925     }
1926     if (item.IsStack())
1927     {
1928       strCheck = strDirectory;
1929       URIUtils::RemoveSlashAtEnd(strCheck);
1930       if (URIUtils::GetFileName(strCheck).size() == 3 && StringUtils::StartsWithNoCase(URIUtils::GetFileName(strCheck), "cd"))
1931         strDirectory = URIUtils::GetDirectory(strCheck);
1932     }
1933     return strDirectory;
1934   }
1935
1936 }