mirror of
https://github.com/mitb-archive/filebot
synced 2025-03-09 13:59:49 -04:00
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:
parent
cf2c7785cc
commit
d0c77c65fc
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user