* try improving support for multi-episodes while trying to not break anything else

This commit is contained in:
Reinhard Pointner 2013-09-18 05:02:55 +00:00
parent 247d6cbe22
commit db11b488c5
2 changed files with 93 additions and 114 deletions

View File

@ -1,7 +1,5 @@
package net.sourceforge.filebot.similarity; package net.sourceforge.filebot.similarity;
import static java.util.Collections.*; import static java.util.Collections.*;
import java.io.File; import java.io.File;
@ -19,7 +17,6 @@ import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE;
import net.sourceforge.filebot.web.Episode; import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.MultiEpisode; import net.sourceforge.filebot.web.MultiEpisode;
public class EpisodeMatcher extends Matcher<File, Object> { public class EpisodeMatcher extends Matcher<File, Object> {
public EpisodeMatcher(Collection<File> values, Collection<Episode> candidates, boolean strict) { public EpisodeMatcher(Collection<File> values, Collection<Episode> candidates, boolean strict) {
@ -27,7 +24,6 @@ public class EpisodeMatcher extends Matcher<File, Object> {
super(values, candidates, strict, strict ? StrictEpisodeMetrics.defaultSequence(false) : EpisodeMetrics.defaultSequence(false)); super(values, candidates, strict, strict ? StrictEpisodeMetrics.defaultSequence(false) : EpisodeMetrics.defaultSequence(false));
} }
@Override @Override
protected void deepMatch(Collection<Match<File, Object>> possibleMatches, int level) throws InterruptedException { protected void deepMatch(Collection<Match<File, Object>> possibleMatches, int level) throws InterruptedException {
Map<File, List<Episode>> episodeSets = new IdentityHashMap<File, List<Episode>>(); Map<File, List<Episode>> episodeSets = new IdentityHashMap<File, List<Episode>>();
@ -74,10 +70,9 @@ public class EpisodeMatcher extends Matcher<File, Object> {
} }
private final SeasonEpisodeMatcher seasonEpisodeMatcher = new SeasonEpisodeMatcher(SeasonEpisodeMatcher.DEFAULT_SANITY, true); private final SeasonEpisodeMatcher seasonEpisodeMatcher = new SeasonEpisodeMatcher(SeasonEpisodeMatcher.DEFAULT_SANITY, false);
private final Map<File, Set<SxE>> transformCache = synchronizedMap(new HashMap<File, Set<SxE>>(64, 4)); private final Map<File, Set<SxE>> transformCache = synchronizedMap(new HashMap<File, Set<SxE>>(64, 4));
private Set<SxE> parseEpisodeIdentifer(File file) { private Set<SxE> parseEpisodeIdentifer(File file) {
Set<SxE> result = transformCache.get(file); Set<SxE> result = transformCache.get(file);
if (result != null) { if (result != null) {
@ -95,7 +90,6 @@ public class EpisodeMatcher extends Matcher<File, Object> {
return result; return result;
} }
private boolean isMultiEpisode(Episode[] episodes) { private boolean isMultiEpisode(Episode[] episodes) {
// check episode sequence integrity // check episode sequence integrity
Integer seqIndex = null; Integer seqIndex = null;

View File

@ -1,8 +1,5 @@
package net.sourceforge.filebot.similarity; package net.sourceforge.filebot.similarity;
import static java.util.Arrays.*;
import static java.util.Collections.*; import static java.util.Collections.*;
import static java.util.regex.Pattern.*; import static java.util.regex.Pattern.*;
@ -10,13 +7,14 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class SeasonEpisodeMatcher { public class SeasonEpisodeMatcher {
public static final SeasonEpisodeFilter DEFAULT_SANITY = new SeasonEpisodeFilter(50, 50, 1000); public static final SeasonEpisodeFilter DEFAULT_SANITY = new SeasonEpisodeFilter(50, 50, 1000);
@ -24,7 +22,6 @@ public class SeasonEpisodeMatcher {
private SeasonEpisodePattern[] patterns; private SeasonEpisodePattern[] patterns;
private Pattern seasonPattern; private Pattern seasonPattern;
public SeasonEpisodeMatcher(SeasonEpisodeFilter sanity, boolean strict) { public SeasonEpisodeMatcher(SeasonEpisodeFilter sanity, boolean strict) {
patterns = new SeasonEpisodePattern[5]; patterns = new SeasonEpisodePattern[5];
@ -69,19 +66,27 @@ public class SeasonEpisodeMatcher {
} }
}; };
// match patterns like 01, 102, 1003 (enclosed in separators) // match patterns like 01, 102, 1003, 10102 (enclosed in separators)
patterns[4] = new SeasonEpisodePattern(sanity, "(?<!\\p{Alnum})([0-2]?\\d?)(\\d{2})(?!\\p{Alnum})") { patterns[4] = new SeasonEpisodePattern(sanity, "(?<!\\p{Alnum})([0-2]?\\d?)(\\d{2})(\\d{2})?(?!\\p{Alnum})") {
@Override @Override
protected Collection<SxE> process(MatchResult match) { protected Collection<SxE> process(MatchResult match) {
// interpret match as season and episode Set<SxE> sxe = new LinkedHashSet<SxE>(2);
SxE seasonEpisode = new SxE(match.group(1), match.group(2));
// interpret match as episode number only // interpret match as season and episode
SxE absoluteEpisode = new SxE(null, match.group(1) + match.group(2)); for (int i = 2; i <= match.groupCount(); i++) {
if (match.group(i) != null) {
sxe.add(new SxE(match.group(1), match.group(i)));
}
}
// interpret match both ways, as SxE match as well as episode number only match if it's not an double episode
if (sxe.size() < 2) {
sxe.add(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(absoluteEpisode) ? singleton(seasonEpisode) : asList(seasonEpisode, absoluteEpisode); return sxe;
} }
}; };
@ -94,13 +99,12 @@ public class SeasonEpisodeMatcher {
seasonPattern = compile("Season[-._ ]?(\\d{1,2})", CASE_INSENSITIVE | UNICODE_CASE); seasonPattern = compile("Season[-._ ]?(\\d{1,2})", CASE_INSENSITIVE | UNICODE_CASE);
} }
/** /**
* Try to get season and episode numbers for the given string. * Try to get season and episode numbers for the given string.
* *
* @param name match this string against the a set of know patterns * @param name
* @return the matches returned by the first pattern that returns any matches for this * match this string against the a set of know patterns
* string, or null if no pattern returned any matches * @return the matches returned by the first pattern that returns any matches for this string, or null if no pattern returned any matches
*/ */
public List<SxE> match(CharSequence name) { public List<SxE> match(CharSequence name) {
for (SeasonEpisodePattern pattern : patterns) { for (SeasonEpisodePattern pattern : patterns) {
@ -114,7 +118,6 @@ public class SeasonEpisodeMatcher {
return null; return null;
} }
public List<SxE> match(File file) { public List<SxE> match(File file) {
for (SeasonEpisodePattern pattern : patterns) { for (SeasonEpisodePattern pattern : patterns) {
List<SxE> match = pattern.match(file.getName()); List<SxE> match = pattern.match(file.getName());
@ -135,7 +138,6 @@ public class SeasonEpisodeMatcher {
return null; return null;
} }
public int find(CharSequence name, int fromIndex) { public int find(CharSequence name, int fromIndex) {
for (SeasonEpisodePattern pattern : patterns) { for (SeasonEpisodePattern pattern : patterns) {
int index = pattern.find(name, fromIndex); int index = pattern.find(name, fromIndex);
@ -149,7 +151,6 @@ public class SeasonEpisodeMatcher {
return -1; return -1;
} }
public Matcher matcher(CharSequence name) { public Matcher matcher(CharSequence name) {
for (SeasonEpisodePattern pattern : patterns) { for (SeasonEpisodePattern pattern : patterns) {
Matcher matcher = pattern.matcher(name); Matcher matcher = pattern.matcher(name);
@ -164,7 +165,6 @@ public class SeasonEpisodeMatcher {
return null; return null;
} }
public static class SxE { public static class SxE {
public static final int UNDEFINED = -1; public static final int UNDEFINED = -1;
@ -172,19 +172,16 @@ public class SeasonEpisodeMatcher {
public final int season; public final int season;
public final int episode; public final int episode;
public SxE(Integer season, Integer episode) { public SxE(Integer season, Integer episode) {
this.season = season != null ? season : UNDEFINED; this.season = season != null ? season : UNDEFINED;
this.episode = episode != null ? episode : UNDEFINED; this.episode = episode != null ? episode : UNDEFINED;
} }
public SxE(String season, String episode) { public SxE(String season, String episode) {
this.season = parse(season); this.season = parse(season);
this.episode = parse(episode); this.episode = parse(episode);
} }
protected int parse(String number) { protected int parse(String number) {
try { try {
return Integer.parseInt(number); return Integer.parseInt(number);
@ -193,7 +190,6 @@ public class SeasonEpisodeMatcher {
} }
} }
@Override @Override
public boolean equals(Object object) { public boolean equals(Object object) {
if (object instanceof SxE) { if (object instanceof SxE) {
@ -204,62 +200,52 @@ public class SeasonEpisodeMatcher {
return false; return false;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(new Object[] { season, episode }); return Arrays.hashCode(new Object[] { season, episode });
} }
@Override @Override
public String toString() { public String toString() {
return season >= 0 ? String.format("%dx%02d", season, episode) : String.format("%02d", episode); return season >= 0 ? String.format("%dx%02d", season, episode) : String.format("%02d", episode);
} }
} }
public static class SeasonEpisodeFilter { public static class SeasonEpisodeFilter {
public final int seasonLimit; public final int seasonLimit;
public final int seasonEpisodeLimit; public final int seasonEpisodeLimit;
public final int absoluteEpisodeLimit; public final int absoluteEpisodeLimit;
public SeasonEpisodeFilter(int seasonLimit, int seasonEpisodeLimit, int absoluteEpisodeLimit) { public SeasonEpisodeFilter(int seasonLimit, int seasonEpisodeLimit, int absoluteEpisodeLimit) {
this.seasonLimit = seasonLimit; this.seasonLimit = seasonLimit;
this.seasonEpisodeLimit = seasonEpisodeLimit; this.seasonEpisodeLimit = seasonEpisodeLimit;
this.absoluteEpisodeLimit = absoluteEpisodeLimit; this.absoluteEpisodeLimit = absoluteEpisodeLimit;
} }
boolean filter(SxE sxe) { boolean filter(SxE sxe) {
return (sxe.season >= 0 && sxe.season < seasonLimit && sxe.episode < seasonEpisodeLimit) || (sxe.season < 0 && sxe.episode < absoluteEpisodeLimit); return (sxe.season >= 0 && sxe.season < seasonLimit && sxe.episode < seasonEpisodeLimit) || (sxe.season < 0 && sxe.episode < absoluteEpisodeLimit);
} }
} }
public static class SeasonEpisodePattern { public static class SeasonEpisodePattern {
protected final Pattern pattern; protected final Pattern pattern;
protected final SeasonEpisodeFilter sanity; protected final SeasonEpisodeFilter sanity;
public SeasonEpisodePattern(SeasonEpisodeFilter sanity, String pattern) { public SeasonEpisodePattern(SeasonEpisodeFilter sanity, String pattern) {
this.pattern = Pattern.compile(pattern); this.pattern = Pattern.compile(pattern);
this.sanity = sanity; this.sanity = sanity;
} }
public Matcher matcher(CharSequence name) { public Matcher matcher(CharSequence name) {
return pattern.matcher(name); return pattern.matcher(name);
} }
protected Collection<SxE> process(MatchResult match) { protected Collection<SxE> process(MatchResult match) {
return singleton(new SxE(match.group(1), match.group(2))); return singleton(new SxE(match.group(1), match.group(2)));
} }
public List<SxE> match(CharSequence name) { public List<SxE> match(CharSequence name) {
// name will probably contain no more than two matches // name will probably contain no more than two matches
List<SxE> matches = new ArrayList<SxE>(2); List<SxE> matches = new ArrayList<SxE>(2);
@ -277,7 +263,6 @@ public class SeasonEpisodeMatcher {
return matches; return matches;
} }
public int find(CharSequence name, int fromIndex) { public int find(CharSequence name, int fromIndex) {
Matcher matcher = matcher(name).region(fromIndex, name.length()); Matcher matcher = matcher(name).region(fromIndex, name.length());