void CMediaMonitor::FilterDuplicates(MOVIELIST& movies) { MOVIELISTITERATOR current = movies.begin(); MOVIELISTITERATOR previous = current; while (current != movies.end()) { if (previous != current) { // ensure at least less than 75% similar compared to the previous selection if (parse_Similar( (*current).strFilepath, (*previous).strFilepath, 75)) { // items are similar, find out which one is more likely to be CD1 long valueA = parse_AggregateValue((*current).strFilepath); long valueB = parse_AggregateValue((*previous).strFilepath); if (valueA < valueB) { *previous = *current; } current = movies.erase(current); continue; } } previous = current; current++; } }
/// This method scans all remote shares and determines the latest 3 movies /// If bUpdateAllMovies is true then IMDB is queried for each and every movie found // otherwise queries are limited to the 3 latest movies. void CMediaMonitor::Scan(bool bUpdateAllMovies) { MOVIELIST movies; GetSharedMovies(g_settings.m_vecMyVideoShares, movies); // sort files by creation date sort(movies.begin(), movies.end(), CMediaMonitor::SortMoviesByDateAndTime ); // remove duplicates FilterDuplicates(movies); // Update movies if necessary if (bUpdateAllMovies) { // query imdb for new info for (int iMovie = 0; iMovie < (int)movies.size(); iMovie++) { UpdateObserver(movies[iMovie], 0, false, false); Sleep(50); } } // Select the 3 newest files (remove similar filenames from consideration): int iLatestMovie = (int) movies.size() - 1; int iOlderMovie = iLatestMovie - RECENT_MOVIES; for (int iMovie = iLatestMovie; (iMovie >= 0) && (iMovie > iOlderMovie); iMovie--) { UpdateObserver(movies[iMovie], iLatestMovie - iMovie, false, true); } movies.clear(); }
void CMediaMonitor::Scan(DIRECTORY::IDirectory& directory, CStdString& aPath, MOVIELIST& movies) { // dispatch any pending commands first before scanning this directory! DispatchNextCommand(); CFileItemList items; if (!directory.GetDirectory(aPath, items)) { return ; } for (int i = 0; i < (int)items.Size(); ++i) { CFileItem* pItem = items[i]; if (pItem->m_bIsFolder) { Scan(directory, pItem->m_strPath, movies); } else { char* szFilename = (char*) pItem->GetLabel().c_str(); char* szExtension = strrchr(szFilename, '.'); char* szExpected = szFilename + (strlen(szFilename) - 4); // ensure file has a 3 letter extension that isn't .nfo if ((szExtension != NULL) && (szExpected == szExtension) && (strcmp(szExtension, ".nfo") != 0) ) { Movie aMovie; aMovie.strFilepath = pItem->m_strPath; aMovie.dwDate = (( (pItem->m_dateTime.GetYear()) << 16 ) | ( (pItem->m_dateTime.GetMonth() & 0x00FF) << 8 ) | (pItem->m_dateTime.GetDay() & 0x00FF) ); aMovie.wTime = (( (pItem->m_dateTime.GetHour() & 0x00FF) << 8 ) | (pItem->m_dateTime.GetMinute() & 0x00FF) ); movies.push_back(aMovie); //OutputDebugString(pItem->GetLabel().c_str()); //OutputDebugString("\n"); } } } }
int CVideoInfoDownloader::FindMovie(const std::string &movieTitle, int movieYear, MOVIELIST& movieList, CGUIDialogProgress *pProgress /* = NULL */) { //CLog::Log(LOGDEBUG,"CVideoInfoDownloader::FindMovie(%s)", strMovie.c_str()); if (pProgress) { // threaded version m_state = FIND_MOVIE; m_movieTitle = movieTitle; m_movieYear = movieYear; m_found = 0; if (IsRunning()) StopThread(); Create(); while (m_state != DO_NOTHING) { pProgress->Progress(); if (pProgress->IsCanceled()) { CloseThread(); return 0; } Sleep(1); } // transfer to our movielist m_movieList.swap(movieList); int found=m_found; CloseThread(); return found; } // unthreaded int success = InternalFindMovie(movieTitle, movieYear, movieList); // NOTE: this might be improved by rescraping if the match quality isn't high? if (success == 1 && movieList.empty()) { // no results. try without cleaning chars like '.' and '_' success = InternalFindMovie(movieTitle, movieYear, movieList, false); } return success; }
bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) { if (m_item == nullptr) return false; // determine the scraper for the item's path VIDEO::SScanSettings scanSettings; ADDON::ScraperPtr scraper = db.GetScraperForPath(m_item->GetPath(), scanSettings); if (scraper == nullptr) return false; // copy the scraper in case we need it again ADDON::ScraperPtr originalScraper(scraper); // get the item's correct title std::string itemTitle = m_searchTitle; if (itemTitle.empty()) itemTitle = m_item->GetMovieName(scanSettings.parent_name); CScraperUrl scraperUrl; VIDEO::CVideoInfoScanner scanner; bool needsRefresh = m_forceRefresh; bool hasDetails = false; bool ignoreNfo = m_ignoreNfo; // run this in a loop in case we need to refresh again bool failure = false; do { if (!ignoreNfo) { // check if there's an NFO for the item CNfoFile::NFOResult nfoResult = scanner.CheckForNFOFile(m_item.get(), scanSettings.parent_name_root, scraper, scraperUrl); // if there's no NFO remember it in case we have to refresh again if (nfoResult == CNfoFile::ERROR_NFO) ignoreNfo = true; else if (nfoResult != CNfoFile::NO_NFO) hasDetails = true; // if we are performing a forced refresh ask the user to choose between using a valid NFO and a valid scraper if (needsRefresh && IsModal() && !scraper->IsNoop() && (nfoResult == CNfoFile::URL_NFO || nfoResult == CNfoFile::COMBINED_NFO || nfoResult == CNfoFile::FULL_NFO)) { int heading = 20159; if (scraper->Content() == CONTENT_MOVIES) heading = 13346; else if (scraper->Content() == CONTENT_TVSHOWS) heading = m_item->m_bIsFolder ? 20351 : 20352; else if (scraper->Content() == CONTENT_MUSICVIDEOS) heading = 20393; if (CGUIDialogYesNo::ShowAndGetInput(heading, 20446)) { hasDetails = false; ignoreNfo = true; scraperUrl.Clear(); scraper = originalScraper; } } } // no need to re-fetch the episode guide for episodes if (scraper->Content() == CONTENT_TVSHOWS && !m_item->m_bIsFolder) hasDetails = true; // if we don't have an url or need to refresh anyway do the web search if (!hasDetails && (needsRefresh || scraperUrl.m_url.empty())) { SetTitle(StringUtils::Format(g_localizeStrings.Get(197).c_str(), scraper->Name().c_str())); SetText(itemTitle); SetProgress(0); // clear any cached data from the scraper scraper->ClearCache(); // create the info downloader for the scraper CVideoInfoDownloader infoDownloader(scraper); // try to find a matching item MOVIELIST itemResultList; int result = infoDownloader.FindMovie(itemTitle, itemResultList, GetProgressDialog()); // close the progress dialog MarkFinished(); if (result > 0) { // there are multiple matches for the item if (!itemResultList.empty()) { // choose the first match if (!IsModal()) scraperUrl = itemResultList.at(0); else { // ask the user what to do CGUIDialogSelect* selectDialog = static_cast<CGUIDialogSelect*>(g_windowManager.GetWindow(WINDOW_DIALOG_SELECT)); selectDialog->Reset(); selectDialog->SetHeading(scraper->Content() == CONTENT_TVSHOWS ? 20356 : 196); for (const auto& itemResult : itemResultList) selectDialog->Add(itemResult.strTitle); selectDialog->EnableButton(true, 413); // "Manual" selectDialog->Open(); // check if the user has chosen one of the results int selectedItem = selectDialog->GetSelectedItem(); if (selectedItem >= 0) scraperUrl = itemResultList.at(selectedItem); // the user hasn't chosen one of the results and but has chosen to manually enter a title to use else if (selectDialog->IsButtonPressed()) { // ask the user to input a title to use if (!CGUIKeyboardFactory::ShowAndGetInput(itemTitle, g_localizeStrings.Get(scraper->Content() == CONTENT_TVSHOWS ? 20357 : 16009), false)) return false; // go through the whole process again needsRefresh = true; continue; } // nothing else we can do else return false; } CLog::Log(LOGDEBUG, "CVideoLibraryRefreshingJob: user selected item '%s' with URL '%s'", scraperUrl.strTitle.c_str(), scraperUrl.m_url.at(0).m_url.c_str()); } } else if (result < 0 || !VIDEO::CVideoInfoScanner::DownloadFailed(GetProgressDialog())) { failure = true; break; } } // if the URL is still empty, check whether or not we're allowed // to prompt and ask the user to input a new search title if (!hasDetails && scraperUrl.m_url.empty()) { if (IsModal()) { // ask the user to input a title to use if (!CGUIKeyboardFactory::ShowAndGetInput(itemTitle, g_localizeStrings.Get(scraper->Content() == CONTENT_TVSHOWS ? 20357 : 16009), false)) return false; // go through the whole process again needsRefresh = true; continue; } // nothing else we can do failure = true; break; } // before we start downloading all the necessary information cleanup any existing artwork and hashes CTextureDatabase textureDb; if (textureDb.Open()) { for (const auto& artwork : m_item->GetArt()) textureDb.InvalidateCachedTexture(artwork.second); textureDb.Close(); } m_item->ClearArt(); // put together the list of items to refresh std::string path = m_item->GetPath(); CFileItemList items; if (m_item->HasVideoInfoTag() && m_item->GetVideoInfoTag()->m_iDbId > 0) { // for a tvshow we need to handle all paths of it std::vector<std::string> tvshowPaths; if (CMediaTypes::IsMediaType(m_item->GetVideoInfoTag()->m_type, MediaTypeTvShow) && m_refreshAll && db.GetPathsLinkedToTvShow(m_item->GetVideoInfoTag()->m_iDbId, tvshowPaths)) { for (const auto& tvshowPath : tvshowPaths) { CFileItemPtr tvshowItem(new CFileItem(*m_item->GetVideoInfoTag())); tvshowItem->SetPath(tvshowPath); items.Add(tvshowItem); } } // otherwise just add a copy of the item else items.Add(CFileItemPtr(new CFileItem(*m_item->GetVideoInfoTag()))); // update the path to the real path (instead of a videodb:// one) path = m_item->GetVideoInfoTag()->m_strPath; } else items.Add(CFileItemPtr(new CFileItem(*m_item))); // set the proper path of the list of items to lookup items.SetPath(m_item->m_bIsFolder ? URIUtils::GetParentPath(path) : URIUtils::GetDirectory(path)); int headingLabel = 198; if (scraper->Content() == CONTENT_TVSHOWS) { if (m_item->m_bIsFolder) headingLabel = 20353; else headingLabel = 20361; } else if (scraper->Content() == CONTENT_MUSICVIDEOS) headingLabel = 20394; // prepare the progress dialog for downloading all the necessary information SetTitle(g_localizeStrings.Get(headingLabel)); SetText(scraperUrl.strTitle); SetProgress(0); // remove any existing data for the item we're going to refresh if (m_item->GetVideoInfoTag()->m_iDbId > 0) { int dbId = m_item->GetVideoInfoTag()->m_iDbId; if (scraper->Content() == CONTENT_MOVIES) db.DeleteMovie(dbId); else if (scraper->Content() == CONTENT_MUSICVIDEOS) db.DeleteMusicVideo(dbId); else if (scraper->Content() == CONTENT_TVSHOWS) { if (!m_item->m_bIsFolder) db.DeleteEpisode(dbId); else if (m_refreshAll) db.DeleteTvShow(dbId); else db.DeleteDetailsForTvShow(dbId); } } // finally download the information for the item if (!scanner.RetrieveVideoInfo(items, scanSettings.parent_name, scraper->Content(), !ignoreNfo, &scraperUrl, m_refreshAll, GetProgressDialog())) { // something went wrong MarkFinished(); // check if the user cancelled if (!IsCancelled() && IsModal()) CGUIDialogOK::ShowAndGetInput(195, itemTitle); return false; } // retrieve the updated information from the database if (scraper->Content() == CONTENT_MOVIES) db.GetMovieInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); else if (scraper->Content() == CONTENT_MUSICVIDEOS) db.GetMusicVideoInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); else if (scraper->Content() == CONTENT_TVSHOWS) { // update tvshow info to get updated episode numbers if (m_item->m_bIsFolder) db.GetTvShowInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); else db.GetEpisodeInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); } // we're finally done MarkFinished(); break; } while (needsRefresh); if (failure && IsModal()) CGUIDialogOK::ShowAndGetInput(195, itemTitle); return true; }