diff --git a/source/net/sourceforge/filebot/web/TMDbClient.java b/source/net/sourceforge/filebot/web/TMDbClient.java index fe1cf463..a196b8a6 100644 --- a/source/net/sourceforge/filebot/web/TMDbClient.java +++ b/source/net/sourceforge/filebot/web/TMDbClient.java @@ -1,10 +1,9 @@ - package net.sourceforge.filebot.web; - -import static java.util.Arrays.*; -import static java.util.Collections.*; -import static net.sourceforge.filebot.web.WebRequest.*; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static java.util.Collections.unmodifiableList; +import static net.sourceforge.filebot.web.WebRequest.encodeParameters; import java.io.File; import java.io.FileNotFoundException; @@ -39,47 +38,43 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; - public class TMDbClient implements MovieIdentificationService { - + private static final String host = "api.themoviedb.org"; private static final String version = "3"; - + private static final FloodLimit SEARCH_LIMIT = new FloodLimit(10, 12, TimeUnit.SECONDS); private static final FloodLimit REQUEST_LIMIT = new FloodLimit(20, 12, TimeUnit.SECONDS); - + private final String apikey; - - + public TMDbClient(String apikey) { this.apikey = apikey; } - - + @Override public String getName() { return "TheMovieDB"; } - - + @Override public Icon getIcon() { return ResourceManager.getIcon("search.themoviedb"); } - - + @Override public List searchMovie(String query, Locale locale) throws IOException { JSONObject response = request("search/movie", singletonMap("query", query), locale, SEARCH_LIMIT); List result = new ArrayList(); - + for (JSONObject it : jsonList(response.get("results"))) { - // 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"} String title = (String) it.get("title"); if (title == null || title.isEmpty()) { title = (String) it.get("original_title"); } - + try { long id = (Long) it.get("id"); int year = -1; @@ -96,13 +91,11 @@ public class TMDbClient implements MovieIdentificationService { } return result; } - - + public URI getMoviePageLink(int tmdbid) { return URI.create("http://www.themoviedb.org/movie/" + tmdbid); } - - + @Override public Movie getMovieDescriptor(int imdbid, Locale locale) throws IOException { String id = String.format("tt%07d", imdbid); @@ -117,14 +110,12 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + @Override public Map getMovieDescriptors(Collection movieFiles, Locale locale) throws Exception { throw new UnsupportedOperationException(); } - - + public MovieInfo getMovieInfo(Movie movie, Locale locale) throws IOException { if (movie.getTmdbId() >= 0) { return getMovieInfo(String.valueOf(movie.getTmdbId()), locale, true); @@ -139,11 +130,10 @@ public class TMDbClient implements MovieIdentificationService { } return null; } - - + public MovieInfo getMovieInfo(String id, Locale locale, boolean extendedInfo) throws IOException { JSONObject response = request("movie/" + id, null, locale, REQUEST_LIMIT); - + Map fields = new EnumMap(MovieProperty.class); for (MovieProperty key : MovieProperty.values()) { Object value = response.get(key.name()); @@ -151,24 +141,24 @@ public class TMDbClient implements MovieIdentificationService { fields.put(key, value.toString()); } } - + try { JSONObject collection = (JSONObject) response.get("belongs_to_collection"); fields.put(MovieProperty.collection, (String) collection.get("name")); } catch (Exception e) { // ignore } - + List genres = new ArrayList(); for (JSONObject it : jsonList(response.get("genres"))) { genres.add((String) it.get("name")); } - + List spokenLanguages = new ArrayList(); for (JSONObject it : jsonList(response.get("spoken_languages"))) { spokenLanguages.add((String) it.get("iso_639_1")); } - + if (extendedInfo) { JSONObject releases = request("movie/" + fields.get(MovieProperty.id) + "/releases", null, null, REQUEST_LIMIT); for (JSONObject it : jsonList(releases.get("countries"))) { @@ -177,7 +167,7 @@ public class TMDbClient implements MovieIdentificationService { } } } - + List cast = new ArrayList(); if (extendedInfo) { JSONObject castResponse = request("movie/" + fields.get(MovieProperty.id) + "/casts", null, null, REQUEST_LIMIT); @@ -194,19 +184,36 @@ public class TMDbClient implements MovieIdentificationService { } } } - - return new MovieInfo(fields, genres, spokenLanguages, cast); + + List trailers = new ArrayList(); + if (extendedInfo) { + JSONObject trailerResponse = request("movie/" + fields.get(MovieProperty.id) + "/trailers", null, null, REQUEST_LIMIT); + for (String section : new String[] { "quicktime", "youtube" }) { + for (JSONObject it : jsonList(trailerResponse.get(section))) { + LinkedHashMap sources = new LinkedHashMap(); + if (it.containsKey("sources")) { + for (JSONObject s : jsonList(it.get("sources"))) { + sources.put(s.get("size").toString(), s.get("source").toString()); + } + } else { + sources.put(it.get("size").toString(), it.get("source").toString()); + } + trailers.add(new Trailer(section, it.get("name").toString(), sources)); + } + } + } + + return new MovieInfo(fields, genres, spokenLanguages, cast, trailers); } - - + public List getArtwork(String id) throws IOException { // http://api.themoviedb.org/3/movie/11/images JSONObject config = request("configuration", null, null, REQUEST_LIMIT); String baseUrl = (String) ((JSONObject) config.get("images")).get("base_url"); - + JSONObject images = request("movie/" + id + "/images", null, null, REQUEST_LIMIT); List artwork = new ArrayList(); - + for (String section : new String[] { "backdrops", "posters" }) { for (JSONObject it : jsonList(images.get(section))) { try { @@ -220,11 +227,10 @@ public class TMDbClient implements MovieIdentificationService { } } } - + return artwork; } - - + public JSONObject request(String resource, Map parameters, Locale locale, final FloodLimit limit) throws IOException { // default parameters LinkedHashMap data = new LinkedHashMap(); @@ -235,17 +241,16 @@ public class TMDbClient implements MovieIdentificationService { data.put("language", locale.getLanguage()); } data.put("api_key", apikey); - + URL url = new URL("http", host, "/" + version + "/" + resource + "?" + encodeParameters(data, true)); - + CachedResource json = new CachedResource(url.toString(), String.class) { - + @Override public String process(ByteBuffer data) throws Exception { return Charset.forName("UTF-8").decode(data).toString(); } - - + @Override protected ByteBuffer fetchData(URL url, long lastModified) throws IOException { try { @@ -259,81 +264,73 @@ public class TMDbClient implements MovieIdentificationService { throw new RuntimeException(e); } } - - + @Override protected Cache getCache() { return CacheManager.getInstance().getCache("web-datasource"); } }; - + JSONObject object = (JSONObject) JSONValue.parse(json.get()); if (object == null || object.isEmpty()) { throw new FileNotFoundException("Resource not found: " + url); } return object; } - - + protected List jsonList(final Object array) { return new AbstractList() { - + @Override public JSONObject get(int index) { return (JSONObject) ((JSONArray) array).get(index); } - - + @Override public int size() { return ((JSONArray) array).size(); } }; } - - + public static class MovieInfo implements Serializable { - + public static enum MovieProperty { adult, backdrop_path, budget, homepage, id, imdb_id, original_title, overview, popularity, poster_path, release_date, revenue, runtime, tagline, title, vote_average, vote_count, certification, collection } - + protected Map fields; - + protected String[] genres; protected String[] spokenLanguages; - + protected Person[] people; - - + protected Trailer[] trailers; + protected MovieInfo() { // used by serializer } - - - protected MovieInfo(Map fields, List genres, List spokenLanguages, List people) { + + protected MovieInfo(Map fields, List genres, List spokenLanguages, List people, List trailers) { this.fields = new EnumMap(fields); this.genres = genres.toArray(new String[0]); this.spokenLanguages = spokenLanguages.toArray(new String[0]); this.people = people.toArray(new Person[0]); + this.trailers = trailers.toArray(new Trailer[0]); } - - + public String get(Object key) { return fields.get(MovieProperty.valueOf(key.toString())); } - - + public String get(MovieProperty key) { return fields.get(key); } - - + public boolean isAdult() { return Boolean.valueOf(get(MovieProperty.adult)); } - - + public List getSpokenLanguages() { try { List locales = new ArrayList(); @@ -345,18 +342,15 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public String getOriginalName() { return get(MovieProperty.original_title); } - - + public String getName() { return get(MovieProperty.title); } - - + public Integer getId() { try { return new Integer(get(MovieProperty.id)); @@ -364,8 +358,7 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public Integer getImdbId() { // e.g. tt0379786 try { @@ -374,8 +367,7 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public URL getHomepage() { try { return new URL(get(MovieProperty.homepage)); @@ -383,13 +375,11 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public String getOverview() { return get(MovieProperty.overview); } - - + public Integer getVotes() { try { return new Integer(get(MovieProperty.vote_count)); @@ -397,8 +387,7 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public Double getRating() { try { return new Double(get(MovieProperty.vote_average)); @@ -406,25 +395,21 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public String getTagline() { return get(MovieProperty.tagline); } - - + public String getCertification() { // e.g. PG-13 return get(MovieProperty.certification); } - - + public String getCollection() { // e.g. Star Wars Collection return get(MovieProperty.collection); } - - + public Date getReleased() { // e.g. 2005-09-30 try { @@ -433,23 +418,19 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + public String getRuntime() { return get(MovieProperty.runtime); } - - + public List getGenres() { return unmodifiableList(asList(genres)); } - - + public List getPeople() { return unmodifiableList(asList(people)); } - - + public List getCast() { List actors = new ArrayList(); for (Person person : people) { @@ -459,8 +440,7 @@ public class TMDbClient implements MovieIdentificationService { } return actors; } - - + public String getDirector() { for (Person person : people) { if (person.isDirector()) @@ -468,8 +448,7 @@ public class TMDbClient implements MovieIdentificationService { } return null; } - - + public String getWriter() { for (Person person : people) { if (person.isWriter()) @@ -477,8 +456,7 @@ public class TMDbClient implements MovieIdentificationService { } return null; } - - + public List getActors() { List actors = new ArrayList(); for (Person actor : getCast()) { @@ -486,8 +464,7 @@ public class TMDbClient implements MovieIdentificationService { } return actors; } - - + public URL getPoster() { try { return new URL(get(MovieProperty.poster_path)); @@ -495,100 +472,88 @@ public class TMDbClient implements MovieIdentificationService { return null; } } - - + + public List getTrailers() { + return unmodifiableList(asList(trailers)); + } + @Override public String toString() { return fields.toString(); } } - - + public static class Person implements Serializable { - + public static enum PersonProperty { name, character, job } - + protected Map fields; - - + protected Person() { // used by serializer } - - + public Person(Map fields) { this.fields = new EnumMap(fields); } - - + public Person(String name, String character, String job) { fields = new EnumMap(PersonProperty.class); fields.put(PersonProperty.name, name); fields.put(PersonProperty.character, character); fields.put(PersonProperty.job, job); } - - + public String get(Object key) { return fields.get(PersonProperty.valueOf(key.toString())); } - - + public String get(PersonProperty key) { return fields.get(key); } - - + public String getName() { return get(PersonProperty.name); } - - + public String getCharacter() { return get(PersonProperty.character); } - - + public String getJob() { return get(PersonProperty.job); } - - + public boolean isActor() { return getJob() == null; } - - + public boolean isDirector() { return "Director".equals(getJob()); } - - + public boolean isWriter() { return "Writer".equals(getJob()); } - - + @Override public String toString() { return fields.toString(); } } - - + public static class Artwork { - + private String category; private String language; - + private int width; private int height; - + private URL url; - - + public Artwork(String category, URL url, int width, int height, String language) { this.category = category; this.url = url; @@ -596,37 +561,66 @@ public class TMDbClient implements MovieIdentificationService { this.height = height; this.language = language; } - - + public String getCategory() { return category; } - - + public String getLanguage() { return language; } - - + public int getWidth() { return width; } - - + public int getHeight() { return height; } - - + public URL getUrl() { return url; } - - + @Override public String toString() { return String.format("{category: %s, width: %s, height: %s, language: %s, url: %s}", category, width, height, language, url); } } - + + public static class Trailer { + + private String type; + private String name; + private Map sources; + + public Trailer(String type, String name, Map sources) { + this.type = type; + this.name = name; + this.sources = sources; + } + + public String getType() { + return type; + } + + public String getName() { + return name; + } + + public Map getSources() { + return sources; + } + + public String getSource(String size) { + return sources.containsKey(size) ? sources.get(size) : sources.values().iterator().next(); + } + + @Override + public String toString() { + return String.format("%s %s (%s)", name, sources.keySet(), type); + } + + } + }