Experiment with TheTVDB API v2

This commit is contained in:
Reinhard Pointner 2016-04-16 21:41:16 +00:00
parent 4c85678975
commit a17423dd95
6 changed files with 508 additions and 53 deletions

View File

@ -7,13 +7,16 @@ import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.w3c.dom.Document;
@ -173,10 +176,14 @@ public class CachedResource<K, R> implements Resource<R> {
}
public static Fetch fetchIfModified() {
return fetchIfModified(Collections::emptyMap);
}
public static Fetch fetchIfModified(Supplier<Map<String, String>> requestParameters) {
return (url, lastModified) -> {
debug.fine(WebRequest.log(url, lastModified, null));
try {
debug.fine(WebRequest.log(url, lastModified, null));
return WebRequest.fetchIfModified(url, lastModified);
return WebRequest.fetch(url, lastModified, null, requestParameters.get(), null);
} catch (FileNotFoundException e) {
return fileNotFound(url, e);
}
@ -185,28 +192,25 @@ public class CachedResource<K, R> implements Resource<R> {
public static Fetch fetchIfNoneMatch(Function<URL, Object> etagRetrieve, BiConsumer<URL, String> etagStore) {
return (url, lastModified) -> {
// record ETag response header
Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
Object etagValue = etagRetrieve.apply(url);
debug.fine(WebRequest.log(url, lastModified, etagValue));
try {
debug.fine(WebRequest.log(url, lastModified, etagValue));
if (etagValue != null) {
return WebRequest.fetch(url, 0, etagValue, null, responseHeaders);
} else {
return WebRequest.fetch(url, lastModified, null, null, responseHeaders);
}
return WebRequest.fetch(url, etagValue == null ? lastModified : 0, etagValue, null, storeETag(url, etagStore));
} catch (FileNotFoundException e) {
return fileNotFound(url, e);
} finally {
WebRequest.getETag(responseHeaders).ifPresent(etag -> {
debug.finest(format("Store ETag: %s", etag));
etagStore.accept(url, etag);
});
}
};
}
private static Consumer<Map<String, List<String>>> storeETag(URL url, BiConsumer<URL, String> etagStore) {
return (responseHeaders) -> {
WebRequest.getETag(responseHeaders).ifPresent(etag -> {
debug.finest(format("Store ETag: %s", etag));
etagStore.accept(url, etag);
});
};
}
private static ByteBuffer fileNotFound(URL url, FileNotFoundException e) {
debug.warning(format("Resource not found: %s", url));
return ByteBuffer.allocate(0);

View File

@ -11,6 +11,7 @@ import java.util.stream.Stream;
import com.cedarsoftware.util.io.JsonObject;
import com.cedarsoftware.util.io.JsonReader;
import com.cedarsoftware.util.io.JsonWriter;
public class JsonUtilities {
@ -23,6 +24,10 @@ public class JsonUtilities {
return JsonReader.jsonToJava(json.toString(), singletonMap(JsonReader.USE_MAPS, true));
}
public static String asJsonString(Object object) {
return JsonWriter.objectToJson(object);
}
public static Map<?, ?> asMap(Object node) {
if (node instanceof Map) {
return (Map<?, ?>) node;

View File

@ -50,6 +50,12 @@ public class SeriesInfo implements Serializable {
this.status = other.status;
}
public SeriesInfo(Datasource database, Locale language, Integer id) {
this.database = database.getIdentifier();
this.language = language.getLanguage();
this.id = id;
}
public SeriesInfo(Datasource database, SortOrder order, Locale language, Integer id) {
this.database = database.getIdentifier();
this.order = order.name();

View File

@ -0,0 +1,294 @@
package net.filebot.web;
import static java.nio.charset.StandardCharsets.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import static net.filebot.CachedResource.fetchIfModified;
import static net.filebot.Logging.*;
import static net.filebot.util.JsonUtilities.*;
import static net.filebot.util.StringUtilities.*;
import static net.filebot.web.EpisodeUtilities.*;
import static net.filebot.web.WebRequest.*;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import javax.swing.Icon;
import net.filebot.Cache;
import net.filebot.CacheType;
import net.filebot.ResourceManager;
public class TheTVDBClient2 extends AbstractEpisodeListProvider {
private String apikey;
public TheTVDBClient2(String apikey) {
this.apikey = apikey;
}
@Override
public String getName() {
return "TheTVDB";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.thetvdb");
}
@Override
public boolean hasSeasonSupport() {
return true;
}
protected Object requestJson(String path, Object post) throws Exception {
// curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' 'https://api.thetvdb.com/login' --data '{"apikey":"XXXXX"}'
ByteBuffer response = post(getEndpoint(path), asJsonString(post).getBytes(UTF_8), "application/json", null);
return readJson(UTF_8.decode(response));
}
protected Object requestJson(String path, Locale locale, Duration expirationTime) throws Exception {
Cache cache = Cache.getCache(locale == null ? getName() : getName() + "_" + locale.getLanguage(), CacheType.Monthly);
return cache.json(path, this::getEndpoint).fetch(fetchIfModified(() -> getRequestHeader(locale))).expire(expirationTime).get();
}
protected URL getEndpoint(String path) throws Exception {
return new URL("https://api.thetvdb.com/" + path);
}
private Map<String, String> getRequestHeader(Locale locale) {
Map<String, String> header = new LinkedHashMap<String, String>(3);
if (locale != null) {
header.put("Accept-Language", locale.getLanguage());
}
header.put("Accept", "application/json");
header.put("Authorization", "Bearer " + getAuthorizationToken());
return header;
}
private String token = null;
private synchronized String getAuthorizationToken() {
// curl -v -X GET --header 'Accept: application/json' --header 'Authorization: Bearer TOKEN' 'https://api.thetvdb.com/languages'
if (token == null) {
try {
Object json = requestJson("login", singletonMap("apikey", apikey));
token = getString(json, "token");
} catch (Exception e) {
throw new IllegalStateException("Failed to retrieve authorization token: " + e.getMessage(), e);
}
}
return token;
}
protected String[] languages() throws Exception {
Object response = requestJson("languages", Cache.ONE_MONTH);
return streamJsonObjects(response, "data").map(it -> getString(it, "abbreviation")).toArray(String[]::new);
}
protected List<SearchResult> search(String path, Map<String, Object> query, Locale locale, Duration expirationTime) throws Exception {
Object json = requestJson(path + "?" + encodeParameters(query, true), locale, expirationTime);
return streamJsonObjects(json, "data").map(it -> {
// e.g. aliases, banner, firstAired, id, network, overview, seriesName, status
int id = getInteger(it, "id");
String seriesName = getString(it, "seriesName");
String[] aliasNames = stream(getArray(it, "aliases")).toArray(String[]::new);
if (seriesName.startsWith("**") && seriesName.endsWith("**")) {
debug.fine(format("Invalid series: %s [%d]", seriesName, id));
return null;
}
return new SearchResult(id, seriesName, aliasNames);
}).filter(Objects::nonNull).collect(toList());
}
@Override
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception {
return search("search/series", singletonMap("name", query), locale, Cache.ONE_DAY);
}
@Override
public SeriesInfo getSeriesInfo(SearchResult series, Locale locale) throws Exception {
Object json = requestJson("series/" + series.getId(), locale, Cache.ONE_WEEK);
Object data = getMap(json, "data");
SeriesInfo info = new SeriesInfo(this, locale, series.getId());
info.setAliasNames(Stream.of(series.getAliasNames(), getArray(data, "aliases")).flatMap(it -> stream(it)).map(Object::toString).distinct().toArray(String[]::new));
info.setName(getString(data, "seriesName"));
info.setCertification(getString(data, "rating"));
info.setNetwork(getString(data, "network"));
info.setStatus(getString(data, "status"));
info.setRating(getDecimal(data, "siteRating"));
info.setRatingCount(getInteger(data, "siteRatingCount")); // TODO rating count not implemented in the new API yet
info.setRuntime(matchInteger(getString(data, "runtime")));
info.setGenres(stream(getArray(data, "genre")).map(Object::toString).collect(toList()));
info.setStartDate(getStringValue(data, "firstAired", SimpleDate::parse));
return info;
}
@Override
protected SeriesData fetchSeriesData(SearchResult series, SortOrder sortOrder, Locale locale) throws Exception {
// fetch series info
SeriesInfo info = getSeriesInfo(series, locale);
info.setOrder(sortOrder.name());
// fetch episode data
List<Episode> episodes = new ArrayList<Episode>();
List<Episode> specials = new ArrayList<Episode>();
for (int page = 1, lastPage = 1; page <= lastPage; page++) {
Object json = requestJson("series/" + series.getId() + "/episodes?page=" + page, locale, Cache.ONE_DAY);
lastPage = getInteger(getMap(json, "links"), "last");
streamJsonObjects(json, "data").forEach(it -> {
String episodeName = getString(it, "episodeName");
Integer absoluteNumber = getInteger(it, "absoluteNumber");
SimpleDate airdate = getStringValue(it, "firstAired", SimpleDate::parse);
// default numbering
Integer episodeNumber = getInteger(it, "airedEpisodeNumber");
Integer seasonNumber = getInteger(it, "airedSeason");
// use preferred numbering if possible
if (sortOrder == SortOrder.DVD) {
Integer dvdSeasonNumber = getInteger(it, "dvdSeason");
Integer dvdEpisodeNumber = getInteger(it, "dvdEpisodeNumber");
// require both values to be valid integer numbers
if (dvdSeasonNumber != null && dvdEpisodeNumber != null) {
seasonNumber = dvdSeasonNumber;
episodeNumber = dvdEpisodeNumber;
}
} else if (sortOrder == SortOrder.Absolute && absoluteNumber != null && absoluteNumber > 0) {
episodeNumber = absoluteNumber;
seasonNumber = null;
}
if (seasonNumber == null || seasonNumber > 0) {
// handle as normal episode
episodes.add(new Episode(info.getName(), seasonNumber, episodeNumber, episodeName, absoluteNumber, null, airdate, new SeriesInfo(info)));
} else {
// handle as special episode
specials.add(new Episode(info.getName(), null, null, episodeName, null, episodeNumber, airdate, new SeriesInfo(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);
}
public SearchResult lookupByID(int id, Locale locale) throws Exception {
if (id <= 0) {
throw new IllegalArgumentException("Illegal TheTVDB ID: " + id);
}
SeriesInfo info = getSeriesInfo(new SearchResult(id, null), locale);
return new SearchResult(id, info.getName(), info.getAliasNames());
}
public SearchResult lookupByIMDbID(int imdbid, Locale locale) throws Exception {
if (imdbid <= 0) {
throw new IllegalArgumentException("Illegal IMDbID ID: " + imdbid);
}
List<SearchResult> result = search("search/series", singletonMap("imdbId", imdbid), locale, Cache.ONE_MONTH);
return result.size() > 0 ? result.get(0) : null;
}
@Override
public URI getEpisodeListLink(SearchResult searchResult) {
return URI.create("http://www.thetvdb.com/?tab=seasonall&id=" + searchResult.getId());
}
public List<Image> getImages(SearchResult series, String keyType) throws Exception {
Object json = requestJson("series/" + series.getId() + "/images/query?keyType=" + keyType, null, Cache.ONE_WEEK);
return streamJsonObjects(json, "data").map(it -> {
Integer id = getInteger(it, "id");
String subKey = getString(it, "subKey");
String fileName = getString(it, "fileName");
String resolution = getString(it, "resolution");
Double rating = getDecimal(getString(it, "ratingsInfo"), "average");
return new Image(id, keyType, subKey, fileName, resolution, rating);
}).collect(toList());
}
public static class Image implements Serializable {
private Integer id;
private String keyType;
private String subKey;
private String fileName;
private String resolution;
private Double rating;
protected Image() {
// used by serializer
}
public Image(Integer id, String keyType, String subKey, String fileName, String resolution, Double rating) {
this.id = id;
this.keyType = keyType;
this.subKey = subKey;
this.fileName = fileName;
this.resolution = resolution;
this.rating = rating;
}
public Integer getId() {
return id;
}
public String getKeyType() {
return keyType;
}
public String getSubKey() {
return subKey;
}
public String getFileName() {
return fileName;
}
public String getResolution() {
return resolution;
}
public Double getRating() {
return rating;
}
@Override
public String toString() {
return "[id=" + id + ", keyType=" + keyType + ", subKey=" + subKey + ", fileName=" + fileName + ", resolution=" + resolution + ", rating=" + rating + "]";
}
}
}

View File

@ -28,13 +28,13 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -56,12 +56,15 @@ import net.filebot.util.ByteBufferOutputStream;
public final class WebRequest {
private static final String ENCODING_GZIP = "gzip";
private static final String CHARSET_UTF8 = "UTF-8";
public static Reader getReader(URLConnection connection) throws IOException {
try {
connection.addRequestProperty("Accept-Encoding", "gzip,deflate");
connection.addRequestProperty("Accept-Charset", "UTF-8,ISO-8859-1");
connection.addRequestProperty("Accept-Encoding", ENCODING_GZIP);
connection.addRequestProperty("Accept-Charset", CHARSET_UTF8);
} catch (IllegalStateException e) {
// too bad, can't request gzipped document anymore
debug.log(Level.WARNING, e, e::toString);
}
Charset charset = getCharset(connection.getContentType());
@ -69,10 +72,8 @@ public final class WebRequest {
InputStream inputStream = connection.getInputStream();
if ("gzip".equalsIgnoreCase(encoding))
if (ENCODING_GZIP.equalsIgnoreCase(encoding)) {
inputStream = new GZIPInputStream(inputStream);
else if ("deflate".equalsIgnoreCase(encoding)) {
inputStream = new InflaterInputStream(inputStream, new Inflater(true));
}
return new InputStreamReader(inputStream, charset);
@ -110,7 +111,7 @@ public final class WebRequest {
return fetch(resource, ifModifiedSince, null, null, null);
}
public static ByteBuffer fetch(URL url, long ifModifiedSince, Object etag, Map<String, String> requestParameters, Map<String, List<String>> responseParameters) throws IOException {
public static ByteBuffer fetch(URL url, long ifModifiedSince, Object etag, Map<String, String> requestParameters, Consumer<Map<String, List<String>>> responseParameters) throws IOException {
URLConnection connection = url.openConnection();
if (ifModifiedSince > 0) {
@ -121,38 +122,33 @@ public final class WebRequest {
}
try {
connection.addRequestProperty("Accept-Encoding", "gzip,deflate");
connection.addRequestProperty("Accept-Charset", "UTF-8");
connection.addRequestProperty("Accept-Encoding", ENCODING_GZIP);
connection.addRequestProperty("Accept-Charset", CHARSET_UTF8);
} catch (IllegalStateException e) {
// too bad, can't request gzipped data
debug.log(Level.WARNING, e, e::toString);
}
if (requestParameters != null) {
for (Entry<String, String> parameter : requestParameters.entrySet()) {
connection.addRequestProperty(parameter.getKey(), parameter.getValue());
}
requestParameters.forEach(connection::addRequestProperty);
}
int contentLength = connection.getContentLength();
String encoding = connection.getContentEncoding();
InputStream in = connection.getInputStream();
if ("gzip".equalsIgnoreCase(encoding))
in = new GZIPInputStream(in);
else if ("deflate".equalsIgnoreCase(encoding)) {
in = new InflaterInputStream(in, new Inflater(true));
InputStream inputStream = connection.getInputStream();
if (ENCODING_GZIP.equalsIgnoreCase(encoding)) {
inputStream = new GZIPInputStream(inputStream);
}
// store response headers
if (responseParameters != null) {
responseParameters.putAll(connection.getHeaderFields());
responseParameters.accept(connection.getHeaderFields());
}
ByteBufferOutputStream buffer = new ByteBufferOutputStream(contentLength >= 0 ? contentLength : 4 * 1024);
ByteBufferOutputStream buffer = new ByteBufferOutputStream(contentLength >= 0 ? contentLength : BUFFER_SIZE);
try {
// read all
buffer.transferFully(in);
buffer.transferFully(inputStream);
} catch (IOException e) {
// if the content length is not known in advance an IOException (Premature EOF)
// is always thrown after all the data has been read
@ -160,7 +156,7 @@ public final class WebRequest {
throw e;
}
} finally {
in.close();
inputStream.close();
}
// no data, e.g. If-Modified-Since requests
@ -173,7 +169,7 @@ public final class WebRequest {
public static ByteBuffer post(URL url, Map<String, ?> parameters, Map<String, String> requestParameters) throws IOException {
byte[] postData = encodeParameters(parameters, true).getBytes("UTF-8");
if (requestParameters != null && "gzip".equals(requestParameters.get("Content-Encoding"))) {
if (requestParameters != null && ENCODING_GZIP.equals(requestParameters.get("Content-Encoding"))) {
postData = gzip(postData);
}
return post(url, postData, "application/x-www-form-urlencoded", requestParameters);
@ -184,6 +180,7 @@ public final class WebRequest {
connection.addRequestProperty("Content-Length", String.valueOf(postData.length));
connection.addRequestProperty("Content-Type", contentType);
connection.setRequestMethod("POST");
connection.setDoOutput(true);
@ -202,17 +199,15 @@ public final class WebRequest {
int contentLength = connection.getContentLength();
String encoding = connection.getContentEncoding();
InputStream in = connection.getInputStream();
if ("gzip".equalsIgnoreCase(encoding))
in = new GZIPInputStream(in);
else if ("deflate".equalsIgnoreCase(encoding)) {
in = new InflaterInputStream(in, new Inflater(true));
InputStream inputStream = connection.getInputStream();
if (ENCODING_GZIP.equalsIgnoreCase(encoding)) {
inputStream = new GZIPInputStream(inputStream);
}
ByteBufferOutputStream buffer = new ByteBufferOutputStream(contentLength >= 0 ? contentLength : BUFFER_SIZE);
try {
// read all
buffer.transferFully(in);
buffer.transferFully(inputStream);
} catch (IOException e) {
// if the content length is not known in advance an IOException (Premature EOF)
// is always thrown after all the data has been read
@ -220,7 +215,7 @@ public final class WebRequest {
throw e;
}
} finally {
in.close();
inputStream.close();
}
return buffer.getByteBuffer();
@ -252,9 +247,9 @@ public final class WebRequest {
private static byte[] gzip(byte[] data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(data.length);
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write(data);
gzip.close();
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(data);
}
return out.toByteArray();
}

View File

@ -0,0 +1,151 @@
package net.filebot.web;
import static java.util.Arrays.*;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Locale;
import org.junit.Test;
import net.filebot.web.TheTVDBClient2.Image;
public class TheTVDBClient2Test {
TheTVDBClient2 thetvdb = new TheTVDBClient2("BA864DEE427E384A");
SearchResult buffy = new SearchResult(70327, "Buffy the Vampire Slayer");
SearchResult wonderfalls = new SearchResult(78845, "Wonderfalls");
SearchResult firefly = new SearchResult(78874, "Firefly");
@Test
public void languages() throws Exception {
String[] languages = thetvdb.languages();
assertEquals("[zh, en, sv, no, da, fi, nl, de, it, es, fr, pl, hu, el, tr, ru, he, ja, pt, cs, sl, hr, ko]", asList(languages).toString());
}
@Test
public void search() throws Exception {
// test default language and query escaping (blanks)
List<SearchResult> results = thetvdb.search("babylon 5", Locale.ENGLISH);
assertEquals(2, results.size());
SearchResult first = results.get(0);
assertEquals("Babylon 5", first.getName());
assertEquals(70726, first.getId());
}
@Test
public void searchGerman() throws Exception {
List<SearchResult> results = thetvdb.search("Buffy the Vampire Slayer", Locale.GERMAN);
assertEquals(2, results.size());
SearchResult first = results.get(0);
assertEquals("Buffy the Vampire Slayer", first.getName());
assertEquals(70327, first.getId());
}
@Test
public void getEpisodeListAll() throws Exception {
List<Episode> list = thetvdb.getEpisodeList(buffy, SortOrder.Airdate, Locale.ENGLISH);
assertEquals(145, list.size());
// 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 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 = thetvdb.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(null, first.getAbsolute()); // should be "1" but data has not yet been entered
assertEquals("2004-03-12", first.getAirdate().toString());
}
@Test
public void getEpisodeListNumbering() throws Exception {
List<Episode> list = thetvdb.getEpisodeList(firefly, SortOrder.DVD, Locale.ENGLISH);
Episode first = list.get(0);
assertEquals("Firefly", first.getSeriesName());
assertEquals("2002-09-20", first.getSeriesInfo().getStartDate().toString());
assertEquals("Serenity", first.getTitle());
assertEquals("1", first.getEpisode().toString());
assertEquals("1", first.getSeason().toString());
assertEquals("1", first.getAbsolute().toString());
assertEquals("2002-12-20", first.getAirdate().toString());
}
public void getEpisodeListLink() {
assertEquals("http://www.thetvdb.com/?tab=seasonall&id=78874", thetvdb.getEpisodeListLink(firefly).toString());
}
@Test
public void lookupByID() throws Exception {
SearchResult series = thetvdb.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);
assertEquals("Firefly", series.getName());
assertEquals(78874, series.getId());
}
@Test
public void getSeriesInfo() throws Exception {
TheTVDBSeriesInfo it = (TheTVDBSeriesInfo) thetvdb.getSeriesInfo(80348, Locale.ENGLISH);
assertEquals(80348, it.getId(), 0);
assertEquals("TV-PG", it.getContentRating());
assertEquals("2007-09-24", it.getFirstAired().toString());
assertEquals("Action", it.getGenres().get(0));
assertEquals("tt0934814", it.getImdbId());
assertEquals("en", it.getLanguage());
assertEquals(987, it.getOverview().length());
assertEquals("45", it.getRuntime().toString());
assertEquals("Chuck", it.getName());
}
@Test
public void getImages() throws Exception {
Image i = thetvdb.getImages(buffy, "fanart").get(0);
assertEquals("fanart", i.getKeyType());
assertEquals(null, i.getSubKey());
assertEquals("1280x720", i.getResolution());
assertEquals("fanart/original/70327-1.jpg", i.getFileName());
}
}