2014-04-19 02:30:29 -04:00
|
|
|
package net.filebot;
|
2010-05-13 10:20:33 -04:00
|
|
|
|
2013-09-11 13:22:00 -04:00
|
|
|
import static java.util.Arrays.*;
|
|
|
|
import static java.util.Collections.*;
|
2016-02-09 06:38:40 -05:00
|
|
|
import static java.util.stream.Collectors.*;
|
2016-03-09 15:36:28 -05:00
|
|
|
import static net.filebot.Logging.*;
|
2014-04-19 02:30:29 -04:00
|
|
|
import static net.filebot.Settings.*;
|
|
|
|
import static net.filebot.media.MediaDetection.*;
|
2015-05-08 04:59:15 -04:00
|
|
|
import static net.filebot.util.FileUtilities.*;
|
2010-05-13 10:20:33 -04:00
|
|
|
|
2012-10-14 07:57:25 -04:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Locale;
|
2016-07-27 01:02:21 -04:00
|
|
|
import java.util.Map;
|
2012-10-14 07:57:25 -04:00
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
import java.util.concurrent.Future;
|
|
|
|
import java.util.logging.Level;
|
|
|
|
|
2014-07-17 03:08:23 -04:00
|
|
|
import net.filebot.media.XattrMetaInfoProvider;
|
2014-10-29 08:55:27 -04:00
|
|
|
import net.filebot.similarity.MetricAvg;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.web.AcoustIDClient;
|
|
|
|
import net.filebot.web.AnidbClient;
|
2016-02-09 04:37:37 -05:00
|
|
|
import net.filebot.web.Datasource;
|
2014-04-19 02:30:29 -04:00
|
|
|
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;
|
2014-08-05 04:23:39 -04:00
|
|
|
import net.filebot.web.OMDbClient;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.web.OpenSubtitlesClient;
|
|
|
|
import net.filebot.web.SearchResult;
|
2015-08-27 13:55:24 -04:00
|
|
|
import net.filebot.web.ShooterSubtitles;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.web.SubtitleProvider;
|
2015-05-11 07:42:59 -04:00
|
|
|
import net.filebot.web.SubtitleSearchResult;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.web.TMDbClient;
|
2016-03-26 15:44:44 -04:00
|
|
|
import net.filebot.web.TMDbTVClient;
|
2015-11-04 03:53:52 -05:00
|
|
|
import net.filebot.web.TVMazeClient;
|
2016-10-30 14:06:15 -04:00
|
|
|
import net.filebot.web.TheTVDBClient;
|
2016-07-27 01:02:21 -04:00
|
|
|
import net.filebot.web.TheTVDBClientV1;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.web.VideoHashSubtitleService;
|
2016-02-09 04:37:37 -05:00
|
|
|
import one.util.streamex.StreamEx;
|
2010-05-13 10:20:33 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reuse the same web service client so login, cache, etc. can be shared.
|
|
|
|
*/
|
|
|
|
public final class WebServices {
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2016-03-26 15:44:44 -04:00
|
|
|
// movie sources
|
|
|
|
public static final OMDbClient OMDb = new OMDbClient();
|
|
|
|
public static final TMDbClient TheMovieDB = new TMDbClient(getApiKey("themoviedb"));
|
|
|
|
|
|
|
|
// episode sources
|
2015-11-04 03:53:52 -05:00
|
|
|
public static final TVMazeClient TVmaze = new TVMazeClient();
|
2014-08-08 12:45:23 -04:00
|
|
|
public static final AnidbClient AniDB = new AnidbClientWithLocalSearch(getApiKey("anidb"), 6);
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2012-10-14 07:57:25 -04:00
|
|
|
// extended TheTVDB module with local search
|
2014-08-07 05:35:19 -04:00
|
|
|
public static final TheTVDBClientWithLocalSearch TheTVDB = new TheTVDBClientWithLocalSearch(getApiKey("thetvdb"));
|
2016-03-26 15:44:44 -04:00
|
|
|
public static final TMDbTVClient TheMovieDB_TV = new TMDbTVClient(TheMovieDB);
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2016-10-30 14:06:15 -04:00
|
|
|
// TheTVDB v2 implementation used for internal purposes and testing
|
|
|
|
public static final TheTVDBClient TheTVDBv2 = new TheTVDBClient(getApiKey("thetvdb"));
|
|
|
|
|
2016-03-26 15:44:44 -04:00
|
|
|
// subtitle sources
|
2015-05-11 05:13:35 -04:00
|
|
|
public static final OpenSubtitlesClient OpenSubtitles = new OpenSubtitlesClientWithLocalSearch(getApiKey("opensubtitles"), getApplicationVersion());
|
2015-08-27 13:55:24 -04:00
|
|
|
public static final ShooterSubtitles Shooter = new ShooterSubtitles();
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2016-03-26 15:44:44 -04:00
|
|
|
// other sources
|
2014-08-07 05:35:19 -04:00
|
|
|
public static final FanartTVClient FanartTV = new FanartTVClient(Settings.getApiKey("fanart.tv"));
|
|
|
|
public static final AcoustIDClient AcoustID = new AcoustIDClient(Settings.getApiKey("acoustid"));
|
2014-07-17 03:08:23 -04:00
|
|
|
public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider();
|
2016-03-26 15:44:44 -04:00
|
|
|
public static final ID3Lookup MediaInfoID3 = new ID3Lookup();
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2010-05-13 10:20:33 -04:00
|
|
|
public static EpisodeListProvider[] getEpisodeListProviders() {
|
2016-04-08 18:59:27 -04:00
|
|
|
return new EpisodeListProvider[] { TheTVDB, AniDB, TheMovieDB_TV, TVmaze };
|
2010-05-13 10:20:33 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2011-09-13 14:16:38 -04:00
|
|
|
public static MovieIdentificationService[] getMovieIdentificationServices() {
|
2014-08-05 04:23:39 -04:00
|
|
|
return new MovieIdentificationService[] { TheMovieDB, OMDb };
|
2011-09-13 14:16:38 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2010-05-13 10:20:33 -04:00
|
|
|
public static SubtitleProvider[] getSubtitleProviders() {
|
2013-06-24 05:25:04 -04:00
|
|
|
return new SubtitleProvider[] { OpenSubtitles };
|
2010-05-13 10:20:33 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2015-08-27 13:55:24 -04:00
|
|
|
public static VideoHashSubtitleService[] getVideoHashSubtitleServices(Locale locale) {
|
|
|
|
if (locale.equals(Locale.CHINESE))
|
|
|
|
return new VideoHashSubtitleService[] { OpenSubtitles, Shooter };
|
|
|
|
else
|
|
|
|
return new VideoHashSubtitleService[] { OpenSubtitles };
|
2010-05-13 10:20:33 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-01-12 10:21:33 -05:00
|
|
|
public static MusicIdentificationService[] getMusicIdentificationServices() {
|
2016-03-26 15:44:44 -04:00
|
|
|
return new MusicIdentificationService[] { AcoustID, MediaInfoID3 };
|
2013-01-12 10:21:33 -05:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2011-09-13 14:16:38 -04:00
|
|
|
public static EpisodeListProvider getEpisodeListProvider(String name) {
|
2016-02-09 04:37:37 -05:00
|
|
|
return getService(name, getEpisodeListProviders());
|
2011-09-13 14:16:38 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2011-09-13 14:16:38 -04:00
|
|
|
public static MovieIdentificationService getMovieIdentificationService(String name) {
|
2016-02-09 04:37:37 -05:00
|
|
|
return getService(name, getMovieIdentificationServices());
|
2011-09-13 14:16:38 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-01-12 10:21:33 -05:00
|
|
|
public static MusicIdentificationService getMusicIdentificationService(String name) {
|
2016-02-09 04:37:37 -05:00
|
|
|
return getService(name, getMusicIdentificationServices());
|
|
|
|
}
|
|
|
|
|
2016-10-23 14:19:41 -04:00
|
|
|
public static <T extends Datasource> T getService(String name, T[] services) {
|
2016-03-28 16:45:43 -04:00
|
|
|
return StreamEx.of(services).findFirst(it -> it.getIdentifier().equalsIgnoreCase(name) || it.getName().equalsIgnoreCase(name)).orElse(null);
|
2013-01-12 10:21:33 -05:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2014-11-06 08:52:16 -05:00
|
|
|
public static final ExecutorService requestThreadPool = Executors.newCachedThreadPool();
|
|
|
|
|
2016-07-27 01:02:21 -04:00
|
|
|
public static class TheTVDBClientWithLocalSearch extends TheTVDBClientV1 {
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2012-10-14 07:57:25 -04:00
|
|
|
public TheTVDBClientWithLocalSearch(String apikey) {
|
|
|
|
super(apikey);
|
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2016-04-08 18:59:27 -04:00
|
|
|
// local TheTVDB search index
|
|
|
|
private final Resource<LocalSearch<SearchResult>> localIndex = Resource.lazy(() -> {
|
|
|
|
return new LocalSearch<SearchResult>(releaseInfo.getTheTVDBIndex(), SearchResult::getEffectiveNames);
|
|
|
|
}).memoize();
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2016-02-09 06:38:40 -05:00
|
|
|
private SearchResult merge(SearchResult prime, List<SearchResult> group) {
|
|
|
|
int id = prime.getId();
|
|
|
|
String name = prime.getName();
|
|
|
|
String[] aliasNames = StreamEx.of(group).flatMap(it -> stream(it.getAliasNames())).remove(name::equals).distinct().toArray(String[]::new);
|
2016-03-26 13:40:54 -04:00
|
|
|
return new SearchResult(id, name, aliasNames);
|
2016-02-09 06:38:40 -05:00
|
|
|
}
|
|
|
|
|
2012-10-14 07:57:25 -04:00
|
|
|
@Override
|
|
|
|
public List<SearchResult> fetchSearchResult(final String query, final Locale locale) throws Exception {
|
2016-02-09 04:37:37 -05:00
|
|
|
// run local search and API search in parallel
|
|
|
|
Future<List<SearchResult>> apiSearch = requestThreadPool.submit(() -> TheTVDBClientWithLocalSearch.super.fetchSearchResult(query, locale));
|
2016-04-08 18:59:27 -04:00
|
|
|
Future<List<SearchResult>> localSearch = requestThreadPool.submit(() -> localIndex.get().search(query));
|
2016-02-09 04:37:37 -05:00
|
|
|
|
|
|
|
// combine alias names into a single search results, and keep API search name as primary name
|
2016-07-27 01:02:21 -04:00
|
|
|
Map<Integer, SearchResult> results = StreamEx.of(apiSearch.get()).append(localSearch.get()).groupingBy(SearchResult::getId, collectingAndThen(toList(), group -> merge(group.get(0), group)));
|
2016-01-23 17:25:05 -05:00
|
|
|
|
2016-07-27 01:02:21 -04:00
|
|
|
return sortBySimilarity(results.values(), singleton(query), getSeriesMatchMetric());
|
2014-11-06 08:52:16 -05:00
|
|
|
}
|
2012-10-14 07:57:25 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-03-17 10:19:11 -04:00
|
|
|
public static class AnidbClientWithLocalSearch extends AnidbClient {
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-03-17 10:19:11 -04:00
|
|
|
public AnidbClientWithLocalSearch(String client, int clientver) {
|
|
|
|
super(client, clientver);
|
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-03-17 10:19:11 -04:00
|
|
|
@Override
|
2016-04-08 18:59:27 -04:00
|
|
|
public SearchResult[] getAnimeTitles() throws Exception {
|
|
|
|
return releaseInfo.getAnidbIndex();
|
2013-03-17 10:19:11 -04:00
|
|
|
}
|
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2014-10-29 08:55:27 -04:00
|
|
|
public static class OpenSubtitlesClientWithLocalSearch extends OpenSubtitlesClient {
|
|
|
|
|
2015-05-11 05:13:35 -04:00
|
|
|
public OpenSubtitlesClientWithLocalSearch(String name, String version) {
|
2014-10-29 08:55:27 -04:00
|
|
|
super(name, version);
|
|
|
|
}
|
|
|
|
|
2016-04-08 18:59:27 -04:00
|
|
|
// local OpenSubtitles search index
|
|
|
|
private final Resource<LocalSearch<SubtitleSearchResult>> localIndex = Resource.lazy(() -> {
|
|
|
|
return new LocalSearch<SubtitleSearchResult>(releaseInfo.getOpenSubtitlesIndex(), SearchResult::getEffectiveNames);
|
|
|
|
}).memoize();
|
2014-10-29 08:55:27 -04:00
|
|
|
|
|
|
|
@Override
|
2016-04-08 18:59:27 -04:00
|
|
|
public List<SubtitleSearchResult> search(final String query) throws Exception {
|
|
|
|
return sortBySimilarity(localIndex.get().search(query), singleton(query), new MetricAvg(getSeriesMatchMetric(), getMovieMatchMetric()));
|
2014-10-29 08:55:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-13 10:20:33 -04:00
|
|
|
/**
|
|
|
|
* Dummy constructor to prevent instantiation.
|
|
|
|
*/
|
|
|
|
private WebServices() {
|
|
|
|
throw new UnsupportedOperationException();
|
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2015-05-08 04:59:15 -04:00
|
|
|
public static final String LOGIN_SEPARATOR = ":";
|
|
|
|
public static final String LOGIN_OPENSUBTITLES = "osdb.user";
|
|
|
|
|
2012-07-16 05:58:18 -04:00
|
|
|
/**
|
|
|
|
* Initialize client settings from system properties
|
|
|
|
*/
|
|
|
|
static {
|
2015-05-08 04:59:15 -04:00
|
|
|
String[] osdbLogin = getLogin(LOGIN_OPENSUBTITLES);
|
2012-07-16 05:58:18 -04:00
|
|
|
OpenSubtitles.setUser(osdbLogin[0], osdbLogin[1]);
|
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-02-26 01:21:02 -05:00
|
|
|
public static String[] getLogin(String key) {
|
2014-08-09 01:57:54 -04:00
|
|
|
try {
|
2015-05-08 04:59:15 -04:00
|
|
|
String[] values = Settings.forPackage(WebServices.class).get(key, LOGIN_SEPARATOR).split(LOGIN_SEPARATOR, 2); // empty username/password by default
|
2014-08-09 01:57:54 -04:00
|
|
|
if (values != null && values.length == 2 && values[0] != null && values[1] != null) {
|
|
|
|
return values;
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
2016-03-09 15:36:28 -05:00
|
|
|
debug.log(Level.SEVERE, e.getMessage(), e);
|
2014-08-09 01:57:54 -04:00
|
|
|
}
|
|
|
|
return new String[] { "", "" };
|
2013-02-26 01:21:02 -05:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2013-02-26 01:21:02 -05:00
|
|
|
public static void setLogin(String id, String user, String password) {
|
2015-05-09 02:38:47 -04:00
|
|
|
// 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();
|
|
|
|
}
|
2013-02-26 01:21:02 -05:00
|
|
|
} else {
|
2015-05-09 02:38:47 -04:00
|
|
|
// 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);
|
2016-03-09 05:32:46 -05:00
|
|
|
Settings.forPackage(WebServices.class).put(id, user + LOGIN_SEPARATOR + password_md5);
|
2015-05-09 02:38:47 -04:00
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException();
|
|
|
|
}
|
2013-02-26 01:21:02 -05:00
|
|
|
}
|
2012-07-16 05:58:18 -04:00
|
|
|
}
|
2013-10-13 10:50:45 -04:00
|
|
|
|
2010-05-13 10:20:33 -04:00
|
|
|
}
|