* improved matching using a 2-level narrowing episode identifier metric sequence
This commit is contained in:
parent
e7d697df0a
commit
4b5f512fcf
|
@ -10,7 +10,7 @@ import net.sourceforge.filebot.ui.rename.MatchSimilarityMetric;
|
||||||
|
|
||||||
public enum StrictMetric implements SimilarityMetric {
|
public enum StrictMetric implements SimilarityMetric {
|
||||||
|
|
||||||
EpisodeIdentifier(MatchSimilarityMetric.EpisodeIdentifier, 1), // only allow 0 or 1
|
EpisodeIdentifier(MatchSimilarityMetric.StrictEpisodeIdentifier, 1), // only allow 0 or 1
|
||||||
Title(MatchSimilarityMetric.SubstringFields, 2), // allow 0 or .5 or 1
|
Title(MatchSimilarityMetric.SubstringFields, 2), // allow 0 or .5 or 1
|
||||||
Name(MatchSimilarityMetric.Name, 2); // allow 0 or .5 or 1
|
Name(MatchSimilarityMetric.Name, 2); // allow 0 or .5 or 1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.similarity;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.lang.Math.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class MetricCascade implements SimilarityMetric {
|
||||||
|
|
||||||
|
private final SimilarityMetric[] cascade;
|
||||||
|
|
||||||
|
|
||||||
|
public MetricCascade(SimilarityMetric... cascade) {
|
||||||
|
this.cascade = cascade;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSimilarity(Object o1, Object o2) {
|
||||||
|
float f = 0;
|
||||||
|
for (SimilarityMetric metric : cascade) {
|
||||||
|
f = max(f, metric.getSimilarity(o1, o2));
|
||||||
|
|
||||||
|
// is match, ignore remaining metrics
|
||||||
|
if (f >= 1) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import java.util.WeakHashMap;
|
||||||
|
|
||||||
import net.sourceforge.filebot.similarity.DateMetric;
|
import net.sourceforge.filebot.similarity.DateMetric;
|
||||||
import net.sourceforge.filebot.similarity.FileSizeMetric;
|
import net.sourceforge.filebot.similarity.FileSizeMetric;
|
||||||
|
import net.sourceforge.filebot.similarity.MetricCascade;
|
||||||
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
||||||
import net.sourceforge.filebot.similarity.NumericSimilarityMetric;
|
import net.sourceforge.filebot.similarity.NumericSimilarityMetric;
|
||||||
import net.sourceforge.filebot.similarity.SeasonEpisodeMetric;
|
import net.sourceforge.filebot.similarity.SeasonEpisodeMetric;
|
||||||
|
@ -29,46 +30,10 @@ import net.sourceforge.filebot.web.Movie;
|
||||||
|
|
||||||
public enum MatchSimilarityMetric implements SimilarityMetric {
|
public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
|
|
||||||
// Match by file length (only works when matching torrents or files)
|
|
||||||
FileSize(new FileSizeMetric() {
|
|
||||||
|
|
||||||
@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() which is quite costly
|
|
||||||
return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long getLength(Object object) {
|
|
||||||
if (object instanceof AbstractFile) {
|
|
||||||
return ((AbstractFile) object).getLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.getLength(object);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Match by season/episode and airdate combined
|
|
||||||
EpisodeIdentifier(new SimilarityMetric() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getSimilarity(Object o1, Object o2) {
|
|
||||||
float sxeSimilarity = SeasonEpisode.getSimilarity(o1, o2);
|
|
||||||
|
|
||||||
// break if SxE is a perfect match already
|
|
||||||
if (sxeSimilarity >= 1)
|
|
||||||
return sxeSimilarity;
|
|
||||||
|
|
||||||
return max(sxeSimilarity, AirDate.getSimilarity(o1, o2));
|
|
||||||
}
|
|
||||||
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Match by season / episode numbers
|
// Match by season / episode numbers
|
||||||
SeasonEpisode(new SeasonEpisodeMetric() {
|
SeasonEpisode(new SeasonEpisodeMetric() {
|
||||||
|
|
||||||
private final Map<Object, Collection<SxE>> matchCache = synchronizedMap(new WeakHashMap<Object, Collection<SxE>>(64, 4));
|
private final Map<Object, Collection<SxE>> transformCache = synchronizedMap(new WeakHashMap<Object, Collection<SxE>>(64, 4));
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,7 +42,7 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
return emptySet();
|
return emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<SxE> result = matchCache.get(object);
|
Collection<SxE> result = transformCache.get(object);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +59,7 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
result = super.parse(object);
|
result = super.parse(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
matchCache.put(object, result);
|
transformCache.put(object, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -102,7 +67,7 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
// Match episode airdate
|
// Match episode airdate
|
||||||
AirDate(new DateMetric() {
|
AirDate(new DateMetric() {
|
||||||
|
|
||||||
private final Map<Object, Date> matchCache = synchronizedMap(new WeakHashMap<Object, Date>(64, 4));
|
private final Map<Object, Date> transformCache = synchronizedMap(new WeakHashMap<Object, Date>(64, 4));
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,17 +83,38 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
return episode.airdate();
|
return episode.airdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
Date result = matchCache.get(object);
|
Date result = transformCache.get(object);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = super.parse(object);
|
result = super.parse(object);
|
||||||
matchCache.put(object, result);
|
transformCache.put(object, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// Match by episode/movie title
|
||||||
|
Title(new SubstringMetric() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String normalize(Object object) {
|
||||||
|
if (object instanceof Episode) {
|
||||||
|
object = ((Episode) object).getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof Movie) {
|
||||||
|
object = ((Movie) object).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeObject(object);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Match by combining season/episode, episode airdate and movie/episode title
|
||||||
|
GeneralEpisodeIdentifier(new MetricCascade(SeasonEpisode, AirDate, Title)),
|
||||||
|
StrictEpisodeIdentifier(new MetricCascade(SeasonEpisode, AirDate)),
|
||||||
|
|
||||||
// Match series title and episode title against folder structure and file name
|
// Match series title and episode title against folder structure and file name
|
||||||
SubstringFields(new SubstringMetric() {
|
SubstringFields(new SubstringMetric() {
|
||||||
|
|
||||||
|
@ -170,7 +156,7 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
|
|
||||||
if (object instanceof File) {
|
if (object instanceof File) {
|
||||||
File file = (File) object;
|
File file = (File) object;
|
||||||
return new Object[] { file.getParentFile(), file };
|
return new Object[] { file.getParentFile().getAbsolutePath(), file };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof Movie) {
|
if (object instanceof Movie) {
|
||||||
|
@ -178,10 +164,6 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
return new Object[] { movie.getName(), movie.getYear() };
|
return new Object[] { movie.getName(), movie.getYear() };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof AbstractFile) {
|
|
||||||
return new Object[] { (AbstractFile) object };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Object[] { object };
|
return new Object[] { object };
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -212,6 +194,26 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
// simplify file name, if possible
|
// simplify file name, if possible
|
||||||
return normalizeObject(object);
|
return normalizeObject(object);
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Match by file length (only works when matching torrents or files)
|
||||||
|
FileSize(new FileSizeMetric() {
|
||||||
|
|
||||||
|
@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() which is quite costly
|
||||||
|
return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long getLength(Object object) {
|
||||||
|
if (object instanceof AbstractFile) {
|
||||||
|
return ((AbstractFile) object).getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getLength(object);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// inner metric
|
// inner metric
|
||||||
|
@ -257,9 +259,9 @@ public enum MatchSimilarityMetric implements SimilarityMetric {
|
||||||
// 4. pass: match by generic name similarity (slow, but most matches will have been determined in second pass)
|
// 4. pass: match by generic name similarity (slow, but most matches will have been determined in second pass)
|
||||||
// 5. pass: match by generic numeric similarity
|
// 5. pass: match by generic numeric similarity
|
||||||
if (includeFileMetrics) {
|
if (includeFileMetrics) {
|
||||||
return new SimilarityMetric[] { FileSize, EpisodeIdentifier, SubstringFields, Name, Numeric };
|
return new SimilarityMetric[] { FileSize, GeneralEpisodeIdentifier, StrictEpisodeIdentifier, SubstringFields, Name, Numeric };
|
||||||
} else {
|
} else {
|
||||||
return new SimilarityMetric[] { EpisodeIdentifier, SubstringFields, Name, Numeric };
|
return new SimilarityMetric[] { GeneralEpisodeIdentifier, StrictEpisodeIdentifier, SubstringFields, Name, Numeric };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class MatchSimilarityMetricTest {
|
||||||
episodes.add(new Episode("Veronica Mars", null, 1, 19, "Hot Dogs"));
|
episodes.add(new Episode("Veronica Mars", null, 1, 19, "Hot Dogs"));
|
||||||
episodes.add(new Episode("Greek", null, 1, 19, "No Campus for Old Rules"));
|
episodes.add(new Episode("Greek", null, 1, 19, "No Campus for Old Rules"));
|
||||||
|
|
||||||
SimilarityMetric[] metrics = new SimilarityMetric[] { EpisodeIdentifier, SubstringFields };
|
SimilarityMetric[] metrics = new SimilarityMetric[] { GeneralEpisodeIdentifier, SubstringFields };
|
||||||
List<Match<File, Episode>> m = new Matcher<File, Episode>(files, episodes, true, metrics).match();
|
List<Match<File, Episode>> m = new Matcher<File, Episode>(files, episodes, true, metrics).match();
|
||||||
|
|
||||||
assertEquals("Greek - S01E19 - No Campus for Old Rules", m.get(0).getValue().getName());
|
assertEquals("Greek - S01E19 - No Campus for Old Rules", m.get(0).getValue().getName());
|
||||||
|
|
Loading…
Reference in New Issue