package net.filebot; import static java.util.Arrays.*; import static java.util.Collections.*; import static net.filebot.Settings.*; import static net.filebot.media.MediaDetection.*; import static net.filebot.util.FileUtilities.*; import static net.filebot.util.StringUtilities.*; import java.io.IOException; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; 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; import net.filebot.web.EpisodeListProvider; import net.filebot.web.FanartTVClient; import net.filebot.web.ID3Lookup; import net.filebot.web.LocalSearch; import net.filebot.web.MovieIdentificationService; import net.filebot.web.MusicIdentificationService; import net.filebot.web.OMDbClient; import net.filebot.web.OpenSubtitlesClient; import net.filebot.web.SearchResult; import net.filebot.web.ShooterSubtitles; import net.filebot.web.SubtitleProvider; import net.filebot.web.SubtitleSearchResult; import net.filebot.web.TMDbClient; import net.filebot.web.TVMazeClient; import net.filebot.web.TheTVDBClient; import net.filebot.web.TheTVDBSearchResult; import net.filebot.web.VideoHashSubtitleService; /** * Reuse the same web service client so login, cache, etc. can be shared. */ public final class WebServices { // episode dbs public static final TVMazeClient TVmaze = new TVMazeClient(); public static final AnidbClient AniDB = new AnidbClientWithLocalSearch(getApiKey("anidb"), 6); // extended TheTVDB module with local search public static final TheTVDBClientWithLocalSearch TheTVDB = new TheTVDBClientWithLocalSearch(getApiKey("thetvdb")); // movie dbs public static final OMDbClient OMDb = new OMDbClient(); public static final TMDbClient TheMovieDB = new TMDbClient(getApiKey("themoviedb")); // subtitle dbs public static final OpenSubtitlesClient OpenSubtitles = new OpenSubtitlesClientWithLocalSearch(getApiKey("opensubtitles"), getApplicationVersion()); public static final ShooterSubtitles Shooter = new ShooterSubtitles(); // misc public static final FanartTVClient FanartTV = new FanartTVClient(Settings.getApiKey("fanart.tv")); public static final AcoustIDClient AcoustID = new AcoustIDClient(Settings.getApiKey("acoustid")); public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider(); public static EpisodeListProvider[] getEpisodeListProviders() { return new EpisodeListProvider[] { TheTVDB, AniDB, TVmaze }; } public static MovieIdentificationService[] getMovieIdentificationServices() { return new MovieIdentificationService[] { TheMovieDB, OMDb }; } public static SubtitleProvider[] getSubtitleProviders() { return new SubtitleProvider[] { OpenSubtitles }; } public static VideoHashSubtitleService[] getVideoHashSubtitleServices(Locale locale) { if (locale.equals(Locale.CHINESE)) return new VideoHashSubtitleService[] { OpenSubtitles, Shooter }; else return new VideoHashSubtitleService[] { OpenSubtitles }; } public static MusicIdentificationService[] getMusicIdentificationServices() { return new MusicIdentificationService[] { AcoustID, new ID3Lookup() }; } public static EpisodeListProvider getEpisodeListProvider(String name) { for (EpisodeListProvider it : WebServices.getEpisodeListProviders()) { if (it.getName().equalsIgnoreCase(name)) return it; } return null; // default } public static MovieIdentificationService getMovieIdentificationService(String name) { for (MovieIdentificationService it : getMovieIdentificationServices()) { if (it.getName().equalsIgnoreCase(name)) return it; } return null; // default } public static MusicIdentificationService getMusicIdentificationService(String name) { for (MusicIdentificationService it : getMusicIdentificationServices()) { if (it.getName().equalsIgnoreCase(name)) return it; } return null; // default } public static final ExecutorService requestThreadPool = Executors.newCachedThreadPool(); public static class TheTVDBClientWithLocalSearch extends TheTVDBClient { public TheTVDBClientWithLocalSearch(String apikey) { super(apikey); } // index of local thetvdb data dump private static LocalSearch localIndex; public synchronized LocalSearch getLocalIndex() throws IOException { if (localIndex == null) { // fetch data dump TheTVDBSearchResult[] data = releaseInfo.getTheTVDBIndex(); // index data dump localIndex = new LocalSearch(asList(data)) { @Override protected Set getFields(SearchResult object) { return set(object.getEffectiveNames()); } }; // make local search more restrictive localIndex.setResultMinimumSimilarity(0.7f); } return localIndex; } @Override public List fetchSearchResult(final String query, final Locale locale) throws Exception { Callable> localSearch = () -> getLocalIndex().search(query); Callable> apiSearch = () -> TheTVDBClientWithLocalSearch.super.fetchSearchResult(query, locale); Set results = new LinkedHashSet(); for (Future> resultSet : requestThreadPool.invokeAll(asList(localSearch, apiSearch))) { try { results.addAll(resultSet.get()); } catch (ExecutionException e) { if (e.getCause() instanceof Exception) { throw (Exception) e.getCause(); // unwrap cause } } } return sortBySimilarity(results, singleton(query), getSeriesMatchMetric()); } } public static class AnidbClientWithLocalSearch extends AnidbClient { public AnidbClientWithLocalSearch(String client, int clientver) { super(client, clientver); } @Override public List getAnimeTitles() throws Exception { return asList(releaseInfo.getAnidbIndex()); } } public static class OpenSubtitlesClientWithLocalSearch extends OpenSubtitlesClient { public OpenSubtitlesClientWithLocalSearch(String name, String version) { super(name, version); } // index of local OpenSubtitles data dump private static LocalSearch localIndex; public synchronized LocalSearch getLocalIndex() throws IOException { if (localIndex == null) { // fetch data dump SubtitleSearchResult[] data = releaseInfo.getOpenSubtitlesIndex(); // index data dump localIndex = new LocalSearch(asList(data)) { @Override protected Set getFields(SubtitleSearchResult object) { return set(object.getEffectiveNames()); } }; } return localIndex; } @Override public synchronized List search(final String query) throws Exception { List results = getLocalIndex().search(query); return sortBySimilarity(results, singleton(query), new MetricAvg(getSeriesMatchMetric(), getMovieMatchMetric())); } } /** * Dummy constructor to prevent instantiation. */ private WebServices() { throw new UnsupportedOperationException(); } public static final String LOGIN_SEPARATOR = ":"; public static final String LOGIN_OPENSUBTITLES = "osdb.user"; /** * Initialize client settings from system properties */ static { String[] osdbLogin = getLogin(LOGIN_OPENSUBTITLES); OpenSubtitles.setUser(osdbLogin[0], osdbLogin[1]); } public static String[] getLogin(String key) { try { String[] values = Settings.forPackage(WebServices.class).get(key, LOGIN_SEPARATOR).split(LOGIN_SEPARATOR, 2); // empty username/password by default if (values != null && values.length == 2 && values[0] != null && values[1] != null) { return values; } } catch (Exception e) { Logger.getLogger(WebServices.class.getName()).log(Level.WARNING, e.getMessage(), e); } return new String[] { "", "" }; } public static void setLogin(String id, String user, String password) { // delete login if ((user == null || user.isEmpty()) && (password == null || password.isEmpty())) { if (LOGIN_OPENSUBTITLES.equals(id)) { OpenSubtitles.setUser("", ""); Settings.forPackage(WebServices.class).remove(id); } else { throw new IllegalArgumentException(); } } else { // enter login if (user == null || password == null || user.contains(LOGIN_SEPARATOR) || (user.isEmpty() && !password.isEmpty()) || (!user.isEmpty() && password.isEmpty())) { throw new IllegalArgumentException(String.format("Illegal login: %s:%s", user, password)); } if (LOGIN_OPENSUBTITLES.equals(id)) { String password_md5 = md5(password); OpenSubtitles.setUser(user, password_md5); Settings.forPackage(WebServices.class).put(id, join(LOGIN_SEPARATOR, user, password_md5)); } else { throw new IllegalArgumentException(); } } } }