Unify Actors/Cast/Crew API

This commit is contained in:
Reinhard Pointner 2016-05-09 22:59:21 +08:00
parent 71bc810f93
commit be01e82589
8 changed files with 202 additions and 150 deletions

View File

@ -14,28 +14,21 @@ import java.util.stream.Stream;
public class Artwork implements Serializable {
private String database;
protected String[] tags;
protected URL url;
private String[] tags;
private URL url;
private String language;
private double rating;
protected String language;
protected Double rating;
public Artwork() {
// used by serializer
}
public Artwork(Datasource database, Stream<?> tags, URL url, Locale language, Double rating) {
this.database = database.getIdentifier();
public Artwork(Stream<?> tags, URL url, Locale language, Double rating) {
this.tags = tags.filter(Objects::nonNull).map(Object::toString).toArray(String[]::new);
this.url = url;
this.language = language == null || language.getLanguage().isEmpty() ? null : language.getLanguage();
this.rating = rating == null ? 0 : rating;
}
public String getDatabase() {
return database;
this.rating = rating;
}
public List<String> getTags() {
@ -51,7 +44,7 @@ public class Artwork implements Serializable {
}
public double getRating() {
return rating;
return rating == null ? 0 : rating;
}
public boolean matches(Object... tags) {

View File

@ -53,7 +53,7 @@ public class FanartTVClient implements Datasource, ArtworkProvider {
String season = getString(it, "season");
String discType = getString(it, "disc_type");
return new Artwork(this, Stream.of(type.getKey(), season, discType), new URL(url), language, likes);
return new Artwork(Stream.of(type.getKey(), season, discType), new URL(url), language, likes);
} catch (Exception e) {
debug.log(Level.WARNING, e, e::getMessage);
return null;

View File

@ -32,8 +32,7 @@ import net.filebot.Cache;
import net.filebot.CacheType;
import net.filebot.ResourceManager;
import net.filebot.web.TMDbClient.MovieInfo;
import net.filebot.web.TMDbClient.MovieInfo.MovieProperty;
import net.filebot.web.TMDbClient.Person;
import net.filebot.web.TMDbClient.MovieProperty;
public class OMDbClient implements MovieIdentificationService {
@ -188,9 +187,9 @@ public class OMDbClient implements MovieIdentificationService {
List<String> languages = split(delim, data.get("language"), String::toString);
List<Person> actors = new ArrayList<Person>();
actors.addAll(split(delim, data.get("actors"), (s) -> new Person(s, null, null)));
actors.addAll(split(delim, data.get("director"), (s) -> new Person(s, null, "Director")));
actors.addAll(split(delim, data.get("writer"), (s) -> new Person(s, null, "Writer")));
actors.addAll(split(delim, data.get("actors"), (s) -> new Person(s, Person.ACTOR)));
actors.addAll(split(delim, data.get("director"), (s) -> new Person(s, Person.DIRECTOR)));
actors.addAll(split(delim, data.get("writer"), (s) -> new Person(s, Person.WRITER)));
return new MovieInfo(fields, emptyList(), genres, emptyMap(), languages, emptyList(), emptyList(), actors, emptyList());
}

View File

@ -0,0 +1,81 @@
package net.filebot.web;
import static java.util.Comparator.*;
import java.io.Serializable;
import java.net.URL;
import java.util.Comparator;
public class Person implements Serializable {
protected String name;
protected String character;
protected String job;
protected String department;
protected Integer order;
protected URL image;
protected Person() {
// used by serializer
}
public Person(String name, String job) {
this(name, null, job, null, null, null);
}
public Person(String name, String character, String job, String department, Integer order, URL image) {
this.name = name;
this.character = character == null || character.isEmpty() ? null : character;
this.job = job == null || job.isEmpty() ? null : job;
this.department = department == null || department.isEmpty() ? null : department;
this.order = order;
this.image = image;
}
public String getName() {
return name;
}
public String getCharacter() {
return character;
}
public String getJob() {
return job;
}
public String getDepartment() {
return department;
}
public Integer getOrder() {
return order;
}
public URL getImage() {
return image;
}
public boolean isActor() {
return character != null || ACTOR.equals(getJob());
}
public boolean isDirector() {
return DIRECTOR.equals(getJob());
}
public boolean isWriter() {
return WRITER.equals(getJob());
}
@Override
public String toString() {
return String.format("%s (%s)", name, isActor() ? character : job);
}
public static final String WRITER = "Writer";
public static final String DIRECTOR = "Director";
public static final String ACTOR = "Actor";
public static final Comparator<Person> CREDIT_ORDER = comparing(Person::getOrder, nullsLast(naturalOrder()));
}

View File

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -35,8 +36,6 @@ import net.filebot.Cache;
import net.filebot.CacheType;
import net.filebot.Language;
import net.filebot.ResourceManager;
import net.filebot.web.TMDbClient.MovieInfo.MovieProperty;
import net.filebot.web.TMDbClient.Person.PersonProperty;
public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
@ -241,9 +240,19 @@ public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
List<Person> cast = new ArrayList<Person>();
try {
// { "cast_id":20, "character":"Gandalf", "credit_id":"52fe4a87c3a368484e158bb7", "id":1327, "name":"Ian McKellen", "order":1, "profile_path":"/c51mP46oPgAgFf7bFWVHlScZynM.jpg" }
Function<String, String> normalize = s -> replaceSpace(s, " ").trim(); // user data may not be well-formed
Stream.of("cast", "crew").flatMap(section -> streamJsonObjects(getMap(response, "casts"), section)).map(it -> {
return getEnumMap(it, PersonProperty.class);
}).map(Person::new).forEach(cast::add);
String name = getStringValue(it, "name", normalize);
String character = getStringValue(it, "character", normalize);
String job = getStringValue(it, "job", normalize);
String department = getStringValue(it, "department", normalize);
Integer order = getInteger(it, "order");
URL image = getStringValue(it, "profile_path", this::resolveImage);
return new Person(name, character, job, department, order, image);
}).sorted(Person.CREDIT_ORDER).forEach(cast::add);
} catch (Exception e) {
debug.warning(format("Bad data: casts => %s", response));
}
@ -271,24 +280,35 @@ public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
@Override
public List<Artwork> getArtwork(int id, String category, Locale locale) throws Exception {
Object config = request("configuration", emptyMap(), Locale.ROOT, REQUEST_LIMIT);
URL baseUrl = new URL(getString(getMap(config, "images"), "secure_base_url"));
Object images = request("movie/" + id + "/images", emptyMap(), Locale.ROOT, REQUEST_LIMIT);
return streamJsonObjects(images, category).map(it -> {
try {
String path = "original" + getString(it, "file_path");
String width = getString(it, "width");
String height = getString(it, "height");
Locale language = getStringValue(it, "iso_639_1", Locale::new);
URL image = getStringValue(it, "file_path", this::resolveImage);
String width = getString(it, "width");
String height = getString(it, "height");
Locale language = getStringValue(it, "iso_639_1", Locale::new);
return new Artwork(this, Stream.of(category, String.join("x", width, height)), new URL(baseUrl, path), language, null);
} catch (Exception e) {
debug.log(Level.WARNING, e, e::getMessage);
return null;
}
}).filter(Objects::nonNull).sorted(Artwork.RATING_ORDER).collect(toList());
return new Artwork(Stream.of(category, String.join("x", width, height)), image, language, null);
}).sorted(Artwork.RATING_ORDER).collect(toList());
}
protected Object getConfiguration() throws Exception {
return request("configuration", emptyMap(), Locale.ROOT, REQUEST_LIMIT);
}
protected URL resolveImage(String path) {
if (path == null || path.isEmpty()) {
return null;
}
try {
String mirror = (String) Cache.getCache(getName(), CacheType.Monthly).computeIfAbsent("configuration.base_url", it -> {
return getString(getMap(getConfiguration(), "images"), "base_url");
});
return new URL(mirror + "original" + path);
} catch (Exception e) {
throw new IllegalArgumentException(path, e);
}
}
protected Object request(String resource, Map<String, Object> parameters, Locale locale, final FloodLimit limit) throws Exception {
@ -334,11 +354,11 @@ public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
throw new IllegalArgumentException("Illegal language code: " + language);
}
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
}
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
}
public static class MovieInfo implements Serializable {
protected Map<MovieProperty, String> fields;
@ -573,77 +593,11 @@ public class TMDbClient implements MovieIdentificationService, ArtworkProvider {
}
}
public static class Person implements Serializable {
public static class Trailer implements Serializable {
public static enum PersonProperty {
name, character, job, department
}
protected Map<PersonProperty, String> fields;
protected Person() {
// used by serializer
}
public Person(Map<PersonProperty, String> fields) {
this.fields = new EnumMap<PersonProperty, String>(fields);
}
public Person(String name, String character, String job) {
fields = new EnumMap<PersonProperty, String>(PersonProperty.class);
fields.put(PersonProperty.name, name);
fields.put(PersonProperty.character, character);
fields.put(PersonProperty.job, job);
}
public String get(Object key) {
return get(PersonProperty.valueOf(key.toString()));
}
public String get(PersonProperty key) {
// replace null with empty string and normalize spaces
return replaceSpace(Objects.toString(fields.get(key), ""), " ").trim();
}
public String getName() {
return get(PersonProperty.name);
}
public String getCharacter() {
return get(PersonProperty.character);
}
public String getJob() {
return get(PersonProperty.job);
}
public String getDepartment() {
return get(PersonProperty.department);
}
public boolean isActor() {
return fields.containsKey(PersonProperty.character);
}
public boolean isDirector() {
return "Director".equals(getJob());
}
public boolean isWriter() {
return "Writer".equals(getJob());
}
@Override
public String toString() {
return fields.toString();
}
}
public static class Trailer {
private String type;
private String name;
private Map<String, String> sources;
protected String type;
protected String name;
protected Map<String, String> sources;
public Trailer(String type, String name, Map<String, String> sources) {
this.type = type;

View File

@ -22,7 +22,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.stream.Stream;
import javax.swing.Icon;
@ -151,7 +150,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider implements Artwor
info.setOverview(getString(data, "overview"));
info.setAirsDayOfWeek(getString(data, "airsDayOfWeek"));
info.setAirsTime(getString(data, "airsTime"));
info.setBannerUrl(getStringValue(data, "banner", this::resolveBanner));
info.setBannerUrl(getStringValue(data, "banner", this::resolveImage));
info.setLastUpdated(getStringValue(data, "lastUpdated", Long::new));
return info;
@ -242,21 +241,16 @@ public class TheTVDBClient extends AbstractEpisodeListProvider implements Artwor
Object json = requestJson("series/" + id + "/images/query?keyType=" + category, locale, Cache.ONE_MONTH);
return streamJsonObjects(json, "data").map(it -> {
try {
String subKey = getString(it, "subKey");
String fileName = getString(it, "fileName");
String resolution = getString(it, "resolution");
Double rating = getDecimal(getMap(it, "ratingsInfo"), "average");
String subKey = getString(it, "subKey");
String resolution = getString(it, "resolution");
URL url = getStringValue(it, "fileName", this::resolveImage);
Double rating = getDecimal(getMap(it, "ratingsInfo"), "average");
return new Artwork(this, Stream.of(category, subKey, resolution), resolveBanner(fileName), locale, rating);
} catch (Exception e) {
debug.log(Level.WARNING, e, e::getMessage);
return null;
}
}).filter(Objects::nonNull).sorted(Artwork.RATING_ORDER).collect(toList());
return new Artwork(Stream.of(category, subKey, resolution), url, locale, rating);
}).sorted(Artwork.RATING_ORDER).collect(toList());
}
protected URL resolveBanner(String path) {
protected URL resolveImage(String path) {
if (path == null || path.isEmpty()) {
return null;
}
@ -265,7 +259,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider implements Artwor
try {
return new URL("http://thetvdb.com/banners/" + path);
} catch (Exception e) {
throw new IllegalArgumentException(path);
throw new IllegalArgumentException(path, e);
}
}
@ -274,9 +268,18 @@ public class TheTVDBClient extends AbstractEpisodeListProvider implements Artwor
return streamJsonObjects(response, "data").map(it -> getString(it, "abbreviation")).collect(toList());
}
public List<Map<?, ?>> getActors(int id, Locale locale) throws Exception {
public List<Person> getActors(int id, Locale locale) throws Exception {
Object response = requestJson("series/" + id + "/actors", locale, Cache.ONE_MONTH);
return streamJsonObjects(response, "data").map(it -> asMap(it)).collect(toList());
// e.g. [id:68414, seriesId:78874, name:Summer Glau, role:River Tam, sortOrder:2, image:actors/68414.jpg, imageAuthor:513, imageAdded:0000-00-00 00:00:00, lastUpdated:2011-08-18 11:53:14]
return streamJsonObjects(response, "data").map(it -> {
String name = getString(it, "name");
String character = getString(it, "role");
Integer order = getInteger(it, "sortOrder");
URL image = getStringValue(it, "image", this::resolveImage);
return new Person(name, character, null, null, order, image);
}).sorted(Person.CREDIT_ORDER).collect(toList());
}
}

View File

@ -19,11 +19,11 @@ import net.filebot.web.TMDbClient.MovieInfo;
public class TMDbClientTest {
TMDbClient tmdb = new TMDbClient("66308fb6e3fd850dde4c7d21df2e8306");
static TMDbClient db = new TMDbClient("66308fb6e3fd850dde4c7d21df2e8306");
@Test
public void searchByName() throws Exception {
List<Movie> result = tmdb.searchMovie("Serenity", Locale.CHINESE);
List<Movie> result = db.searchMovie("Serenity", Locale.CHINESE);
Movie movie = result.get(0);
assertEquals("冲出宁静号", movie.getName());
@ -34,7 +34,7 @@ public class TMDbClientTest {
@Test
public void searchByNameWithYearShortName() throws Exception {
List<Movie> result = tmdb.searchMovie("Up 2009", Locale.ENGLISH);
List<Movie> result = db.searchMovie("Up 2009", Locale.ENGLISH);
Movie movie = result.get(0);
assertEquals("Up", movie.getName());
@ -45,7 +45,7 @@ public class TMDbClientTest {
@Test
public void searchByNameWithYearNumberName() throws Exception {
List<Movie> result = tmdb.searchMovie("9 (2009)", Locale.ENGLISH);
List<Movie> result = db.searchMovie("9 (2009)", Locale.ENGLISH);
Movie movie = result.get(0);
assertEquals("9", movie.getName());
@ -56,7 +56,7 @@ public class TMDbClientTest {
@Test
public void searchByNameGermanResults() throws Exception {
List<Movie> result = tmdb.searchMovie("East of Eden", Locale.GERMAN);
List<Movie> result = db.searchMovie("East of Eden", Locale.GERMAN);
Movie movie = result.get(0);
assertEquals("Jenseits von Eden", movie.getName());
@ -66,7 +66,7 @@ public class TMDbClientTest {
@Test
public void searchByIMDB() throws Exception {
Movie movie = tmdb.getMovieDescriptor(new Movie(418279), Locale.ENGLISH);
Movie movie = db.getMovieDescriptor(new Movie(418279), Locale.ENGLISH);
assertEquals("Transformers", movie.getName());
assertEquals(2007, movie.getYear(), 0);
@ -76,7 +76,7 @@ public class TMDbClientTest {
@Test
public void getMovieInfo() throws Exception {
MovieInfo movie = tmdb.getMovieInfo(new Movie(418279), Locale.ENGLISH, true);
MovieInfo movie = db.getMovieInfo(new Movie(418279), Locale.ENGLISH, true);
assertEquals("Transformers", movie.getName());
assertEquals("2007-06-27", movie.getReleased().toString());
@ -88,16 +88,27 @@ public class TMDbClientTest {
@Test
public void getArtwork() throws Exception {
Artwork a = tmdb.getArtwork(16320, "backdrops", Locale.ROOT).get(0);
Artwork a = db.getArtwork(16320, "backdrops", Locale.ROOT).get(0);
assertEquals("[backdrops, 1920x1080]", a.getTags().toString());
assertEquals("https://image.tmdb.org/t/p/original/424MxHQe5Hfu92hTeRvZb5Giv0X.jpg", a.getUrl().toString());
}
@Test
public void getPeople() throws Exception {
Person p = db.getMovieInfo("16320", Locale.ENGLISH, true).getPeople().get(0);
assertEquals("Nathan Fillion", p.getName());
assertEquals("Mal", p.getCharacter());
assertEquals(null, p.getJob());
assertEquals(null, p.getDepartment());
assertEquals("0", p.getOrder().toString());
assertEquals("http://image.tmdb.org/t/p/original/B7VTVtnKyFk0AtYjEbqzBQlPec.jpg", p.getImage().toString());
}
@Ignore
@Test
public void floodLimit() throws Exception {
for (Locale it : Locale.getAvailableLocales()) {
List<Movie> results = tmdb.searchMovie("Serenity", it);
List<Movie> results = db.searchMovie("Serenity", it);
assertEquals(16320, results.get(0).getTmdbId());
}
}

View File

@ -9,7 +9,7 @@ import org.junit.Test;
public class TheTVDBClientTest {
static TheTVDBClient thetvdb = new TheTVDBClient("BA864DEE427E384A");
static TheTVDBClient db = new TheTVDBClient("BA864DEE427E384A");
SearchResult buffy = new SearchResult(70327, "Buffy the Vampire Slayer");
SearchResult wonderfalls = new SearchResult(78845, "Wonderfalls");
@ -18,7 +18,7 @@ public class TheTVDBClientTest {
@Test
public void search() throws Exception {
// test default language and query escaping (blanks)
List<SearchResult> results = thetvdb.search("babylon 5", Locale.ENGLISH);
List<SearchResult> results = db.search("babylon 5", Locale.ENGLISH);
assertEquals(2, results.size());
@ -30,7 +30,7 @@ public class TheTVDBClientTest {
@Test
public void searchGerman() throws Exception {
List<SearchResult> results = thetvdb.search("Buffy the Vampire Slayer", Locale.GERMAN);
List<SearchResult> results = db.search("Buffy the Vampire Slayer", Locale.GERMAN);
assertEquals(2, results.size());
@ -42,7 +42,7 @@ public class TheTVDBClientTest {
@Test
public void getEpisodeListAll() throws Exception {
List<Episode> list = thetvdb.getEpisodeList(buffy, SortOrder.Airdate, Locale.ENGLISH);
List<Episode> list = db.getEpisodeList(buffy, SortOrder.Airdate, Locale.ENGLISH);
assertEquals(145, list.size());
@ -69,7 +69,7 @@ public class TheTVDBClientTest {
@Test
public void getEpisodeListSingleSeason() throws Exception {
List<Episode> list = thetvdb.getEpisodeList(wonderfalls, SortOrder.Airdate, Locale.ENGLISH);
List<Episode> list = db.getEpisodeList(wonderfalls, SortOrder.Airdate, Locale.ENGLISH);
Episode first = list.get(0);
@ -84,7 +84,7 @@ public class TheTVDBClientTest {
@Test
public void getEpisodeListNumbering() throws Exception {
List<Episode> list = thetvdb.getEpisodeList(firefly, SortOrder.DVD, Locale.ENGLISH);
List<Episode> list = db.getEpisodeList(firefly, SortOrder.DVD, Locale.ENGLISH);
Episode first = list.get(0);
assertEquals("Firefly", first.getSeriesName());
@ -97,26 +97,26 @@ public class TheTVDBClientTest {
}
public void getEpisodeListLink() {
assertEquals("http://www.thetvdb.com/?tab=seasonall&id=78874", thetvdb.getEpisodeListLink(firefly).toString());
assertEquals("http://www.thetvdb.com/?tab=seasonall&id=78874", db.getEpisodeListLink(firefly).toString());
}
@Test
public void lookupByID() throws Exception {
SearchResult series = thetvdb.lookupByID(78874, Locale.ENGLISH);
SearchResult series = db.lookupByID(78874, Locale.ENGLISH);
assertEquals("Firefly", series.getName());
assertEquals(78874, series.getId());
}
@Test
public void lookupByIMDbID() throws Exception {
SearchResult series = thetvdb.lookupByIMDbID(303461, Locale.ENGLISH);
SearchResult series = db.lookupByIMDbID(303461, Locale.ENGLISH);
assertEquals("Firefly", series.getName());
assertEquals(78874, series.getId());
}
@Test
public void getSeriesInfo() throws Exception {
TheTVDBSeriesInfo it = thetvdb.getSeriesInfo(80348, Locale.ENGLISH);
TheTVDBSeriesInfo it = db.getSeriesInfo(80348, Locale.ENGLISH);
assertEquals(80348, it.getId(), 0);
assertEquals("Action", it.getGenres().get(0));
@ -134,7 +134,7 @@ public class TheTVDBClientTest {
@Test
public void getArtwork() throws Exception {
Artwork i = thetvdb.getArtwork(buffy.getId(), "fanart", Locale.ENGLISH).get(0);
Artwork i = db.getArtwork(buffy.getId(), "fanart", Locale.ENGLISH).get(0);
assertEquals("[fanart, 1920x1080]", i.getTags().toString());
assertEquals("http://thetvdb.com/banners/fanart/original/70327-7.jpg", i.getUrl().toString());
@ -145,8 +145,19 @@ public class TheTVDBClientTest {
@Test
public void getLanguages() throws Exception {
List<String> languages = thetvdb.getLanguages();
List<String> languages = db.getLanguages();
assertEquals("[zh, en, sv, no, da, fi, nl, de, it, es, fr, pl, hu, el, tr, ru, he, ja, pt, cs, sl, hr, ko]", languages.toString());
}
@Test
public void getActors() throws Exception {
Person p = db.getActors(firefly.getId(), Locale.ENGLISH).get(0);
assertEquals("Alan Tudyk", p.getName());
assertEquals("Hoban 'Wash' Washburne", p.getCharacter());
assertEquals(null, p.getJob());
assertEquals(null, p.getDepartment());
assertEquals("0", p.getOrder().toString());
assertEquals("http://thetvdb.com/banners/actors/68409.jpg", p.getImage().toString());
}
}