From 13a1b3fa6acd95610e5b73d41855ee71a0a69dbd Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Tue, 9 Nov 2010 08:04:12 +0000 Subject: [PATCH] * add support for absolute episode numbers alongside SxE -> add absolute episode number support to TVRage and TheTVDB -> check against absolute episode number when matching files and episodes -> add naming scheme binding for absolute episode number --- .../filebot/format/EpisodeBindingBean.java | 14 ++++++- .../similarity/SeasonEpisodeMatcher.java | 4 +- .../similarity/SeasonEpisodeMetric.java | 4 +- .../rename/EpisodeBindingDialog.properties | 6 ++- .../ui/panel/rename/EpisodeFormatDialog.java | 2 +- .../panel/rename/MatchSimilarityMetric.java | 20 +++++++--- .../sourceforge/filebot/web/AnidbClient.java | 2 +- .../net/sourceforge/filebot/web/Episode.java | 13 ++++++- .../filebot/web/EpisodeFormat.java | 4 +- .../sourceforge/filebot/web/IMDbClient.java | 2 +- .../filebot/web/TVDotComClient.java | 2 +- .../sourceforge/filebot/web/TVRageClient.java | 5 ++- .../filebot/web/TheTVDBClient.java | 38 +++++++++++++++---- .../filebot/web/AnidbClientTest.java | 4 +- .../filebot/web/TVRageClientTest.java | 2 + .../filebot/web/TheTVDBClientTest.java | 3 ++ 16 files changed, 95 insertions(+), 30 deletions(-) diff --git a/source/net/sourceforge/filebot/format/EpisodeBindingBean.java b/source/net/sourceforge/filebot/format/EpisodeBindingBean.java index e8176856..1cd51bea 100644 --- a/source/net/sourceforge/filebot/format/EpisodeBindingBean.java +++ b/source/net/sourceforge/filebot/format/EpisodeBindingBean.java @@ -70,12 +70,24 @@ public class EpisodeBindingBean { } - @Define("air") + @Define("airdate") public Date airdate() { return episode.airdate(); } + @Define("absolute") + public Integer getAbsoluteEpisodeNumber() { + return episode.getAbsolute(); + } + + + @Define("special") + public Integer getSpecialNumber() { + return episode.getSpecial(); + } + + @Define("vc") public String getVideoCodec() { // e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc. diff --git a/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java b/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java index ade85840..62098f6d 100644 --- a/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java +++ b/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java @@ -35,10 +35,10 @@ public class SeasonEpisodeMatcher { SxE seasonEpisode = new SxE(match.group(1), match.group(2)); // interpret match as episode number only - SxE episodeOnly = new SxE(null, match.group(1) + match.group(2)); + SxE absoluteEpisode = new SxE(null, match.group(1) + match.group(2)); // return both matches, unless they are one and the same - return seasonEpisode.equals(episodeOnly) ? Collections.singleton(episodeOnly) : Arrays.asList(seasonEpisode, episodeOnly); + return seasonEpisode.equals(absoluteEpisode) ? Collections.singleton(absoluteEpisode) : Arrays.asList(seasonEpisode, absoluteEpisode); } }; } diff --git a/source/net/sourceforge/filebot/similarity/SeasonEpisodeMetric.java b/source/net/sourceforge/filebot/similarity/SeasonEpisodeMetric.java index 24876116..627c0579 100644 --- a/source/net/sourceforge/filebot/similarity/SeasonEpisodeMetric.java +++ b/source/net/sourceforge/filebot/similarity/SeasonEpisodeMetric.java @@ -27,12 +27,12 @@ public class SeasonEpisodeMetric implements SimilarityMetric { for (SxE sxe1 : sxeVector1) { for (SxE sxe2 : sxeVector2) { - if (sxe1.episode == sxe2.episode && sxe1.season == sxe2.season) { + if (sxe1.season == sxe2.season && sxe1.episode == sxe2.episode) { // vectors have at least one perfect episode match in common return 1; } - if (sxe1.episode == sxe2.episode || sxe1.season == sxe2.season) { + if (sxe1.season == sxe2.season || sxe1.episode == sxe2.episode) { // at least we have a partial match similarity = 0.5f; } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties index 83a95556..805b26c3 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeBindingDialog.properties @@ -8,8 +8,10 @@ expr[a1]: n expr[a2]: s expr[a3]: e expr[a4]: t -expr[a5]: air -expr[a6]: episode +expr[a5]: airdate +expr[a6]: absolute +expr[a7]: special +expr[a8]: episode # simple mediainfo expressions expr[b1]: vc diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java index 5e7c6870..b7cebd90 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java @@ -291,7 +291,7 @@ class EpisodeFormatDialog extends JDialog { episode = EpisodeFormat.getDefaultInstance().parseObject(persistentSampleEpisode.getValue()); } catch (Exception e) { // default sample - episode = new Episode("Dark Angel", 3, 1, "Labyrinth", null, new Date(2009, 6, 1)); + episode = new Episode("Dark Angel", 3, 1, "Labyrinth", 42, null, new Date(2009, 6, 1)); } // restore media file diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java index ecb832e7..d2bfe442 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MatchSimilarityMetric.java @@ -2,11 +2,12 @@ package net.sourceforge.filebot.ui.panel.rename; -import static java.util.Collections.*; import static net.sourceforge.filebot.hash.VerificationUtilities.*; import java.io.File; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import net.sourceforge.filebot.similarity.DateMetric; import net.sourceforge.filebot.similarity.FileSizeMetric; @@ -27,7 +28,7 @@ public enum MatchSimilarityMetric implements SimilarityMetric { @Override public float getSimilarity(Object o1, Object o2) { - // order of arguments is logically irrelevant, but we might be able to save us a call to File.length() this way + // order of arguments is logically irrelevant, but we might be able to save us a call to File.length() which is quite costly return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2); } @@ -47,7 +48,13 @@ public enum MatchSimilarityMetric implements SimilarityMetric { @Override public float getSimilarity(Object o1, Object o2) { - return Math.max(SeasonEpisode.getSimilarity(o1, o2), AirDate.getSimilarity(o1, o2)); + float sxeSimilarity = SeasonEpisode.getSimilarity(o1, o2); + + // break if SxE is a perfect match already + if (sxeSimilarity >= 1) + return sxeSimilarity; + + return Math.max(sxeSimilarity, AirDate.getSimilarity(o1, o2)); } }), @@ -60,8 +67,11 @@ public enum MatchSimilarityMetric implements SimilarityMetric { if (object instanceof Episode) { Episode episode = (Episode) object; - // create SxE from episode - return singleton(new SxE(episode.getSeason(), episode.getEpisode())); + // get SxE from episode, both SxE for season/episode numbering and SxE for absolute episode numbering + SxE seasonEpisode = new SxE(episode.getSeason(), episode.getEpisode()); + SxE absoluteEpisode = new SxE(null, episode.getAbsolute()); + + return seasonEpisode.equals(absoluteEpisode) ? Collections.singleton(absoluteEpisode) : Arrays.asList(seasonEpisode, absoluteEpisode); } return super.parse(object); diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index 6b3f47ab..d9bd10bc 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -149,7 +149,7 @@ public class AnidbClient implements EpisodeListProvider { String airdate = selectString(".//date/@rel", node); // no seasons for anime - episodes.add(new Episode(animeTitle, null, number, title, null, Date.parse(airdate, "yyyy-MM-dd"))); + episodes.add(new Episode(animeTitle, null, number, title, number, null, Date.parse(airdate, "yyyy-MM-dd"))); } } diff --git a/source/net/sourceforge/filebot/web/Episode.java b/source/net/sourceforge/filebot/web/Episode.java index 421d0bbf..abc72904 100644 --- a/source/net/sourceforge/filebot/web/Episode.java +++ b/source/net/sourceforge/filebot/web/Episode.java @@ -13,6 +13,9 @@ public class Episode implements Serializable { private Integer episode; private String title; + // absolute episode number + private Integer absolute; + // special number private Integer special; @@ -26,15 +29,16 @@ public class Episode implements Serializable { public Episode(String seriesName, Integer season, Integer episode, String title) { - this(seriesName, season, episode, title, null, null); + this(seriesName, season, episode, title, null, null, null); } - public Episode(String seriesName, Integer season, Integer episode, String title, Integer special, Date airdate) { + public Episode(String seriesName, Integer season, Integer episode, String title, Integer absolute, Integer special, Date airdate) { this.seriesName = seriesName; this.season = season; this.episode = episode; this.title = title; + this.absolute = absolute; this.special = special; this.airdate = airdate; } @@ -60,6 +64,11 @@ public class Episode implements Serializable { } + public Integer getAbsolute() { + return absolute; + } + + public Integer getSpecial() { return special; } diff --git a/source/net/sourceforge/filebot/web/EpisodeFormat.java b/source/net/sourceforge/filebot/web/EpisodeFormat.java index 94811f30..7a91b02a 100644 --- a/source/net/sourceforge/filebot/web/EpisodeFormat.java +++ b/source/net/sourceforge/filebot/web/EpisodeFormat.java @@ -88,7 +88,7 @@ public class EpisodeFormat extends Format { } if ((m = sxePattern.matcher(source)).find()) { - season = new Integer(m.group(1)); + season = (m.group(1) == null) ? null : new Integer(m.group(1)); if (m.group(2) == null) episode = new Integer(m.group(3)); else @@ -102,7 +102,7 @@ public class EpisodeFormat extends Format { // did parse input pos.setIndex(source.length()); - return new Episode(name, season, episode, title, special, airdate); + return new Episode(name, season, episode, title, season == null ? episode : null, special, airdate); } // failed to parse input diff --git a/source/net/sourceforge/filebot/web/IMDbClient.java b/source/net/sourceforge/filebot/web/IMDbClient.java index cef7bcb5..0f67efd7 100644 --- a/source/net/sourceforge/filebot/web/IMDbClient.java +++ b/source/net/sourceforge/filebot/web/IMDbClient.java @@ -106,7 +106,7 @@ public class IMDbClient implements EpisodeListProvider { // e.g. 20 May 2003 String airdate = selectString("./following::STRONG", node); - episodes.add(new Episode(seriesName, season, episode, title, null, Date.parse(airdate, "dd MMMMM yyyyy"))); + episodes.add(new Episode(seriesName, season, episode, title, null, null, Date.parse(airdate, "dd MMMMM yyyyy"))); } return episodes; diff --git a/source/net/sourceforge/filebot/web/TVDotComClient.java b/source/net/sourceforge/filebot/web/TVDotComClient.java index eda90fbd..380bba77 100644 --- a/source/net/sourceforge/filebot/web/TVDotComClient.java +++ b/source/net/sourceforge/filebot/web/TVDotComClient.java @@ -177,7 +177,7 @@ public class TVDotComClient implements EpisodeListProvider { // add episode if SxE info has been found if (season != null && episode != null) { - episodes.add(new Episode(searchResult.getName(), season, episode, title, null, airdate)); + episodes.add(new Episode(searchResult.getName(), season, episode, title, null, null, airdate)); } } diff --git a/source/net/sourceforge/filebot/web/TVRageClient.java b/source/net/sourceforge/filebot/web/TVRageClient.java index 94948b29..e96adeb1 100644 --- a/source/net/sourceforge/filebot/web/TVRageClient.java +++ b/source/net/sourceforge/filebot/web/TVRageClient.java @@ -86,6 +86,7 @@ public class TVRageClient implements EpisodeListProvider { for (Node node : selectNodes("//episode", dom)) { String title = getTextContent("title", node); Integer episodeNumber = getIntegerContent("seasonnum", node); + Integer absoluteNumber = getIntegerContent("epnum", node); String seasonIdentifier = getAttribute("no", node.getParentNode()); Integer seasonNumber = seasonIdentifier == null ? null : new Integer(seasonIdentifier); Date airdate = Date.parse(getTextContent("airdate", node), "yyyy-MM-dd"); @@ -95,10 +96,10 @@ public class TVRageClient implements EpisodeListProvider { // handle as special episode seasonNumber = getIntegerContent("season", node); int specialNumber = filterBySeason(specials, seasonNumber).size() + 1; - specials.add(new Episode(seriesName, seasonNumber, null, title, specialNumber, airdate)); + specials.add(new Episode(seriesName, seasonNumber, null, title, null, specialNumber, airdate)); } else { // handle as normal episode - episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, title, null, airdate)); + episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, title, absoluteNumber, null, airdate)); } } diff --git a/source/net/sourceforge/filebot/web/TheTVDBClient.java b/source/net/sourceforge/filebot/web/TheTVDBClient.java index 033c832c..9189a24e 100644 --- a/source/net/sourceforge/filebot/web/TheTVDBClient.java +++ b/source/net/sourceforge/filebot/web/TheTVDBClient.java @@ -79,6 +79,13 @@ public class TheTVDBClient implements EpisodeListProvider { public List search(String query, Locale language) throws Exception { + // check if the exact series name is already cached + Integer cachedResult = cache.getSeriesId(query, language); + + if (cachedResult != null) + return Arrays.asList(new SearchResult[] { new TheTVDBSearchResult(query, cachedResult) }); + + // perform online search URL url = getResource(null, "/api/GetSeries.php?seriesname=" + URLEncoder.encode(query, "UTF-8") + "&language=" + language.getLanguage()); Document dom = getDocument(url); @@ -86,10 +93,11 @@ public class TheTVDBClient implements EpisodeListProvider { List searchResults = new ArrayList(nodes.size()); for (Node node : nodes) { - int seriesId = Integer.parseInt(getTextContent("seriesid", node)); + int seriesId = getIntegerContent("seriesid", node); String seriesName = getTextContent("SeriesName", node); searchResults.add(new TheTVDBSearchResult(seriesName, seriesId)); + cache.putSeriesId(seriesName, language, seriesId); } return searchResults; @@ -134,6 +142,7 @@ public class TheTVDBClient implements EpisodeListProvider { for (Node node : nodes) { String episodeName = getTextContent("EpisodeName", node); Integer episodeNumber = getIntegerContent("EpisodeNumber", node); + Integer absoluteNumber = getIntegerContent("absolute_number", node); Integer seasonNumber = getIntegerContent("SeasonNumber", node); Date airdate = Date.parse(getTextContent("FirstAired", node), "yyyy-MM-dd"); @@ -145,10 +154,10 @@ public class TheTVDBClient implements EpisodeListProvider { } Integer specialNumber = filterBySeason(specials, seasonNumber).size() + 1; - specials.add(new Episode(seriesName, seasonNumber, null, episodeName, specialNumber, airdate)); + specials.add(new Episode(seriesName, seasonNumber, null, episodeName, null, specialNumber, airdate)); } else { // handle as normal episode - episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, episodeName, null, airdate)); + episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, episodeName, absoluteNumber, null, airdate)); } if (episodeNumber == 1) { @@ -338,13 +347,28 @@ public class TheTVDBClient implements EpisodeListProvider { } + public void putSeriesId(String seriesName, Locale language, int seriesId) { + cache.put(new Element(key(host, "SeriesId", seriesName, language.getLanguage()), seriesId)); + } + + + public Integer getSeriesId(String seriesName, Locale language) { + Element element = cache.get(key(host, "SeriesId", seriesName, language.getLanguage())); + + if (element != null) + return (Integer) element.getValue(); + + return null; + } + + public void putSeasonId(int seriesId, int seasonNumber, int seasonId) { - cache.put(new Element(key(host, seriesId, seasonNumber, "SeasonId"), seasonId)); + cache.put(new Element(key(host, "SeasonId", seriesId, seasonNumber), seasonId)); } public Integer getSeasonId(int seriesId, int seasonNumber) { - Element element = cache.get(key(host, seriesId, seasonNumber, "SeasonId")); + Element element = cache.get(key(host, "SeasonId", seriesId, seasonNumber)); if (element != null) return (Integer) element.getValue(); @@ -354,13 +378,13 @@ public class TheTVDBClient implements EpisodeListProvider { public void putEpisodeList(int seriesId, Locale language, List episodes) { - cache.put(new Element(key(host, seriesId, language.getLanguage(), "EpisodeList"), episodes)); + cache.put(new Element(key(host, "EpisodeList", seriesId, language.getLanguage()), episodes)); } @SuppressWarnings("unchecked") public List getEpisodeList(int seriesId, Locale language) { - Element element = cache.get(key(host, seriesId, language.getLanguage(), "EpisodeList")); + Element element = cache.get(key(host, "EpisodeList", seriesId, language.getLanguage())); if (element != null) return (List) element.getValue(); diff --git a/test/net/sourceforge/filebot/web/AnidbClientTest.java b/test/net/sourceforge/filebot/web/AnidbClientTest.java index de2252bc..faf9e359 100644 --- a/test/net/sourceforge/filebot/web/AnidbClientTest.java +++ b/test/net/sourceforge/filebot/web/AnidbClientTest.java @@ -84,6 +84,7 @@ public class AnidbClientTest { assertEquals("Monster", first.getSeriesName()); assertEquals("Herr Dr. Tenma", first.getTitle()); assertEquals("1", first.getEpisode().toString()); + assertEquals("1", first.getAbsolute().toString()); assertEquals(null, first.getSeason()); assertEquals("2004-04-07", first.airdate().toString()); } @@ -97,9 +98,10 @@ public class AnidbClientTest { Episode first = list.get(0); - assertEquals("Juuni Kokuki", first.getSeriesName()); + assertEquals("Juuni Kokki", first.getSeriesName()); assertEquals("Shadow of the Moon, The Sea of Shadow - Chapter 1", first.getTitle()); assertEquals("1", first.getEpisode().toString()); + assertEquals("1", first.getAbsolute().toString()); assertEquals(null, first.getSeason()); assertEquals("2002-04-09", first.airdate().toString()); } diff --git a/test/net/sourceforge/filebot/web/TVRageClientTest.java b/test/net/sourceforge/filebot/web/TVRageClientTest.java index 17329568..e1913c09 100644 --- a/test/net/sourceforge/filebot/web/TVRageClientTest.java +++ b/test/net/sourceforge/filebot/web/TVRageClientTest.java @@ -46,6 +46,7 @@ public class TVRageClientTest { assertEquals("Chosen", chosen.getTitle()); assertEquals("22", chosen.getEpisode().toString()); assertEquals("7", chosen.getSeason().toString()); + assertEquals("144", chosen.getAbsolute().toString()); assertEquals("2003-05-20", chosen.airdate().toString()); } @@ -62,6 +63,7 @@ public class TVRageClientTest { assertEquals("Unaired Pilot", first.getTitle()); assertEquals("0", first.getEpisode().toString()); assertEquals("0", first.getSeason().toString()); + assertEquals("0", first.getAbsolute().toString()); assertEquals(null, first.airdate()); } diff --git a/test/net/sourceforge/filebot/web/TheTVDBClientTest.java b/test/net/sourceforge/filebot/web/TheTVDBClientTest.java index 1b407c95..cf65c480 100644 --- a/test/net/sourceforge/filebot/web/TheTVDBClientTest.java +++ b/test/net/sourceforge/filebot/web/TheTVDBClientTest.java @@ -66,6 +66,7 @@ public class TheTVDBClientTest { assertEquals("Welcome to the Hellmouth (1)", first.getTitle()); assertEquals("1", first.getEpisode().toString()); assertEquals("1", first.getSeason().toString()); + assertEquals("1", first.getAbsolute().toString()); assertEquals("1997-03-10", first.airdate().toString()); // check special episode @@ -74,6 +75,7 @@ public class TheTVDBClientTest { assertEquals("Unaired Pilot", last.getTitle()); assertEquals("1", last.getSeason().toString()); assertEquals(null, last.getEpisode()); + assertEquals("1", first.getAbsolute().toString()); assertEquals("1", last.getSpecial().toString()); assertEquals(null, last.airdate()); } @@ -91,6 +93,7 @@ public class TheTVDBClientTest { assertEquals("Wax Lion", first.getTitle()); assertEquals("1", first.getEpisode().toString()); assertEquals("1", first.getSeason().toString()); + assertEquals(null, first.getAbsolute()); // should be "1" but data has not yet been entered assertEquals("2004-03-12", first.airdate().toString()); }