Refactor EpisodeMetrics / SubtitleMetrics to make sure nothing is static so that GC can do it's job (i.e. fix memory leaks)

This commit is contained in:
Reinhard Pointner 2019-02-05 14:45:57 +07:00
parent cf2c7785cc
commit d0c77c65fc
9 changed files with 160 additions and 177 deletions

View File

@ -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) {

View File

@ -32,7 +32,7 @@ public class EpisodeMatcher extends Matcher<File, Object> {
public EpisodeMatcher(Collection<File> values, Collection<Episode> 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

View File

@ -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<Object, Collection<SxE>> seasonEpisodeCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build();
private final Map<Object, Collection<SxE>> cache = synchronizedMap(new HashMap<Object, Collection<SxE>>(64, 4));
@Override
protected Collection<SxE> parse(Object object) {
@ -63,14 +58,7 @@ public enum EpisodeMetrics implements SimilarityMetric {
return emptySet();
}
try {
return seasonEpisodeCache.get(object, () -> {
Collection<SxE> sxe = super.parse(object);
return sxe == null ? emptySet() : sxe;
});
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
return cache.computeIfAbsent(object, super::parse);
}
private Set<SxE> 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<Object, Optional<SimpleDate>> airdateCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build();
private final Map<Object, SimpleDate> cache = synchronizedMap(new HashMap<Object, SimpleDate>(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<Integer> 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<String, Object> getProperties(Object object) {
@ -703,43 +699,28 @@ public enum EpisodeMetrics implements SimilarityMetric {
return emptyMap();
}
});
};
// inner metric
private final SimilarityMetric metric;
protected final Map<Object, String> transformCache = synchronizedMap(new HashMap<Object, String>(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<Object, String> 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);
}
}

View File

@ -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<File, Map<String, Object>> videoPropertiesCache = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build();
private final Map<File, Map<String, Object>> cache = synchronizedMap(new HashMap<File, Map<String, Object>>(64, 4));
private Map<String, Object> 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();
}
}

View File

@ -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<File, SubtitleDescriptor> subtitleByVideo = new LinkedHashMap<File, SubtitleDescriptor>();
// 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<File, SubtitleDescriptor> matcher = new Matcher<File, SubtitleDescriptor>(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

View File

@ -41,7 +41,7 @@ class MatchAction extends AbstractAction {
withWaitCursor(evt.getSource(), () -> {
try {
Matcher<Object, File> matcher = new Matcher<Object, File>(model.values(), model.candidates(), false, EpisodeMetrics.defaultSequence(true));
Matcher<Object, File> matcher = new Matcher<Object, File>(model.values(), model.candidates(), false, new EpisodeMetrics().matchFileSequence());
List<Match<Object, File>> 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();

View File

@ -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)
}

View File

@ -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);
}
}

View File

@ -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<Match<File, Episode>> m = new Matcher<File, Episode>(files, episodes, false, metrics).match();
List<Match<File, Episode>> m = new Matcher<File, Episode>(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);
}
}