+ ReleaseDate metric matching step for differentiating multiple shows with the same name, usually nudging things towards the more recent episode/series.

This commit is contained in:
Reinhard Pointner 2012-10-09 13:30:32 +00:00
parent bf6cccfbbb
commit a248021ebf
4 changed files with 81 additions and 19 deletions

View File

@ -284,6 +284,26 @@ public enum EpisodeMetrics implements SimilarityMetric {
return null; return null;
} }
}),
// Match by file last modified and episode release dates
TimeStamp(new TimeStampMetric() {
@Override
public float getSimilarity(Object o1, Object o2) {
// adjust differentiation accuracy to about a year
return (float) (floor(super.getSimilarity(o1, o2) * 40) / 40);
}
@Override
public long getTimeStamp(Object object) {
if (object instanceof Episode) {
return ((Episode) object).airdate().getTimeStamp();
}
return super.getTimeStamp(object);
}
}); });
// inner metric // inner metric
@ -338,11 +358,12 @@ public enum EpisodeMetrics implements SimilarityMetric {
// 4 pass: divide by folder / file name and show name / episode title // 4 pass: divide by folder / file name and show name / episode title
// 5 pass: divide by name (rounded into n levels) // 5 pass: divide by name (rounded into n levels)
// 6 pass: divide by generic numeric similarity // 6 pass: divide by generic numeric similarity
// 7 pass: resolve remaining collisions via absolute string similarity // 7 pass: prefer episodes that were aired closer to the last modified date of the file
// 8 pass: resolve remaining collisions via absolute string similarity
if (includeFileMetrics) { if (includeFileMetrics) {
return new SimilarityMetric[] { FileSize, new MetricCascade(FileName, EpisodeFunnel), EpisodeBalancer, SubstringFields, new MetricCascade(SubstringSequence, Name), Numeric, new NameSimilarityMetric() }; return new SimilarityMetric[] { FileSize, new MetricCascade(FileName, EpisodeFunnel), EpisodeBalancer, SubstringFields, new MetricCascade(SubstringSequence, Name), Numeric, Name, TimeStamp, new NameSimilarityMetric() };
} else { } else {
return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, SubstringFields, new MetricCascade(SubstringSequence, Name), Numeric, new NameSimilarityMetric() }; return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, SubstringFields, new MetricCascade(SubstringSequence, Name), Numeric, Name, TimeStamp, new NameSimilarityMetric() };
} }
} }

View File

@ -0,0 +1,38 @@
package net.sourceforge.filebot.similarity;
import static java.lang.Math.*;
import java.io.File;
public class TimeStampMetric implements SimilarityMetric {
@Override
public float getSimilarity(Object o1, Object o2) {
long t1 = getTimeStamp(o1);
long t2 = getTimeStamp(o2);
if (t1 <= 0 || t2 <= 0)
return 0;
float min = min(t1, t2);
float max = max(t1, t2);
return min / max;
}
public long getTimeStamp(Object obj) {
if (obj instanceof File) {
return ((File) obj).lastModified();
}
if (obj instanceof Number) {
return ((Number) obj).longValue();
}
return -1;
}
}

View File

@ -3,9 +3,8 @@ package net.sourceforge.filebot.subtitle;
import static java.lang.Math.*; import static java.lang.Math.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.MediaTypes.*; import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.similarity.EpisodeMetrics.*;
import static net.sourceforge.filebot.similarity.Normalization.*; import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.FileUtilities.*;
@ -30,6 +29,7 @@ import net.sourceforge.filebot.similarity.EpisodeMetrics;
import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher; import net.sourceforge.filebot.similarity.Matcher;
import net.sourceforge.filebot.similarity.MetricAvg; import net.sourceforge.filebot.similarity.MetricAvg;
import net.sourceforge.filebot.similarity.MetricCascade;
import net.sourceforge.filebot.similarity.NameSimilarityMetric; import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.SequenceMatchSimilarity; import net.sourceforge.filebot.similarity.SequenceMatchSimilarity;
import net.sourceforge.filebot.similarity.SimilarityMetric; import net.sourceforge.filebot.similarity.SimilarityMetric;
@ -46,10 +46,8 @@ public final class SubtitleUtilities {
public static Map<File, SubtitleDescriptor> matchSubtitles(Collection<File> files, Collection<SubtitleDescriptor> subtitles, boolean strict) throws InterruptedException { public static Map<File, SubtitleDescriptor> matchSubtitles(Collection<File> files, Collection<SubtitleDescriptor> subtitles, boolean strict) throws InterruptedException {
Map<File, SubtitleDescriptor> subtitleByVideo = new LinkedHashMap<File, SubtitleDescriptor>(); Map<File, SubtitleDescriptor> subtitleByVideo = new LinkedHashMap<File, SubtitleDescriptor>();
SimilarityMetric[] metrics = EpisodeMetrics.defaultSequence(false);
// optimize for generic media <-> subtitle matching // optimize for generic media <-> subtitle matching
replaceAll(asList(metrics), EpisodeMetrics.SubstringFields, EpisodeMetrics.SubstringSequence); SimilarityMetric[] metrics = new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, SubstringSequence, new MetricCascade(SubstringSequence, Name), Numeric, new NameSimilarityMetric() };
// first match everything as best as possible, then filter possibly bad matches // first match everything as best as possible, then filter possibly bad matches
Matcher<File, SubtitleDescriptor> matcher = new Matcher<File, SubtitleDescriptor>(files, subtitles, false, metrics); Matcher<File, SubtitleDescriptor> matcher = new Matcher<File, SubtitleDescriptor>(files, subtitles, false, metrics);

View File

@ -21,34 +21,39 @@ public class Date implements Serializable {
private int month; private int month;
private int day; private int day;
protected Date() { protected Date() {
// used by serializer // used by serializer
} }
public Date(int year, int month, int day) { public Date(int year, int month, int day) {
this.year = year; this.year = year;
this.month = month; this.month = month;
this.day = day; this.day = day;
} }
public int getYear() { public int getYear() {
return year; return year;
} }
public int getMonth() { public int getMonth() {
return month; return month;
} }
public int getDay() { public int getDay() {
return day; return day;
} }
public long getTimeStamp() {
return new GregorianCalendar(year, month, day).getTimeInMillis();
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof Date) { if (obj instanceof Date) {
@ -59,29 +64,29 @@ public class Date implements Serializable {
return super.equals(obj); return super.equals(obj);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(new Object[] { year, month, day }); return Arrays.hashCode(new Object[] { year, month, day });
} }
@Override @Override
public String toString() { public String toString() {
return String.format("%04d-%02d-%02d", year, month, day); return String.format("%04d-%02d-%02d", year, month, day);
} }
public String format(String pattern) { public String format(String pattern) {
return format(pattern, Locale.ROOT); return format(pattern, Locale.ROOT);
} }
public String format(String pattern, Locale locale) { public String format(String pattern, Locale locale) {
return new SimpleDateFormat(pattern, locale).format(new GregorianCalendar(year, month - 1, day).getTime()); // Calendar months start at 0 return new SimpleDateFormat(pattern, locale).format(new GregorianCalendar(year, month - 1, day).getTime()); // Calendar months start at 0
} }
public static Date parse(String string, String pattern) { public static Date parse(String string, String pattern) {
if (string == null || string.isEmpty()) if (string == null || string.isEmpty())
return null; return null;