diff --git a/source/net/filebot/WebServices.java b/source/net/filebot/WebServices.java index c01ee7b1..498822e9 100644 --- a/source/net/filebot/WebServices.java +++ b/source/net/filebot/WebServices.java @@ -6,6 +6,7 @@ import static net.filebot.Settings.*; import static net.filebot.media.MediaDetection.*; import java.io.IOException; +import java.net.URI; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -19,6 +20,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.filebot.media.XattrMetaInfoProvider; +import net.filebot.similarity.MetricAvg; import net.filebot.web.AcoustIDClient; import net.filebot.web.AnidbClient; import net.filebot.web.AnidbSearchResult; @@ -33,11 +35,13 @@ import net.filebot.web.MusicIdentificationService; import net.filebot.web.OMDbClient; import net.filebot.web.OpenSubtitlesClient; import net.filebot.web.SearchResult; +import net.filebot.web.SubtitleDescriptor; import net.filebot.web.SubtitleProvider; import net.filebot.web.TMDbClient; import net.filebot.web.TVRageClient; import net.filebot.web.TVRageSearchResult; import net.filebot.web.TheTVDBClient; +import net.filebot.web.TheTVDBClient.SeriesInfo; import net.filebot.web.TheTVDBSearchResult; import net.filebot.web.VideoHashSubtitleService; @@ -58,7 +62,7 @@ public final class WebServices { public static final TMDbClient TheMovieDB = new TMDbClient(getApiKey("themoviedb")); // subtitle dbs - public static final OpenSubtitlesClient OpenSubtitles = new OpenSubtitlesClient(String.format("%s v%s", getApiKey("opensubtitles"), getApplicationVersion())); + public static final OpenSubtitlesClient OpenSubtitles = new OpenSubtitlesClientWithLocalSearch(getApiKey("opensubtitles"), getApplicationVersion(), TheTVDB, TheMovieDB); // misc public static final FanartTVClient FanartTV = new FanartTVClient(Settings.getApiKey("fanart.tv")); @@ -218,6 +222,70 @@ public final class WebServices { } } + public static class OpenSubtitlesClientWithLocalSearch extends OpenSubtitlesClient { + + private final EpisodeListProvider seriesIndex; + private final MovieIdentificationService movieIndex; + + public OpenSubtitlesClientWithLocalSearch(String name, String version, EpisodeListProvider seriesIndex, MovieIdentificationService movieIndex) { + super(name, version); + this.seriesIndex = seriesIndex; + this.movieIndex = movieIndex; + } + + @Override + public synchronized List search(final String query) throws Exception { + Callable> seriesSearch = () -> seriesIndex.search(query, Locale.ENGLISH); + Callable> movieSearch = () -> movieIndex.searchMovie(query, Locale.ENGLISH); + + ExecutorService executor = Executors.newFixedThreadPool(2); + try { + Set results = new LinkedHashSet(); + for (Future> resultSet : executor.invokeAll(asList(seriesSearch, movieSearch))) { + try { + results.addAll(resultSet.get()); + } catch (ExecutionException e) { + if (e.getCause() instanceof Exception) { + throw (Exception) e.getCause(); // unwrap cause + } + } + } + return sortBySimilarity(results, singleton(query), new MetricAvg(getSeriesMatchMetric(), getMovieMatchMetric()), false); + } finally { + executor.shutdownNow(); + } + } + + @Override + public synchronized List getSubtitleList(SearchResult searchResult, String languageName) throws Exception { + return super.getSubtitleList(getIMDbID(searchResult), languageName); + } + + @Override + public URI getSubtitleListLink(SearchResult searchResult, String languageName) { + try { + return super.getSubtitleListLink(getIMDbID(searchResult), languageName); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Movie getIMDbID(SearchResult result) throws Exception { + if (result instanceof TheTVDBSearchResult) { + TheTVDBSearchResult s = (TheTVDBSearchResult) result; + SeriesInfo seriesInfo = ((TheTVDBClient) seriesIndex).getSeriesInfo(s, Locale.ENGLISH); + if (seriesInfo.getImdbId() != null) { + return new Movie(seriesInfo.getName(), seriesInfo.getFirstAired().getYear(), seriesInfo.getImdbId(), -1); + } + } + if (result instanceof Movie) { + Movie m = (Movie) result; + return m.getImdbId() > 0 ? m : movieIndex.getMovieDescriptor(m, Locale.ENGLISH); + } + throw new IllegalArgumentException(String.format("'%s' not found", result)); + } + } + /** * Dummy constructor to prevent instantiation. */ diff --git a/source/net/filebot/subtitle/SubtitleUtilities.java b/source/net/filebot/subtitle/SubtitleUtilities.java index f1f61643..4e3e1d99 100644 --- a/source/net/filebot/subtitle/SubtitleUtilities.java +++ b/source/net/filebot/subtitle/SubtitleUtilities.java @@ -181,7 +181,7 @@ public final class SubtitleUtilities { // search for and automatically select movie / show entry Set resultSet = new HashSet(); for (String query : querySet) { - resultSet.addAll(findProbableSearchResults(query, service.search(query))); + resultSet.addAll(findProbableSearchResults(query, service.search(query), querySet.size() == 1 ? 4 : 2)); } // fetch subtitles for all search results @@ -192,7 +192,7 @@ public final class SubtitleUtilities { return subtitles; } - protected static Collection findProbableSearchResults(String query, Iterable searchResults) { + protected static Collection findProbableSearchResults(String query, Iterable searchResults, int limit) { // auto-select most probable search result Set probableMatches = new LinkedHashSet(); @@ -201,8 +201,10 @@ public final class SubtitleUtilities { // find probable matches using name similarity > threshold for (SearchResult result : searchResults) { - if (metric.getSimilarity(query, removeTrailingBrackets(result.getName())) > 0.8f || result.getName().toLowerCase().startsWith(query.toLowerCase())) { - probableMatches.add(result); + if (probableMatches.size() <= limit) { + if (metric.getSimilarity(query, removeTrailingBrackets(result.getName())) > 0.8f || result.getName().toLowerCase().startsWith(query.toLowerCase())) { + probableMatches.add(result); + } } } diff --git a/source/net/filebot/ui/subtitle/SubtitlePanel.java b/source/net/filebot/ui/subtitle/SubtitlePanel.java index 44df9528..b2e3158e 100644 --- a/source/net/filebot/ui/subtitle/SubtitlePanel.java +++ b/source/net/filebot/ui/subtitle/SubtitlePanel.java @@ -304,7 +304,7 @@ public class SubtitlePanel extends AbstractSearchPanel 0 && osdbPass.getPassword().length > 0) { - OpenSubtitlesClient osdb = new OpenSubtitlesClient(String.format("%s v%s", getApplicationName(), getApplicationVersion())); + OpenSubtitlesClient osdb = new OpenSubtitlesClient(getApplicationName(), getApplicationVersion()); osdb.setUser(osdbUser.getText(), new String(osdbPass.getPassword())); osdb.login(); } diff --git a/source/net/filebot/ui/subtitle/SubtitleUploadDialog.java b/source/net/filebot/ui/subtitle/SubtitleUploadDialog.java index b108cb85..2c54e13e 100644 --- a/source/net/filebot/ui/subtitle/SubtitleUploadDialog.java +++ b/source/net/filebot/ui/subtitle/SubtitleUploadDialog.java @@ -164,7 +164,7 @@ public class SubtitleUploadDialog extends JDialog { File video = mapping.getVideo() != null ? mapping.getVideo() : mapping.getSubtitle(); String input = showInputDialog("Enter movie / series name:", stripReleaseInfo(FileUtilities.getName(video)), String.format("%s/%s", video.getParentFile().getName(), video.getName()), SubtitleUploadDialog.this); if (input != null && input.length() > 0) { - List options = database.searchMovie(input, Locale.ENGLISH); + List options = database.searchIMDB(input); if (options.size() > 0) { SelectDialog dialog = new SelectDialog(SubtitleUploadDialog.this, options); dialog.setLocation(getOffsetLocation(dialog.getOwner())); diff --git a/source/net/filebot/web/OpenSubtitlesClient.java b/source/net/filebot/web/OpenSubtitlesClient.java index ef960369..ff922975 100644 --- a/source/net/filebot/web/OpenSubtitlesClient.java +++ b/source/net/filebot/web/OpenSubtitlesClient.java @@ -54,8 +54,8 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS private String username = ""; private String password = ""; - public OpenSubtitlesClient(String useragent) { - this.xmlrpc = new OpenSubtitlesXmlRpcWithRetryAndFloodLimit(useragent, 2, 3000); + public OpenSubtitlesClient(String name, String version) { + this.xmlrpc = new OpenSubtitlesXmlRpcWithRetryAndFloodLimit(String.format("%s v%s", name, version), 2, 3000); } public synchronized void setUser(String username, String password) { @@ -90,12 +90,12 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS } @Override - public List search(String query) throws Exception { - throw new UnsupportedOperationException("SearchMoviesOnIMDB is not supported due to abuse"); + public synchronized List search(String query) throws Exception { + throw new UnsupportedOperationException("SearchMoviesOnIMDB is not allowed due to abuse"); } @Override - public List getSubtitleList(SearchResult searchResult, String languageName) throws Exception { + public synchronized List getSubtitleList(SearchResult searchResult, String languageName) throws Exception { List subtitles = getCache().getSubtitleDescriptorList(searchResult, languageName); if (subtitles != null) { return subtitles; @@ -142,7 +142,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS // max numbers of queries to submit in a single XML-RPC request, but currently only batchSize == 1 is supported private final int batchSize = 1; - public Map> getSubtitleListByHash(File[] files, String languageName) throws Exception { + public synchronized Map> getSubtitleListByHash(File[] files, String languageName) throws Exception { // singleton array with or empty array String[] languageFilter = languageName != null ? new String[] { getSubLanguageID(languageName) } : new String[0]; @@ -205,7 +205,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS return resultMap; } - public Map> getSubtitleListByTag(File[] files, String languageName) throws Exception { + public synchronized Map> getSubtitleListByTag(File[] files, String languageName) throws Exception { // singleton array with or empty array String[] languageFilter = languageName != null ? new String[] { getSubLanguageID(languageName) } : new String[0]; @@ -362,12 +362,50 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS @Override public List searchMovie(String query, Locale locale) throws Exception { - throw new UnsupportedOperationException("SearchMoviesOnIMDB is not supported due to abuse"); + throw new UnsupportedOperationException(); + } + + public synchronized List searchIMDB(String query) throws Exception { + // search for movies and series + List result = getCache().getSearchResult("search", query, null); + if (result != null) { + return (List) result; + } + + // require login + login(); + + try { + // search for movies / series + List resultSet = xmlrpc.searchMoviesOnIMDB(query); + result = asList(resultSet.toArray(new SearchResult[0])); + } catch (ClassCastException e) { + // unexpected xmlrpc responses (e.g. error messages instead of results) will trigger this + throw new XmlRpcException("Illegal XMLRPC response on searchMoviesOnIMDB"); + } + + getCache().putSearchResult("search", query, null, result); + return (List) result; } @Override - public Movie getMovieDescriptor(Movie id, Locale locale) throws Exception { - throw new UnsupportedOperationException("GetIMDBMovieDetails is not supported due to abuse"); + public synchronized Movie getMovieDescriptor(Movie id, Locale locale) throws Exception { + if (id.getImdbId() <= 0) { + throw new IllegalArgumentException("id must not be " + id.getImdbId()); + } + + Movie result = getCache().getData("getMovieDescriptor", id.getImdbId(), locale, Movie.class); + if (result != null) { + return result; + } + + // require login + login(); + + Movie movie = xmlrpc.getIMDBMovieDetails(id.getImdbId()); + + getCache().putData("getMovieDescriptor", id.getImdbId(), locale, movie); + return movie; } public Movie getMovieDescriptor(File movieFile, Locale locale) throws Exception { @@ -375,7 +413,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS } @Override - public Map getMovieDescriptors(Collection movieFiles, Locale locale) throws Exception { + public synchronized Map getMovieDescriptors(Collection movieFiles, Locale locale) throws Exception { // create result array Map result = new HashMap(); diff --git a/source/net/filebot/web/OpenSubtitlesXmlRpc.java b/source/net/filebot/web/OpenSubtitlesXmlRpc.java index d571865b..91776bc9 100644 --- a/source/net/filebot/web/OpenSubtitlesXmlRpc.java +++ b/source/net/filebot/web/OpenSubtitlesXmlRpc.java @@ -112,7 +112,6 @@ public class OpenSubtitlesXmlRpc { } @SuppressWarnings("unchecked") - @Deprecated public List searchMoviesOnIMDB(String query) throws XmlRpcFault { Map response = invoke("SearchMoviesOnIMDB", token, query); @@ -146,7 +145,6 @@ public class OpenSubtitlesXmlRpc { } @SuppressWarnings("unchecked") - @Deprecated public Movie getIMDBMovieDetails(int imdbid) throws XmlRpcFault { Map response = invoke("GetIMDBMovieDetails", token, imdbid);