diff --git a/source/net/filebot/media/MediaDetection.java b/source/net/filebot/media/MediaDetection.java index 38a7ab06..8c163e8d 100644 --- a/source/net/filebot/media/MediaDetection.java +++ b/source/net/filebot/media/MediaDetection.java @@ -761,7 +761,7 @@ public class MediaDetection { } public static boolean isEpisodeNumberMatch(File f, Episode e) { - float similarity = EpisodeMetrics.EpisodeIdentifier.getSimilarity(f, e); + float similarity = new EpisodeMetrics().numbers().getSimilarity(f, e); if (similarity >= 1) { return true; } else if (similarity >= 0.5 && e.getSeason() == null && e.getEpisode() != null && e.getSpecial() == null) { diff --git a/source/net/filebot/similarity/EpisodeMatcher.java b/source/net/filebot/similarity/EpisodeMatcher.java index f12d37ad..456fcf75 100644 --- a/source/net/filebot/similarity/EpisodeMatcher.java +++ b/source/net/filebot/similarity/EpisodeMatcher.java @@ -32,7 +32,7 @@ public class EpisodeMatcher extends Matcher { public EpisodeMatcher(Collection values, Collection candidates, boolean strict) { // use strict matcher as to force a result from the final top similarity set - super(values, candidates, strict, EpisodeMetrics.defaultSequence(false)); + super(values, candidates, strict, new EpisodeMetrics().matchSequence()); } @Override diff --git a/source/net/filebot/similarity/EpisodeMetrics.java b/source/net/filebot/similarity/EpisodeMetrics.java index 1f9aac48..63b434a8 100644 --- a/source/net/filebot/similarity/EpisodeMetrics.java +++ b/source/net/filebot/similarity/EpisodeMetrics.java @@ -17,20 +17,15 @@ import java.time.Instant; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; -import com.google.common.base.Optional; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.ibm.icu.text.Transliterator; import net.filebot.media.MediaCharacteristics; @@ -44,12 +39,12 @@ import net.filebot.web.Movie; import net.filebot.web.SeriesInfo; import net.filebot.web.SimpleDate; -public enum EpisodeMetrics implements SimilarityMetric { +public class EpisodeMetrics { // Match by season / episode numbers - SeasonEpisode(new SeasonEpisodeMetric(new SmartSeasonEpisodeMatcher(null, false)) { + public final SimilarityMetric SeasonEpisode = new SeasonEpisodeMetric(new SmartSeasonEpisodeMatcher(null, false)) { - private final Cache> seasonEpisodeCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(); + private final Map> cache = synchronizedMap(new HashMap>(64, 4)); @Override protected Collection parse(Object object) { @@ -63,14 +58,7 @@ public enum EpisodeMetrics implements SimilarityMetric { return emptySet(); } - try { - return seasonEpisodeCache.get(object, () -> { - Collection sxe = super.parse(object); - return sxe == null ? emptySet() : sxe; - }); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } + return cache.computeIfAbsent(object, super::parse); } private Set parse(Episode e) { @@ -95,12 +83,12 @@ public enum EpisodeMetrics implements SimilarityMetric { return sxe; } - }), + }; // Match episode airdate - AirDate(new DateMetric(getDateMatcher()) { + public final SimilarityMetric AirDate = new DateMetric(getDateMatcher()) { - private final Cache> airdateCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(); + private final Map cache = synchronizedMap(new HashMap(64, 4)); @Override public SimpleDate parse(Object object) { @@ -113,16 +101,13 @@ public enum EpisodeMetrics implements SimilarityMetric { return null; } - try { - return airdateCache.get(object, () -> Optional.fromNullable(super.parse(object))).orNull(); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } + return cache.computeIfAbsent(object, super::parse); } - }), + + }; // Match by episode/movie title - Title(new SubstringMetric() { + public final SimilarityMetric Title = new SubstringMetric() { @Override protected String normalize(Object object) { @@ -145,16 +130,17 @@ public enum EpisodeMetrics implements SimilarityMetric { String s = normalizeObject(object); return s.length() >= 4 ? s : null; // only consider long enough strings to avoid false matches } - }), + + }; // Match by SxE and airdate - EpisodeIdentifier(new MetricCascade(SeasonEpisode, AirDate)), + public final SimilarityMetric EpisodeIdentifier = new MetricCascade(SeasonEpisode, AirDate); // Advanced episode <-> file matching Lv1 - EpisodeFunnel(new MetricCascade(SeasonEpisode, AirDate, Title)), + public final SimilarityMetric EpisodeFunnel = new MetricCascade(SeasonEpisode, AirDate, Title); // Advanced episode <-> file matching Lv2 - EpisodeBalancer(new SimilarityMetric() { + public final SimilarityMetric EpisodeBalancer = new SimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -183,10 +169,11 @@ public enum EpisodeMetrics implements SimilarityMetric { } return o; } - }), + + }; // Match series title and episode title against folder structure and file name - SubstringFields(new SubstringMetric() { + public final SimilarityMetric SubstringFields = new SubstringMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -214,10 +201,10 @@ public enum EpisodeMetrics implements SimilarityMetric { protected String[] normalize(Object[] objects) { // normalize objects (and make sure to keep word boundaries) - return stream(objects).map(EpisodeMetrics::normalizeObject).toArray(String[]::new); + return stream(objects).map(EpisodeMetrics.this::normalizeObject).toArray(String[]::new); } - protected static final int MAX_FIELDS = 5; + protected final int MAX_FIELDS = 5; protected Object[] fields(Object object) { if (object instanceof Episode) { @@ -242,10 +229,11 @@ public enum EpisodeMetrics implements SimilarityMetric { return new Object[] { object }; } - }), + + }; // Match via common word sequence in episode name and file name - NameSubstringSequence(new SequenceMatchSimilarity() { + public final SimilarityMetric NameSubstringSequence = new SequenceMatchSimilarity() { @Override public float getSimilarity(Object o1, Object o2) { @@ -290,10 +278,11 @@ public enum EpisodeMetrics implements SimilarityMetric { } return singletonList(object); } - }), + + }; // Match by generic name similarity (round rank) - Name(new NameSimilarityMetric() { + public final SimilarityMetric Name = new NameSimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -307,10 +296,11 @@ public enum EpisodeMetrics implements SimilarityMetric { // simplify file name, if possible return normalizeObject(object); } - }), + + }; // Match by generic name similarity (absolute) - SeriesName(new NameSimilarityMetric() { + public final SimilarityMetric SeriesName = new NameSimilarityMetric() { private final SeriesNameMatcher seriesNameMatcher = getSeriesNameMatcher(false); @@ -338,7 +328,7 @@ public enum EpisodeMetrics implements SimilarityMetric { } protected String[] getNormalizedEffectiveIdentifiers(Object object) { - return getEffectiveIdentifiers(object).stream().map(EpisodeMetrics::normalizeObject).toArray(String[]::new); + return getEffectiveIdentifiers(object).stream().map(EpisodeMetrics.this::normalizeObject).toArray(String[]::new); } protected List getEffectiveIdentifiers(Object object) { @@ -360,12 +350,13 @@ public enum EpisodeMetrics implements SimilarityMetric { return emptyList(); } - }), - SeriesNameBalancer(new MetricCascade(NameSubstringSequence, Name, SeriesName)), + }; + + public final SimilarityMetric SeriesNameBalancer = new MetricCascade(NameSubstringSequence, Name, SeriesName); // Match by generic name similarity (absolute) - FilePath(new NameSimilarityMetric() { + public final SimilarityMetric FilePath = new NameSimilarityMetric() { @Override protected String normalize(Object object) { @@ -374,9 +365,10 @@ public enum EpisodeMetrics implements SimilarityMetric { } return normalizeObject(object.toString()); // simplify file name, if possible } - }), - FilePathBalancer(new NameSimilarityMetric() { + }; + + public final SimilarityMetric FilePathBalancer = new NameSimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -397,9 +389,10 @@ public enum EpisodeMetrics implements SimilarityMetric { protected String normalize(Object object) { return object.toString(); } - }), - NumericSequence(new SequenceMatchSimilarity() { + }; + + public final SimilarityMetric NumericSequence = new SequenceMatchSimilarity() { @Override public float getSimilarity(Object o1, Object o2) { @@ -435,10 +428,11 @@ public enum EpisodeMetrics implements SimilarityMetric { List numbers = matchIntegers(normalizeObject(object)); return join(numbers, " "); } - }), + + }; // Match by generic numeric similarity - Numeric(new NumericSimilarityMetric() { + public final SimilarityMetric Numeric = new NumericSimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -477,10 +471,11 @@ public enum EpisodeMetrics implements SimilarityMetric { return new String[] { normalizeObject(object) }; } - }), + + }; // Prioritize proper episodes over specials - SpecialNumber(new SimilarityMetric() { + public final SimilarityMetric SpecialNumber = new SimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -494,10 +489,11 @@ public enum EpisodeMetrics implements SimilarityMetric { } return 0; } - }), + + }; // Match by file length (only works when matching torrents or files) - FileSize(new FileSizeMetric() { + public final SimilarityMetric FileSize = new FileSizeMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -510,26 +506,26 @@ public enum EpisodeMetrics implements SimilarityMetric { if (object instanceof FileInfo) { return ((FileInfo) object).getLength(); } - return super.getLength(object); } - }), + + }; // Match by common words at the beginning of both files - FileName(new FileNameMetric() { + public final SimilarityMetric FileName = new FileNameMetric() { @Override protected String getFileName(Object object) { if (object instanceof File || object instanceof FileInfo) { return normalizeObject(object); } - return null; } - }), + + }; // Match by file last modified and episode release dates - TimeStamp(new TimeStampMetric(10, ChronoUnit.YEARS) { + public final SimilarityMetric TimeStamp = new TimeStampMetric(10, ChronoUnit.YEARS) { @Override public float getSimilarity(Object o1, Object o2) { @@ -582,9 +578,9 @@ public enum EpisodeMetrics implements SimilarityMetric { return -1; } - }), + }; - SeriesRating(new SimilarityMetric() { + public final SimilarityMetric SeriesRating = new SimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -612,9 +608,9 @@ public enum EpisodeMetrics implements SimilarityMetric { } return 0; } - }), + }; - VoteRate(new SimilarityMetric() { + public final SimilarityMetric VoteRate = new SimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -636,10 +632,10 @@ public enum EpisodeMetrics implements SimilarityMetric { } return 0; } - }), + }; // Match by (region) or (year) hints - RegionHint(new SimilarityMetric() { + public final SimilarityMetric RegionHint = new SimilarityMetric() { private final Pattern hint = compile("[(](\\p{Alpha}+|\\p{Digit}+)[)]$"); @@ -679,10 +675,10 @@ public enum EpisodeMetrics implements SimilarityMetric { return emptySet(); } - }), + }; // Match by stored MetaAttributes if possible - MetaAttributes(new CrossPropertyMetric() { + public final SimilarityMetric MetaAttributes = new CrossPropertyMetric() { @Override protected Map getProperties(Object object) { @@ -703,43 +699,28 @@ public enum EpisodeMetrics implements SimilarityMetric { return emptyMap(); } - }); + }; - // inner metric - private final SimilarityMetric metric; + protected final Map transformCache = synchronizedMap(new HashMap(64, 4)); - private EpisodeMetrics(SimilarityMetric metric) { - this.metric = metric; - } + protected final Transliterator transliterator = Transliterator.getInstance("Any-Latin;Latin-ASCII;[:Diacritic:]remove"); - @Override - public float getSimilarity(Object o1, Object o2) { - return metric.getSimilarity(o1, o2); - } - - private static final Cache normalizeObjectCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(); - - private static final Transliterator transliterator = Transliterator.getInstance("Any-Latin;Latin-ASCII;[:Diacritic:]remove"); - - public static String normalizeObject(Object object) { - if (object != null) { - try { - return normalizeObjectCache.get(object, () -> { - // 1. convert to string - // 2. remove checksums, any [...] or (...) - // 3. remove obvious release info - // 4. apply transliterator - // 5. remove or normalize special characters - return normalizePunctuation(transliterator.transform(stripFormatInfo(removeEmbeddedChecksum(normalizeFileName(object))))).toLowerCase(); - }); - } catch (ExecutionException e) { - debug.log(Level.SEVERE, e, e::toString); - } + protected String normalizeObject(Object object) { + if (object == null) { + return ""; } - return ""; + + return transformCache.computeIfAbsent(object, o -> { + // 1. convert to string + // 2. remove checksums, any [...] or (...) + // 3. remove obvious release info + // 4. apply transliterator + // 5. remove or normalize special characters + return normalizePunctuation(transliterator.transform(stripFormatInfo(removeEmbeddedChecksum(normalizeFileName(object))))).toLowerCase(); + }); } - private static String normalizeFileName(Object object) { + protected String normalizeFileName(Object object) { if (object instanceof File) { return getName((File) object); } else if (object instanceof FileInfo) { @@ -748,7 +729,7 @@ public enum EpisodeMetrics implements SimilarityMetric { return object.toString(); } - public static SimilarityMetric[] defaultSequence(boolean includeFileMetrics) { + public SimilarityMetric[] matchSequence() { // 1 pass: divide by file length (only works for matching torrent entries or files) // 2-3 pass: divide by title or season / episode numbers // 4 pass: divide by folder / file name and show name / episode title @@ -756,15 +737,23 @@ public enum EpisodeMetrics implements SimilarityMetric { // 6 pass: divide by generic numeric 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, AirDate, MetaAttributes, SubstringFields, SeriesNameBalancer, SeriesName, RegionHint, SpecialNumber, Numeric, NumericSequence, SeriesRating, VoteRate, TimeStamp, FilePathBalancer, FilePath }; - } else { - return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, AirDate, MetaAttributes, SubstringFields, SeriesNameBalancer, SeriesName, RegionHint, SpecialNumber, Numeric, NumericSequence, SeriesRating, VoteRate, TimeStamp, FilePathBalancer, FilePath }; - } + return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, AirDate, MetaAttributes, SubstringFields, SeriesNameBalancer, SeriesName, RegionHint, SpecialNumber, Numeric, NumericSequence, SeriesRating, VoteRate, TimeStamp, FilePathBalancer, FilePath }; } - public static SimilarityMetric verificationMetric() { + public SimilarityMetric[] matchFileSequence() { + return new SimilarityMetric[] { FileSize, new MetricCascade(FileName, EpisodeFunnel), EpisodeBalancer, AirDate, MetaAttributes, SubstringFields, SeriesNameBalancer, SeriesName, RegionHint, SpecialNumber, Numeric, NumericSequence, SeriesRating, VoteRate, TimeStamp, FilePathBalancer, FilePath }; + } + + public SimilarityMetric numbers() { + return EpisodeIdentifier; + } + + public SimilarityMetric verification() { return new MetricCascade(FileName, SeasonEpisode, AirDate, Title, Name); } + public SimilarityMetric sanity() { + return new MetricCascade(new MetricMin(FileSize, 0), FileName, EpisodeIdentifier); + } + } diff --git a/source/net/filebot/subtitle/SubtitleMetrics.java b/source/net/filebot/subtitle/SubtitleMetrics.java index ec1e44c0..89e40d38 100644 --- a/source/net/filebot/subtitle/SubtitleMetrics.java +++ b/source/net/filebot/subtitle/SubtitleMetrics.java @@ -4,22 +4,15 @@ import static java.util.Collections.*; import static net.filebot.Logging.*; import static net.filebot.media.MediaDetection.*; import static net.filebot.media.XattrMetaInfo.*; -import static net.filebot.similarity.EpisodeMetrics.*; import static net.filebot.util.FileUtilities.*; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; - import net.filebot.media.MediaCharacteristics; import net.filebot.media.MediaCharacteristicsParser; import net.filebot.similarity.CrossPropertyMetric; @@ -33,10 +26,10 @@ import net.filebot.similarity.SimilarityMetric; import net.filebot.web.OpenSubtitlesSubtitleDescriptor; import net.filebot.web.SubtitleDescriptor; -public enum SubtitleMetrics implements SimilarityMetric { +public class SubtitleMetrics extends EpisodeMetrics { // subtitle verification metric specifically excluding SxE mismatches - AbsoluteSeasonEpisode(new SimilarityMetric() { + public final SimilarityMetric AbsoluteSeasonEpisode = new SimilarityMetric() { @Override public float getSimilarity(Object o1, Object o2) { @@ -46,9 +39,10 @@ public enum SubtitleMetrics implements SimilarityMetric { } return f < 1 ? -1 : 1; } - }), - DiskNumber(new NumericSimilarityMetric() { + }; + + public final SimilarityMetric DiskNumber = new NumericSimilarityMetric() { private final Pattern CDNO = Pattern.compile("(?:CD|DISK)(\\d+)", Pattern.CASE_INSENSITIVE); @@ -71,9 +65,10 @@ public enum SubtitleMetrics implements SimilarityMetric { } return cd; } - }), - NameSubstringSequenceExists(new SequenceMatchSimilarity() { + }; + + public final SimilarityMetric NameSubstringSequenceExists = new SequenceMatchSimilarity() { @Override public float getSimilarity(Object o1, Object o2) { @@ -106,7 +101,7 @@ public enum SubtitleMetrics implements SimilarityMetric { String[] names = new String[identifiers.size()]; for (int i = 0; i < names.length; i++) { - names[i] = EpisodeMetrics.normalizeObject(identifiers.get(i)); + names[i] = normalizeObject(identifiers.get(i)); } return names; @@ -120,9 +115,10 @@ public enum SubtitleMetrics implements SimilarityMetric { } return emptyList(); } - }), - OriginalFileName(new SequenceMatchSimilarity() { + }; + + public final SimilarityMetric OriginalFileName = new SequenceMatchSimilarity() { @Override protected float similarity(String match, String s1, String s2) { @@ -144,9 +140,10 @@ public enum SubtitleMetrics implements SimilarityMetric { } return super.normalize(object); } - }), - VideoProperties(new CrossPropertyMetric() { + }; + + public final SimilarityMetric VideoProperties = new CrossPropertyMetric() { private final String FPS = "FPS"; private final String SECONDS = "SECS"; @@ -186,43 +183,38 @@ public enum SubtitleMetrics implements SimilarityMetric { return emptyMap(); } - private final Cache> videoPropertiesCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(); + private final Map> cache = synchronizedMap(new HashMap>(64, 4)); private Map getVideoProperties(File file) { - try { - return videoPropertiesCache.get(file, () -> { - try (MediaCharacteristics mi = MediaCharacteristicsParser.DEFAULT.open(file)) { - return getProperties(mi.getFrameRate(), mi.getDuration().toMillis()); - } catch (Exception e) { - debug.warning(cause("Failed to read video properties", e)); - } - return emptyMap(); - }); - } catch (ExecutionException e) { - debug.log(Level.SEVERE, e, e::toString); - } - return emptyMap(); + return cache.computeIfAbsent(file, f -> { + try (MediaCharacteristics mi = MediaCharacteristicsParser.DEFAULT.open(f)) { + return getProperties(mi.getFrameRate(), mi.getDuration().toMillis()); + } catch (Exception e) { + debug.warning(cause("Failed to read video properties", e)); + } + return emptyMap(); + }); } - }); - - // inner metric - private final SimilarityMetric metric; - - private SubtitleMetrics(SimilarityMetric metric) { - this.metric = metric; - } + }; @Override - public float getSimilarity(Object o1, Object o2) { - return metric.getSimilarity(o1, o2); - } - - public static SimilarityMetric[] defaultSequence() { + public SimilarityMetric[] matchSequence() { return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, OriginalFileName, NameSubstringSequenceExists, new MetricAvg(NameSubstringSequenceExists, Name), Numeric, FileName, DiskNumber, VideoProperties, new NameSimilarityMetric() }; } - public static SimilarityMetric verificationMetric() { + @Override + public SimilarityMetric[] matchFileSequence() { + return matchSequence(); + } + + @Override + public SimilarityMetric verification() { return new MetricCascade(AbsoluteSeasonEpisode, AirDate, new MetricAvg(NameSubstringSequenceExists, Name), getMovieMatchMetric(), OriginalFileName); } + @Override + public SimilarityMetric sanity() { + return verification(); + } + } diff --git a/source/net/filebot/subtitle/SubtitleUtilities.java b/source/net/filebot/subtitle/SubtitleUtilities.java index 0f6d4a1f..dd52a5cf 100644 --- a/source/net/filebot/subtitle/SubtitleUtilities.java +++ b/source/net/filebot/subtitle/SubtitleUtilities.java @@ -196,7 +196,9 @@ public final class SubtitleUtilities { } // add other possible matches to the options - SimilarityMetric sanity = SubtitleMetrics.verificationMetric(); + SubtitleMetrics metrics = new SubtitleMetrics(); + + SimilarityMetric sanity = metrics.verification(); float minMatchSimilarity = strict ? 0.9f : 0.6f; // first match everything as best as possible, then filter possibly bad matches @@ -219,7 +221,7 @@ public final class SubtitleUtilities { continue; // ignore if we're sure that SxE is a negative match - if ((isEpisode(it.getName(), true) || isEpisode(file.getPath(), true)) && EpisodeMetrics.EpisodeIdentifier.getSimilarity(file, it) < 1) + if ((isEpisode(it.getName(), true) || isEpisode(file.getPath(), true)) && metrics.numbers().getSimilarity(file, it) < 1) continue; // ignore if it's not similar enough @@ -239,7 +241,7 @@ public final class SubtitleUtilities { Map subtitleByVideo = new LinkedHashMap(); // optimize for generic media <-> subtitle matching - SimilarityMetric[] metrics = SubtitleMetrics.defaultSequence(); + SimilarityMetric[] metrics = new SubtitleMetrics().matchSequence(); // first match everything as best as possible, then filter possibly bad matches Matcher matcher = new Matcher(files, subtitles, false, metrics); @@ -295,7 +297,7 @@ public final class SubtitleUtilities { try { // add other possible matches to the options - SimilarityMetric sanity = SubtitleMetrics.verificationMetric(); + SimilarityMetric sanity = new SubtitleMetrics().verification(); float minMatchSimilarity = strict ? 0.8f : 0.2f; // first match everything as best as possible, then filter possibly bad matches diff --git a/source/net/filebot/ui/rename/MatchAction.java b/source/net/filebot/ui/rename/MatchAction.java index 60fa8f8e..d9fde815 100644 --- a/source/net/filebot/ui/rename/MatchAction.java +++ b/source/net/filebot/ui/rename/MatchAction.java @@ -41,7 +41,7 @@ class MatchAction extends AbstractAction { withWaitCursor(evt.getSource(), () -> { try { - Matcher matcher = new Matcher(model.values(), model.candidates(), false, EpisodeMetrics.defaultSequence(true)); + Matcher matcher = new Matcher(model.values(), model.candidates(), false, new EpisodeMetrics().matchFileSequence()); List> matches = ProgressMonitor.runTask("Match", "Finding optimal alignment. This may take a while.", (message, progress, cancelled) -> { message.accept(String.format("Checking %d combinations...", matcher.remainingCandidates().size() * matcher.remainingValues().size())); return matcher.match(); diff --git a/source/net/filebot/ui/rename/RenameListCellRenderer.java b/source/net/filebot/ui/rename/RenameListCellRenderer.java index 4d8d727e..401fb08c 100644 --- a/source/net/filebot/ui/rename/RenameListCellRenderer.java +++ b/source/net/filebot/ui/rename/RenameListCellRenderer.java @@ -1,6 +1,5 @@ package net.filebot.ui.rename; -import static net.filebot.similarity.EpisodeMetrics.*; import static net.filebot.util.FileUtilities.*; import static net.filebot.util.ui.SwingUI.*; @@ -21,10 +20,8 @@ import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import net.filebot.ResourceManager; +import net.filebot.similarity.EpisodeMetrics; import net.filebot.similarity.Match; -import net.filebot.similarity.MetricCascade; -import net.filebot.similarity.MetricMin; -import net.filebot.similarity.SimilarityMetric; import net.filebot.ui.rename.RenameModel.FormattedFuture; import net.filebot.util.FileUtilities; import net.filebot.util.ui.DefaultFancyListCellRenderer; @@ -196,13 +193,15 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer { return 1; // assume match is ok } + // check match probability by running a few metrics, such as checking if episode numbers / file size / etc match + EpisodeMetrics metrics = new EpisodeMetrics(); + if (match.getValue() instanceof Episode) { - float f = verificationMetric().getSimilarity(match.getValue(), match.getCandidate()); + float f = metrics.verification().getSimilarity(match.getValue(), match.getCandidate()); return (f + 1) / 2; // normalize -1..1 to 0..1 } - SimilarityMetric fsm = new MetricCascade(new MetricMin(FileSize, 0), FileName, EpisodeIdentifier); - float f = fsm.getSimilarity(match.getValue(), match.getCandidate()); + float f = metrics.sanity().getSimilarity(match.getValue(), match.getCandidate()); if (f != 0) { return (Math.max(f, 0)); // normalize -1..1 and boost by 0.25 (because file <-> file matches are not necessarily about Episodes) } diff --git a/source/net/filebot/ui/subtitle/SubtitleAutoMatchDialog.java b/source/net/filebot/ui/subtitle/SubtitleAutoMatchDialog.java index f3937114..3aba4876 100644 --- a/source/net/filebot/ui/subtitle/SubtitleAutoMatchDialog.java +++ b/source/net/filebot/ui/subtitle/SubtitleAutoMatchDialog.java @@ -923,6 +923,7 @@ class SubtitleAutoMatchDialog extends JDialog { protected static class SubtitleProviderBean extends SubtitleServiceBean { private SubtitleProvider service; + private SubtitleMetrics metrics = new SubtitleMetrics(); public SubtitleProviderBean(SubtitleProvider service, SubtitleAutoMatchDialog inputProvider) { super(service.getName(), service.getIcon(), service.getLink()); @@ -941,7 +942,7 @@ class SubtitleAutoMatchDialog extends JDialog { @Override public float getMatchProbabilty(File videoFile, SubtitleDescriptor descriptor) { - return SubtitleMetrics.verificationMetric().getSimilarity(videoFile, descriptor); + return metrics.verification().getSimilarity(videoFile, descriptor); } } diff --git a/test/net/filebot/similarity/EpisodeMetricsTest.java b/test/net/filebot/similarity/EpisodeMetricsTest.java index c08a7a34..5a2741cf 100644 --- a/test/net/filebot/similarity/EpisodeMetricsTest.java +++ b/test/net/filebot/similarity/EpisodeMetricsTest.java @@ -1,6 +1,5 @@ package net.filebot.similarity; -import static net.filebot.similarity.EpisodeMetrics.*; import static org.junit.Assert.*; import java.io.File; @@ -14,6 +13,8 @@ import net.filebot.web.SimpleDate; public class EpisodeMetricsTest { + public EpisodeMetrics metrics = new EpisodeMetrics(); + @Test public void substringMetrics() { Episode eY1T1 = new Episode("Doctor Who", 1, 1, "Rose"); @@ -21,8 +22,8 @@ public class EpisodeMetricsTest { File fY1T1 = new File("Doctor Who (2005)/Doctor Who - 1x01 - Rose"); File fY2T2 = new File("Doctor Who (1963)/Doctor Who - 1x01 - An Unearthly Child"); - assertEquals(0.5, SubstringFields.getSimilarity(eY1T1, fY1T1), 0.1); - assertEquals(0.5, SubstringFields.getSimilarity(eY1T1, fY2T2), 0.1); + assertEquals(0.5, metrics.SubstringFields.getSimilarity(eY1T1, fY1T1), 0.1); + assertEquals(0.5, metrics.SubstringFields.getSimilarity(eY1T1, fY2T2), 0.1); } @Test @@ -35,8 +36,7 @@ public class EpisodeMetricsTest { episodes.add(new Episode("Veronica Mars", 1, 19, "Hot Dogs")); episodes.add(new Episode("Greek", 1, 19, "No Campus for Old Rules")); - SimilarityMetric[] metrics = new SimilarityMetric[] { EpisodeIdentifier, SubstringFields }; - List> m = new Matcher(files, episodes, false, metrics).match(); + List> m = new Matcher(files, episodes, false, new SimilarityMetric[] { metrics.EpisodeIdentifier, metrics.SubstringFields }).match(); assertEquals("Greek - S01E19 - No Campus for Old Rules", m.get(0).getValue().getName()); assertEquals("Greek - 1x19 - No Campus for Old Rules", m.get(0).getCandidate().toString()); @@ -46,12 +46,12 @@ public class EpisodeMetricsTest { @Test public void nameIgnoreEmbeddedChecksum() { - assertEquals(1, Name.getSimilarity("test", "test [EF62DF13]"), 0); + assertEquals(1, metrics.Name.getSimilarity("test", "test [EF62DF13]"), 0); } @Test public void numericIgnoreEmbeddedChecksum() { - assertEquals(1, Numeric.getSimilarity("S01E02", "Season 1, Episode 2 [00A01E02]"), 0); + assertEquals(1, metrics.Numeric.getSimilarity("S01E02", "Season 1, Episode 2 [00A01E02]"), 0); } @Test @@ -60,8 +60,8 @@ public class EpisodeMetricsTest { Episode e1 = new Episode("SEED", null, 1, "Enraged Eyes", 1, null, new SimpleDate(2004, 10, 9), null, null); Episode s1 = new Episode("SEED", null, null, "EDITED", null, 1, new SimpleDate(2005, 1, 29), null, null); - assertEquals(0.5, Numeric.getSimilarity(fn, e1), 0.01); - assertEquals(0.5, Numeric.getSimilarity(fn, s1), 0.01); + assertEquals(0.5, metrics.Numeric.getSimilarity(fn, e1), 0.01); + assertEquals(0.5, metrics.Numeric.getSimilarity(fn, s1), 0.01); } }