+ 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; package net.sourceforge.filebot.format;
import static java.util.Arrays.asList;
import static java.util.Arrays.*; import static java.util.Arrays.sort;
import static java.util.Collections.*; import static java.util.Collections.singleton;
import static net.sourceforge.filebot.MediaTypes.*; import static net.sourceforge.filebot.MediaTypes.SUBTITLE_FILES;
import static net.sourceforge.filebot.Settings.*; import static net.sourceforge.filebot.MediaTypes.VIDEO_FILES;
import static net.sourceforge.filebot.format.Define.*; import static net.sourceforge.filebot.Settings.useExtendedFileAttributes;
import static net.sourceforge.filebot.hash.VerificationUtilities.*; import static net.sourceforge.filebot.format.Define.undefined;
import static net.sourceforge.filebot.media.MediaDetection.*; import static net.sourceforge.filebot.hash.VerificationUtilities.computeHash;
import static net.sourceforge.filebot.similarity.Normalization.*; import static net.sourceforge.filebot.hash.VerificationUtilities.getEmbeddedChecksum;
import static net.sourceforge.filebot.web.EpisodeFormat.*; import static net.sourceforge.filebot.hash.VerificationUtilities.getHashFromVerificationFile;
import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.filebot.media.MediaDetection.releaseInfo;
import static net.sourceforge.tuned.StringUtilities.*; 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.File;
import java.io.IOException; import java.io.IOException;
@ -50,31 +58,27 @@ import net.sourceforge.tuned.FileUtilities.ExtensionFileFilter;
import com.cedarsoftware.util.io.JsonWriter; import com.cedarsoftware.util.io.JsonWriter;
public class MediaBindingBean { public class MediaBindingBean {
private final Object infoObject; private final Object infoObject;
private final File mediaFile; private final File mediaFile;
private final Map<File, Object> context; private final Map<File, Object> context;
private MediaInfo mediaInfo; private MediaInfo mediaInfo;
private Object metaInfo; private Object metaInfo;
public MediaBindingBean(Object infoObject, File mediaFile, Map<File, Object> context) { public MediaBindingBean(Object infoObject, File mediaFile, Map<File, Object> context) {
this.infoObject = infoObject; this.infoObject = infoObject;
this.mediaFile = mediaFile; this.mediaFile = mediaFile;
this.context = context; this.context = context;
} }
@Define(undefined) @Define(undefined)
public <T> T undefined() { public <T> T undefined() {
// omit expressions that depend on undefined values // omit expressions that depend on undefined values
throw new RuntimeException("undefined"); throw new RuntimeException("undefined");
} }
@Define("n") @Define("n")
public String getName() { public String getName() {
if (infoObject instanceof Episode) if (infoObject instanceof Episode)
@ -83,11 +87,10 @@ public class MediaBindingBean {
return getMovie().getName(); return getMovie().getName();
if (infoObject instanceof AudioTrack) if (infoObject instanceof AudioTrack)
return getAlbumArtist() != null ? getAlbumArtist() : getArtist(); return getAlbumArtist() != null ? getAlbumArtist() : getArtist();
return null; return null;
} }
@Define("y") @Define("y")
public Integer getYear() { public Integer getYear() {
if (infoObject instanceof Episode) if (infoObject instanceof Episode)
@ -96,23 +99,20 @@ public class MediaBindingBean {
return getMovie().getYear(); return getMovie().getYear();
if (infoObject instanceof AudioTrack) if (infoObject instanceof AudioTrack)
return getReleaseDate() != null ? ((Date) getReleaseDate()).getYear() : new Scanner(getMediaInfo(StreamKind.General, 0, "Recorded_Date")).useDelimiter("\\D+").nextInt(); return getReleaseDate() != null ? ((Date) getReleaseDate()).getYear() : new Scanner(getMediaInfo(StreamKind.General, 0, "Recorded_Date")).useDelimiter("\\D+").nextInt();
return null; return null;
} }
@Define("s") @Define("s")
public Integer getSeasonNumber() { public Integer getSeasonNumber() {
return getEpisode().getSeason(); return getEpisode().getSeason();
} }
@Define("e") @Define("e")
public Integer getEpisodeNumber() { public Integer getEpisodeNumber() {
return getEpisode().getEpisode(); return getEpisode().getEpisode();
} }
@Define("es") @Define("es")
public List<Integer> getEpisodeNumbers() { public List<Integer> getEpisodeNumbers() {
List<Integer> n = new ArrayList<Integer>(); List<Integer> n = new ArrayList<Integer>();
@ -121,31 +121,28 @@ public class MediaBindingBean {
} }
return n; return n;
} }
@Define("sxe") @Define("sxe")
public String getSxE() { public String getSxE() {
return SeasonEpisode.formatSxE(getEpisode()); return SeasonEpisode.formatSxE(getEpisode());
} }
@Define("s00e00") @Define("s00e00")
public String getS00E00() { public String getS00E00() {
return SeasonEpisode.formatS00E00(getEpisode()); return SeasonEpisode.formatS00E00(getEpisode());
} }
@Define("t") @Define("t")
public String getTitle() { public String getTitle() {
if (infoObject instanceof AudioTrack) { if (infoObject instanceof AudioTrack) {
return getMusic().getTrackTitle() != null ? getMusic().getTrackTitle() : getMusic().getTitle(); return getMusic().getTrackTitle() != null ? getMusic().getTrackTitle() : getMusic().getTitle();
} }
// single episode format // single episode format
if (getEpisodes().size() == 1) { if (getEpisodes().size() == 1) {
return getEpisode().getTitle(); return getEpisode().getTitle();
} }
// multi-episode format // multi-episode format
Set<String> title = new LinkedHashSet<String>(); Set<String> title = new LinkedHashSet<String>();
for (Episode it : getEpisodes()) { for (Episode it : getEpisodes()) {
@ -153,8 +150,7 @@ public class MediaBindingBean {
} }
return join(title, " & "); return join(title, " & ");
} }
@Define("d") @Define("d")
public Object getReleaseDate() { public Object getReleaseDate() {
if (infoObject instanceof Episode) { if (infoObject instanceof Episode) {
@ -166,125 +162,132 @@ public class MediaBindingBean {
if (infoObject instanceof AudioTrack) { if (infoObject instanceof AudioTrack) {
return getMusic().getAlbumReleaseDate(); return getMusic().getAlbumReleaseDate();
} }
// no date info for the model // no date info for the model
return null; return null;
} }
@Define("airdate") @Define("airdate")
public Date airdate() { public Date airdate() {
return getEpisode().getAirdate(); return getEpisode().getAirdate();
} }
@Define("startdate") @Define("startdate")
public Date startdate() { public Date startdate() {
return getEpisode().getSeriesStartDate(); return getEpisode().getSeriesStartDate();
} }
@Define("absolute") @Define("absolute")
public Integer getAbsoluteEpisodeNumber() { public Integer getAbsoluteEpisodeNumber() {
return getEpisode().getAbsolute(); return getEpisode().getAbsolute();
} }
@Define("special") @Define("special")
public Integer getSpecialNumber() { public Integer getSpecialNumber() {
return getEpisode().getSpecial(); return getEpisode().getSpecial();
} }
@Define("series") @Define("series")
public SearchResult getSeriesObject() { public SearchResult getSeriesObject() {
return getEpisode().getSeries(); 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") @Define("primaryTitle")
public String getPrimaryTitle() throws Exception { public String getPrimaryTitle() throws Exception {
if (getSeriesObject() instanceof TheTVDBSearchResult) { if (infoObject instanceof Movie) {
return WebServices.TheTVDB.getSeriesInfo((TheTVDBSearchResult) getSeriesObject(), Locale.ENGLISH).getName(); return WebServices.TMDb.getMovieInfo(getMovie(), Locale.ENGLISH).getName();
} }
if (getSeriesObject() instanceof AnidbSearchResult) {
return ((AnidbSearchResult) getSeriesObject()).getPrimaryTitle(); 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();
}
return getSeriesObject().getName(); // default to original search result
} }
// default to original search result return null;
return getSeriesObject().getName();
} }
@Define("tmdbid") @Define("tmdbid")
public String getTmdbId() throws Exception { public String getTmdbId() throws Exception {
int tmdbid = getMovie().getTmdbId(); int tmdbid = getMovie().getTmdbId();
if (tmdbid <= 0) { if (tmdbid <= 0) {
if (getMovie().getImdbId() <= 0) { if (getMovie().getImdbId() <= 0) {
return null; return null;
} }
// lookup IMDbID for TMDbID // lookup IMDbID for TMDbID
tmdbid = WebServices.TMDb.getMovieInfo(getMovie(), null).getId(); tmdbid = WebServices.TMDb.getMovieInfo(getMovie(), null).getId();
} }
return String.valueOf(tmdbid); return String.valueOf(tmdbid);
} }
@Define("imdbid") @Define("imdbid")
public String getImdbId() throws Exception { public String getImdbId() throws Exception {
int imdbid = getMovie().getImdbId(); int imdbid = getMovie().getImdbId();
if (imdbid <= 0) { if (imdbid <= 0) {
if (getMovie().getTmdbId() <= 0) { if (getMovie().getTmdbId() <= 0) {
return null; return null;
} }
// lookup IMDbID for TMDbID // lookup IMDbID for TMDbID
imdbid = WebServices.TMDb.getMovieInfo(getMovie(), null).getImdbId(); imdbid = WebServices.TMDb.getMovieInfo(getMovie(), null).getImdbId();
} }
return String.format("tt%07d", imdbid); return String.format("tt%07d", imdbid);
} }
@Define("vc") @Define("vc")
public String getVideoCodec() { public String getVideoCodec() {
// e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc. // e.g. XviD, x264, DivX 5, MPEG-4 Visual, AVC, etc.
String codec = getMediaInfo(StreamKind.Video, 0, "Encoded_Library/Name", "CodecID/Hint", "Format"); String codec = getMediaInfo(StreamKind.Video, 0, "Encoded_Library/Name", "CodecID/Hint", "Format");
// get first token (e.g. DivX 5 => DivX) // get first token (e.g. DivX 5 => DivX)
return new Scanner(codec).next(); return new Scanner(codec).next();
} }
@Define("ac") @Define("ac")
public String getAudioCodec() { public String getAudioCodec() {
// e.g. AC-3, DTS, AAC, Vorbis, MP3, etc. // e.g. AC-3, DTS, AAC, Vorbis, MP3, etc.
String codec = getMediaInfo(StreamKind.Audio, 0, "CodecID/Hint", "Format"); String codec = getMediaInfo(StreamKind.Audio, 0, "CodecID/Hint", "Format");
// remove punctuation (e.g. AC-3 => AC3) // remove punctuation (e.g. AC-3 => AC3)
return codec.replaceAll("\\p{Punct}", ""); return codec.replaceAll("\\p{Punct}", "");
} }
@Define("cf") @Define("cf")
public String getContainerFormat() { public String getContainerFormat() {
// container format extensions (e.g. avi, mkv mka mks, OGG, etc.) // container format extensions (e.g. avi, mkv mka mks, OGG, etc.)
String extensions = getMediaInfo(StreamKind.General, 0, "Codec/Extensions", "Format"); String extensions = getMediaInfo(StreamKind.General, 0, "Codec/Extensions", "Format");
// get first extension // get first extension
return new Scanner(extensions).next().toLowerCase(); return new Scanner(extensions).next().toLowerCase();
} }
@Define("vf") @Define("vf")
public String getVideoFormat() { public String getVideoFormat() {
int width = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Width")); int width = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Width"));
int height = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Height")); int height = Integer.parseInt(getMediaInfo(StreamKind.Video, 0, "Height"));
int ns = 0; int ns = 0;
int[] ws = new int[] { 1920, 1280, 720, 360, 240, 120 }; int[] ws = new int[] { 1920, 1280, 720, 360, 240, 120 };
int[] hs = new int[] { 1080, 720, 480, 360, 240, 120 }; int[] hs = new int[] { 1080, 720, 480, 360, 240, 120 };
@ -300,160 +303,146 @@ public class MediaBindingBean {
} }
return null; // video too small return null; // video too small
} }
@Define("hpi") @Define("hpi")
public String getExactVideoFormat() { public String getExactVideoFormat() {
String height = getMediaInfo(StreamKind.Video, 0, "Height"); String height = getMediaInfo(StreamKind.Video, 0, "Height");
String scanType = getMediaInfo(StreamKind.Video, 0, "ScanType"); String scanType = getMediaInfo(StreamKind.Video, 0, "ScanType");
if (height == null || scanType == null) if (height == null || scanType == null)
return null; return null;
// e.g. 720p // e.g. 720p
return height + Character.toLowerCase(scanType.charAt(0)); return height + Character.toLowerCase(scanType.charAt(0));
} }
@Define("af") @Define("af")
public String getAudioChannels() { public String getAudioChannels() {
String channels = getMediaInfo(StreamKind.Audio, 0, "Channel(s)"); String channels = getMediaInfo(StreamKind.Audio, 0, "Channel(s)");
if (channels == null) if (channels == null)
return null; return null;
// e.g. 6ch // e.g. 6ch
return channels + "ch"; return channels + "ch";
} }
@Define("resolution") @Define("resolution")
public String getVideoResolution() { public String getVideoResolution() {
List<Integer> dim = getDimension(); List<Integer> dim = getDimension();
if (dim.contains(null)) if (dim.contains(null))
return null; return null;
// e.g. 1280x720 // e.g. 1280x720
return join(dim, "x"); return join(dim, "x");
} }
@Define("ws") @Define("ws")
public String getWidescreen() { public String getWidescreen() {
List<Integer> dim = getDimension(); List<Integer> dim = getDimension();
// width-to-height aspect ratio greater than 1.37:1 // width-to-height aspect ratio greater than 1.37:1
return (float) dim.get(0) / dim.get(1) > 1.37f ? "ws" : null; return (float) dim.get(0) / dim.get(1) > 1.37f ? "ws" : null;
} }
@Define("sdhd") @Define("sdhd")
public String getVideoDefinitionCategory() { public String getVideoDefinitionCategory() {
List<Integer> dim = getDimension(); List<Integer> dim = getDimension();
// SD (less than 720 lines) or HD (more than 720 lines) // SD (less than 720 lines) or HD (more than 720 lines)
return dim.get(0) >= 1280 || dim.get(1) >= 720 ? "HD" : "SD"; return dim.get(0) >= 1280 || dim.get(1) >= 720 ? "HD" : "SD";
} }
@Define("dim") @Define("dim")
public List<Integer> getDimension() { public List<Integer> getDimension() {
String width = getMediaInfo(StreamKind.Video, 0, "Width"); String width = getMediaInfo(StreamKind.Video, 0, "Width");
String height = getMediaInfo(StreamKind.Video, 0, "Height"); String height = getMediaInfo(StreamKind.Video, 0, "Height");
return asList(width != null ? Integer.parseInt(width) : null, height != null ? Integer.parseInt(height) : null); return asList(width != null ? Integer.parseInt(width) : null, height != null ? Integer.parseInt(height) : null);
} }
@Define("original") @Define("original")
public String getOriginalFileName() { public String getOriginalFileName() {
return getOriginalFileName(mediaFile); return getOriginalFileName(mediaFile);
} }
@Define("xattr") @Define("xattr")
public Object getMetaAttributesObject() { public Object getMetaAttributesObject() {
return getMetaAttributesObject(mediaFile); return getMetaAttributesObject(mediaFile);
} }
@Define("crc32") @Define("crc32")
public String getCRC32() throws IOException, InterruptedException { public String getCRC32() throws IOException, InterruptedException {
// use inferred media file // use inferred media file
File inferredMediaFile = getInferredMediaFile(); File inferredMediaFile = getInferredMediaFile();
// try to get checksum from file name // try to get checksum from file name
String checksum = getEmbeddedChecksum(inferredMediaFile.getName()); String checksum = getEmbeddedChecksum(inferredMediaFile.getName());
if (checksum != null) if (checksum != null)
return checksum; return checksum;
// try to get checksum from sfv file // try to get checksum from sfv file
checksum = getHashFromVerificationFile(inferredMediaFile, HashType.SFV, 3); checksum = getHashFromVerificationFile(inferredMediaFile, HashType.SFV, 3);
if (checksum != null) if (checksum != null)
return checksum; return checksum;
// calculate checksum from file // calculate checksum from file
return crc32(inferredMediaFile); return crc32(inferredMediaFile);
} }
@Define("fn") @Define("fn")
public String getFileName() { public String getFileName() {
// make sure media file is defined // make sure media file is defined
checkMediaFile(); checkMediaFile();
// file extension // file extension
return FileUtilities.getName(mediaFile); return FileUtilities.getName(mediaFile);
} }
@Define("ext") @Define("ext")
public String getExtension() { public String getExtension() {
// make sure media file is defined // make sure media file is defined
checkMediaFile(); checkMediaFile();
// file extension // file extension
return FileUtilities.getExtension(mediaFile); return FileUtilities.getExtension(mediaFile);
} }
@Define("source") @Define("source")
public String getVideoSource() { public String getVideoSource() {
// use inferred media file // use inferred media file
File inferredMediaFile = getInferredMediaFile(); File inferredMediaFile = getInferredMediaFile();
// look for video source patterns in media file and it's parent folder // look for video source patterns in media file and it's parent folder
return releaseInfo.getVideoSource(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile)); return releaseInfo.getVideoSource(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile));
} }
@Define("group") @Define("group")
public String getReleaseGroup() throws IOException { public String getReleaseGroup() throws IOException {
// use inferred media file // use inferred media file
File inferredMediaFile = getInferredMediaFile(); File inferredMediaFile = getInferredMediaFile();
// look for release group names in media file and it's parent folder // look for release group names in media file and it's parent folder
return releaseInfo.getReleaseGroup(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile)); return releaseInfo.getReleaseGroup(inferredMediaFile.getParent(), inferredMediaFile.getName(), getOriginalFileName(inferredMediaFile));
} }
@Define("lang") @Define("lang")
public Locale detectSubtitleLanguage() throws Exception { public Locale detectSubtitleLanguage() throws Exception {
// make sure media file is defined // make sure media file is defined
checkMediaFile(); checkMediaFile();
Locale languageSuffix = releaseInfo.getLanguageSuffix(FileUtilities.getName(mediaFile)); Locale languageSuffix = releaseInfo.getLanguageSuffix(FileUtilities.getName(mediaFile));
if (languageSuffix != null) if (languageSuffix != null)
return new Locale(languageSuffix.getISO3Language()); // force ISO3 letter-code return new Locale(languageSuffix.getISO3Language()); // force ISO3 letter-code
// require subtitle file // require subtitle file
if (!SUBTITLE_FILES.accept(mediaFile)) { if (!SUBTITLE_FILES.accept(mediaFile)) {
return null; return null;
} }
// exclude VobSub from any normal text-based subtitle processing // exclude VobSub from any normal text-based subtitle processing
if (hasExtension(mediaFile, "idx")) { if (hasExtension(mediaFile, "idx")) {
return null; return null;
@ -464,51 +453,44 @@ public class MediaBindingBean {
} }
} }
} }
// try statistical language detection // try statistical language detection
return WebServices.OpenSubtitles.detectLanguage(readFile(mediaFile)); return WebServices.OpenSubtitles.detectLanguage(readFile(mediaFile));
} }
@Define("actors") @Define("actors")
public Object getActors() { public Object getActors() {
return getMetaInfo().getProperty("actors"); return getMetaInfo().getProperty("actors");
} }
@Define("genres") @Define("genres")
public Object getGenres() { public Object getGenres() {
if (infoObject instanceof AudioTrack) if (infoObject instanceof AudioTrack)
return asList(getMediaInfo(StreamKind.General, 0, "Genre").split(";")); return asList(getMediaInfo(StreamKind.General, 0, "Genre").split(";"));
return getMetaInfo().getProperty("genres"); return getMetaInfo().getProperty("genres");
} }
@Define("director") @Define("director")
public Object getDirector() { public Object getDirector() {
return getMetaInfo().getProperty("director"); return getMetaInfo().getProperty("director");
} }
@Define("certification") @Define("certification")
public Object getCertification() { public Object getCertification() {
return getMetaInfo().getProperty("certification"); return getMetaInfo().getProperty("certification");
} }
@Define("rating") @Define("rating")
public Object getRating() { public Object getRating() {
return getMetaInfo().getProperty("rating"); return getMetaInfo().getProperty("rating");
} }
@Define("collection") @Define("collection")
public Object getCollection() { public Object getCollection() {
return getMetaInfo().getProperty("collection"); return getMetaInfo().getProperty("collection");
} }
@Define("info") @Define("info")
public synchronized AssociativeScriptObject getMetaInfo() { public synchronized AssociativeScriptObject getMetaInfo() {
if (metaInfo == null) { if (metaInfo == null) {
@ -521,15 +503,14 @@ public class MediaBindingBean {
throw new RuntimeException("Failed to retrieve metadata: " + infoObject, e); throw new RuntimeException("Failed to retrieve metadata: " + infoObject, e);
} }
} }
return createMapBindings(new PropertyBindings(metaInfo, null)); return createMapBindings(new PropertyBindings(metaInfo, null));
} }
@Define("imdb") @Define("imdb")
public synchronized AssociativeScriptObject getImdbApiInfo() { public synchronized AssociativeScriptObject getImdbApiInfo() {
Object data = null; Object data = null;
try { try {
if (infoObject instanceof Episode) { if (infoObject instanceof Episode) {
data = WebServices.IMDb.getImdbApiMovieInfo(new Movie(getEpisode().getSeriesName(), getEpisode().getSeriesStartDate().getYear(), -1, -1)); data = WebServices.IMDb.getImdbApiMovieInfo(new Movie(getEpisode().getSeriesName(), getEpisode().getSeriesStartDate().getYear(), -1, -1));
@ -541,153 +522,130 @@ public class MediaBindingBean {
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Failed to retrieve metadata: " + infoObject, e); throw new RuntimeException("Failed to retrieve metadata: " + infoObject, e);
} }
return createMapBindings(new PropertyBindings(data, null)); return createMapBindings(new PropertyBindings(data, null));
} }
@Define("episodelist") @Define("episodelist")
public Object getEpisodeList() throws Exception { public Object getEpisodeList() throws Exception {
return WebServices.TheTVDB.getEpisodeList(WebServices.TheTVDB.search(getEpisode().getSeriesName()).get(0), SortOrder.Airdate, Locale.ENGLISH); return WebServices.TheTVDB.getEpisodeList(WebServices.TheTVDB.search(getEpisode().getSeriesName()).get(0), SortOrder.Airdate, Locale.ENGLISH);
} }
@Define("media") @Define("media")
public AssociativeScriptObject getGeneralMediaInfo() { public AssociativeScriptObject getGeneralMediaInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.General, 0)); return createMapBindings(getMediaInfo().snapshot(StreamKind.General, 0));
} }
@Define("video") @Define("video")
public AssociativeScriptObject getVideoInfo() { public AssociativeScriptObject getVideoInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Video, 0)); return createMapBindings(getMediaInfo().snapshot(StreamKind.Video, 0));
} }
@Define("audio") @Define("audio")
public AssociativeScriptObject getAudioInfo() { public AssociativeScriptObject getAudioInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Audio, 0)); return createMapBindings(getMediaInfo().snapshot(StreamKind.Audio, 0));
} }
@Define("text") @Define("text")
public AssociativeScriptObject getTextInfo() { public AssociativeScriptObject getTextInfo() {
return createMapBindings(getMediaInfo().snapshot(StreamKind.Text, 0)); return createMapBindings(getMediaInfo().snapshot(StreamKind.Text, 0));
} }
@Define("videos") @Define("videos")
public List<AssociativeScriptObject> getVideoInfoList() { public List<AssociativeScriptObject> getVideoInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Video)); return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Video));
} }
@Define("audios") @Define("audios")
public List<AssociativeScriptObject> getAudioInfoList() { public List<AssociativeScriptObject> getAudioInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Audio)); return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Audio));
} }
@Define("texts") @Define("texts")
public List<AssociativeScriptObject> getTextInfoList() { public List<AssociativeScriptObject> getTextInfoList() {
return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Text)); return createMapBindingsList(getMediaInfo().snapshot().get(StreamKind.Text));
} }
@Define("artist") @Define("artist")
public String getArtist() { public String getArtist() {
return getMusic().getArtist(); return getMusic().getArtist();
} }
@Define("albumArtist") @Define("albumArtist")
public String getAlbumArtist() { public String getAlbumArtist() {
return getMusic().getAlbumArtist(); return getMusic().getAlbumArtist();
} }
@Define("album") @Define("album")
public String getAlbum() { public String getAlbum() {
return getMusic().getAlbum(); return getMusic().getAlbum();
} }
@Define("episode") @Define("episode")
public Episode getEpisode() { public Episode getEpisode() {
return (Episode) infoObject; return (Episode) infoObject;
} }
@Define("episodes") @Define("episodes")
public List<Episode> getEpisodes() { public List<Episode> getEpisodes() {
return infoObject instanceof MultiEpisode ? ((MultiEpisode) infoObject).getEpisodes() : asList(getEpisode()); return infoObject instanceof MultiEpisode ? ((MultiEpisode) infoObject).getEpisodes() : asList(getEpisode());
} }
@Define("movie") @Define("movie")
public Movie getMovie() { public Movie getMovie() {
return (Movie) infoObject; return (Movie) infoObject;
} }
@Define("music") @Define("music")
public AudioTrack getMusic() { public AudioTrack getMusic() {
return (AudioTrack) infoObject; return (AudioTrack) infoObject;
} }
@Define("pi") @Define("pi")
public Integer getPart() { public Integer getPart() {
if (infoObject instanceof AudioTrack) if (infoObject instanceof AudioTrack)
return getMusic().getTrack() != null ? getMusic().getTrack() : Integer.parseInt(getMediaInfo(StreamKind.General, 0, "Track/Position")); return getMusic().getTrack() != null ? getMusic().getTrack() : Integer.parseInt(getMediaInfo(StreamKind.General, 0, "Track/Position"));
if (infoObject instanceof MoviePart) if (infoObject instanceof MoviePart)
return ((MoviePart) infoObject).getPartIndex(); return ((MoviePart) infoObject).getPartIndex();
return null; return null;
} }
@Define("pn") @Define("pn")
public Integer getPartCount() { public Integer getPartCount() {
if (infoObject instanceof AudioTrack) if (infoObject instanceof AudioTrack)
return getMusic().getTrackCount(); return getMusic().getTrackCount();
if (infoObject instanceof MoviePart) if (infoObject instanceof MoviePart)
return ((MoviePart) infoObject).getPartCount(); return ((MoviePart) infoObject).getPartCount();
return null; return null;
} }
@Define("file") @Define("file")
public File getMediaFile() { public File getMediaFile() {
return mediaFile; return mediaFile;
} }
@Define("folder") @Define("folder")
public File getMediaParentFolder() { public File getMediaParentFolder() {
return getMediaFile().getParentFile(); return getMediaFile().getParentFile();
} }
@Define("home") @Define("home")
public File getUserHome() throws IOException { public File getUserHome() throws IOException {
return new File(System.getProperty("user.home")); return new File(System.getProperty("user.home"));
} }
@Define("object") @Define("object")
public Object getInfoObject() { public Object getInfoObject() {
return infoObject; return infoObject;
} }
@Define("i") @Define("i")
public Integer getModelIndex() { public Integer getModelIndex() {
return identityIndexOf(getContext().values(), getInfoObject()); return identityIndexOf(getContext().values(), getInfoObject());
} }
@Define("di") @Define("di")
public Integer getDuplicateIndex() { public Integer getDuplicateIndex() {
List<Object> duplicates = new ArrayList<Object>(); List<Object> duplicates = new ArrayList<Object>();
@ -699,24 +657,21 @@ public class MediaBindingBean {
int di = identityIndexOf(duplicates, getInfoObject()); int di = identityIndexOf(duplicates, getInfoObject());
return di == 0 ? null : di; return di == 0 ? null : di;
} }
@Define("model") @Define("model")
public Map<File, Object> getContext() { public Map<File, Object> getContext() {
return context; return context;
} }
@Define("json") @Define("json")
public String getInfoObjectDump() throws Exception { public String getInfoObjectDump() throws Exception {
return JsonWriter.objectToJson(infoObject); return JsonWriter.objectToJson(infoObject);
} }
private File getInferredMediaFile() { private File getInferredMediaFile() {
// make sure media file is defined // make sure media file is defined
checkMediaFile(); checkMediaFile();
if (mediaFile.isDirectory()) { if (mediaFile.isDirectory()) {
// just select the first video file in the folder as media sample // just select the first video file in the folder as media sample
SortedSet<File> videos = new TreeSet<File>(filter(listFiles(singleton(mediaFile), 2, false), VIDEO_FILES)); SortedSet<File> videos = new TreeSet<File>(filter(listFiles(singleton(mediaFile), 2, false), VIDEO_FILES));
@ -732,22 +687,22 @@ public class MediaBindingBean {
} }
} }
} }
// file is a subtitle, or nfo, etc // file is a subtitle, or nfo, etc
String baseName = stripReleaseInfo(FileUtilities.getName(mediaFile)).toLowerCase(); String baseName = stripReleaseInfo(FileUtilities.getName(mediaFile)).toLowerCase();
File[] videos = mediaFile.getParentFile().listFiles(VIDEO_FILES); File[] videos = mediaFile.getParentFile().listFiles(VIDEO_FILES);
// find corresponding movie file // find corresponding movie file
for (File movieFile : videos) { for (File movieFile : videos) {
if (!baseName.isEmpty() && stripReleaseInfo(FileUtilities.getName(movieFile)).toLowerCase().startsWith(baseName)) { if (!baseName.isEmpty() && stripReleaseInfo(FileUtilities.getName(movieFile)).toLowerCase().startsWith(baseName)) {
return movieFile; return movieFile;
} }
} }
// still no good match found -> just take the most probable video from the same folder // still no good match found -> just take the most probable video from the same folder
if (videos.length > 0) { if (videos.length > 0) {
sort(videos, new SimilarityComparator(FileUtilities.getName(mediaFile)) { sort(videos, new SimilarityComparator(FileUtilities.getName(mediaFile)) {
@Override @Override
public int compare(Object o1, Object o2) { public int compare(Object o1, Object o2) {
return super.compare(FileUtilities.getName((File) o1), FileUtilities.getName((File) o2)); return super.compare(FileUtilities.getName((File) o1), FileUtilities.getName((File) o2));
@ -756,37 +711,34 @@ public class MediaBindingBean {
return videos[0]; return videos[0];
} }
} }
return mediaFile; return mediaFile;
} }
private void checkMediaFile() throws RuntimeException { private void checkMediaFile() throws RuntimeException {
// make sure file is not null, and that it is an existing file // make sure file is not null, and that it is an existing file
if (mediaFile == null) if (mediaFile == null)
throw new RuntimeException("Invalid media file: " + mediaFile); throw new RuntimeException("Invalid media file: " + mediaFile);
} }
private synchronized MediaInfo getMediaInfo() { private synchronized MediaInfo getMediaInfo() {
if (mediaInfo == null) { if (mediaInfo == null) {
// make sure media file is defined // make sure media file is defined
checkMediaFile(); checkMediaFile();
MediaInfo newMediaInfo = new MediaInfo(); MediaInfo newMediaInfo = new MediaInfo();
// use inferred media file (e.g. actual movie file instead of subtitle file) // use inferred media file (e.g. actual movie file instead of subtitle file)
if (!newMediaInfo.open(getInferredMediaFile())) { if (!newMediaInfo.open(getInferredMediaFile())) {
throw new RuntimeException("Cannot open media file: " + mediaFile); throw new RuntimeException("Cannot open media file: " + mediaFile);
} }
mediaInfo = newMediaInfo; mediaInfo = newMediaInfo;
} }
return mediaInfo; return mediaInfo;
} }
private Integer identityIndexOf(Iterable<?> c, Object o) { private Integer identityIndexOf(Iterable<?> c, Object o) {
Iterator<?> itr = c.iterator(); Iterator<?> itr = c.iterator();
for (int i = 0; itr.hasNext(); i++) { for (int i = 0; itr.hasNext(); i++) {
@ -796,42 +748,39 @@ public class MediaBindingBean {
} }
return null; return null;
} }
private String getMediaInfo(StreamKind streamKind, int streamNumber, String... keys) { private String getMediaInfo(StreamKind streamKind, int streamNumber, String... keys) {
for (String key : keys) { for (String key : keys) {
String value = getMediaInfo().get(streamKind, streamNumber, key); String value = getMediaInfo().get(streamKind, streamNumber, key);
if (value.length() > 0) if (value.length() > 0)
return value; return value;
} }
return null; return null;
} }
private AssociativeScriptObject createMapBindings(Map<?, ?> map) { private AssociativeScriptObject createMapBindings(Map<?, ?> map) {
return new AssociativeScriptObject(map) { return new AssociativeScriptObject(map) {
@Override @Override
public Object getProperty(String name) { public Object getProperty(String name) {
Object value = super.getProperty(name); Object value = super.getProperty(name);
if (value == null) { if (value == null) {
throw new BindingException(name, "undefined"); throw new BindingException(name, "undefined");
} }
// auto-clean value of path separators // auto-clean value of path separators
if (value instanceof CharSequence) { if (value instanceof CharSequence) {
return replacePathSeparators(value.toString()).trim(); return replacePathSeparators(value.toString()).trim();
} }
return value; return value;
} }
}; };
} }
private List<AssociativeScriptObject> createMapBindingsList(List<Map<String, String>> mapList) { private List<AssociativeScriptObject> createMapBindingsList(List<Map<String, String>> mapList) {
List<AssociativeScriptObject> bindings = new ArrayList<AssociativeScriptObject>(); List<AssociativeScriptObject> bindings = new ArrayList<AssociativeScriptObject>();
for (Map<?, ?> it : mapList) { for (Map<?, ?> it : mapList) {
@ -839,24 +788,22 @@ public class MediaBindingBean {
} }
return bindings; return bindings;
} }
private String crc32(File file) throws IOException, InterruptedException { private String crc32(File file) throws IOException, InterruptedException {
// try to get checksum from cache // try to get checksum from cache
Cache cache = Cache.getCache("checksum"); Cache cache = Cache.getCache("checksum");
String hash = cache.get(file, String.class); String hash = cache.get(file, String.class);
if (hash != null) { if (hash != null) {
return hash; return hash;
} }
// compute and cache checksum // compute and cache checksum
hash = computeHash(file, HashType.SFV); hash = computeHash(file, HashType.SFV);
cache.put(file, hash); cache.put(file, hash);
return hash; return hash;
} }
private String getOriginalFileName(File file) { private String getOriginalFileName(File file) {
if (useExtendedFileAttributes()) { if (useExtendedFileAttributes()) {
try { try {
@ -867,8 +814,7 @@ public class MediaBindingBean {
} }
return null; return null;
} }
private Object getMetaAttributesObject(File file) { private Object getMetaAttributesObject(File file) {
if (useExtendedFileAttributes()) { if (useExtendedFileAttributes()) {
try { try {
@ -879,5 +825,5 @@ public class MediaBindingBean {
} }
return null; return null;
} }
} }

View File

@ -611,7 +611,7 @@ public class MediaDetection {
sort(sorted, new SimilarityComparator(getMovieMatchMetric(), paragon.toArray())); sort(sorted, new SimilarityComparator(getMovieMatchMetric(), paragon.toArray()));
// DEBUG // DEBUG
// System.out.format("sortBySimilarity %s => %s", terms, options); // System.out.format("sortBySimilarity %s => %s", terms, sorted);
return 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 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 # 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 # 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 # 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 # 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) # 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 # 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 # 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 # disk folder matcher
pattern.diskfolder.entry: BDMV|HVDVD_TS|VIDEO_TS|AUDIO_TS|VCD|movie.nfo pattern.diskfolder.entry: BDMV|HVDVD_TS|VIDEO_TS|AUDIO_TS|VCD|movie.nfo

View File

@ -1,20 +1,31 @@
package net.sourceforge.filebot.similarity; package net.sourceforge.filebot.similarity;
import static java.lang.Math.ceil;
import static java.lang.Math.*; import static java.lang.Math.floor;
import static java.util.Arrays.*; import static java.lang.Math.max;
import static java.util.Collections.*; import static java.lang.Math.min;
import static net.sourceforge.filebot.Settings.*; import static java.util.Arrays.asList;
import static net.sourceforge.filebot.similarity.Normalization.*; import static java.util.Collections.emptyMap;
import static net.sourceforge.tuned.FileUtilities.*; import static java.util.Collections.emptySet;
import static net.sourceforge.tuned.StringUtilities.*; 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.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -36,125 +47,121 @@ import net.sourceforge.filebot.web.TheTVDBSearchResult;
import com.ibm.icu.text.Transliterator; import com.ibm.icu.text.Transliterator;
public enum EpisodeMetrics implements SimilarityMetric { public enum EpisodeMetrics implements SimilarityMetric {
// Match by season / episode numbers // Match by season / episode numbers
SeasonEpisode(new SeasonEpisodeMetric() { SeasonEpisode(new SeasonEpisodeMetric() {
private final Map<Object, Collection<SxE>> transformCache = synchronizedMap(new HashMap<Object, Collection<SxE>>(64, 4)); private final Map<Object, Collection<SxE>> transformCache = synchronizedMap(new HashMap<Object, Collection<SxE>>(64, 4));
@Override @Override
protected Collection<SxE> parse(Object object) { protected Collection<SxE> parse(Object object) {
if (object instanceof Movie) { if (object instanceof Movie) {
return emptySet(); return emptySet();
} }
Collection<SxE> result = transformCache.get(object); Collection<SxE> result = transformCache.get(object);
if (result != null) { if (result != null) {
return result; return result;
} }
if (object instanceof Episode) { if (object instanceof Episode) {
Episode episode = (Episode) object; Episode episode = (Episode) object;
if (episode.getSpecial() != null) { if (episode.getSpecial() != null) {
return singleton(new SxE(0, episode.getSpecial())); return singleton(new SxE(0, episode.getSpecial()));
} }
// get SxE from episode, both SxE for season/episode numbering and SxE for absolute episode numbering // get SxE from episode, both SxE for season/episode numbering and SxE for absolute episode numbering
SxE sxe = new SxE(episode.getSeason(), episode.getEpisode()); SxE sxe = new SxE(episode.getSeason(), episode.getEpisode());
SxE abs = new SxE(null, episode.getAbsolute()); SxE abs = new SxE(null, episode.getAbsolute());
result = (abs.episode < 0 || sxe.equals(abs)) ? singleton(sxe) : asList(sxe, abs); result = (abs.episode < 0 || sxe.equals(abs)) ? singleton(sxe) : asList(sxe, abs);
} else { } else {
result = super.parse(object); result = super.parse(object);
} }
transformCache.put(object, result); transformCache.put(object, result);
return result; return result;
} }
}), }),
// Match episode airdate // Match episode airdate
AirDate(new DateMetric() { AirDate(new DateMetric() {
private final Map<Object, Date> transformCache = synchronizedMap(new HashMap<Object, Date>(64, 4)); private final Map<Object, Date> transformCache = synchronizedMap(new HashMap<Object, Date>(64, 4));
@Override @Override
public Date parse(Object object) { public Date parse(Object object) {
if (object instanceof Movie) { if (object instanceof Movie) {
return null; return null;
} }
if (object instanceof Episode) { if (object instanceof Episode) {
Episode episode = (Episode) object; Episode episode = (Episode) object;
// use airdate from episode // use airdate from episode
return episode.getAirdate(); return episode.getAirdate();
} }
Date result = transformCache.get(object); Date result = transformCache.get(object);
if (result != null) { if (result != null) {
return result; return result;
} }
result = super.parse(object); result = super.parse(object);
transformCache.put(object, result); transformCache.put(object, result);
return result; return result;
} }
}), }),
// Match by episode/movie title // Match by episode/movie title
Title(new SubstringMetric() { Title(new SubstringMetric() {
@Override @Override
protected String normalize(Object object) { protected String normalize(Object object) {
if (object instanceof Episode) { if (object instanceof Episode) {
Episode e = (Episode) object; Episode e = (Episode) object;
// don't use title for matching if title equals series name // don't use title for matching if title equals series name
String normalizedToken = normalizeObject(e.getTitle()); String normalizedToken = normalizeObject(e.getTitle());
if (normalizedToken.length() >= 3 && !normalizeObject(e.getSeriesName()).contains(normalizedToken)) { if (normalizedToken.length() >= 3 && !normalizeObject(e.getSeriesName()).contains(normalizedToken)) {
return normalizedToken; return normalizedToken;
} }
} }
if (object instanceof Movie) { if (object instanceof Movie) {
object = ((Movie) object).getName(); object = ((Movie) object).getName();
} }
return normalizeObject(object); return normalizeObject(object);
} }
}), }),
// Match by SxE and airdate // Match by SxE and airdate
EpisodeIdentifier(new MetricCascade(SeasonEpisode, AirDate)), EpisodeIdentifier(new MetricCascade(SeasonEpisode, AirDate)),
// Advanced episode <-> file matching Lv1 // Advanced episode <-> file matching Lv1
EpisodeFunnel(new MetricCascade(SeasonEpisode, AirDate, Title)), EpisodeFunnel(new MetricCascade(SeasonEpisode, AirDate, Title)),
// Advanced episode <-> file matching Lv2 // Advanced episode <-> file matching Lv2
EpisodeBalancer(new SimilarityMetric() { EpisodeBalancer(new SimilarityMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
float sxe = EpisodeIdentifier.getSimilarity(o1, o2); float sxe = EpisodeIdentifier.getSimilarity(o1, o2);
float title = Title.getSimilarity(o1, o2); float title = Title.getSimilarity(o1, o2);
// account for misleading SxE patterns in the episode title // account for misleading SxE patterns in the episode title
if (sxe < 0 && title == 1 && EpisodeIdentifier.getSimilarity(getTitle(o1), getTitle(o2)) == 1) { if (sxe < 0 && title == 1 && EpisodeIdentifier.getSimilarity(getTitle(o1), getTitle(o2)) == 1) {
sxe = 1; sxe = 1;
title = 0; title = 0;
} }
// 1:SxE && Title, 2:SxE // 1:SxE && Title, 2:SxE
return (float) ((max(sxe, 0) * title) + (floor(sxe) / 10)); return (float) ((max(sxe, 0) * title) + (floor(sxe) / 10));
} }
public Object getTitle(Object o) { public Object getTitle(Object o) {
if (o instanceof Episode) { if (o instanceof Episode) {
Episode e = (Episode) o; Episode e = (Episode) o;
@ -163,15 +170,15 @@ public enum EpisodeMetrics implements SimilarityMetric {
return o; return o;
} }
}), }),
// Match series title and episode title against folder structure and file name // Match series title and episode title against folder structure and file name
SubstringFields(new SubstringMetric() { SubstringFields(new SubstringMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
String[] f1 = normalize(fields(o1)); String[] f1 = normalize(fields(o1));
String[] f2 = normalize(fields(o2)); String[] f2 = normalize(fields(o2));
// match all fields and average similarity // match all fields and average similarity
float sum = 0; float sum = 0;
for (String s1 : f1) { for (String s1 : f1) {
@ -180,60 +187,64 @@ public enum EpisodeMetrics implements SimilarityMetric {
} }
} }
sum /= f1.length * f2.length; sum /= f1.length * f2.length;
// normalize into 3 similarity levels // normalize into 3 similarity levels
return (float) (ceil(sum * 3) / 3); return (float) (ceil(sum * 3) / 3);
} }
protected String[] normalize(Object[] objects) { protected String[] normalize(Object[] objects) {
String[] names = new String[objects.length]; String[] names = new String[objects.length];
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
names[i] = normalizeObject(objects[i]).replaceAll("\\s", ""); names[i] = normalizeObject(objects[i]).replaceAll("\\s", "");
} }
return names; return names;
} }
protected Object[] fields(Object object) { protected Object[] fields(Object object) {
if (object instanceof Episode) { if (object instanceof Episode) {
Episode episode = (Episode) object; Episode episode = (Episode) object;
String seriesName = removeTrailingBrackets(episode.getSeriesName()); LinkedHashSet<String> set = new LinkedHashSet<String>(4);
String episodeTitle = episode.getTitle(); set.add(removeTrailingBrackets(episode.getSeriesName()));
if (!seriesName.equalsIgnoreCase(episodeTitle)) { set.add(removeTrailingBrackets(episode.getTitle()));
return new Object[] { seriesName, episodeTitle }; set.add(removeTrailingBrackets(episode.getSeries().getName()));
} else { for (String it : episode.getSeries().getAliasNames()) {
return new Object[] { seriesName, null }; 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) { if (object instanceof File) {
File file = (File) object; File file = (File) object;
return new Object[] { file.getParentFile().getAbsolutePath(), file }; return new Object[] { file.getParentFile().getAbsolutePath(), file };
} }
if (object instanceof Movie) { if (object instanceof Movie) {
Movie movie = (Movie) object; Movie movie = (Movie) object;
return new Object[] { movie.getName(), movie.getYear() }; return new Object[] { movie.getName(), movie.getYear() };
} }
return new Object[] { object }; return new Object[] { object };
} }
}), }),
// Match via common word sequence in episode name and file name // Match via common word sequence in episode name and file name
NameSubstringSequence(new SequenceMatchSimilarity() { NameSubstringSequence(new SequenceMatchSimilarity() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
// normalize absolute similarity to similarity rank (4 ranks in total), // normalize absolute similarity to similarity rank (4 ranks in total),
// so we are less likely to fall for false positives in this pass, and move on to the next one // so we are less likely to fall for false positives in this pass, and move on to the next one
return (float) (floor(super.getSimilarity(o1, o2) * 4) / 4); return (float) (floor(super.getSimilarity(o1, o2) * 4) / 4);
} }
@Override @Override
protected String normalize(Object object) { protected String normalize(Object object) {
if (object instanceof Episode) { if (object instanceof Episode) {
@ -247,47 +258,43 @@ public enum EpisodeMetrics implements SimilarityMetric {
return normalizeObject(object); return normalizeObject(object);
} }
}), }),
// Match by generic name similarity (round rank) // Match by generic name similarity (round rank)
Name(new NameSimilarityMetric() { Name(new NameSimilarityMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
// normalize absolute similarity to similarity rank (4 ranks in total), // normalize absolute similarity to similarity rank (4 ranks in total),
// so we are less likely to fall for false positives in this pass, and move on to the next one // so we are less likely to fall for false positives in this pass, and move on to the next one
return (float) (floor(super.getSimilarity(o1, o2) * 4) / 4); return (float) (floor(super.getSimilarity(o1, o2) * 4) / 4);
} }
@Override @Override
protected String normalize(Object object) { protected String normalize(Object object) {
// simplify file name, if possible // simplify file name, if possible
return normalizeObject(object); return normalizeObject(object);
} }
}), }),
// Match by generic name similarity (absolute) // Match by generic name similarity (absolute)
SeriesName(new NameSimilarityMetric() { SeriesName(new NameSimilarityMetric() {
private ReleaseInfo releaseInfo = new ReleaseInfo(); private ReleaseInfo releaseInfo = new ReleaseInfo();
private SeriesNameMatcher seriesNameMatcher = new SeriesNameMatcher(); private SeriesNameMatcher seriesNameMatcher = new SeriesNameMatcher();
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
float lowerBound = super.getSimilarity(normalize(o1, true), normalize(o2, true)); float lowerBound = super.getSimilarity(normalize(o1, true), normalize(o2, true));
float upperBound = super.getSimilarity(normalize(o1, false), normalize(o2, false)); float upperBound = super.getSimilarity(normalize(o1, false), normalize(o2, false));
return (float) (floor(max(lowerBound, upperBound) * 4) / 4); return (float) (floor(max(lowerBound, upperBound) * 4) / 4);
}; };
@Override @Override
protected String normalize(Object object) { protected String normalize(Object object) {
return object.toString(); return object.toString();
}; };
protected String normalize(Object object, boolean strict) { protected String normalize(Object object, boolean strict) {
if (object instanceof Episode) { if (object instanceof Episode) {
if (strict) { if (strict) {
@ -302,7 +309,7 @@ public enum EpisodeMetrics implements SimilarityMetric {
object = sn; object = sn;
} }
} }
// equally strip away strip potential any clutter // equally strip away strip potential any clutter
try { try {
object = releaseInfo.cleanRelease(singleton(object.toString()), strict).iterator().next(); object = releaseInfo.cleanRelease(singleton(object.toString()), strict).iterator().next();
@ -311,15 +318,15 @@ public enum EpisodeMetrics implements SimilarityMetric {
} catch (IOException e) { } catch (IOException e) {
Logger.getLogger(EpisodeMetrics.class.getName()).log(Level.WARNING, e.getMessage()); Logger.getLogger(EpisodeMetrics.class.getName()).log(Level.WARNING, e.getMessage());
} }
// simplify file name, if possible // simplify file name, if possible
return normalizeObject(object); return normalizeObject(object);
} }
}), }),
// Match by generic name similarity (absolute) // Match by generic name similarity (absolute)
AbsolutePath(new NameSimilarityMetric() { AbsolutePath(new NameSimilarityMetric() {
@Override @Override
protected String normalize(Object object) { protected String normalize(Object object) {
if (object instanceof File) { if (object instanceof File) {
@ -328,24 +335,22 @@ public enum EpisodeMetrics implements SimilarityMetric {
return normalizeObject(object.toString()); // simplify file name, if possible return normalizeObject(object.toString()); // simplify file name, if possible
} }
}), }),
NumericSequence(new SequenceMatchSimilarity() { NumericSequence(new SequenceMatchSimilarity() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
float lowerBound = super.getSimilarity(normalize(o1, true), normalize(o2, true)); float lowerBound = super.getSimilarity(normalize(o1, true), normalize(o2, true));
float upperBound = super.getSimilarity(normalize(o1, false), normalize(o2, false)); float upperBound = super.getSimilarity(normalize(o1, false), normalize(o2, false));
return max(lowerBound, upperBound); return max(lowerBound, upperBound);
}; };
@Override @Override
protected String normalize(Object object) { protected String normalize(Object object) {
return object.toString(); return object.toString();
}; };
protected String normalize(Object object, boolean numbersOnly) { protected String normalize(Object object, boolean numbersOnly) {
if (object instanceof Episode) { if (object instanceof Episode) {
Episode e = (Episode) object; Episode e = (Episode) object;
@ -362,7 +367,7 @@ public enum EpisodeMetrics implements SimilarityMetric {
object = String.format("%s %s", m.getName(), m.getYear()); object = String.format("%s %s", m.getName(), m.getYear());
} }
} }
// simplify file name if possible and extract numbers // simplify file name if possible and extract numbers
List<Integer> numbers = new ArrayList<Integer>(4); List<Integer> numbers = new ArrayList<Integer>(4);
Scanner scanner = new Scanner(normalizeObject(object)).useDelimiter("\\D+"); Scanner scanner = new Scanner(normalizeObject(object)).useDelimiter("\\D+");
@ -372,15 +377,15 @@ public enum EpisodeMetrics implements SimilarityMetric {
return join(numbers, " "); return join(numbers, " ");
} }
}), }),
// Match by generic numeric similarity // Match by generic numeric similarity
Numeric(new NumericSimilarityMetric() { Numeric(new NumericSimilarityMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
String[] f1 = fields(o1); String[] f1 = fields(o1);
String[] f2 = fields(o2); String[] f2 = fields(o2);
// match all fields and average similarity // match all fields and average similarity
float max = 0; float max = 0;
for (String s1 : f1) { for (String s1 : f1) {
@ -390,106 +395,102 @@ public enum EpisodeMetrics implements SimilarityMetric {
} }
return max; return max;
} }
protected String[] fields(Object object) { protected String[] fields(Object object) {
if (object instanceof Episode) { if (object instanceof Episode) {
Episode episode = (Episode) object; Episode episode = (Episode) object;
return new String[] { episode.getSeriesName(), EpisodeFormat.SeasonEpisode.formatSxE(episode), String.valueOf(episode.getAbsolute()) }; return new String[] { episode.getSeriesName(), EpisodeFormat.SeasonEpisode.formatSxE(episode), String.valueOf(episode.getAbsolute()) };
} }
if (object instanceof Movie) { if (object instanceof Movie) {
Movie movie = (Movie) object; Movie movie = (Movie) object;
return new String[] { movie.getName(), String.valueOf(movie.getYear()) }; return new String[] { movie.getName(), String.valueOf(movie.getYear()) };
} }
return new String[] { normalizeObject(object) }; return new String[] { normalizeObject(object) };
} }
}), }),
// Match by file length (only works when matching torrents or files) // Match by file length (only works when matching torrents or files)
FileSize(new FileSizeMetric() { FileSize(new FileSizeMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
// order of arguments is logically irrelevant, but we might be able to save us a call to File.length() which is quite costly // order of arguments is logically irrelevant, but we might be able to save us a call to File.length() which is quite costly
return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2); return o1 instanceof File ? super.getSimilarity(o2, o1) : super.getSimilarity(o1, o2);
} }
@Override @Override
protected long getLength(Object object) { protected long getLength(Object object) {
if (object instanceof FileInfo) { if (object instanceof FileInfo) {
return ((FileInfo) object).getLength(); return ((FileInfo) object).getLength();
} }
return super.getLength(object); return super.getLength(object);
} }
}), }),
// Match by common words at the beginning of both files // Match by common words at the beginning of both files
FileName(new FileNameMetric() { FileName(new FileNameMetric() {
@Override @Override
protected String getFileName(Object object) { protected String getFileName(Object object) {
if (object instanceof File || object instanceof FileInfo) { if (object instanceof File || object instanceof FileInfo) {
return normalizeObject(object); return normalizeObject(object);
} }
return null; return null;
} }
}), }),
// Match by file last modified and episode release dates // Match by file last modified and episode release dates
TimeStamp(new TimeStampMetric() { TimeStamp(new TimeStampMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
// adjust differentiation accuracy to about a year // adjust differentiation accuracy to about a year
float f = super.getSimilarity(o1, o2); float f = super.getSimilarity(o1, o2);
return f >= 0.8 ? 1 : f >= 0 ? 0 : -1; return f >= 0.8 ? 1 : f >= 0 ? 0 : -1;
} }
@Override @Override
public long getTimeStamp(Object object) { public long getTimeStamp(Object object) {
if (object instanceof Episode) { if (object instanceof Episode) {
try { try {
long ts = ((Episode) object).getAirdate().getTimeStamp(); long ts = ((Episode) object).getAirdate().getTimeStamp();
// big penalty for episodes not yet aired // big penalty for episodes not yet aired
if (ts > System.currentTimeMillis()) { if (ts > System.currentTimeMillis()) {
return -1; return -1;
} }
return ts; return ts;
} catch (RuntimeException e) { } catch (RuntimeException e) {
return -1; // some episodes may not have airdate defined return -1; // some episodes may not have airdate defined
} }
} }
return super.getTimeStamp(object); return super.getTimeStamp(object);
} }
}), }),
SeriesRating(new SimilarityMetric() { SeriesRating(new SimilarityMetric() {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
float r1 = getRating(o1); float r1 = getRating(o1);
float r2 = getRating(o2); float r2 = getRating(o2);
return max(r1, r2) >= 0.4 ? 1 : min(r1, r2) < 0 ? -1 : 0; return max(r1, r2) >= 0.4 ? 1 : min(r1, r2) < 0 ? -1 : 0;
} }
private final Map<String, SeriesInfo> seriesInfoCache = new HashMap<String, SeriesInfo>(); private final Map<String, SeriesInfo> seriesInfoCache = new HashMap<String, SeriesInfo>();
public float getRating(Object o) { public float getRating(Object o) {
if (o instanceof Episode) { if (o instanceof Episode) {
try { try {
synchronized (seriesInfoCache) { synchronized (seriesInfoCache) {
String n = ((Episode) o).getSeriesName(); String n = ((Episode) o).getSeriesName();
SeriesInfo seriesInfo = seriesInfoCache.get(n); SeriesInfo seriesInfo = seriesInfoCache.get(n);
if (seriesInfo == null && !seriesInfoCache.containsKey(n)) { if (seriesInfo == null && !seriesInfoCache.containsKey(n)) {
try { try {
@ -499,7 +500,7 @@ public enum EpisodeMetrics implements SimilarityMetric {
} }
seriesInfoCache.put(n, seriesInfo); seriesInfoCache.put(n, seriesInfo);
} }
if (seriesInfo != null) { if (seriesInfo != null) {
if (seriesInfo.getRatingCount() > 0) { if (seriesInfo.getRatingCount() > 0) {
float rating = max(0, seriesInfo.getRating().floatValue()); float rating = max(0, seriesInfo.getRating().floatValue());
@ -516,18 +517,18 @@ public enum EpisodeMetrics implements SimilarityMetric {
return 0; return 0;
} }
}), }),
// Match by stored MetaAttributes if possible // Match by stored MetaAttributes if possible
MetaAttributes(new CrossPropertyMetric() { MetaAttributes(new CrossPropertyMetric() {
@Override @Override
protected Map<String, Object> getProperties(Object object) { protected Map<String, Object> getProperties(Object object) {
// Episode / Movie objects // Episode / Movie objects
if (object instanceof Episode || object instanceof Movie) { if (object instanceof Episode || object instanceof Movie) {
return super.getProperties(object); return super.getProperties(object);
} }
// deserialize MetaAttributes if enabled and available // deserialize MetaAttributes if enabled and available
if (object instanceof File && useExtendedFileAttributes()) { if (object instanceof File && useExtendedFileAttributes()) {
try { try {
return super.getProperties(new net.sourceforge.filebot.media.MetaAttributes((File) object).getObject()); return super.getProperties(new net.sourceforge.filebot.media.MetaAttributes((File) object).getObject());
@ -535,68 +536,64 @@ public enum EpisodeMetrics implements SimilarityMetric {
// ignore // ignore
} }
} }
// ignore everything else // ignore everything else
return emptyMap(); return emptyMap();
}; };
}); });
// inner metric // inner metric
private final SimilarityMetric metric; private final SimilarityMetric metric;
private EpisodeMetrics(SimilarityMetric metric) { private EpisodeMetrics(SimilarityMetric metric) {
this.metric = metric; this.metric = metric;
} }
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
return metric.getSimilarity(o1, o2); return metric.getSimilarity(o1, o2);
} }
private static final Map<Object, String> transformCache = synchronizedMap(new HashMap<Object, String>(64, 4)); 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"); private static final Transliterator transliterator = Transliterator.getInstance("Any-Latin;Latin-ASCII;[:Diacritic:]remove");
protected static String normalizeObject(Object object) { protected static String normalizeObject(Object object) {
if (object == null) { if (object == null) {
return ""; return "";
} }
String result = transformCache.get(object); String result = transformCache.get(object);
if (result != null) { if (result != null) {
return result; return result;
} }
String name = object.toString(); String name = object.toString();
// use name without extension // use name without extension
if (object instanceof File) { if (object instanceof File) {
name = getName((File) object); name = getName((File) object);
} else if (object instanceof FileInfo) { } else if (object instanceof FileInfo) {
name = ((FileInfo) object).getName(); name = ((FileInfo) object).getName();
} }
// remove checksums, any [...] or (...) // remove checksums, any [...] or (...)
name = removeEmbeddedChecksum(name); name = removeEmbeddedChecksum(name);
synchronized (transliterator) { synchronized (transliterator) {
name = transliterator.transform(name); name = transliterator.transform(name);
} }
// remove/normalize special characters // remove/normalize special characters
name = normalizePunctuation(name); name = normalizePunctuation(name);
// normalize to lower case // normalize to lower case
name = name.toLowerCase(); name = name.toLowerCase();
transformCache.put(object, name); transformCache.put(object, name);
return name; return name;
} }
public static SimilarityMetric[] defaultSequence(boolean includeFileMetrics) { public static SimilarityMetric[] defaultSequence(boolean includeFileMetrics) {
// 1 pass: divide by file length (only works for matching torrent entries or files) // 1 pass: divide by file length (only works for matching torrent entries or files)
// 2-3 pass: divide by title or season / episode numbers // 2-3 pass: divide by title or season / episode numbers
@ -611,10 +608,9 @@ public enum EpisodeMetrics implements SimilarityMetric {
return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, SubstringFields, MetaAttributes, new MetricCascade(NameSubstringSequence, Name), Numeric, NumericSequence, SeriesName, SeriesRating, TimeStamp, AbsolutePath }; return new SimilarityMetric[] { EpisodeFunnel, EpisodeBalancer, SubstringFields, MetaAttributes, new MetricCascade(NameSubstringSequence, Name), Numeric, NumericSequence, SeriesName, SeriesRating, TimeStamp, AbsolutePath };
} }
} }
public static SimilarityMetric verificationMetric() { public static SimilarityMetric verificationMetric() {
return new MetricCascade(FileSize, FileName, SeasonEpisode, AirDate, Title, Name); return new MetricCascade(FileSize, FileName, SeasonEpisode, AirDate, Title, Name);
} }
} }

View File

@ -1,35 +1,34 @@
package net.sourceforge.filebot.similarity; package net.sourceforge.filebot.similarity;
import static net.sourceforge.filebot.similarity.Normalization.normalizePunctuation;
import static net.sourceforge.filebot.similarity.Normalization.*;
public class SubstringMetric implements SimilarityMetric { public class SubstringMetric implements SimilarityMetric {
@Override @Override
public float getSimilarity(Object o1, Object o2) { public float getSimilarity(Object o1, Object o2) {
String s1 = normalize(o1); String s1 = normalize(o1);
if (s1 == null || s1.isEmpty()) if (s1 == null || s1.isEmpty())
return 0; return 0;
String s2 = normalize(o2); String s2 = normalize(o2);
if (s2 == null || s2.isEmpty()) if (s2 == null || s2.isEmpty())
return 0; return 0;
return s1.contains(s2) || s2.contains(s1) ? 1 : 0; return s1.contains(s2) || s2.contains(s1) ? 1 : 0;
} }
protected String normalize(Object object) { protected String normalize(Object object) {
if (object == null)
return null;
// use string representation // use string representation
String name = object.toString(); String name = object.toString();
// normalize separators // normalize separators
name = normalizePunctuation(name); name = normalizePunctuation(name);
// normalize case and trim // normalize case and trim
return name.trim().toLowerCase(); return name.trim().toLowerCase();
} }
} }

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> 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> 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"}} 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"} 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} 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 # basic 1.01

View File

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

View File

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

View File

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

View File

@ -74,8 +74,9 @@ public class TMDbClient implements MovieIdentificationService {
// e.g. // e.g.
// {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"} // {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"}
String title = (String) it.get("title"); String title = (String) it.get("title");
String originalTitle = (String) it.get("original_title");
if (title == null || title.isEmpty()) { if (title == null || title.isEmpty()) {
title = (String) it.get("original_title"); title = originalTitle;
} }
try { try {
@ -87,7 +88,7 @@ public class TMDbClient implements MovieIdentificationService {
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException("Missing data: year"); 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) { } catch (Exception e) {
Logger.getLogger(TMDbClient.class.getName()).log(Level.FINE, String.format("Ignore movie [%s]: %s", title, e.getMessage())); 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; package net.sourceforge.filebot.web;
import static java.util.Arrays.asList;
import static java.util.Arrays.*; import static net.sourceforge.filebot.web.EpisodeUtilities.filterBySeason;
import static net.sourceforge.filebot.web.EpisodeUtilities.*; import static net.sourceforge.filebot.web.EpisodeUtilities.sortEpisodes;
import static net.sourceforge.filebot.web.WebRequest.*; import static net.sourceforge.filebot.web.WebRequest.encode;
import static net.sourceforge.tuned.XPathUtilities.*; 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.FileNotFoundException;
import java.io.Serializable; import java.io.Serializable;
@ -38,51 +42,44 @@ import net.sourceforge.tuned.FileUtilities;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
public class TheTVDBClient extends AbstractEpisodeListProvider { public class TheTVDBClient extends AbstractEpisodeListProvider {
private final String host = "www.thetvdb.com"; private final String host = "www.thetvdb.com";
private final Map<MirrorType, String> mirrors = new EnumMap<MirrorType, String>(MirrorType.class); private final Map<MirrorType, String> mirrors = new EnumMap<MirrorType, String>(MirrorType.class);
private final String apikey; private final String apikey;
public TheTVDBClient(String apikey) { public TheTVDBClient(String apikey) {
if (apikey == null) if (apikey == null)
throw new NullPointerException("apikey must not be null"); throw new NullPointerException("apikey must not be null");
this.apikey = apikey; this.apikey = apikey;
} }
@Override @Override
public String getName() { public String getName() {
return "TheTVDB"; return "TheTVDB";
} }
@Override @Override
public Icon getIcon() { public Icon getIcon() {
return ResourceManager.getIcon("search.thetvdb"); return ResourceManager.getIcon("search.thetvdb");
} }
@Override @Override
public boolean hasSingleSeasonSupport() { public boolean hasSingleSeasonSupport() {
return true; return true;
} }
@Override @Override
public boolean hasLocaleSupport() { public boolean hasLocaleSupport() {
return true; return true;
} }
public String getLanguageCode(Locale locale) { public String getLanguageCode(Locale locale) {
String code = locale.getLanguage(); String code = locale.getLanguage();
// Java language code => TheTVDB language code // Java language code => TheTVDB language code
if (code.equals("iw")) // Hebrew if (code.equals("iw")) // Hebrew
return "he"; return "he";
@ -92,71 +89,73 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return "id"; return "id";
if (code.equals("ro")) // Russian if (code.equals("ro")) // Russian
return "ru"; return "ru";
return code; return code;
} }
@Override @Override
public ResultCache getCache() { public ResultCache getCache() {
return new ResultCache(host, Cache.getCache("web-datasource")); return new ResultCache(host, Cache.getCache("web-datasource"));
} }
@Override @Override
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception { public List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception {
// perform online search // perform online search
URL url = getResource(MirrorType.SEARCH, "/api/GetSeries.php?seriesname=" + encode(query, true) + "&language=" + getLanguageCode(locale)); URL url = getResource(MirrorType.SEARCH, "/api/GetSeries.php?seriesname=" + encode(query, true) + "&language=" + getLanguageCode(locale));
Document dom = getDocument(url); Document dom = getDocument(url);
List<Node> nodes = selectNodes("Data/Series", dom); List<Node> nodes = selectNodes("Data/Series", dom);
Map<Integer, TheTVDBSearchResult> resultSet = new LinkedHashMap<Integer, TheTVDBSearchResult>(); Map<Integer, TheTVDBSearchResult> resultSet = new LinkedHashMap<Integer, TheTVDBSearchResult>();
for (Node node : nodes) { for (Node node : nodes) {
int sid = getIntegerContent("seriesid", node); int sid = getIntegerContent("seriesid", node);
String seriesName = getTextContent("SeriesName", 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)) { 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()); return new ArrayList<SearchResult>(resultSet.values());
} }
@Override @Override
public List<Episode> fetchEpisodeList(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception { public List<Episode> fetchEpisodeList(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
TheTVDBSearchResult series = (TheTVDBSearchResult) searchResult; TheTVDBSearchResult series = (TheTVDBSearchResult) searchResult;
Document seriesRecord = getSeriesRecord(series, getLanguageCode(locale)); Document seriesRecord = getSeriesRecord(series, getLanguageCode(locale));
// we could get the series name from the search result, but the language may not match the given parameter // we could get the series name from the search result, but the language may not match the given parameter
String seriesName = selectString("Data/Series/SeriesName", seriesRecord); String seriesName = selectString("Data/Series/SeriesName", seriesRecord);
Date seriesStartDate = Date.parse(selectString("Data/Series/FirstAired", seriesRecord), "yyyy-MM-dd"); Date seriesStartDate = Date.parse(selectString("Data/Series/FirstAired", seriesRecord), "yyyy-MM-dd");
List<Node> nodes = selectNodes("Data/Episode", seriesRecord); List<Node> nodes = selectNodes("Data/Episode", seriesRecord);
List<Episode> episodes = new ArrayList<Episode>(nodes.size()); List<Episode> episodes = new ArrayList<Episode>(nodes.size());
List<Episode> specials = new ArrayList<Episode>(5); List<Episode> specials = new ArrayList<Episode>(5);
for (Node node : nodes) { for (Node node : nodes) {
String episodeName = getTextContent("EpisodeName", node); String episodeName = getTextContent("EpisodeName", node);
String dvdSeasonNumber = getTextContent("DVD_season", node); String dvdSeasonNumber = getTextContent("DVD_season", node);
String dvdEpisodeNumber = getTextContent("DVD_episodenumber", node); String dvdEpisodeNumber = getTextContent("DVD_episodenumber", node);
Integer absoluteNumber = getIntegerContent("absolute_number", node); Integer absoluteNumber = getIntegerContent("absolute_number", node);
Date airdate = Date.parse(getTextContent("FirstAired", node), "yyyy-MM-dd"); Date airdate = Date.parse(getTextContent("FirstAired", node), "yyyy-MM-dd");
// default numbering // default numbering
Integer episodeNumber = getIntegerContent("EpisodeNumber", node); Integer episodeNumber = getIntegerContent("EpisodeNumber", node);
Integer seasonNumber = getIntegerContent("SeasonNumber", node); Integer seasonNumber = getIntegerContent("SeasonNumber", node);
if (seasonNumber == null || seasonNumber == 0) { if (seasonNumber == null || seasonNumber == 0) {
// handle as special episode // handle as special episode
Integer airsBefore = getIntegerContent("airsbefore_season", node); Integer airsBefore = getIntegerContent("airsbefore_season", node);
if (airsBefore != null) { if (airsBefore != null) {
seasonNumber = airsBefore; seasonNumber = airsBefore;
} }
// use given episode number as special number or count specials by ourselves // use given episode number as special number or count specials by ourselves
Integer specialNumber = (episodeNumber != null) ? episodeNumber : filterBySeason(specials, seasonNumber).size() + 1; Integer specialNumber = (episodeNumber != null) ? episodeNumber : filterBySeason(specials, seasonNumber).size() + 1;
specials.add(new Episode(seriesName, seriesStartDate, seasonNumber, null, episodeName, null, specialNumber, airdate, searchResult)); specials.add(new Episode(seriesName, seriesStartDate, seasonNumber, null, episodeName, null, specialNumber, airdate, searchResult));
@ -175,38 +174,37 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
// ignore, fallback to default numbering // ignore, fallback to default numbering
} }
} }
episodes.add(new Episode(seriesName, seriesStartDate, seasonNumber, episodeNumber, episodeName, absoluteNumber, null, airdate, searchResult)); episodes.add(new Episode(seriesName, seriesStartDate, seasonNumber, episodeNumber, episodeName, absoluteNumber, null, airdate, searchResult));
} }
} }
// episodes my not be ordered by DVD episode number // episodes my not be ordered by DVD episode number
sortEpisodes(episodes); sortEpisodes(episodes);
// add specials at the end // add specials at the end
episodes.addAll(specials); episodes.addAll(specials);
return episodes; return episodes;
} }
public Document getSeriesRecord(TheTVDBSearchResult searchResult, String languageCode) throws Exception { public Document getSeriesRecord(TheTVDBSearchResult searchResult, String languageCode) throws Exception {
URL seriesRecord = getResource(MirrorType.ZIP, "/api/" + apikey + "/series/" + searchResult.getSeriesId() + "/all/" + languageCode + ".zip"); URL seriesRecord = getResource(MirrorType.ZIP, "/api/" + apikey + "/series/" + searchResult.getSeriesId() + "/all/" + languageCode + ".zip");
try { try {
ZipInputStream zipInputStream = new ZipInputStream(seriesRecord.openStream()); ZipInputStream zipInputStream = new ZipInputStream(seriesRecord.openStream());
ZipEntry zipEntry; ZipEntry zipEntry;
try { try {
String seriesRecordName = languageCode + ".xml"; String seriesRecordName = languageCode + ".xml";
while ((zipEntry = zipInputStream.getNextEntry()) != null) { while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (seriesRecordName.equals(zipEntry.getName())) { if (seriesRecordName.equals(zipEntry.getName())) {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(zipInputStream); return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(zipInputStream);
} }
} }
// zip file must contain the series record // zip file must contain the series record
throw new FileNotFoundException(String.format("Archive must contain %s: %s", seriesRecordName, seriesRecord)); throw new FileNotFoundException(String.format("Archive must contain %s: %s", seriesRecordName, seriesRecord));
} finally { } finally {
@ -216,20 +214,19 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
throw new FileNotFoundException(String.format("Series record not found: %s [%s]: %s", searchResult.getName(), languageCode, seriesRecord)); throw new FileNotFoundException(String.format("Series record not found: %s [%s]: %s", searchResult.getName(), languageCode, seriesRecord));
} }
} }
public TheTVDBSearchResult lookupByID(int id, Locale locale) throws Exception { public TheTVDBSearchResult lookupByID(int id, Locale locale) throws Exception {
TheTVDBSearchResult cachedItem = getCache().getData("lookupByID", id, locale, TheTVDBSearchResult.class); TheTVDBSearchResult cachedItem = getCache().getData("lookupByID", id, locale, TheTVDBSearchResult.class);
if (cachedItem != null) { if (cachedItem != null) {
return cachedItem; return cachedItem;
} }
try { try {
URL baseRecordLocation = getResource(MirrorType.XML, "/api/" + apikey + "/series/" + id + "/all/" + getLanguageCode(locale) + ".xml"); URL baseRecordLocation = getResource(MirrorType.XML, "/api/" + apikey + "/series/" + id + "/all/" + getLanguageCode(locale) + ".xml");
Document baseRecord = getDocument(baseRecordLocation); Document baseRecord = getDocument(baseRecordLocation);
String name = selectString("//SeriesName", baseRecord); String name = selectString("//SeriesName", baseRecord);
TheTVDBSearchResult series = new TheTVDBSearchResult(name, id); TheTVDBSearchResult series = new TheTVDBSearchResult(name, id);
getCache().putData("lookupByID", id, locale, series); getCache().putData("lookupByID", id, locale, series);
return series; return series;
@ -239,35 +236,32 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public TheTVDBSearchResult lookupByIMDbID(int imdbid, Locale locale) throws Exception { public TheTVDBSearchResult lookupByIMDbID(int imdbid, Locale locale) throws Exception {
TheTVDBSearchResult cachedItem = getCache().getData("lookupByIMDbID", imdbid, locale, TheTVDBSearchResult.class); TheTVDBSearchResult cachedItem = getCache().getData("lookupByIMDbID", imdbid, locale, TheTVDBSearchResult.class);
if (cachedItem != null) { if (cachedItem != null) {
return cachedItem; return cachedItem;
} }
URL query = getResource(null, "/api/GetSeriesByRemoteID.php?imdbid=" + imdbid + "&language=" + getLanguageCode(locale)); URL query = getResource(null, "/api/GetSeriesByRemoteID.php?imdbid=" + imdbid + "&language=" + getLanguageCode(locale));
Document dom = getDocument(query); Document dom = getDocument(query);
String id = selectString("//seriesid", dom); String id = selectString("//seriesid", dom);
String name = selectString("//SeriesName", dom); String name = selectString("//SeriesName", dom);
if (id == null || id.isEmpty() || name == null || name.isEmpty()) if (id == null || id.isEmpty() || name == null || name.isEmpty())
return null; return null;
TheTVDBSearchResult series = new TheTVDBSearchResult(name, Integer.parseInt(id)); TheTVDBSearchResult series = new TheTVDBSearchResult(name, Integer.parseInt(id));
getCache().putData("lookupByIMDbID", imdbid, locale, series); getCache().putData("lookupByIMDbID", imdbid, locale, series);
return series; return series;
} }
@Override @Override
public URI getEpisodeListLink(SearchResult searchResult) { public URI getEpisodeListLink(SearchResult searchResult) {
return URI.create("http://" + host + "/?tab=seasonall&id=" + ((TheTVDBSearchResult) searchResult).getSeriesId()); return URI.create("http://" + host + "/?tab=seasonall&id=" + ((TheTVDBSearchResult) searchResult).getSeriesId());
} }
protected String getMirror(MirrorType mirrorType) throws Exception { protected String getMirror(MirrorType mirrorType) throws Exception {
synchronized (mirrors) { synchronized (mirrors) {
if (mirrors.isEmpty()) { if (mirrors.isEmpty()) {
@ -282,49 +276,48 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e); Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
} }
// initialize mirrors // initialize mirrors
Document dom = getDocument(getResource(null, "/api/" + apikey + "/mirrors.xml")); Document dom = getDocument(getResource(null, "/api/" + apikey + "/mirrors.xml"));
// all mirrors by type // all mirrors by type
Map<MirrorType, List<String>> mirrorListMap = new EnumMap<MirrorType, List<String>>(MirrorType.class); Map<MirrorType, List<String>> mirrorListMap = new EnumMap<MirrorType, List<String>>(MirrorType.class);
// initialize mirror list per type // initialize mirror list per type
for (MirrorType type : MirrorType.values()) { for (MirrorType type : MirrorType.values()) {
mirrorListMap.put(type, new ArrayList<String>(5)); mirrorListMap.put(type, new ArrayList<String>(5));
} }
// traverse all mirrors // traverse all mirrors
for (Node node : selectNodes("Mirrors/Mirror", dom)) { for (Node node : selectNodes("Mirrors/Mirror", dom)) {
// mirror data // mirror data
String mirror = getTextContent("mirrorpath", node); String mirror = getTextContent("mirrorpath", node);
int typeMask = Integer.parseInt(getTextContent("typemask", node)); int typeMask = Integer.parseInt(getTextContent("typemask", node));
// add mirror to the according type lists // add mirror to the according type lists
for (MirrorType type : MirrorType.fromTypeMask(typeMask)) { for (MirrorType type : MirrorType.fromTypeMask(typeMask)) {
mirrorListMap.get(type).add(mirror); mirrorListMap.get(type).add(mirror);
} }
} }
// put random entry from each type list into mirrors // put random entry from each type list into mirrors
Random random = new Random(); Random random = new Random();
for (MirrorType type : MirrorType.values()) { for (MirrorType type : MirrorType.values()) {
List<String> list = mirrorListMap.get(type); List<String> list = mirrorListMap.get(type);
if (!list.isEmpty()) { if (!list.isEmpty()) {
mirrors.put(type, list.get(random.nextInt(list.size()))); mirrors.put(type, list.get(random.nextInt(list.size())));
} }
} }
getCache().putData("mirrors", null, null, mirrors); getCache().putData("mirrors", null, null, mirrors);
} }
return mirrors.get(mirrorType); return mirrors.get(mirrorType);
} }
} }
protected URL getResource(MirrorType mirrorType, String path) throws Exception { protected URL getResource(MirrorType mirrorType, String path) throws Exception {
if (mirrorType != null) { if (mirrorType != null) {
// use mirror // use mirror
@ -333,23 +326,20 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return new URL(mirror + path); return new URL(mirror + path);
} }
} }
// use default server // use default server
return new URL("http", host, path); return new URL("http", host, path);
} }
protected static enum MirrorType { protected static enum MirrorType {
XML(1), BANNER(2), ZIP(4), SEARCH(1); XML(1), BANNER(2), ZIP(4), SEARCH(1);
private final int bitMask; private final int bitMask;
private MirrorType(int bitMask) { private MirrorType(int bitMask) {
this.bitMask = bitMask; this.bitMask = bitMask;
} }
public static EnumSet<MirrorType> fromTypeMask(int typeMask) { public static EnumSet<MirrorType> fromTypeMask(int typeMask) {
// initialize enum set with all types // initialize enum set with all types
EnumSet<MirrorType> enumSet = EnumSet.allOf(MirrorType.class); EnumSet<MirrorType> enumSet = EnumSet.allOf(MirrorType.class);
@ -361,46 +351,42 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} }
return enumSet; return enumSet;
}; };
} }
public SeriesInfo getSeriesInfoByID(int thetvdbid, Locale locale) throws Exception { public SeriesInfo getSeriesInfoByID(int thetvdbid, Locale locale) throws Exception {
return getSeriesInfo(new TheTVDBSearchResult(null, thetvdbid), locale); return getSeriesInfo(new TheTVDBSearchResult(null, thetvdbid), locale);
} }
public SeriesInfo getSeriesInfoByIMDbID(int imdbid, Locale locale) throws Exception { public SeriesInfo getSeriesInfoByIMDbID(int imdbid, Locale locale) throws Exception {
return getSeriesInfo(lookupByIMDbID(imdbid, locale), locale); return getSeriesInfo(lookupByIMDbID(imdbid, locale), locale);
} }
public SeriesInfo getSeriesInfoByName(String name, Locale locale) throws Exception { public SeriesInfo getSeriesInfoByName(String name, Locale locale) throws Exception {
for (SearchResult it : search(name, locale)) { for (SearchResult it : search(name, locale)) {
if (name.equalsIgnoreCase(it.getName())) { if (name.equalsIgnoreCase(it.getName())) {
return getSeriesInfo((TheTVDBSearchResult) it, locale); return getSeriesInfo((TheTVDBSearchResult) it, locale);
} }
} }
return null; return null;
} }
public SeriesInfo getSeriesInfo(TheTVDBSearchResult searchResult, Locale locale) throws Exception { public SeriesInfo getSeriesInfo(TheTVDBSearchResult searchResult, Locale locale) throws Exception {
// check cache first // check cache first
SeriesInfo cachedItem = getCache().getData("seriesInfo", searchResult.seriesId, locale, SeriesInfo.class); SeriesInfo cachedItem = getCache().getData("seriesInfo", searchResult.seriesId, locale, SeriesInfo.class);
if (cachedItem != null) { if (cachedItem != null) {
return cachedItem; return cachedItem;
} }
Document dom = getDocument(getResource(MirrorType.XML, "/api/" + apikey + "/series/" + searchResult.seriesId + "/" + getLanguageCode(locale) + ".xml")); Document dom = getDocument(getResource(MirrorType.XML, "/api/" + apikey + "/series/" + searchResult.seriesId + "/" + getLanguageCode(locale) + ".xml"));
Node node = selectNode("//Series", dom); Node node = selectNode("//Series", dom);
Map<SeriesProperty, String> fields = new EnumMap<SeriesProperty, String>(SeriesProperty.class); Map<SeriesProperty, String> fields = new EnumMap<SeriesProperty, String>(SeriesProperty.class);
// remember banner mirror // remember banner mirror
fields.put(SeriesProperty.BannerMirror, getResource(MirrorType.BANNER, "/banners/").toString()); fields.put(SeriesProperty.BannerMirror, getResource(MirrorType.BANNER, "/banners/").toString());
// copy values from xml // copy values from xml
for (SeriesProperty key : SeriesProperty.values()) { for (SeriesProperty key : SeriesProperty.values()) {
String value = getTextContent(key.name(), node); String value = getTextContent(key.name(), node);
@ -408,42 +394,36 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
fields.put(key, value); fields.put(key, value);
} }
} }
SeriesInfo seriesInfo = new SeriesInfo(fields); SeriesInfo seriesInfo = new SeriesInfo(fields);
getCache().putData("seriesInfo", searchResult.seriesId, locale, seriesInfo); getCache().putData("seriesInfo", searchResult.seriesId, locale, seriesInfo);
return seriesInfo; return seriesInfo;
} }
public static class SeriesInfo implements Serializable { public static class SeriesInfo implements Serializable {
public static enum SeriesProperty { public static enum SeriesProperty {
id, Actors, Airs_DayOfWeek, Airs_Time, ContentRating, FirstAired, Genre, IMDB_ID, Language, Network, Overview, Rating, RatingCount, Runtime, SeriesName, Status, BannerMirror, banner, fanart, poster id, Actors, Airs_DayOfWeek, Airs_Time, ContentRating, FirstAired, Genre, IMDB_ID, Language, Network, Overview, Rating, RatingCount, Runtime, SeriesName, Status, BannerMirror, banner, fanart, poster
} }
protected Map<SeriesProperty, String> fields; protected Map<SeriesProperty, String> fields;
protected SeriesInfo() { protected SeriesInfo() {
// used by serializer // used by serializer
} }
protected SeriesInfo(Map<SeriesProperty, String> fields) { protected SeriesInfo(Map<SeriesProperty, String> fields) {
this.fields = new EnumMap<SeriesProperty, String>(fields); this.fields = new EnumMap<SeriesProperty, String>(fields);
} }
public String get(Object key) { public String get(Object key) {
return fields.get(SeriesProperty.valueOf(key.toString())); return fields.get(SeriesProperty.valueOf(key.toString()));
} }
public String get(SeriesProperty key) { public String get(SeriesProperty key) {
return fields.get(key); return fields.get(key);
} }
public Integer getId() { public Integer getId() {
// e.g. 80348 // e.g. 80348
try { try {
@ -452,20 +432,17 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public List<String> getActors() { public List<String> getActors() {
// e.g. |Zachary Levi|Adam Baldwin|Yvonne Strzechowski| // e.g. |Zachary Levi|Adam Baldwin|Yvonne Strzechowski|
return split(get(SeriesProperty.Actors)); return split(get(SeriesProperty.Actors));
} }
public List<String> getGenres() { public List<String> getGenres() {
// e.g. |Comedy| // e.g. |Comedy|
return split(get(SeriesProperty.Genre)); return split(get(SeriesProperty.Genre));
} }
protected List<String> split(String values) { protected List<String> split(String values) {
List<String> items = new ArrayList<String>(); List<String> items = new ArrayList<String>();
if (values != null && values.length() > 0) { if (values != null && values.length() > 0) {
@ -478,37 +455,31 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} }
return items; return items;
} }
public String getAirDayOfWeek() { public String getAirDayOfWeek() {
// e.g. Monday // e.g. Monday
return get(SeriesProperty.Airs_DayOfWeek); return get(SeriesProperty.Airs_DayOfWeek);
} }
public String getAirTime() { public String getAirTime() {
// e.g. 8:00 PM // e.g. 8:00 PM
return get(SeriesProperty.Airs_Time); return get(SeriesProperty.Airs_Time);
} }
public Date getFirstAired() { public Date getFirstAired() {
// e.g. 2007-09-24 // e.g. 2007-09-24
return Date.parse(get(SeriesProperty.FirstAired), "yyyy-MM-dd"); return Date.parse(get(SeriesProperty.FirstAired), "yyyy-MM-dd");
} }
public String getContentRating() { public String getContentRating() {
// e.g. TV-PG // e.g. TV-PG
return get(SeriesProperty.ContentRating); return get(SeriesProperty.ContentRating);
} }
public String getCertification() { public String getCertification() {
return getContentRating(); // another getter for compability reasons return getContentRating(); // another getter for compability reasons
} }
public Integer getImdbId() { public Integer getImdbId() {
// e.g. tt0934814 // e.g. tt0934814
try { try {
@ -517,8 +488,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public Locale getLanguage() { public Locale getLanguage() {
// e.g. en // e.g. en
try { try {
@ -527,14 +497,12 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public String getOverview() { public String getOverview() {
// e.g. Zachary Levi (Less Than Perfect) plays Chuck... // e.g. Zachary Levi (Less Than Perfect) plays Chuck...
return get(SeriesProperty.Overview); return get(SeriesProperty.Overview);
} }
public Double getRating() { public Double getRating() {
// e.g. 9.0 // e.g. 9.0
try { try {
@ -543,8 +511,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public Integer getRatingCount() { public Integer getRatingCount() {
// e.g. 696 // e.g. 696
try { try {
@ -553,32 +520,27 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public String getRuntime() { public String getRuntime() {
// e.g. 30 // e.g. 30
return get(SeriesProperty.Runtime); return get(SeriesProperty.Runtime);
} }
public String getName() { public String getName() {
// e.g. Chuck // e.g. Chuck
return get(SeriesProperty.SeriesName); return get(SeriesProperty.SeriesName);
} }
public String getNetwork() { public String getNetwork() {
// e.g. CBS // e.g. CBS
return get(SeriesProperty.Network); return get(SeriesProperty.Network);
} }
public String getStatus() { public String getStatus() {
// e.g. Continuing // e.g. Continuing
return get(SeriesProperty.Status); return get(SeriesProperty.Status);
} }
public URL getBannerMirrorUrl() { public URL getBannerMirrorUrl() {
try { try {
return new URL(get(BannerProperty.BannerMirror)); return new URL(get(BannerProperty.BannerMirror));
@ -586,8 +548,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public URL getBannerUrl() throws MalformedURLException { public URL getBannerUrl() throws MalformedURLException {
try { try {
return new URL(getBannerMirrorUrl(), get(SeriesProperty.banner)); return new URL(getBannerMirrorUrl(), get(SeriesProperty.banner));
@ -595,8 +556,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public URL getFanartUrl() { public URL getFanartUrl() {
try { try {
return new URL(getBannerMirrorUrl(), get(SeriesProperty.fanart)); return new URL(getBannerMirrorUrl(), get(SeriesProperty.fanart));
@ -604,8 +564,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public URL getPosterUrl() { public URL getPosterUrl() {
try { try {
return new URL(getBannerMirrorUrl(), get(SeriesProperty.poster)); return new URL(getBannerMirrorUrl(), get(SeriesProperty.poster));
@ -613,15 +572,13 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
@Override @Override
public String toString() { public String toString() {
return fields.toString(); return fields.toString();
} }
} }
/** /**
* Search for a series banner matching the given parameters * Search for a series banner matching the given parameters
* *
@ -634,37 +591,36 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
filter.put(BannerProperty.valueOf(it.getKey().toString()), it.getValue().toString()); filter.put(BannerProperty.valueOf(it.getKey().toString()), it.getValue().toString());
} }
} }
// search for a banner matching the selector // search for a banner matching the selector
for (BannerDescriptor it : getBannerList(series)) { for (BannerDescriptor it : getBannerList(series)) {
if (it.fields.entrySet().containsAll(filter.entrySet())) { if (it.fields.entrySet().containsAll(filter.entrySet())) {
return it; return it;
} }
} }
return null; return null;
} }
public List<BannerDescriptor> getBannerList(TheTVDBSearchResult series) throws Exception { public List<BannerDescriptor> getBannerList(TheTVDBSearchResult series) throws Exception {
// check cache first // check cache first
BannerDescriptor[] cachedList = getCache().getData("banners", series.seriesId, null, BannerDescriptor[].class); BannerDescriptor[] cachedList = getCache().getData("banners", series.seriesId, null, BannerDescriptor[].class);
if (cachedList != null) { if (cachedList != null) {
return asList(cachedList); return asList(cachedList);
} }
Document dom = getDocument(getResource(MirrorType.XML, "/api/" + apikey + "/series/" + series.seriesId + "/banners.xml")); Document dom = getDocument(getResource(MirrorType.XML, "/api/" + apikey + "/series/" + series.seriesId + "/banners.xml"));
List<Node> nodes = selectNodes("//Banner", dom); List<Node> nodes = selectNodes("//Banner", dom);
List<BannerDescriptor> banners = new ArrayList<BannerDescriptor>(); List<BannerDescriptor> banners = new ArrayList<BannerDescriptor>();
for (Node node : nodes) { for (Node node : nodes) {
try { try {
Map<BannerProperty, String> item = new EnumMap<BannerProperty, String>(BannerProperty.class); Map<BannerProperty, String> item = new EnumMap<BannerProperty, String>(BannerProperty.class);
// insert banner mirror // insert banner mirror
item.put(BannerProperty.BannerMirror, getResource(MirrorType.BANNER, "/banners/").toString()); item.put(BannerProperty.BannerMirror, getResource(MirrorType.BANNER, "/banners/").toString());
// copy values from xml // copy values from xml
for (BannerProperty key : BannerProperty.values()) { for (BannerProperty key : BannerProperty.values()) {
String value = getTextContent(key.name(), node); String value = getTextContent(key.name(), node);
@ -672,48 +628,42 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
item.put(key, value); item.put(key, value);
} }
} }
banners.add(new BannerDescriptor(item)); banners.add(new BannerDescriptor(item));
} catch (Exception e) { } catch (Exception e) {
// log and ignore // log and ignore
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid banner descriptor", e); Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid banner descriptor", e);
} }
} }
getCache().putData("banners", series.seriesId, null, banners.toArray(new BannerDescriptor[0])); getCache().putData("banners", series.seriesId, null, banners.toArray(new BannerDescriptor[0]));
return banners; return banners;
} }
public static class BannerDescriptor implements Serializable { public static class BannerDescriptor implements Serializable {
public static enum BannerProperty { public static enum BannerProperty {
id, BannerMirror, BannerPath, BannerType, BannerType2, Season, Colors, Language, Rating, RatingCount, SeriesName, ThumbnailPath, VignettePath id, BannerMirror, BannerPath, BannerType, BannerType2, Season, Colors, Language, Rating, RatingCount, SeriesName, ThumbnailPath, VignettePath
} }
protected Map<BannerProperty, String> fields; protected Map<BannerProperty, String> fields;
protected BannerDescriptor() { protected BannerDescriptor() {
// used by serializer // used by serializer
} }
protected BannerDescriptor(Map<BannerProperty, String> fields) { protected BannerDescriptor(Map<BannerProperty, String> fields) {
this.fields = new EnumMap<BannerProperty, String>(fields); this.fields = new EnumMap<BannerProperty, String>(fields);
} }
public String get(Object key) { public String get(Object key) {
return fields.get(BannerProperty.valueOf(key.toString())); return fields.get(BannerProperty.valueOf(key.toString()));
} }
public String get(BannerProperty key) { public String get(BannerProperty key) {
return fields.get(key); return fields.get(key);
} }
public URL getBannerMirrorUrl() throws MalformedURLException { public URL getBannerMirrorUrl() throws MalformedURLException {
try { try {
return new URL(get(BannerProperty.BannerMirror)); return new URL(get(BannerProperty.BannerMirror));
@ -721,8 +671,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public URL getUrl() throws MalformedURLException { public URL getUrl() throws MalformedURLException {
try { try {
return new URL(getBannerMirrorUrl(), get(BannerProperty.BannerPath)); return new URL(getBannerMirrorUrl(), get(BannerProperty.BannerPath));
@ -730,13 +679,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public String getExtension() { public String getExtension() {
return FileUtilities.getExtension(get(BannerProperty.BannerPath)); return FileUtilities.getExtension(get(BannerProperty.BannerPath));
} }
public Integer getId() { public Integer getId() {
try { try {
return new Integer(get(BannerProperty.id)); return new Integer(get(BannerProperty.id));
@ -744,18 +691,15 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public String getBannerType() { public String getBannerType() {
return get(BannerProperty.BannerType); return get(BannerProperty.BannerType);
} }
public String getBannerType2() { public String getBannerType2() {
return get(BannerProperty.BannerType2); return get(BannerProperty.BannerType2);
} }
public Integer getSeason() { public Integer getSeason() {
try { try {
return new Integer(get(BannerProperty.Season)); return new Integer(get(BannerProperty.Season));
@ -763,13 +707,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public String getColors() { public String getColors() {
return get(BannerProperty.Colors); return get(BannerProperty.Colors);
} }
public Locale getLocale() { public Locale getLocale() {
try { try {
return new Locale(get(BannerProperty.Language)); return new Locale(get(BannerProperty.Language));
@ -777,8 +719,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public Double getRating() { public Double getRating() {
try { try {
return new Double(get(BannerProperty.Rating)); return new Double(get(BannerProperty.Rating));
@ -786,8 +727,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public Integer getRatingCount() { public Integer getRatingCount() {
try { try {
return new Integer(get(BannerProperty.RatingCount)); return new Integer(get(BannerProperty.RatingCount));
@ -795,13 +735,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public boolean hasSeriesName() { public boolean hasSeriesName() {
return Boolean.parseBoolean(get(BannerProperty.SeriesName)); return Boolean.parseBoolean(get(BannerProperty.SeriesName));
} }
public URL getThumbnailUrl() throws MalformedURLException { public URL getThumbnailUrl() throws MalformedURLException {
try { try {
return new URL(getBannerMirrorUrl(), get(BannerProperty.ThumbnailPath)); return new URL(getBannerMirrorUrl(), get(BannerProperty.ThumbnailPath));
@ -809,8 +747,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
public URL getVignetteUrl() throws MalformedURLException { public URL getVignetteUrl() throws MalformedURLException {
try { try {
return new URL(getBannerMirrorUrl(), get(BannerProperty.VignettePath)); return new URL(getBannerMirrorUrl(), get(BannerProperty.VignettePath));
@ -818,12 +755,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
return null; return null;
} }
} }
@Override @Override
public String toString() { public String toString() {
return fields.toString(); return fields.toString();
} }
} }
} }

View File

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