mirror of
https://github.com/mitb-archive/filebot
synced 2024-08-13 17:03:45 -04:00
221 lines
6.7 KiB
Java
221 lines
6.7 KiB
Java
package net.filebot.subtitle;
|
|
|
|
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.WeakHashMap;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import net.filebot.mediainfo.MediaInfo;
|
|
import net.filebot.mediainfo.MediaInfo.StreamKind;
|
|
import net.filebot.similarity.CrossPropertyMetric;
|
|
import net.filebot.similarity.EpisodeMetrics;
|
|
import net.filebot.similarity.MetricAvg;
|
|
import net.filebot.similarity.MetricCascade;
|
|
import net.filebot.similarity.NameSimilarityMetric;
|
|
import net.filebot.similarity.NumericSimilarityMetric;
|
|
import net.filebot.similarity.SequenceMatchSimilarity;
|
|
import net.filebot.similarity.SimilarityMetric;
|
|
import net.filebot.web.OpenSubtitlesSubtitleDescriptor;
|
|
import net.filebot.web.SubtitleDescriptor;
|
|
|
|
public enum SubtitleMetrics implements SimilarityMetric {
|
|
|
|
// subtitle verification metric specifically excluding SxE mismatches
|
|
AbsoluteSeasonEpisode(new SimilarityMetric() {
|
|
|
|
@Override
|
|
public float getSimilarity(Object o1, Object o2) {
|
|
float f = SeasonEpisode.getSimilarity(o1, o2);
|
|
if (f == 0 && (getEpisodeIdentifier(o1.toString(), true) == null) == (getEpisodeIdentifier(o2.toString(), true) == null)) {
|
|
return 0;
|
|
}
|
|
return f < 1 ? -1 : 1;
|
|
}
|
|
}),
|
|
|
|
DiskNumber(new NumericSimilarityMetric() {
|
|
|
|
private final Pattern CDNO = Pattern.compile("(?:CD|DISK)(\\d+)", Pattern.CASE_INSENSITIVE);
|
|
|
|
@Override
|
|
public float getSimilarity(Object o1, Object o2) {
|
|
int c1 = getDiskNumber(o1);
|
|
int c2 = getDiskNumber(o2);
|
|
|
|
if (c1 == 0 && c2 == 0) // undefined
|
|
return 0;
|
|
|
|
return c1 == c2 ? 1 : -1; // positive or negative match
|
|
}
|
|
|
|
public int getDiskNumber(Object o) {
|
|
int cd = 0;
|
|
Matcher matcher = CDNO.matcher(o.toString());
|
|
while (matcher.find()) {
|
|
cd = Integer.parseInt(matcher.group(1));
|
|
}
|
|
return cd;
|
|
}
|
|
}),
|
|
|
|
NameSubstringSequenceExists(new SequenceMatchSimilarity() {
|
|
|
|
@Override
|
|
public float getSimilarity(Object o1, Object o2) {
|
|
String[] f1 = getNormalizedEffectiveIdentifiers(o1);
|
|
String[] f2 = getNormalizedEffectiveIdentifiers(o2);
|
|
|
|
for (String s1 : f1) {
|
|
for (String s2 : f2) {
|
|
if (super.getSimilarity(s1, s2) >= 1) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
protected float similarity(String match, String s1, String s2) {
|
|
return match.length() > 0 ? 1 : 0;
|
|
}
|
|
|
|
@Override
|
|
protected String normalize(Object object) {
|
|
return object.toString();
|
|
}
|
|
|
|
protected String[] getNormalizedEffectiveIdentifiers(Object object) {
|
|
List<?> identifiers = getEffectiveIdentifiers(object);
|
|
String[] names = new String[identifiers.size()];
|
|
|
|
for (int i = 0; i < names.length; i++) {
|
|
names[i] = EpisodeMetrics.normalizeObject(identifiers.get(i));
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
protected List<?> getEffectiveIdentifiers(Object object) {
|
|
if (object instanceof OpenSubtitlesSubtitleDescriptor) {
|
|
return singletonList(((OpenSubtitlesSubtitleDescriptor) object).getName());
|
|
} else if (object instanceof File) {
|
|
return listPathTail((File) object, 2, true);
|
|
}
|
|
return emptyList();
|
|
}
|
|
}),
|
|
|
|
OriginalFileName(new SequenceMatchSimilarity() {
|
|
|
|
@Override
|
|
protected float similarity(String match, String s1, String s2) {
|
|
return (float) match.length() / Math.max(s1.length(), s2.length()) > 0.8 ? 1 : 0;
|
|
}
|
|
|
|
@Override
|
|
public String normalize(Object object) {
|
|
if (object instanceof File) {
|
|
File file = (File) object;
|
|
String name = xattr.getOriginalName(file);
|
|
if (name == null) {
|
|
name = file.getName();
|
|
}
|
|
return super.normalize(getNameWithoutExtension(name));
|
|
} else if (object instanceof OpenSubtitlesSubtitleDescriptor) {
|
|
String name = ((OpenSubtitlesSubtitleDescriptor) object).getName();
|
|
return super.normalize(name);
|
|
}
|
|
return super.normalize(object);
|
|
}
|
|
}),
|
|
|
|
VideoProperties(new CrossPropertyMetric() {
|
|
|
|
private final String FPS = "FPS";
|
|
private final String SECONDS = "SECS";
|
|
|
|
@Override
|
|
public float getSimilarity(Object o1, Object o2) {
|
|
return o1 instanceof SubtitleDescriptor ? super.getSimilarity(o1, o2) : super.getSimilarity(o2, o1); // make sure that SubtitleDescriptor is o1
|
|
};
|
|
|
|
@Override
|
|
protected Map<String, Object> getProperties(Object object) {
|
|
if (object instanceof OpenSubtitlesSubtitleDescriptor) {
|
|
return getSubtitleProperties((OpenSubtitlesSubtitleDescriptor) object);
|
|
} else if (object instanceof File) {
|
|
return getVideoProperties((File) object);
|
|
}
|
|
return emptyMap();
|
|
};
|
|
|
|
private Map<String, Object> getProperties(float fps, long millis) {
|
|
Map<String, Object> props = new HashMap<String, Object>(2);
|
|
if (fps > 0) {
|
|
props.put(FPS, Math.round(fps)); // round because most FPS values in the database are bad anyway
|
|
}
|
|
if (millis > 0) {
|
|
props.put(SECONDS, Math.round(Math.floor(millis / 1000d)));
|
|
}
|
|
return props;
|
|
}
|
|
|
|
private Map<String, Object> getSubtitleProperties(OpenSubtitlesSubtitleDescriptor subtitle) {
|
|
try {
|
|
return getProperties(subtitle.getMovieFPS(), subtitle.getMovieTimeMS());
|
|
} catch (Exception e) {
|
|
debug.warning("Failed to read subtitle properties: " + e);
|
|
}
|
|
return emptyMap();
|
|
}
|
|
|
|
private final Map<File, Map<String, Object>> mediaInfoCache = synchronizedMap(new WeakHashMap<File, Map<String, Object>>(64));
|
|
|
|
private Map<String, Object> getVideoProperties(File file) {
|
|
return mediaInfoCache.computeIfAbsent(file, key -> {
|
|
try (MediaInfo mi = new MediaInfo().open(file)) {
|
|
float fps = Float.parseFloat(mi.get(StreamKind.Video, 0, "FrameRate"));
|
|
long millis = Long.parseLong(mi.get(StreamKind.Video, 0, "Duration"));
|
|
return getProperties(fps, millis);
|
|
} catch (Exception e) {
|
|
debug.warning("Failed to read video properties: " + e.getMessage());
|
|
}
|
|
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() {
|
|
return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, OriginalFileName, NameSubstringSequenceExists, new MetricAvg(NameSubstringSequenceExists, Name), Numeric, FileName, DiskNumber, VideoProperties, new NameSimilarityMetric() };
|
|
}
|
|
|
|
public static SimilarityMetric verificationMetric() {
|
|
return new MetricCascade(AbsoluteSeasonEpisode, AirDate, new MetricAvg(NameSubstringSequenceExists, Name), getMovieMatchMetric(), OriginalFileName);
|
|
}
|
|
|
|
}
|