+ experimental support for alias titles
This commit is contained in:
parent
b4b4cf2e9b
commit
1058484593
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user