+ experimental support for alias titles

This commit is contained in:
Reinhard Pointner 2013-09-06 07:55:13 +00:00
parent b4b4cf2e9b
commit 1058484593
12 changed files with 572 additions and 721 deletions

View File

@ -1,18 +1,26 @@
package net.sourceforge.filebot.format;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.Settings.*;
import static net.sourceforge.filebot.format.Define.*;
import static net.sourceforge.filebot.hash.VerificationUtilities.*;
import static net.sourceforge.filebot.media.MediaDetection.*;
import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.filebot.web.EpisodeFormat.*;
import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.StringUtilities.*;
import static java.util.Arrays.asList;
import static java.util.Arrays.sort;
import static java.util.Collections.singleton;
import static net.sourceforge.filebot.MediaTypes.SUBTITLE_FILES;
import static net.sourceforge.filebot.MediaTypes.VIDEO_FILES;
import static net.sourceforge.filebot.Settings.useExtendedFileAttributes;
import static net.sourceforge.filebot.format.Define.undefined;
import static net.sourceforge.filebot.hash.VerificationUtilities.computeHash;
import static net.sourceforge.filebot.hash.VerificationUtilities.getEmbeddedChecksum;
import static net.sourceforge.filebot.hash.VerificationUtilities.getHashFromVerificationFile;
import static net.sourceforge.filebot.media.MediaDetection.releaseInfo;
import static net.sourceforge.filebot.media.MediaDetection.stripReleaseInfo;
import static net.sourceforge.filebot.similarity.Normalization.removeTrailingBrackets;
import static net.sourceforge.filebot.web.EpisodeFormat.SeasonEpisode;
import static net.sourceforge.tuned.FileUtilities.filter;
import static net.sourceforge.tuned.FileUtilities.hasExtension;
import static net.sourceforge.tuned.FileUtilities.isDerived;
import static net.sourceforge.tuned.FileUtilities.listFiles;
import static net.sourceforge.tuned.FileUtilities.readFile;
import static net.sourceforge.tuned.FileUtilities.replacePathSeparators;
import static net.sourceforge.tuned.StringUtilities.join;
import java.io.File;
import java.io.IOException;
@ -50,7 +58,6 @@ import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
import com.cedarsoftware.util.io.JsonWriter;
public class MediaBindingBean {
private final Object infoObject;
@ -60,21 +67,18 @@ public class MediaBindingBean {
private MediaInfo mediaInfo;
private Object metaInfo;
public MediaBindingBean(Object infoObject, File mediaFile, Map<File, Object> context) {
this.infoObject = infoObject;
this.mediaFile = mediaFile;
this.context = context;
}
@Define(undefined)
public <T> T undefined() {
// omit expressions that depend on undefined values
throw new RuntimeException("undefined");
}
@Define("n")
public String getName() {
if (infoObject instanceof Episode)
@ -87,7 +91,6 @@ public class MediaBindingBean {
return null;
}
@Define("y")
public Integer getYear() {
if (infoObject instanceof Episode)
@ -100,19 +103,16 @@ public class MediaBindingBean {
return null;
}
@Define("s")
public Integer getSeasonNumber() {
return getEpisode().getSeason();
}
@Define("e")
public Integer getEpisodeNumber() {
return getEpisode().getEpisode();
}
@Define("es")
public List<Integer> getEpisodeNumbers() {
List<Integer> n = new ArrayList<Integer>();
@ -122,19 +122,16 @@ public class MediaBindingBean {
return n;
}
@Define("sxe")
public String getSxE() {
return SeasonEpisode.formatSxE(getEpisode());
}
@Define("s00e00")
public String getS00E00() {
return SeasonEpisode.formatS00E00(getEpisode());
}
@Define("t")
public String getTitle() {
if (infoObject instanceof AudioTrack) {
@ -154,7 +151,6 @@ public class MediaBindingBean {
return join(title, " & ");
}
@Define("d")
public Object getReleaseDate() {
if (infoObject instanceof Episode) {
@ -171,50 +167,62 @@ public class MediaBindingBean {
return null;
}
@Define("airdate")
public Date airdate() {
return getEpisode().getAirdate();
}
@Define("startdate")
public Date startdate() {
return getEpisode().getSeriesStartDate();
}
@Define("absolute")
public Integer getAbsoluteEpisodeNumber() {
return getEpisode().getAbsolute();
}
@Define("special")
public Integer getSpecialNumber() {
return getEpisode().getSpecial();
}
@Define("series")
public SearchResult getSeriesObject() {
return getEpisode().getSeries();
}
@Define("alias")
public List<String> getAliasNames() {
if (infoObject instanceof Movie) {
return asList(getMovie().getAliasNames());
}
if (infoObject instanceof Episode) {
return asList(getSeriesObject().getAliasNames());
}
return null;
}
@Define("primaryTitle")
public String getPrimaryTitle() throws Exception {
if (infoObject instanceof Movie) {
return WebServices.TMDb.getMovieInfo(getMovie(), Locale.ENGLISH).getName();
}
if (infoObject instanceof Episode) {
if (getSeriesObject() instanceof TheTVDBSearchResult) {
return WebServices.TheTVDB.getSeriesInfo((TheTVDBSearchResult) getSeriesObject(), Locale.ENGLISH).getName();
}
if (getSeriesObject() instanceof AnidbSearchResult) {
return ((AnidbSearchResult) getSeriesObject()).getPrimaryTitle();
}
// default to original search result
return getSeriesObject().getName();
return getSeriesObject().getName(); // default to original search result
}
return null;
}
@Define("tmdbid")
public String getTmdbId() throws Exception {
@ -232,7 +240,6 @@ public class MediaBindingBean {
return String.valueOf(tmdbid);
}
@Define("imdbid")
public String getImdbId() throws Exception {
int imdbid = getMovie().getImdbId();
@ -249,7 +256,6 @@ public class MediaBindingBean {
return String.format("tt%07d", imdbid);
}
@Define("vc")
public String getVideoCodec() {
// e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc.
@ -259,7 +265,6 @@ public class MediaBindingBean {
return new Scanner(codec).next();
}
@Define("ac")
public String getAudioCodec() {
// e.g. AC-3, DTS, AAC, Vorbis, MP3, etc.
@ -269,7 +274,6 @@ public class MediaBindingBean {
return codec.replaceAll("\\p{Punct}", "");
}
@Define("cf")
public String getContainerFormat() {
// container format extensions (e.g. avi, mkv mka mks, OGG, etc.)
@ -279,7 +283,6 @@ public class MediaBindingBean {
return new Scanner(extensions).next().toLowerCase();
}
@Define("vf")
public String getVideoFormat() {
int width = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Width"));
@ -301,7 +304,6 @@ public class MediaBindingBean {
return null; // video too small
}
@Define("hpi")
public String getExactVideoFormat() {
String height = getMediaInfo(StreamKind.Video, 0, "Height");
@ -314,7 +316,6 @@ public class MediaBindingBean {
return height + Character.toLowerCase(scanType.charAt(0));
}
@Define("af")
public String getAudioChannels() {
String channels = getMediaInfo(StreamKind.Audio, 0, "Channel(s)");
@ -326,7 +327,6 @@ public class MediaBindingBean {
return channels + "ch";
}
@Define("resolution")
public String getVideoResolution() {
List<Integer> dim = getDimension();
@ -338,7 +338,6 @@ public class MediaBindingBean {
return join(dim, "x");
}
@Define("ws")
public String getWidescreen() {
List<Integer> dim = getDimension();
@ -347,7 +346,6 @@ public class MediaBindingBean {
return (float) dim.get(0) / dim.get(1) > 1.37f ? "ws" : null;
}
@Define("sdhd")
public String getVideoDefinitionCategory() {
List<Integer> dim = getDimension();
@ -356,7 +354,6 @@ public class MediaBindingBean {
return dim.get(0) >= 1280 || dim.get(1) >= 720 ? "HD" : "SD";
}
@Define("dim")
public List<Integer> getDimension() {
String width = getMediaInfo(StreamKind.Video, 0, "Width");
@ -365,19 +362,16 @@ public class MediaBindingBean {
return asList(width != null ? Integer.parseInt(width) : null, height != null ? Integer.parseInt(height) : null);
}
@Define("original")
public String getOriginalFileName() {
return getOriginalFileName(mediaFile);
}
@Define("xattr")
public Object getMetaAttributesObject() {
return getMetaAttributesObject(mediaFile);
}
@Define("crc32")
public String getCRC32() throws IOException, InterruptedException {
// use inferred media file
@ -399,7 +393,6 @@ public class MediaBindingBean {
return crc32(inferredMediaFile);
}
@Define("fn")
public String getFileName() {
// make sure media file is defined
@ -409,7 +402,6 @@ public class MediaBindingBean {
return FileUtilities.getName(mediaFile);
}
@Define("ext")
public String getExtension() {
// make sure media file is defined
@ -419,7 +411,6 @@ public class MediaBindingBean {
return FileUtilities.getExtension(mediaFile);
}
@Define("source")
public String getVideoSource() {
// use inferred media file
@ -429,7 +420,6 @@ public class MediaBindingBean {
return releaseInfo.getVideoSource(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile));
}
@Define("group")
public String getReleaseGroup() throws IOException {
// use inferred media file
@ -439,7 +429,6 @@ public class MediaBindingBean {
return releaseInfo.getReleaseGroup(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile));
}
@Define("lang")
public Locale detectSubtitleLanguage() throws Exception {
// make sure media file is defined
@ -469,13 +458,11 @@ public class MediaBindingBean {
return WebServices.OpenSubtitles.detectLanguage(readFile(mediaFile));
}
@Define("actors")
public Object getActors() {
return getMetaInfo().getProperty("actors");
}
@Define("genres")
public Object getGenres() {
if (infoObject instanceof AudioTrack)
@ -484,31 +471,26 @@ public class MediaBindingBean {
return getMetaInfo().getProperty("genres");
}
@Define("director")
public Object getDirector() {
return getMetaInfo().getProperty("director");
}
@Define("certification")
public Object getCertification() {
return getMetaInfo().getProperty("certification");
}
@Define("rating")
public Object getRating() {
return getMetaInfo().getProperty("rating");
}
@Define("collection")
public Object getCollection() {
return getMetaInfo().getProperty("collection");
}
@Define("info")
public synchronized AssociativeScriptObject getMetaInfo() {
if (metaInfo == null) {
@ -525,7 +507,6 @@ public class MediaBindingBean {
return createMapBindings(new PropertyBindings(metaInfo, null));
}
@Define("imdb")
public synchronized AssociativeScriptObject getImdbApiInfo() {
Object data = null;
@ -545,97 +526,81 @@ public class MediaBindingBean {
return createMapBindings(new PropertyBindings(data, null));
}
@Define("episodelist")
public Object getEpisodeList() throws Exception {
return WebServices.TheTVDB.getEpisodeList(WebServices.TheTVDB.search(getEpisode().getSeriesName()).get(0), SortOrder.Airdate, Locale.ENGLISH);
}
@Define("media")
public AssociativeScriptObject getGeneralMediaInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.General, 0));
}
@Define("video")
public AssociativeScriptObject getVideoInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Video, 0));
}
@Define("audio")
public AssociativeScriptObject getAudioInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Audio, 0));
}
@Define("text")
public AssociativeScriptObject getTextInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Text, 0));
}
@Define("videos")
public List<AssociativeScriptObject> getVideoInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Video));
}
@Define("audios")
public List<AssociativeScriptObject> getAudioInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Audio));
}
@Define("texts")
public List<AssociativeScriptObject> getTextInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Text));
}
@Define("artist")
public String getArtist() {
return getMusic().getArtist();
}
@Define("albumArtist")
public String getAlbumArtist() {
return getMusic().getAlbumArtist();
}
@Define("album")
public String getAlbum() {
return getMusic().getAlbum();
}
@Define("episode")
public Episode getEpisode() {
return (Episode) infoObject;
}
@Define("episodes")
public List<Episode> getEpisodes() {
return infoObject instanceof MultiEpisode ? ((MultiEpisode) infoObject).getEpisodes() : asList(getEpisode());
}
@Define("movie")
public Movie getMovie() {
return (Movie) infoObject;
}
@Define("music")
public AudioTrack getMusic() {
return (AudioTrack) infoObject;
}
@Define("pi")
public Integer getPart() {
if (infoObject instanceof AudioTrack)
@ -646,7 +611,6 @@ public class MediaBindingBean {
return null;
}
@Define("pn")
public Integer getPartCount() {
if (infoObject instanceof AudioTrack)
@ -657,37 +621,31 @@ public class MediaBindingBean {
return null;
}
@Define("file")
public File getMediaFile() {
return mediaFile;
}
@Define("folder")
public File getMediaParentFolder() {
return getMediaFile().getParentFile();
}
@Define("home")
public File getUserHome() throws IOException {
return new File(System.getProperty("user.home"));
}
@Define("object")
public Object getInfoObject() {
return infoObject;
}
@Define("i")
public Integer getModelIndex() {
return identityIndexOf(getContext().values(), getInfoObject());
}
@Define("di")
public Integer getDuplicateIndex() {
List<Object> duplicates = new ArrayList<Object>();
@ -700,19 +658,16 @@ public class MediaBindingBean {
return di == 0 ? null : di;
}
@Define("model")
public Map<File, Object> getContext() {
return context;
}
@Define("json")
public String getInfoObjectDump() throws Exception {
return JsonWriter.objectToJson(infoObject);
}
private File getInferredMediaFile() {
// make sure media file is defined
checkMediaFile();
@ -760,14 +715,12 @@ public class MediaBindingBean {
return mediaFile;
}
private void checkMediaFile() throws RuntimeException {
// make sure file is not null, and that it is an existing file
if (mediaFile == null)
throw new RuntimeException("Invalid media file: " + mediaFile);
}
private synchronized MediaInfo getMediaInfo() {
if (mediaInfo == null) {
// make sure media file is defined
@ -786,7 +739,6 @@ public class MediaBindingBean {
return mediaInfo;
}
private Integer identityIndexOf(Iterable<?> c, Object o) {
Iterator<?> itr = c.iterator();
for (int i = 0; itr.hasNext(); i++) {
@ -797,7 +749,6 @@ public class MediaBindingBean {
return null;
}
private String getMediaInfo(StreamKind streamKind, int streamNumber, String... keys) {
for (String key : keys) {
String value = getMediaInfo().get(streamKind, streamNumber, key);
@ -809,7 +760,6 @@ public class MediaBindingBean {
return null;
}
private AssociativeScriptObject createMapBindings(Map<?, ?> map) {
return new AssociativeScriptObject(map) {
@ -831,7 +781,6 @@ public class MediaBindingBean {
};
}
private List<AssociativeScriptObject> createMapBindingsList(List<Map<String, String>> mapList) {
List<AssociativeScriptObject> bindings = new ArrayList<AssociativeScriptObject>();
for (Map<?, ?> it : mapList) {
@ -840,7 +789,6 @@ public class MediaBindingBean {
return bindings;
}
private String crc32(File file) throws IOException, InterruptedException {
// try to get checksum from cache
Cache cache = Cache.getCache("checksum");
@ -856,7 +804,6 @@ public class MediaBindingBean {
return hash;
}
private String getOriginalFileName(File file) {
if (useExtendedFileAttributes()) {
try {
@ -868,7 +815,6 @@ public class MediaBindingBean {
return null;
}
private Object getMetaAttributesObject(File file) {
if (useExtendedFileAttributes()) {
try {

View File

@ -611,7 +611,7 @@ public class MediaDetection {
sort(sorted, new SimilarityComparator(getMovieMatchMetric(), paragon.toArray()));
// DEBUG
// System.out.format("sortBySimilarity %s => %s", terms, options);
// System.out.format("sortBySimilarity %s => %s", terms, sorted);
return sorted;
}

View File

@ -5,25 +5,25 @@ pattern.video.source: CAMRip|CAM|PDVD|TS|TELESYNC|PDVD|PPV|PPVRip|Screener|SCR|S
pattern.video.format: DivX|Xvid|AVC|x264|h264|3ivx|mpg|mpeg|mpeg4|mp3|AAC|AAC2.0|AAC5.1|AAC.2.0|AAC.5.1|AC3|dd20|dd51|2ch|6ch|TS|DTS|DTS.HD|DTS.HD.MA|TrueHD|WS|HR|7p|720p|18p|1080p|PAL|NTSC|3D
# known release group names
url.release-groups: http://filebot.net/data/release-groups.txt
url.release-groups: file:///d:/workspace/filebot/website/data/release-groups.txt
# blacklisted terms that will be ignored
url.query-blacklist: http://filebot.net/data/query-blacklist.txt
url.query-blacklist: file:///d:/workspace/filebot/website/data/query-blacklist.txt
# clutter files that will be ignored
url.exclude-blacklist: http://filebot.net/data/exclude-blacklist.txt
url.exclude-blacklist: file:///d:/workspace/filebot/website/data/exclude-blacklist.txt
# list of patterns directly matching files to series names
url.series-mappings: http://filebot.net/data/series-mappings.txt
url.series-mappings: file:///d:/workspace/filebot/website/data/series-mappings.txt
# list of all movies (id, name, year)
url.movie-list: http://filebot.net/data/movies.txt.xz
url.movie-list: file:///d:/workspace/filebot/website/data/movies.txt.xz
# TheTVDB index
url.thetvdb-index: http://filebot.net/data/thetvdb.txt.xz
url.thetvdb-index: file:///d:/workspace/filebot/website/data/thetvdb.txt.xz
# AniDB index
url.anidb-index: http://filebot.net/data/anidb.txt.xz
url.anidb-index: file:///d:/workspace/filebot/website/data/anidb.txt.xz
# disk folder matcher
pattern.diskfolder.entry: BDMV|HVDVD_TS|VIDEO_TS|AUDIO_TS|VCD|movie.nfo

View File

@ -1,20 +1,31 @@
package net.sourceforge.filebot.similarity;
import static java.lang.Math.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.Settings.*;
import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.StringUtilities.*;
import static java.lang.Math.ceil;
import static java.lang.Math.floor;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.synchronizedMap;
import static net.sourceforge.filebot.Settings.useExtendedFileAttributes;
import static net.sourceforge.filebot.similarity.Normalization.normalizePunctuation;
import static net.sourceforge.filebot.similarity.Normalization.removeEmbeddedChecksum;
import static net.sourceforge.filebot.similarity.Normalization.removeTrailingBrackets;
import static net.sourceforge.tuned.FileUtilities.getName;
import static net.sourceforge.tuned.FileUtilities.getNameWithoutExtension;
import static net.sourceforge.tuned.FileUtilities.getRelativePathTail;
import static net.sourceforge.tuned.FileUtilities.normalizePathSeparators;
import static net.sourceforge.tuned.StringUtilities.join;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -36,7 +47,6 @@ import net.sourceforge.filebot.web.TheTVDBSearchResult;
import com.ibm.icu.text.Transliterator;
public enum EpisodeMetrics implements SimilarityMetric {
// Match by season / episode numbers
@ -44,7 +54,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
private final Map<Object, Collection<SxE>> transformCache = synchronizedMap(new HashMap<Object, Collection<SxE>>(64, 4));
@Override
protected Collection<SxE> parse(Object object) {
if (object instanceof Movie) {
@ -82,7 +91,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
private final Map<Object, Date> transformCache = synchronizedMap(new HashMap<Object, Date>(64, 4));
@Override
public Date parse(Object object) {
if (object instanceof Movie) {
@ -154,7 +162,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return (float) ((max(sxe, 0) * title) + (floor(sxe) / 10));
}
public Object getTitle(Object o) {
if (o instanceof Episode) {
Episode e = (Episode) o;
@ -185,7 +192,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return (float) (ceil(sum * 3) / 3);
}
protected String[] normalize(Object[] objects) {
String[] names = new String[objects.length];
@ -196,17 +202,23 @@ public enum EpisodeMetrics implements SimilarityMetric {
return names;
}
protected Object[] fields(Object object) {
if (object instanceof Episode) {
Episode episode = (Episode) object;
String seriesName = removeTrailingBrackets(episode.getSeriesName());
String episodeTitle = episode.getTitle();
if (!seriesName.equalsIgnoreCase(episodeTitle)) {
return new Object[] { seriesName, episodeTitle };
} else {
return new Object[] { seriesName, null };
LinkedHashSet<String> set = new LinkedHashSet<String>(4);
set.add(removeTrailingBrackets(episode.getSeriesName()));
set.add(removeTrailingBrackets(episode.getTitle()));
set.add(removeTrailingBrackets(episode.getSeries().getName()));
for (String it : episode.getSeries().getAliasNames()) {
set.add(removeTrailingBrackets(it));
}
Iterator<String> itr = set.iterator();
Object[] f = new Object[4];
for (int i = 0; i < f.length; i++) {
f[i] = itr.hasNext() ? itr.next() : null;
}
return f;
}
if (object instanceof File) {
@ -233,7 +245,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return (float) (floor(super.getSimilarity(o1, o2) * 4) / 4);
}
@Override
protected String normalize(Object object) {
if (object instanceof Episode) {
@ -258,7 +269,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return (float) (floor(super.getSimilarity(o1, o2) * 4) / 4);
}
@Override
protected String normalize(Object object) {
// simplify file name, if possible
@ -272,7 +282,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
private ReleaseInfo releaseInfo = new ReleaseInfo();
private SeriesNameMatcher seriesNameMatcher = new SeriesNameMatcher();
@Override
public float getSimilarity(Object o1, Object o2) {
float lowerBound = super.getSimilarity(normalize(o1, true), normalize(o2, true));
@ -281,13 +290,11 @@ public enum EpisodeMetrics implements SimilarityMetric {
return (float) (floor(max(lowerBound, upperBound) * 4) / 4);
};
@Override
protected String normalize(Object object) {
return object.toString();
};
protected String normalize(Object object, boolean strict) {
if (object instanceof Episode) {
if (strict) {
@ -339,13 +346,11 @@ public enum EpisodeMetrics implements SimilarityMetric {
return max(lowerBound, upperBound);
};
@Override
protected String normalize(Object object) {
return object.toString();
};
protected String normalize(Object object, boolean numbersOnly) {
if (object instanceof Episode) {
Episode e = (Episode) object;
@ -391,7 +396,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return max;
}
protected String[] fields(Object object) {
if (object instanceof Episode) {
Episode episode = (Episode) object;
@ -416,7 +420,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2);
}
@Override
protected long getLength(Object object) {
if (object instanceof FileInfo) {
@ -450,7 +453,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return f >= 0.8 ? 1 : f >= 0 ? 0 : -1;
}
@Override
public long getTimeStamp(Object object) {
if (object instanceof Episode) {
@ -483,7 +485,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
private final Map<String, SeriesInfo> seriesInfoCache = new HashMap<String, SeriesInfo>();
public float getRating(Object o) {
if (o instanceof Episode) {
try {
@ -545,12 +546,10 @@ public enum EpisodeMetrics implements SimilarityMetric {
// inner metric
private final SimilarityMetric metric;
private EpisodeMetrics(SimilarityMetric metric) {
this.metric = metric;
}
@Override
public float getSimilarity(Object o1, Object o2) {
return metric.getSimilarity(o1, o2);
@ -559,7 +558,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
private static final Map<Object, String> transformCache = synchronizedMap(new HashMap<Object, String>(64, 4));
private static final Transliterator transliterator = Transliterator.getInstance("Any-Latin;Latin-ASCII;[:Diacritic:]remove");
protected static String normalizeObject(Object object) {
if (object == null) {
return "";
@ -596,7 +594,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
return name;
}
public static SimilarityMetric[] defaultSequence(boolean includeFileMetrics) {
// 1 pass: divide by file length (only works for matching torrent entries or files)
// 2-3 pass: divide by title or season / episode numbers
@ -612,7 +609,6 @@ public enum EpisodeMetrics implements SimilarityMetric {
}
}
public static SimilarityMetric verificationMetric() {
return new MetricCascade(FileSize, FileName, SeasonEpisode, AirDate, Title, Name);
}

View File

@ -1,9 +1,6 @@
package net.sourceforge.filebot.similarity;
import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.filebot.similarity.Normalization.normalizePunctuation;
public class SubstringMetric implements SimilarityMetric {
@ -20,8 +17,10 @@ public class SubstringMetric implements SimilarityMetric {
return s1.contains(s2) || s2.contains(s1) ? 1 : 0;
}
protected String normalize(Object object) {
if (object == null)
return null;
// use string representation
String name = object.toString();

View File

@ -4,8 +4,8 @@ episode.syntax: <html><b>{</b> <b>}</b> \u2026 expression, <b>n</b> \u2026 name,
movie.syntax: <html><b>{</b> <b>}</b> \u2026 expression, <b>n</b> \u2026 name, <b>y</b> \u2026 year</html>
music.syntax: <html><b>{</b> <b>}</b> \u2026 expression, <b>n</b> \u2026 album artist, <b>t</b> \u2026 title, <b>album</b> \u2026 album, <b>pi</b> \u2026 track</html>
episode.sample: {"@type":"net.sourceforge.filebot.web.Episode","seriesName":"Firefly","seriesStartDate":{"year":2002,"month":9,"day":20},"season":1,"episode":1,"title":"Serenity","absolute":1,"special":null,"airdate":{"year":2002,"month":12,"day":20},"series":{"@type":"net.sourceforge.filebot.web.TheTVDBSearchResult","seriesId":78874,"name":"Firefly"}}
movie.sample: {"@type":"net.sourceforge.filebot.web.MoviePart","partIndex":1,"partCount":2,"year":2009,"imdbId":-1,"tmdbId":19995,"name":"Avatar"}
episode.sample: {"@type":"net.sourceforge.filebot.web.Episode","seriesName":"Firefly","seriesStartDate":{"year":2002,"month":9,"day":20},"season":1,"episode":1,"title":"Serenity","absolute":1,"special":null,"airdate":{"year":2002,"month":12,"day":20},"series":{"@type":"net.sourceforge.filebot.web.TheTVDBSearchResult","seriesId":78874,"name":"Firefly","aliasNames":[]}}
movie.sample: {"@type":"net.sourceforge.filebot.web.MoviePart","partIndex":1,"partCount":2,"year":2009,"imdbId":-1,"tmdbId":19995,"name":"Avatar","aliasNames":[]}
music.sample: {"@type":"net.sourceforge.filebot.web.AudioTrack","artist":"Leona Lewis","title":"I See You","album":"Avatar","albumArtist":"James Horner","trackTitle":null,"albumReleaseDate":{"year":2009,"month":12,"day":11},"mediumIndex":1,"mediumCount":1,"trackIndex":14,"trackCount":14}
# basic 1.01

View File

@ -1,58 +1,44 @@
package net.sourceforge.filebot.web;
public class AnidbSearchResult extends SearchResult {
protected int aid;
protected String primaryTitle; // one per anime
protected String englishTitle; // one per language
protected AnidbSearchResult() {
// used by serializer
}
public AnidbSearchResult(int aid, String primaryTitle, String englishTitle) {
super(primaryTitle, englishTitle);
this.aid = aid;
this.primaryTitle = primaryTitle;
this.englishTitle = englishTitle;
}
public int getId() {
return aid;
}
public int getAnimeId() {
return aid;
}
@Override
public String getName() {
return primaryTitle;
return name;
}
public String getPrimaryTitle() {
return primaryTitle;
return name;
}
public String getEnglishTitle() {
return englishTitle;
return aliasNames.length > 0 ? aliasNames[0] : null;
}
@Override
public int hashCode() {
return aid;
}
@Override
public boolean equals(Object object) {
if (object instanceof AnidbSearchResult) {

View File

@ -1,50 +1,44 @@
package net.sourceforge.filebot.web;
import java.util.Arrays;
public class Movie extends SearchResult {
protected int year;
protected int imdbId;
protected int tmdbId;
protected Movie() {
// used by serializer
}
public Movie(Movie obj) {
this(obj.name, obj.year, obj.imdbId, obj.tmdbId);
this(obj.name, obj.aliasNames, obj.year, obj.imdbId, obj.tmdbId);
}
public Movie(String name, int year, int imdbId, int tmdbId) {
super(name);
this(name, new String[0], year, imdbId, tmdbId);
}
public Movie(String name, String[] aliasNames, int year, int imdbId, int tmdbId) {
super(name, aliasNames);
this.year = year;
this.imdbId = imdbId;
this.tmdbId = tmdbId;
}
public int getYear() {
return year;
}
public int getImdbId() {
return imdbId;
}
public int getTmdbId() {
return tmdbId;
}
@Override
public boolean equals(Object object) {
if (object instanceof Movie) {
@ -55,25 +49,22 @@ public class Movie extends SearchResult {
return tmdbId == other.tmdbId;
}
return year == other.year && name.equalsIgnoreCase(other.name);
return year == other.year && name.equals(other.name);
}
return false;
}
@Override
public Movie clone() {
return new Movie(this);
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] { name.toLowerCase(), year });
}
@Override
public String toString() {
return String.format("%s (%04d)", name, year < 0 ? 0 : year);

View File

@ -1,33 +1,32 @@
package net.sourceforge.filebot.web;
import java.io.Serializable;
public abstract class SearchResult implements Serializable {
protected final String name;
protected String name;
protected String[] aliasNames;
protected SearchResult() {
this.name = null;
// used by serializer
}
public SearchResult(String name) {
public SearchResult(String name, String... aliasNames) {
this.name = name;
this.aliasNames = aliasNames;
}
public String getName() {
return name;
}
public String[] getAliasNames() {
return aliasNames.clone();
}
@Override
public String toString() {
return getName();
return name;
}
}

View File

@ -74,8 +74,9 @@ public class TMDbClient implements MovieIdentificationService {
// e.g.
// {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"}
String title = (String) it.get("title");
String originalTitle = (String) it.get("original_title");
if (title == null || title.isEmpty()) {
title = (String) it.get("original_title");
title = originalTitle;
}
try {
@ -87,7 +88,7 @@ public class TMDbClient implements MovieIdentificationService {
} catch (Exception e) {
throw new IllegalArgumentException("Missing data: year");
}
result.add(new Movie(title, year, -1, (int) id));
result.add(new Movie(title, title.equals(originalTitle) ? new String[] {} : new String[] { originalTitle }, year, -1, (int) id));
} catch (Exception e) {
Logger.getLogger(TMDbClient.class.getName()).log(Level.FINE, String.format("Ignore movie [%s]: %s", title, e.getMessage()));
}

View File

@ -1,11 +1,15 @@
package net.sourceforge.filebot.web;
import static java.util.Arrays.*;
import static net.sourceforge.filebot.web.EpisodeUtilities.*;
import static net.sourceforge.filebot.web.WebRequest.*;
import static net.sourceforge.tuned.XPathUtilities.*;
import static java.util.Arrays.asList;
import static net.sourceforge.filebot.web.EpisodeUtilities.filterBySeason;
import static net.sourceforge.filebot.web.EpisodeUtilities.sortEpisodes;
import static net.sourceforge.filebot.web.WebRequest.encode;
import static net.sourceforge.filebot.web.WebRequest.getDocument;
import static net.sourceforge.tuned.XPathUtilities.getIntegerContent;
import static net.sourceforge.tuned.XPathUtilities.getTextContent;
import static net.sourceforge.tuned.XPathUtilities.selectNode;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import static net.sourceforge.tuned.XPathUtilities.selectString;
import java.io.FileNotFoundException;
import java.io.Serializable;
@ -38,7 +42,6 @@ import net.sourceforge.tuned.FileUtilities;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class TheTVDBClient extends AbstractEpisodeListProvider {
private final String host = "www.thetvdb.com";
@ -47,7 +50,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
private final String apikey;
public TheTVDBClient(String apikey) {
if (apikey == null)
throw new NullPointerException("apikey must not be null");
@ -55,31 +57,26 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
this.apikey = apikey;
}
@Override
public String getName() {
return "TheTVDB";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.thetvdb");
}
@Override
public boolean hasSingleSeasonSupport() {
return true;
}
@Override
public boolean hasLocaleSupport() {
return true;
}
public String getLanguageCode(Locale locale) {
String code = locale.getLanguage();
@ -96,13 +93,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return code;
}
@Override
public ResultCache getCache() {
return new ResultCache(host, Cache.getCache("web-datasource"));
}
@Override
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception {
// perform online search
@ -116,15 +111,19 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
int sid = getIntegerContent("seriesid", node);
String seriesName = getTextContent("SeriesName", node);
List<String> aliasNames = new ArrayList<String>(2);
for (Node aliasNode : selectNodes("AliasNames", node)) {
aliasNames.add(getTextContent(aliasNode));
}
if (!resultSet.containsKey(sid)) {
resultSet.put(sid, new TheTVDBSearchResult(seriesName, sid));
resultSet.put(sid, new TheTVDBSearchResult(seriesName, aliasNames.toArray(new String[0]), sid));
}
}
return new ArrayList<SearchResult>(resultSet.values());
}
@Override
public List<Episode> fetchEpisodeList(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
TheTVDBSearchResult series = (TheTVDBSearchResult) searchResult;
@ -189,7 +188,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return episodes;
}
public Document getSeriesRecord(TheTVDBSearchResult searchResult, String languageCode) throws Exception {
URL seriesRecord = getResource(MirrorType.ZIP, "/api/" + apikey + "/series/" + searchResult.getSeriesId() + "/all/" + languageCode + ".zip");
@ -217,7 +215,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public TheTVDBSearchResult lookupByID(int id, Locale locale) throws Exception {
TheTVDBSearchResult cachedItem = getCache().getData("lookupByID", id, locale, TheTVDBSearchResult.class);
if (cachedItem != null) {
@ -240,7 +237,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public TheTVDBSearchResult lookupByIMDbID(int imdbid, Locale locale) throws Exception {
TheTVDBSearchResult cachedItem = getCache().getData("lookupByIMDbID", imdbid, locale, TheTVDBSearchResult.class);
if (cachedItem != null) {
@ -261,13 +257,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return series;
}
@Override
public URI getEpisodeListLink(SearchResult searchResult) {
return URI.create("http://" + host + "/?tab=seasonall&id=" + ((TheTVDBSearchResult) searchResult).getSeriesId());
}
protected String getMirror(MirrorType mirrorType) throws Exception {
synchronized (mirrors) {
if (mirrors.isEmpty()) {
@ -324,7 +318,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
protected URL getResource(MirrorType mirrorType, String path) throws Exception {
if (mirrorType != null) {
// use mirror
@ -338,18 +331,15 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return new URL("http", host, path);
}
protected static enum MirrorType {
XML(1), BANNER(2), ZIP(4), SEARCH(1);
private final int bitMask;
private MirrorType(int bitMask) {
this.bitMask = bitMask;
}
public static EnumSet<MirrorType> fromTypeMask(int typeMask) {
// initialize enum set with all types
EnumSet<MirrorType> enumSet = EnumSet.allOf(MirrorType.class);
@ -364,17 +354,14 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
public SeriesInfo getSeriesInfoByID(int thetvdbid, Locale locale) throws Exception {
return getSeriesInfo(new TheTVDBSearchResult(null, thetvdbid), locale);
}
public SeriesInfo getSeriesInfoByIMDbID(int imdbid, Locale locale) throws Exception {
return getSeriesInfo(lookupByIMDbID(imdbid, locale), locale);
}
public SeriesInfo getSeriesInfoByName(String name, Locale locale) throws Exception {
for (SearchResult it : search(name, locale)) {
if (name.equalsIgnoreCase(it.getName())) {
@ -385,7 +372,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null;
}
public SeriesInfo getSeriesInfo(TheTVDBSearchResult searchResult, Locale locale) throws Exception {
// check cache first
SeriesInfo cachedItem = getCache().getData("seriesInfo", searchResult.seriesId, locale, SeriesInfo.class);
@ -414,7 +400,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return seriesInfo;
}
public static class SeriesInfo implements Serializable {
public static enum SeriesProperty {
@ -423,27 +408,22 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
protected Map<SeriesProperty, String> fields;
protected SeriesInfo() {
// used by serializer
}
protected SeriesInfo(Map<SeriesProperty, String> fields) {
this.fields = new EnumMap<SeriesProperty, String>(fields);
}
public String get(Object key) {
return fields.get(SeriesProperty.valueOf(key.toString()));
}
public String get(SeriesProperty key) {
return fields.get(key);
}
public Integer getId() {
// e.g. 80348
try {
@ -453,19 +433,16 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public List<String> getActors() {
// e.g. |Zachary Levi|Adam Baldwin|Yvonne Strzechowski|
return split(get(SeriesProperty.Actors));
}
public List<String> getGenres() {
// e.g. |Comedy|
return split(get(SeriesProperty.Genre));
}
protected List<String> split(String values) {
List<String> items = new ArrayList<String>();
if (values != null && values.length() > 0) {
@ -479,36 +456,30 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return items;
}
public String getAirDayOfWeek() {
// e.g. Monday
return get(SeriesProperty.Airs_DayOfWeek);
}
public String getAirTime() {
// e.g. 8:00 PM
return get(SeriesProperty.Airs_Time);
}
public Date getFirstAired() {
// e.g. 2007-09-24
return Date.parse(get(SeriesProperty.FirstAired), "yyyy-MM-dd");
}
public String getContentRating() {
// e.g. TV-PG
return get(SeriesProperty.ContentRating);
}
public String getCertification() {
return getContentRating(); // another getter for compability reasons
}
public Integer getImdbId() {
// e.g. tt0934814
try {
@ -518,7 +489,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public Locale getLanguage() {
// e.g. en
try {
@ -528,13 +498,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public String getOverview() {
// e.g. Zachary Levi (Less Than Perfect) plays Chuck...
return get(SeriesProperty.Overview);
}
public Double getRating() {
// e.g. 9.0
try {
@ -544,7 +512,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public Integer getRatingCount() {
// e.g. 696
try {
@ -554,31 +521,26 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public String getRuntime() {
// e.g. 30
return get(SeriesProperty.Runtime);
}
public String getName() {
// e.g. Chuck
return get(SeriesProperty.SeriesName);
}
public String getNetwork() {
// e.g. CBS
return get(SeriesProperty.Network);
}
public String getStatus() {
// e.g. Continuing
return get(SeriesProperty.Status);
}
public URL getBannerMirrorUrl() {
try {
return new URL(get(BannerProperty.BannerMirror));
@ -587,7 +549,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public URL getBannerUrl() throws MalformedURLException {
try {
return new URL(getBannerMirrorUrl(), get(SeriesProperty.banner));
@ -596,7 +557,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public URL getFanartUrl() {
try {
return new URL(getBannerMirrorUrl(), get(SeriesProperty.fanart));
@ -605,7 +565,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public URL getPosterUrl() {
try {
return new URL(getBannerMirrorUrl(), get(SeriesProperty.poster));
@ -614,14 +573,12 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
@Override
public String toString() {
return fields.toString();
}
}
/**
* Search for a series banner matching the given parameters
*
@ -645,7 +602,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null;
}
public List<BannerDescriptor> getBannerList(TheTVDBSearchResult series) throws Exception {
// check cache first
BannerDescriptor[] cachedList = getCache().getData("banners", series.seriesId, null, BannerDescriptor[].class);
@ -684,7 +640,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return banners;
}
public static class BannerDescriptor implements Serializable {
public static enum BannerProperty {
@ -693,27 +648,22 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
protected Map<BannerProperty, String> fields;
protected BannerDescriptor() {
// used by serializer
}
protected BannerDescriptor(Map<BannerProperty, String> fields) {
this.fields = new EnumMap<BannerProperty, String>(fields);
}
public String get(Object key) {
return fields.get(BannerProperty.valueOf(key.toString()));
}
public String get(BannerProperty key) {
return fields.get(key);
}
public URL getBannerMirrorUrl() throws MalformedURLException {
try {
return new URL(get(BannerProperty.BannerMirror));
@ -722,7 +672,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public URL getUrl() throws MalformedURLException {
try {
return new URL(getBannerMirrorUrl(), get(BannerProperty.BannerPath));
@ -731,12 +680,10 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public String getExtension() {
return FileUtilities.getExtension(get(BannerProperty.BannerPath));
}
public Integer getId() {
try {
return new Integer(get(BannerProperty.id));
@ -745,17 +692,14 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public String getBannerType() {
return get(BannerProperty.BannerType);
}
public String getBannerType2() {
return get(BannerProperty.BannerType2);
}
public Integer getSeason() {
try {
return new Integer(get(BannerProperty.Season));
@ -764,12 +708,10 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public String getColors() {
return get(BannerProperty.Colors);
}
public Locale getLocale() {
try {
return new Locale(get(BannerProperty.Language));
@ -778,7 +720,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public Double getRating() {
try {
return new Double(get(BannerProperty.Rating));
@ -787,7 +728,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public Integer getRatingCount() {
try {
return new Integer(get(BannerProperty.RatingCount));
@ -796,12 +736,10 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public boolean hasSeriesName() {
return Boolean.parseBoolean(get(BannerProperty.SeriesName));
}
public URL getThumbnailUrl() throws MalformedURLException {
try {
return new URL(getBannerMirrorUrl(), get(BannerProperty.ThumbnailPath));
@ -810,7 +748,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
public URL getVignetteUrl() throws MalformedURLException {
try {
return new URL(getBannerMirrorUrl(), get(BannerProperty.VignettePath));
@ -819,7 +756,6 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
@Override
public String toString() {
return fields.toString();

View File

@ -1,4 +1,3 @@
package net.sourceforge.filebot.web;
@ -6,34 +5,32 @@ public class TheTVDBSearchResult extends SearchResult {
protected int seriesId;
protected TheTVDBSearchResult() {
// used by serializer
}
public TheTVDBSearchResult(String seriesName, int seriesId) {
super(seriesName);
this.seriesId = seriesId;
this(seriesName, new String[0], seriesId);
}
public TheTVDBSearchResult(String seriesName, String[] aliasNames, int seriesId) {
super(seriesName, aliasNames);
this.seriesId = seriesId;
}
public int getId() {
return seriesId;
}
public int getSeriesId() {
return seriesId;
}
@Override
public int hashCode() {
return seriesId;
}
@Override
public boolean equals(Object object) {
if (object instanceof TheTVDBSearchResult) {