From b9f5bd50ed115892c38d78262f23fcfaf64d212e Mon Sep 17 00:00:00 2001 From: Jonathan Marshall Date: Sat, 2 Nov 2013 23:49:17 +1300 Subject: [PATCH 1/5] [videodatabase] adds GetTvShowSeasons --- xbmc/video/VideoDatabase.cpp | 30 ++++++++++++++++++++++++------ xbmc/video/VideoDatabase.h | 1 + 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index f83d500..bc29ac6 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -3742,7 +3742,7 @@ string CVideoDatabase::GetArtForItem(int mediaId, const string &mediaType, const return GetSingleValue(query, m_pDS2); } -bool CVideoDatabase::GetTvShowSeasonArt(int showId, map > &seasonArt) +bool CVideoDatabase::GetTvShowSeasons(int showId, map &seasons) { try { @@ -3753,19 +3753,37 @@ bool CVideoDatabase::GetTvShowSeasonArt(int showId, map CStdString sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId); m_pDS2->query(sql.c_str()); - vector< pair > seasons; + seasons.clear(); while (!m_pDS2->eof()) { - seasons.push_back(make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asInt())); + seasons.insert(make_pair(m_pDS2->fv(1).get_asInt(), m_pDS2->fv(0).get_asInt())); m_pDS2->next(); } m_pDS2->close(); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, showId); + } + return false; +} + +bool CVideoDatabase::GetTvShowSeasonArt(int showId, map > &seasonArt) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1 + + map seasons; + GetTvShowSeasons(showId, seasons); - for (vector< pair >::const_iterator i = seasons.begin(); i != seasons.end(); ++i) + for (map::const_iterator i = seasons.begin(); i != seasons.end(); ++i) { map art; - GetArtForItem(i->first, "season", art); - seasonArt.insert(make_pair(i->second,art)); + GetArtForItem(i->second, "season", art); + seasonArt.insert(make_pair(i->first,art)); } return true; } diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 1532dde..21b349a 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -692,6 +692,7 @@ class CVideoDatabase : public CDatabase std::string GetArtForItem(int mediaId, const std::string &mediaType, const std::string &artType); bool RemoveArtForItem(int mediaId, const std::string &mediaType, const std::string &artType); bool RemoveArtForItem(int mediaId, const std::string &mediaType, const std::set &artTypes); + bool GetTvShowSeasons(int showId, std::map &seasons); bool GetTvShowSeasonArt(int mediaId, std::map > &seasonArt); bool GetArtTypes(const std::string &mediaType, std::vector &artTypes); -- 1.8.5.1 From e6eae9ac6052b5cd78cd3797b196f2e48957c6aa Mon Sep 17 00:00:00 2001 From: Jonathan Marshall Date: Sat, 2 Nov 2013 23:50:10 +1300 Subject: [PATCH 2/5] [videodatabase] move AddSeason() public. --- xbmc/video/VideoDatabase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 21b349a..9a11c54 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -703,6 +703,7 @@ class CVideoDatabase : public CDatabase virtual bool GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting); + int AddSeason(int showID, int season); int AddSet(const CStdString& strSet); void ClearMovieSet(int idMovie); void SetMovieSet(int idMovie, int idSet); @@ -733,7 +734,6 @@ class CVideoDatabase : public CDatabase int AddTvShow(const CStdString& strPath); int AddMusicVideo(const CStdString& strFilenameAndPath); - int AddSeason(int showID, int season); // link functions - these two do all the work void AddLinkToActor(const char *table, int actorID, const char *secondField, int secondID, const CStdString &role, int order); -- 1.8.5.1 From 84cc9e0d43e3e30599ad83666e9e7fc037b8f533 Mon Sep 17 00:00:00 2001 From: Jonathan Marshall Date: Sat, 2 Nov 2013 23:48:24 +1300 Subject: [PATCH 3/5] [scrapers] adds GetArt function to (video) scraper, allowing art to be fetched given the video identifier. --- xbmc/addons/Scraper.cpp | 38 ++++++++++++++++++++++++++++++++++++++ xbmc/addons/Scraper.h | 3 +++ xbmc/video/VideoInfoDownloader.cpp | 5 +++++ xbmc/video/VideoInfoDownloader.h | 7 +++++++ 4 files changed, 53 insertions(+) diff --git a/xbmc/addons/Scraper.cpp b/xbmc/addons/Scraper.cpp index b00d968..6cd5241 100644 --- a/xbmc/addons/Scraper.cpp +++ b/xbmc/addons/Scraper.cpp @@ -925,6 +925,44 @@ EPISODELIST CScraper::GetEpisodeList(XFILE::CCurlFile &fcurl, const CScraperUrl return vcep; } +// takes URL; returns true and populates art XML details on success, false otherwise +bool CScraper::GetArt(XFILE::CCurlFile &fcurl, const std::string &id, CVideoInfoTag &video) +{ + CLog::Log(LOGDEBUG, "%s: Reading art for '%s' using %s scraper " + "(file: '%s', content: '%s', version: '%s')", __FUNCTION__, id.c_str(), Name().c_str(), Path().c_str(), + ADDON::TranslateContent(Content()).c_str(), Version().c_str()); + + video.Reset(); + vector vcsIn; + CScraperUrl scurl; + vcsIn.push_back(id); + vector vcsOut = RunNoThrow("GetArt", scurl, fcurl, &vcsIn); + + // parse XML output + bool fRet(false); + for (CStdStringArray::const_iterator i = vcsOut.begin(); i != vcsOut.end(); ++i) + { + CXBMCTinyXML doc; + doc.Parse(*i, TIXML_ENCODING_UTF8); + if (!doc.RootElement()) + { + CLog::Log(LOGERROR, "%s: Unable to parse XML", __FUNCTION__); + continue; + } + + TiXmlHandle xhDoc(&doc); + TiXmlElement *pxeDetails = xhDoc.FirstChild("details").Element(); + if (!pxeDetails) + { + CLog::Log(LOGERROR, "%s: Invalid XML file (want
)", __FUNCTION__); + continue; + } + video.Load(pxeDetails, true/*fChain*/); + fRet = true; // but don't exit in case of chaining + } + return fRet; +} + // takes URL; returns true and populates video details on success, false otherwise bool CScraper::GetVideoDetails(XFILE::CCurlFile &fcurl, const CScraperUrl &scurl, bool fMovie/*else episode*/, CVideoInfoTag &video) diff --git a/xbmc/addons/Scraper.h b/xbmc/addons/Scraper.h index 40a90e5..b7e431c 100644 --- a/xbmc/addons/Scraper.h +++ b/xbmc/addons/Scraper.h @@ -18,6 +18,8 @@ * . * */ + +#include #include "addons/Addon.h" #include "XBDateTime.h" #include "utils/ScraperUrl.h" @@ -146,6 +148,7 @@ class CScraper : public CAddon CAlbum &album); bool GetArtistDetails(XFILE::CCurlFile &fcurl, const CScraperUrl &scurl, const CStdString &sSearch, CArtist &artist); + bool GetArt(XFILE::CCurlFile &fcurl, const std::string &id, CVideoInfoTag &video); private: CScraper(const CScraper &rhs); diff --git a/xbmc/video/VideoInfoDownloader.cpp b/xbmc/video/VideoInfoDownloader.cpp index f33ac8a..5d84734 100644 --- a/xbmc/video/VideoInfoDownloader.cpp +++ b/xbmc/video/VideoInfoDownloader.cpp @@ -191,6 +191,11 @@ bool CVideoInfoDownloader::GetDetails(const CScraperUrl &url, return m_info->GetVideoDetails(*m_http, url, true/*fMovie*/, movieDetails); } +bool CVideoInfoDownloader::GetArt(const std::string &id, CVideoInfoTag &details) +{ + return m_info->GetArt(*m_http, id, details); +} + bool CVideoInfoDownloader::GetEpisodeDetails(const CScraperUrl &url, CVideoInfoTag &movieDetails, CGUIDialogProgress *pProgress /* = NULL */) diff --git a/xbmc/video/VideoInfoDownloader.h b/xbmc/video/VideoInfoDownloader.h index 22ac229..75bc341 100644 --- a/xbmc/video/VideoInfoDownloader.h +++ b/xbmc/video/VideoInfoDownloader.h @@ -59,6 +59,13 @@ class CVideoInfoDownloader : public CThread static void ShowErrorDialog(const ADDON::CScraperError &sce); + /*! \brief Grab art URLs for an item with the scraper + \param id the unique identifier used by the scraper to describe the item. + \param details [out] the video info tag structure to fill with art. + \return true on success, false on failure. + */ + bool GetArt(const std::string &id, CVideoInfoTag &details); + protected: enum LOOKUP_STATE { DO_NOTHING = 0, FIND_MOVIE = 1, -- 1.8.5.1 From 27063540477a8caa3ca96a22359c5655c84ff94c Mon Sep 17 00:00:00 2001 From: Jonathan Marshall Date: Sat, 2 Nov 2013 23:53:14 +1300 Subject: [PATCH 4/5] [videoscanner] refresh season art if a new season is found that isn't recorded in the database yet. Fixes #14339 --- xbmc/video/VideoInfoScanner.cpp | 33 ++++++++++++++++++++++++++++++++- xbmc/video/VideoInfoScanner.h | 2 ++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index cef6aa4..da0c0c5 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -1308,6 +1308,10 @@ pDlgProgress->Progress(); } + bool updateSeasons = false; + map seasons; + m_database.GetTvShowSeasons(showInfo.m_iDbId, seasons); + EPISODELIST episodes; bool hasEpisodeGuide = false; @@ -1356,6 +1360,8 @@ } if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, true, &showInfo) < 0) return INFO_ERROR; + if (seasons.find(item.GetVideoInfoTag()->m_iSeason) == seasons.end()) + updateSeasons = true; continue; } @@ -1482,6 +1488,8 @@ if (AddVideo(&item, CONTENT_TVSHOWS, file->isFolder, useLocal, &showInfo) < 0) return INFO_ERROR; + if (seasons.find(item.GetVideoInfoTag()->m_iSeason) == seasons.end()) + updateSeasons = true; } else { @@ -1490,9 +1498,27 @@ file->cDate.GetAsLocalizedDate().c_str(), file->strTitle.c_str()); } } + if (updateSeasons) + UpdateSeasons(showInfo, scraper, useLocal); return INFO_ADDED; } + void CVideoInfoScanner::UpdateSeasons(const CVideoInfoTag &showInfo, const ADDON::ScraperPtr &scraper, bool useLocal) + { + map > seasonArt; + m_database.GetTvShowSeasonArt(showInfo.m_iDbId, seasonArt); + CVideoInfoTag details; + CVideoInfoDownloader loader(scraper); + loader.GetArt(showInfo.m_strIMDBNumber, details); + details.m_strPath = showInfo.m_strPath; + GetSeasonThumbs(details, seasonArt, CVideoThumbLoader::GetArtTypes("season"), useLocal); + for (map >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i) + { + int seasonID = m_database.AddSeason(showInfo.m_iDbId, i->first); + m_database.SetArtForItem(seasonID, "season", i->second); + } + } + CStdString CVideoInfoScanner::GetnfoFile(CFileItem *item, bool bGrabAny) const { CStdString nfoFile; @@ -1717,6 +1743,11 @@ } for (int season = -1; season <= maxSeasons; season++) { + // skip if we already have some art + map >::const_iterator i = seasonArt.find(season); + if (i != seasonArt.end() && !i->second.empty()) + continue; + map art; if (useLocal) { @@ -1770,7 +1801,7 @@ art.insert(make_pair(artTypes.front(), image)); } - seasonArt.insert(make_pair(season, art)); + seasonArt[season] = art; } } diff --git a/xbmc/video/VideoInfoScanner.h b/xbmc/video/VideoInfoScanner.h index bb177d4..1ea47fd 100644 --- a/xbmc/video/VideoInfoScanner.h +++ b/xbmc/video/VideoInfoScanner.h @@ -212,6 +212,8 @@ */ INFO_RET OnProcessSeriesFolder(EPISODELIST& files, const ADDON::ScraperPtr &scraper, bool useLocal, const CVideoInfoTag& showInfo, CGUIDialogProgress* pDlgProgress = NULL); + void UpdateSeasons(const CVideoInfoTag &showInfo, const ADDON::ScraperPtr &scraper, bool useLocal); + void EnumerateSeriesFolder(CFileItem* item, EPISODELIST& episodeList); bool EnumerateEpisodeItem(const CFileItem *item, EPISODELIST& episodeList); bool ProcessItemByVideoInfoTag(const CFileItem *item, EPISODELIST &episodeList); -- 1.8.5.1 From 8ea62d7ff8936e00fe2e30b9f5a0a293dc38ab94 Mon Sep 17 00:00:00 2001 From: Jonathan Marshall Date: Sat, 2 Nov 2013 23:53:34 +1300 Subject: [PATCH 5/5] REMOVEME: updated thetvdb.com scraper to support art updates --- addons/metadata.tvdb.com/tvdb.xml | 59 +++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/addons/metadata.tvdb.com/tvdb.xml b/addons/metadata.tvdb.com/tvdb.xml index 4e2cc2f..2636c56 100644 --- a/addons/metadata.tvdb.com/tvdb.xml +++ b/addons/metadata.tvdb.com/tvdb.xml @@ -99,57 +99,74 @@ <Actor>.*?<Image>([^<]*)</Image>.*?<Name>([^<]*)</Name>.*?<Role>([^<]*) - + + + + + .*/(.*).zip + + + + + + + + + + + + + + + + + + + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>series</BannerType>[^<]*<BannerType2>graphical</BannerType2>[^<]*<Language>$INFO[language]</Language> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>series</BannerType>[^<]*<BannerType2>graphical</BannerType2>[^<]*<Language>((?!$INFO[language])[a-z])*</Language> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>series</BannerType>[^<]*<BannerType2>text</BannerType2>[^<]*<Language>$INFO[language]</Language> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>series</BannerType>[^<]*<BannerType2>text</BannerType2>[^<]*<Language>((?!$INFO[language])[a-z])*</Language> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>series</BannerType>[^<]*<BannerType2>blank</BannerType2>[^<]*<Language></Language> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>season</BannerType>[^<]*<BannerType2>season</BannerType2>[^<]*<Language>$INFO[language]</Language>[^<]*[^S]*Season>([0-9]+)</Season> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>season</BannerType>[^<]*<BannerType2>season</BannerType2>[^<]*<Language>((?!$INFO[language])[a-z])*</Language>[^<]*[^S]*Season>([0-9]+)</Season> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>season</BannerType>[^<]*<BannerType2>seasonwide</BannerType2>[^<]*<Language>$INFO[language]</Language>[^<]*[^S]*Season>([0-9]+)</Season> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>season</BannerType>[^<]*<BannerType2>seasonwide</BannerType2>[^<]*<Language>((?!$INFO[language])[a-z])*</Language>[^<]*[^S]*Season>([0-9]+)</Season> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>poster</BannerType> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>poster</BannerType> - - + + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>fanart</BannerType>[^<]*<BannerType2>([^<]*)</BannerType2>[^<]*<Colors>([^<]*)</Colors>[^<]*<Language>$INFO[language]</Language> - + <BannerPath>([^<]*)</BannerPath>[^<]*<BannerType>fanart</BannerType>[^<]*<BannerType2>([^<]*)</BannerType2>[^<]*<Colors>([^<]*)</Colors>[^<]*<Language>((?!$INFO[language])[a-z])*</Language> - - .*/(.*).zip - - - - - + -- 1.8.5.1