mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-24 16:58:51 -05:00
Support TheMovieDB in Episode mode
This commit is contained in:
parent
1c74c2ef39
commit
ac069f5a1c
@ -66,7 +66,7 @@ public final class WebServices {
|
|||||||
public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider();
|
public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider();
|
||||||
|
|
||||||
public static EpisodeListProvider[] getEpisodeListProviders() {
|
public static EpisodeListProvider[] getEpisodeListProviders() {
|
||||||
return new EpisodeListProvider[] { TheTVDB, AniDB, TVmaze };
|
return new EpisodeListProvider[] { TheTVDB, TheMovieDB, AniDB, TVmaze };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MovieIdentificationService[] getMovieIdentificationServices() {
|
public static MovieIdentificationService[] getMovieIdentificationServices() {
|
||||||
@ -89,6 +89,12 @@ public final class WebServices {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static EpisodeListProvider getEpisodeListProvider(String name) {
|
public static EpisodeListProvider getEpisodeListProvider(String name) {
|
||||||
|
// special handling for TheMovieDB which is the only datasource that supports both series and movie mode
|
||||||
|
if (name.equalsIgnoreCase(TheMovieDB.getName()))
|
||||||
|
return null;
|
||||||
|
if (name.equalsIgnoreCase(TheMovieDB.getName() + "::TV"))
|
||||||
|
return TheMovieDB;
|
||||||
|
|
||||||
return getService(name, getEpisodeListProviders());
|
return getService(name, getEpisodeListProviders());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ public class AnidbClient extends AbstractEpisodeListProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure episodes are in ordered correctly
|
// make sure episodes are in ordered correctly
|
||||||
sort(episodes, episodeComparator());
|
episodes.sort(episodeComparator());
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
if (episodes.isEmpty()) {
|
if (episodes.isEmpty()) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.filebot.web;
|
package net.filebot.web;
|
||||||
|
|
||||||
import static java.util.Arrays.*;
|
import static java.util.Arrays.*;
|
||||||
|
import static java.util.Collections.*;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -97,7 +98,7 @@ public class SeriesInfo implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getAliasNames() {
|
public List<String> getAliasNames() {
|
||||||
return aliasNames == null ? asList() : asList(aliasNames.clone());
|
return aliasNames == null ? emptyList() : asList(aliasNames.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAliasNames(String... aliasNames) {
|
public void setAliasNames(String... aliasNames) {
|
||||||
@ -105,7 +106,7 @@ public class SeriesInfo implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getActors() {
|
public List<String> getActors() {
|
||||||
return actors == null ? asList() : asList(actors.clone());
|
return actors == null ? emptyList() : asList(actors.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setActors(List<String> actors) {
|
public void setActors(List<String> actors) {
|
||||||
@ -129,7 +130,7 @@ public class SeriesInfo implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getGenres() {
|
public List<String> getGenres() {
|
||||||
return genres == null ? asList() : asList(genres.clone());
|
return genres == null ? emptyList() : asList(genres.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGenres(List<String> genres) {
|
public void setGenres(List<String> genres) {
|
||||||
|
@ -7,6 +7,7 @@ import static net.filebot.CachedResource.*;
|
|||||||
import static net.filebot.Logging.*;
|
import static net.filebot.Logging.*;
|
||||||
import static net.filebot.util.JsonUtilities.*;
|
import static net.filebot.util.JsonUtilities.*;
|
||||||
import static net.filebot.util.StringUtilities.*;
|
import static net.filebot.util.StringUtilities.*;
|
||||||
|
import static net.filebot.web.EpisodeUtilities.*;
|
||||||
import static net.filebot.web.WebRequest.*;
|
import static net.filebot.web.WebRequest.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -40,7 +41,7 @@ import net.filebot.ResourceManager;
|
|||||||
import net.filebot.web.TMDbClient.MovieInfo.MovieProperty;
|
import net.filebot.web.TMDbClient.MovieInfo.MovieProperty;
|
||||||
import net.filebot.web.TMDbClient.Person.PersonProperty;
|
import net.filebot.web.TMDbClient.Person.PersonProperty;
|
||||||
|
|
||||||
public class TMDbClient implements MovieIdentificationService {
|
public class TMDbClient extends AbstractEpisodeListProvider implements MovieIdentificationService {
|
||||||
|
|
||||||
private static final String host = "api.themoviedb.org";
|
private static final String host = "api.themoviedb.org";
|
||||||
private static final String version = "3";
|
private static final String version = "3";
|
||||||
@ -64,14 +65,18 @@ public class TMDbClient implements MovieIdentificationService {
|
|||||||
return ResourceManager.getIcon("search.themoviedb");
|
return ResourceManager.getIcon("search.themoviedb");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Matcher getNameYearMatcher(String query) {
|
||||||
|
return Pattern.compile("(.+)\\b[(]?((?:19|20)\\d{2})[)]?$").matcher(query.trim());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Movie> searchMovie(String query, Locale locale) throws Exception {
|
public List<Movie> searchMovie(String query, Locale locale) throws Exception {
|
||||||
// query by name with year filter if possible
|
// query by name with year filter if possible
|
||||||
Matcher nameYear = Pattern.compile("(.+)\\b\\(?(19\\d{2}|20\\d{2})\\)?$").matcher(query.trim());
|
Matcher nameYear = getNameYearMatcher(query);
|
||||||
if (nameYear.matches()) {
|
if (nameYear.matches()) {
|
||||||
return searchMovie(nameYear.group(1).trim(), Integer.parseInt(nameYear.group(2)), locale, false);
|
return searchMovie(nameYear.group(1).trim(), Integer.parseInt(nameYear.group(2)), locale, false);
|
||||||
} else {
|
} else {
|
||||||
return searchMovie(query, -1, locale, false);
|
return searchMovie(query.trim(), -1, locale, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +86,12 @@ public class TMDbClient implements MovieIdentificationService {
|
|||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> param = new LinkedHashMap<String, Object>(2);
|
Map<String, Object> query = new LinkedHashMap<String, Object>(2);
|
||||||
param.put("query", movieName);
|
query.put("query", movieName);
|
||||||
if (movieYear > 0) {
|
if (movieYear > 0) {
|
||||||
param.put("year", movieYear);
|
query.put("year", movieYear);
|
||||||
}
|
}
|
||||||
|
Object response = request("search/movie", query, locale, SEARCH_LIMIT);
|
||||||
Object response = request("search/movie", param, locale, SEARCH_LIMIT);
|
|
||||||
|
|
||||||
// e.g. {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"}
|
// e.g. {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"}
|
||||||
return streamJsonObjects(response, "results").map(it -> {
|
return streamJsonObjects(response, "results").map(it -> {
|
||||||
@ -106,6 +110,13 @@ public class TMDbClient implements MovieIdentificationService {
|
|||||||
title = originalTitle;
|
title = originalTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> alternativeTitles = getAlternativeTitles("movie/" + id, "titles", title, originalTitle, extendedInfo);
|
||||||
|
|
||||||
|
return new Movie(title, alternativeTitles.toArray(new String[0]), year, -1, id, locale);
|
||||||
|
}).filter(Objects::nonNull).collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getAlternativeTitles(String path, String key, String title, String originalTitle, boolean extendedInfo) {
|
||||||
Set<String> alternativeTitles = new LinkedHashSet<String>();
|
Set<String> alternativeTitles = new LinkedHashSet<String>();
|
||||||
if (originalTitle != null) {
|
if (originalTitle != null) {
|
||||||
alternativeTitles.add(originalTitle);
|
alternativeTitles.add(originalTitle);
|
||||||
@ -113,24 +124,23 @@ public class TMDbClient implements MovieIdentificationService {
|
|||||||
|
|
||||||
if (extendedInfo) {
|
if (extendedInfo) {
|
||||||
try {
|
try {
|
||||||
Object titles = request("movie/" + id + "/alternative_titles", emptyMap(), Locale.ENGLISH, REQUEST_LIMIT);
|
Object response = request(path + "/alternative_titles", emptyMap(), Locale.ENGLISH, REQUEST_LIMIT);
|
||||||
streamJsonObjects(titles, "titles").map(n -> {
|
streamJsonObjects(response, key).map(n -> {
|
||||||
return getString(n, "title");
|
return getString(n, "title");
|
||||||
}).filter(t -> t != null && t.length() >= 3).forEach(alternativeTitles::add);
|
}).filter(Objects::nonNull).filter(n -> n.length() >= 2).forEach(alternativeTitles::add);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
debug.warning(format("Failed to fetch alternative titles for %s [%d] => %s", title, id, e));
|
debug.warning(format("Failed to fetch alternative titles for %s => %s", path, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure main title is not in the set of alternative titles
|
// make sure main title is not in the set of alternative titles
|
||||||
alternativeTitles.remove(title);
|
alternativeTitles.remove(title);
|
||||||
|
|
||||||
return new Movie(title, alternativeTitles.toArray(new String[0]), year, -1, id, locale);
|
return alternativeTitles;
|
||||||
}).filter(Objects::nonNull).collect(toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI getMoviePageLink(int tmdbid) {
|
public URI getMoviePageLink(int tmdbid) {
|
||||||
return URI.create("http://www.themoviedb.org/movie/" + tmdbid);
|
return URI.create("https://www.themoviedb.org/movie/" + tmdbid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -716,4 +726,107 @@ public class TMDbClient implements MovieIdentificationService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeasonSupport() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SortOrder vetoRequestParameter(SortOrder order) {
|
||||||
|
return SortOrder.Airdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getEpisodeListLink(SearchResult searchResult) {
|
||||||
|
return URI.create("https://www.themoviedb.org/tv/" + searchResult.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception {
|
||||||
|
// query by name with year filter if possible
|
||||||
|
Matcher nameYear = getNameYearMatcher(query);
|
||||||
|
if (nameYear.matches()) {
|
||||||
|
return searchTV(nameYear.group(1).trim(), Integer.parseInt(nameYear.group(2)), locale, true);
|
||||||
|
} else {
|
||||||
|
return searchTV(query.trim(), -1, locale, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SearchResult> searchTV(String seriesName, int startYear, Locale locale, boolean extendedInfo) throws Exception {
|
||||||
|
Map<String, Object> query = new LinkedHashMap<String, Object>(2);
|
||||||
|
query.put("query", seriesName);
|
||||||
|
if (startYear > 0) {
|
||||||
|
query.put("first_air_date_year", startYear);
|
||||||
|
}
|
||||||
|
Object response = request("search/tv", query, locale, SEARCH_LIMIT);
|
||||||
|
|
||||||
|
return streamJsonObjects(response, "results").map(it -> {
|
||||||
|
Integer id = getInteger(it, "id");
|
||||||
|
String name = getString(it, "name");
|
||||||
|
String originalName = getString(it, "original_name");
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
name = originalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == null || name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> alternativeTitles = getAlternativeTitles("tv/" + id, "results", name, originalName, extendedInfo);
|
||||||
|
|
||||||
|
return new SearchResult(id, name, alternativeTitles);
|
||||||
|
}).filter(Objects::nonNull).collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SeriesData fetchSeriesData(SearchResult series, SortOrder sortOrder, Locale locale) throws Exception {
|
||||||
|
// http://api.themoviedb.org/3/tv/id
|
||||||
|
Object tv = request("tv/" + series.getId(), emptyMap(), locale, REQUEST_LIMIT);
|
||||||
|
|
||||||
|
SeriesInfo info = new SeriesInfo(getName(), sortOrder, locale, series.getId());
|
||||||
|
info.setName(Stream.of("original_name", "name").map(key -> getString(tv, key)).filter(Objects::nonNull).findFirst().orElse(series.getName()));
|
||||||
|
info.setAliasNames(series.getAliasNames());
|
||||||
|
info.setStatus(getString(tv, "status"));
|
||||||
|
info.setLanguage(getString(tv, "original_language"));
|
||||||
|
info.setStartDate(getStringValue(tv, "first_air_date", SimpleDate::parse));
|
||||||
|
info.setRating(getStringValue(tv, "vote_average", Double::new));
|
||||||
|
info.setRatingCount(getStringValue(tv, "vote_count", Integer::new));
|
||||||
|
info.setRuntime(stream(getArray(tv, "episode_run_time")).map(Object::toString).map(Integer::new).findFirst().orElse(null));
|
||||||
|
info.setGenres(streamJsonObjects(tv, "genres").map(it -> getString(it, "name")).collect(toList()));
|
||||||
|
info.setNetwork(streamJsonObjects(tv, "networks").map(it -> getString(it, "name")).findFirst().orElse(null));
|
||||||
|
|
||||||
|
int[] seasons = streamJsonObjects(tv, "seasons").mapToInt(it -> getInteger(it, "season_number")).toArray();
|
||||||
|
List<Episode> episodes = new ArrayList<Episode>();
|
||||||
|
List<Episode> specials = new ArrayList<Episode>();
|
||||||
|
|
||||||
|
for (int s : seasons) {
|
||||||
|
// http://api.themoviedb.org/3/tv/id/season/season_number
|
||||||
|
Object season = request("tv/" + series.getId() + "/season/" + s, emptyMap(), locale, REQUEST_LIMIT);
|
||||||
|
|
||||||
|
streamJsonObjects(season, "episodes").forEach(episode -> {
|
||||||
|
Integer episodeNumber = getInteger(episode, "episode_number");
|
||||||
|
Integer seasonNumber = getInteger(episode, "season_number");
|
||||||
|
String episodeTitle = getString(episode, "name");
|
||||||
|
SimpleDate airdate = getStringValue(episode, "air_date", SimpleDate::parse);
|
||||||
|
|
||||||
|
Integer absoluteNumber = episodes.size() + 1;
|
||||||
|
|
||||||
|
if (s > 0) {
|
||||||
|
episodes.add(new Episode(series.getName(), seasonNumber, episodeNumber, episodeTitle, absoluteNumber, null, airdate, info));
|
||||||
|
} else {
|
||||||
|
specials.add(new Episode(series.getName(), null, null, episodeTitle, null, episodeNumber, airdate, info));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// episodes my not be ordered by DVD episode number
|
||||||
|
episodes.sort(episodeComparator());
|
||||||
|
|
||||||
|
// add specials at the end
|
||||||
|
episodes.addAll(specials);
|
||||||
|
|
||||||
|
return new SeriesData(info, episodes);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.filebot.web;
|
package net.filebot.web;
|
||||||
|
|
||||||
import static java.util.Collections.*;
|
|
||||||
import static java.util.stream.Collectors.*;
|
import static java.util.stream.Collectors.*;
|
||||||
import static net.filebot.Logging.*;
|
import static net.filebot.Logging.*;
|
||||||
import static net.filebot.util.StringUtilities.*;
|
import static net.filebot.util.StringUtilities.*;
|
||||||
@ -199,7 +198,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// episodes my not be ordered by DVD episode number
|
// episodes my not be ordered by DVD episode number
|
||||||
sort(episodes, episodeComparator());
|
episodes.sort(episodeComparator());
|
||||||
|
|
||||||
// add specials at the end
|
// add specials at the end
|
||||||
episodes.addAll(specials);
|
episodes.addAll(specials);
|
||||||
|
@ -90,8 +90,64 @@ public class TMDbClientTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getArtwork() throws Exception {
|
public void getArtwork() throws Exception {
|
||||||
List<Artwork> artwork = tmdb.getArtwork("tt0418279");
|
List<Artwork> artwork = tmdb.getArtwork("tt0418279");
|
||||||
assertEquals("posters", artwork.get(0).getCategory());
|
assertEquals("backdrops", artwork.get(0).getCategory());
|
||||||
assertEquals("http://image.tmdb.org/t/p/original/bgSHbGEA1OM6qDs3Qba4VlSZsNG.jpg", artwork.get(0).getUrl().toString());
|
assertEquals("https://image.tmdb.org/t/p/original/ac0HwGJIU3GxjjGujlIjLJmAGPR.jpg", artwork.get(0).getUrl().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResult buffy = new SearchResult(95, "Buffy the Vampire Slayer");
|
||||||
|
SearchResult wonderfalls = new SearchResult(1982, "Wonderfalls");
|
||||||
|
SearchResult firefly = new SearchResult(1437, "Firefly");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void search() throws Exception {
|
||||||
|
// test default language and query escaping (blanks)
|
||||||
|
List<SearchResult> results = tmdb.search("babylon 5", Locale.ENGLISH);
|
||||||
|
|
||||||
|
assertEquals(1, results.size());
|
||||||
|
|
||||||
|
assertEquals("Babylon 5", results.get(0).getName());
|
||||||
|
assertEquals(3137, results.get(0).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getEpisodeListAll() throws Exception {
|
||||||
|
List<Episode> list = tmdb.getEpisodeList(buffy, SortOrder.Airdate, Locale.ENGLISH);
|
||||||
|
|
||||||
|
assertTrue(list.size() >= 144);
|
||||||
|
|
||||||
|
// check ordinary episode
|
||||||
|
Episode first = list.get(0);
|
||||||
|
assertEquals("Buffy the Vampire Slayer", first.getSeriesName());
|
||||||
|
assertEquals("1997-03-10", first.getSeriesInfo().getStartDate().toString());
|
||||||
|
assertEquals("Welcome to the Hellmouth (1)", first.getTitle());
|
||||||
|
assertEquals("1", first.getEpisode().toString());
|
||||||
|
assertEquals("1", first.getSeason().toString());
|
||||||
|
assertEquals("1", first.getAbsolute().toString());
|
||||||
|
assertEquals("1997-03-10", first.getAirdate().toString());
|
||||||
|
|
||||||
|
// check special episode
|
||||||
|
Episode last = list.get(list.size() - 1);
|
||||||
|
assertEquals("Buffy the Vampire Slayer", last.getSeriesName());
|
||||||
|
assertEquals("Unaired Buffy the Vampire Slayer pilot", last.getTitle());
|
||||||
|
assertEquals(null, last.getSeason());
|
||||||
|
assertEquals(null, last.getEpisode());
|
||||||
|
assertEquals(null, last.getAbsolute());
|
||||||
|
assertEquals("1", last.getSpecial().toString());
|
||||||
|
assertEquals(null, last.getAirdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getEpisodeListSingleSeason() throws Exception {
|
||||||
|
List<Episode> list = tmdb.getEpisodeList(wonderfalls, SortOrder.Airdate, Locale.ENGLISH);
|
||||||
|
|
||||||
|
Episode first = list.get(0);
|
||||||
|
assertEquals("Wonderfalls", first.getSeriesName());
|
||||||
|
assertEquals("2004-03-12", first.getSeriesInfo().getStartDate().toString());
|
||||||
|
assertEquals("Wax Lion", first.getTitle());
|
||||||
|
assertEquals("1", first.getEpisode().toString());
|
||||||
|
assertEquals("1", first.getSeason().toString());
|
||||||
|
assertEquals("1", first.getAbsolute().toString());
|
||||||
|
assertEquals("2004-03-12", first.getAirdate().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
|
Loading…
Reference in New Issue
Block a user