diff --git a/source/net/sourceforge/filebot/similarity/EpisodeMetrics.java b/source/net/sourceforge/filebot/similarity/EpisodeMetrics.java index 6c34451b..8cfa1154 100644 --- a/source/net/sourceforge/filebot/similarity/EpisodeMetrics.java +++ b/source/net/sourceforge/filebot/similarity/EpisodeMetrics.java @@ -284,6 +284,26 @@ public enum EpisodeMetrics implements SimilarityMetric { 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 @@ -338,11 +358,12 @@ public enum EpisodeMetrics implements SimilarityMetric { // 4 pass: divide by folder / file name and show name / episode title // 5 pass: divide by name (rounded into n levels) // 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) { - 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 { - 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() }; } } diff --git a/source/net/sourceforge/filebot/similarity/TimeStampMetric.java b/source/net/sourceforge/filebot/similarity/TimeStampMetric.java new file mode 100644 index 00000000..c963bf9e --- /dev/null +++ b/source/net/sourceforge/filebot/similarity/TimeStampMetric.java @@ -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; + } + +} diff --git a/source/net/sourceforge/filebot/subtitle/SubtitleUtilities.java b/source/net/sourceforge/filebot/subtitle/SubtitleUtilities.java index 5f65c57b..5620f4e3 100644 --- a/source/net/sourceforge/filebot/subtitle/SubtitleUtilities.java +++ b/source/net/sourceforge/filebot/subtitle/SubtitleUtilities.java @@ -3,9 +3,8 @@ package net.sourceforge.filebot.subtitle; 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.similarity.EpisodeMetrics.*; import static net.sourceforge.filebot.similarity.Normalization.*; 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.Matcher; import net.sourceforge.filebot.similarity.MetricAvg; +import net.sourceforge.filebot.similarity.MetricCascade; import net.sourceforge.filebot.similarity.NameSimilarityMetric; import net.sourceforge.filebot.similarity.SequenceMatchSimilarity; import net.sourceforge.filebot.similarity.SimilarityMetric; @@ -46,10 +46,8 @@ public final class SubtitleUtilities { public static Map matchSubtitles(Collection files, Collection subtitles, boolean strict) throws InterruptedException { Map subtitleByVideo = new LinkedHashMap(); - SimilarityMetric[] metrics = EpisodeMetrics.defaultSequence(false); - // 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 Matcher matcher = new Matcher(files, subtitles, false, metrics); diff --git a/source/net/sourceforge/filebot/web/Date.java b/source/net/sourceforge/filebot/web/Date.java index 5e0f9446..c3f746c6 100644 --- a/source/net/sourceforge/filebot/web/Date.java +++ b/source/net/sourceforge/filebot/web/Date.java @@ -21,34 +21,39 @@ public class Date implements Serializable { private int month; private int day; - + protected Date() { // used by serializer } - + public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } - + public int getYear() { return year; } - + public int getMonth() { return month; } - + public int getDay() { return day; } - + + public long getTimeStamp() { + return new GregorianCalendar(year, month, day).getTimeInMillis(); + } + + @Override public boolean equals(Object obj) { if (obj instanceof Date) { @@ -59,29 +64,29 @@ public class Date implements Serializable { return super.equals(obj); } - + @Override public int hashCode() { return Arrays.hashCode(new Object[] { year, month, day }); } - + @Override public String toString() { return String.format("%04d-%02d-%02d", year, month, day); } - + public String format(String pattern) { return format(pattern, Locale.ROOT); } - + 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 } - + public static Date parse(String string, String pattern) { if (string == null || string.isEmpty()) return null;