* 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
This commit is contained in:
Reinhard Pointner 2010-11-09 08:04:12 +00:00
parent 78d2a3c44a
commit 13a1b3fa6a
16 changed files with 95 additions and 30 deletions

View File

@ -70,12 +70,24 @@ public class EpisodeBindingBean {
} }
@Define("air") @Define("airdate")
public Date airdate() { public Date airdate() {
return episode.airdate(); return episode.airdate();
} }
@Define("absolute")
public Integer getAbsoluteEpisodeNumber() {
return episode.getAbsolute();
}
@Define("special")
public Integer getSpecialNumber() {
return episode.getSpecial();
}
@Define("vc") @Define("vc")
public String getVideoCodec() { public String getVideoCodec() {
// e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc. // e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc.

View File

@ -35,10 +35,10 @@ public class SeasonEpisodeMatcher {
SxE seasonEpisode = new SxE(match.group(1), match.group(2)); SxE seasonEpisode = new SxE(match.group(1), match.group(2));
// interpret match as episode number only // 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 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);
} }
}; };
} }

View File

@ -27,12 +27,12 @@ public class SeasonEpisodeMetric implements SimilarityMetric {
for (SxE sxe1 : sxeVector1) { for (SxE sxe1 : sxeVector1) {
for (SxE sxe2 : sxeVector2) { 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 // vectors have at least one perfect episode match in common
return 1; 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 // at least we have a partial match
similarity = 0.5f; similarity = 0.5f;
} }

View File

@ -8,8 +8,10 @@ expr[a1]: n
expr[a2]: s expr[a2]: s
expr[a3]: e expr[a3]: e
expr[a4]: t expr[a4]: t
expr[a5]: air expr[a5]: airdate
expr[a6]: episode expr[a6]: absolute
expr[a7]: special
expr[a8]: episode
# simple mediainfo expressions # simple mediainfo expressions
expr[b1]: vc expr[b1]: vc

View File

@ -291,7 +291,7 @@ class EpisodeFormatDialog extends JDialog {
episode = EpisodeFormat.getDefaultInstance().parseObject(persistentSampleEpisode.getValue()); episode = EpisodeFormat.getDefaultInstance().parseObject(persistentSampleEpisode.getValue());
} catch (Exception e) { } catch (Exception e) {
// default sample // 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 // restore media file

View File

@ -2,11 +2,12 @@
package net.sourceforge.filebot.ui.panel.rename; package net.sourceforge.filebot.ui.panel.rename;
import static java.util.Collections.*;
import static net.sourceforge.filebot.hash.VerificationUtilities.*; import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import java.io.File; import java.io.File;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import net.sourceforge.filebot.similarity.DateMetric; import net.sourceforge.filebot.similarity.DateMetric;
import net.sourceforge.filebot.similarity.FileSizeMetric; import net.sourceforge.filebot.similarity.FileSizeMetric;
@ -27,7 +28,7 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
@Override @Override
public float getSimilarity(Object o1, Object o2) { 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); return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2);
} }
@ -47,7 +48,13 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
@Override @Override
public float getSimilarity(Object o1, Object o2) { 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) { if (object instanceof Episode) {
Episode episode = (Episode) object; Episode episode = (Episode) object;
// create SxE from episode // get SxE from episode, both SxE for season/episode numbering and SxE for absolute episode numbering
return singleton(new SxE(episode.getSeason(), episode.getEpisode())); 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); return super.parse(object);

View File

@ -149,7 +149,7 @@ public class AnidbClient implements EpisodeListProvider {
String airdate = selectString(".//date/@rel", node); String airdate = selectString(".//date/@rel", node);
// no seasons for anime // 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")));
} }
} }

View File

@ -13,6 +13,9 @@ public class Episode implements Serializable {
private Integer episode; private Integer episode;
private String title; private String title;
// absolute episode number
private Integer absolute;
// special number // special number
private Integer special; private Integer special;
@ -26,15 +29,16 @@ public class Episode implements Serializable {
public Episode(String seriesName, Integer season, Integer episode, String title) { 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.seriesName = seriesName;
this.season = season; this.season = season;
this.episode = episode; this.episode = episode;
this.title = title; this.title = title;
this.absolute = absolute;
this.special = special; this.special = special;
this.airdate = airdate; this.airdate = airdate;
} }
@ -60,6 +64,11 @@ public class Episode implements Serializable {
} }
public Integer getAbsolute() {
return absolute;
}
public Integer getSpecial() { public Integer getSpecial() {
return special; return special;
} }

View File

@ -88,7 +88,7 @@ public class EpisodeFormat extends Format {
} }
if ((m = sxePattern.matcher(source)).find()) { 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) if (m.group(2) == null)
episode = new Integer(m.group(3)); episode = new Integer(m.group(3));
else else
@ -102,7 +102,7 @@ public class EpisodeFormat extends Format {
// did parse input // did parse input
pos.setIndex(source.length()); 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 // failed to parse input

View File

@ -106,7 +106,7 @@ public class IMDbClient implements EpisodeListProvider {
// e.g. 20 May 2003 // e.g. 20 May 2003
String airdate = selectString("./following::STRONG", node); 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; return episodes;

View File

@ -177,7 +177,7 @@ public class TVDotComClient implements EpisodeListProvider {
// add episode if SxE info has been found // add episode if SxE info has been found
if (season != null && episode != null) { 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));
} }
} }

View File

@ -86,6 +86,7 @@ public class TVRageClient implements EpisodeListProvider {
for (Node node : selectNodes("//episode", dom)) { for (Node node : selectNodes("//episode", dom)) {
String title = getTextContent("title", node); String title = getTextContent("title", node);
Integer episodeNumber = getIntegerContent("seasonnum", node); Integer episodeNumber = getIntegerContent("seasonnum", node);
Integer absoluteNumber = getIntegerContent("epnum", node);
String seasonIdentifier = getAttribute("no", node.getParentNode()); String seasonIdentifier = getAttribute("no", node.getParentNode());
Integer seasonNumber = seasonIdentifier == null ? null : new Integer(seasonIdentifier); Integer seasonNumber = seasonIdentifier == null ? null : new Integer(seasonIdentifier);
Date airdate = Date.parse(getTextContent("airdate", node), "yyyy-MM-dd"); Date airdate = Date.parse(getTextContent("airdate", node), "yyyy-MM-dd");
@ -95,10 +96,10 @@ public class TVRageClient implements EpisodeListProvider {
// handle as special episode // handle as special episode
seasonNumber = getIntegerContent("season", node); seasonNumber = getIntegerContent("season", node);
int specialNumber = filterBySeason(specials, seasonNumber).size() + 1; 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 { } else {
// handle as normal episode // 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));
} }
} }

View File

@ -79,6 +79,13 @@ public class TheTVDBClient implements EpisodeListProvider {
public List<SearchResult> search(String query, Locale language) throws Exception { public List<SearchResult> 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()); URL url = getResource(null, "/api/GetSeries.php?seriesname=" + URLEncoder.encode(query, "UTF-8") + "&language=" + language.getLanguage());
Document dom = getDocument(url); Document dom = getDocument(url);
@ -86,10 +93,11 @@ public class TheTVDBClient implements EpisodeListProvider {
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size()); List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
for (Node node : nodes) { for (Node node : nodes) {
int seriesId = Integer.parseInt(getTextContent("seriesid", node)); int seriesId = getIntegerContent("seriesid", node);
String seriesName = getTextContent("SeriesName", node); String seriesName = getTextContent("SeriesName", node);
searchResults.add(new TheTVDBSearchResult(seriesName, seriesId)); searchResults.add(new TheTVDBSearchResult(seriesName, seriesId));
cache.putSeriesId(seriesName, language, seriesId);
} }
return searchResults; return searchResults;
@ -134,6 +142,7 @@ public class TheTVDBClient implements EpisodeListProvider {
for (Node node : nodes) { for (Node node : nodes) {
String episodeName = getTextContent("EpisodeName", node); String episodeName = getTextContent("EpisodeName", node);
Integer episodeNumber = getIntegerContent("EpisodeNumber", node); Integer episodeNumber = getIntegerContent("EpisodeNumber", node);
Integer absoluteNumber = getIntegerContent("absolute_number", node);
Integer seasonNumber = getIntegerContent("SeasonNumber", node); Integer seasonNumber = getIntegerContent("SeasonNumber", node);
Date airdate = Date.parse(getTextContent("FirstAired", node), "yyyy-MM-dd"); 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; 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 { } else {
// handle as normal episode // 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) { 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) { 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) { 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) if (element != null)
return (Integer) element.getValue(); return (Integer) element.getValue();
@ -354,13 +378,13 @@ public class TheTVDBClient implements EpisodeListProvider {
public void putEpisodeList(int seriesId, Locale language, List<Episode> episodes) { public void putEpisodeList(int seriesId, Locale language, List<Episode> 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") @SuppressWarnings("unchecked")
public List<Episode> getEpisodeList(int seriesId, Locale language) { public List<Episode> 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) if (element != null)
return (List<Episode>) element.getValue(); return (List<Episode>) element.getValue();

View File

@ -84,6 +84,7 @@ public class AnidbClientTest {
assertEquals("Monster", first.getSeriesName()); assertEquals("Monster", first.getSeriesName());
assertEquals("Herr Dr. Tenma", first.getTitle()); assertEquals("Herr Dr. Tenma", first.getTitle());
assertEquals("1", first.getEpisode().toString()); assertEquals("1", first.getEpisode().toString());
assertEquals("1", first.getAbsolute().toString());
assertEquals(null, first.getSeason()); assertEquals(null, first.getSeason());
assertEquals("2004-04-07", first.airdate().toString()); assertEquals("2004-04-07", first.airdate().toString());
} }
@ -97,9 +98,10 @@ public class AnidbClientTest {
Episode first = list.get(0); 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("Shadow of the Moon, The Sea of Shadow - Chapter 1", first.getTitle());
assertEquals("1", first.getEpisode().toString()); assertEquals("1", first.getEpisode().toString());
assertEquals("1", first.getAbsolute().toString());
assertEquals(null, first.getSeason()); assertEquals(null, first.getSeason());
assertEquals("2002-04-09", first.airdate().toString()); assertEquals("2002-04-09", first.airdate().toString());
} }

View File

@ -46,6 +46,7 @@ public class TVRageClientTest {
assertEquals("Chosen", chosen.getTitle()); assertEquals("Chosen", chosen.getTitle());
assertEquals("22", chosen.getEpisode().toString()); assertEquals("22", chosen.getEpisode().toString());
assertEquals("7", chosen.getSeason().toString()); assertEquals("7", chosen.getSeason().toString());
assertEquals("144", chosen.getAbsolute().toString());
assertEquals("2003-05-20", chosen.airdate().toString()); assertEquals("2003-05-20", chosen.airdate().toString());
} }
@ -62,6 +63,7 @@ public class TVRageClientTest {
assertEquals("Unaired Pilot", first.getTitle()); assertEquals("Unaired Pilot", first.getTitle());
assertEquals("0", first.getEpisode().toString()); assertEquals("0", first.getEpisode().toString());
assertEquals("0", first.getSeason().toString()); assertEquals("0", first.getSeason().toString());
assertEquals("0", first.getAbsolute().toString());
assertEquals(null, first.airdate()); assertEquals(null, first.airdate());
} }

View File

@ -66,6 +66,7 @@ public class TheTVDBClientTest {
assertEquals("Welcome to the Hellmouth (1)", first.getTitle()); assertEquals("Welcome to the Hellmouth (1)", first.getTitle());
assertEquals("1", first.getEpisode().toString()); assertEquals("1", first.getEpisode().toString());
assertEquals("1", first.getSeason().toString()); assertEquals("1", first.getSeason().toString());
assertEquals("1", first.getAbsolute().toString());
assertEquals("1997-03-10", first.airdate().toString()); assertEquals("1997-03-10", first.airdate().toString());
// check special episode // check special episode
@ -74,6 +75,7 @@ public class TheTVDBClientTest {
assertEquals("Unaired Pilot", last.getTitle()); assertEquals("Unaired Pilot", last.getTitle());
assertEquals("1", last.getSeason().toString()); assertEquals("1", last.getSeason().toString());
assertEquals(null, last.getEpisode()); assertEquals(null, last.getEpisode());
assertEquals("1", first.getAbsolute().toString());
assertEquals("1", last.getSpecial().toString()); assertEquals("1", last.getSpecial().toString());
assertEquals(null, last.airdate()); assertEquals(null, last.airdate());
} }
@ -91,6 +93,7 @@ public class TheTVDBClientTest {
assertEquals("Wax Lion", first.getTitle()); assertEquals("Wax Lion", first.getTitle());
assertEquals("1", first.getEpisode().toString()); assertEquals("1", first.getEpisode().toString());
assertEquals("1", first.getSeason().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()); assertEquals("2004-03-12", first.airdate().toString());
} }