+ replace TVRage with TVmaze

This commit is contained in:
Reinhard Pointner 2015-11-04 08:53:52 +00:00
parent bd5a5a6fc6
commit a2c84e22dc
15 changed files with 265 additions and 226 deletions

View File

@ -20,7 +20,6 @@
<classpathentry kind="lib" path="lib/ivy/jar/xz.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/slf4j-api.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/commons-io.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/json-io.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/jsoup.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/groovy-all.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/slf4j-jdk14.jar"/>
@ -30,5 +29,6 @@
<classpathentry kind="lib" path="lib/ivy/jar/commons-vfs2.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/commons-logging.jar"/>
<classpathentry kind="lib" path="lib/ivy/jar/sevenzipjbinding.jar"/>
<classpathentry kind="lib" path="lib/ivy/bundle/json-io.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -83,7 +83,7 @@
<include name="org/json/simple/**" />
</zipfileset>
<zipfileset src="${dir.lib}/ivy/jar/json-io.jar">
<zipfileset src="${dir.lib}/ivy/bundle/json-io.jar">
<include name="com/cedarsoftware/util/**" />
</zipfileset>

View File

@ -11,7 +11,7 @@
<dependency org="com.ibm.icu" name="icu4j" rev="55.1" />
<dependency org="org.jsoup" name="jsoup" rev="1.8.2" />
<dependency org="org.tukaani" name="xz" rev="1.5" />
<dependency org="com.cedarsoftware" name="json-io" rev="3.0.2" />
<dependency org="com.cedarsoftware" name="json-io" rev="4.1.9" />
<dependency org="com.googlecode.json-simple" name="json-simple" rev="1.1.1" />
<dependency org="commons-io" name="commons-io" rev="2.4" />
<dependency org="commons-net" name="commons-net" rev="3.3" />

View File

@ -19,14 +19,14 @@
/>
<!--
Short-lived (12 hours) persistent disk cache for web responses
Short-lived (24 hours) persistent disk cache for web responses
-->
<cache name="web-datasource"
maxElementsInMemory="200"
maxElementsOnDisk="80000"
eternal="false"
timeToIdleSeconds="43200"
timeToLiveSeconds="43200"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
overflowToDisk="true"
diskPersistent="true"
memoryStoreEvictionPolicy="LRU"
@ -50,7 +50,7 @@
Long-lived (2 months) persistent disk cache for web responses (that can be updated via If-Modified or If-None-Match)
-->
<cache name="web-datasource-lv3"
maxElementsInMemory="200"
maxElementsInMemory="100"
maxElementsOnDisk="200000"
eternal="false"
timeToIdleSeconds="5256000"

View File

@ -29,7 +29,6 @@ link.help.mas: https://www.filebot.net/forums/viewforum.php?f=12
# api keys for webservices
apikey.fanart.tv: 780b986b22c35e6f7a134a2f392c2deb
apikey.thetvdb: 694FAD89942D3827
apikey.tvrage: 5AhRvLfKAP10unE9Vnfr
apikey.themoviedb: 66308fb6e3fd850dde4c7d21df2e8306
apikey.acoustid: 0B3qZnQc
apikey.anidb: filebot
@ -37,7 +36,6 @@ apikey.opensubtitles: FileBot
# api keys used by the appstore distribution
apikey.appstore.thetvdb: 5F6E873A88CF70D9
apikey.appstore.tvrage: mugksCbTg35PLBIhCXdG
apikey.appstore.themoviedb: 28bce8224bd3282a41bec4c5df528249
apikey.appstore.acoustid: VCujaMw9
apikey.appstore.anidb: filebotappstore

View File

@ -38,6 +38,7 @@ import net.filebot.web.ShooterSubtitles;
import net.filebot.web.SubtitleProvider;
import net.filebot.web.SubtitleSearchResult;
import net.filebot.web.TMDbClient;
import net.filebot.web.TVMazeClient;
import net.filebot.web.TheTVDBClient;
import net.filebot.web.TheTVDBSearchResult;
import net.filebot.web.VideoHashSubtitleService;
@ -48,6 +49,7 @@ import net.filebot.web.VideoHashSubtitleService;
public final class WebServices {
// episode dbs
public static final TVMazeClient TVmaze = new TVMazeClient();
public static final AnidbClient AniDB = new AnidbClientWithLocalSearch(getApiKey("anidb"), 6);
// extended TheTVDB module with local search
@ -67,7 +69,7 @@ public final class WebServices {
public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider();
public static EpisodeListProvider[] getEpisodeListProviders() {
return new EpisodeListProvider[] { TheTVDB, AniDB };
return new EpisodeListProvider[] { TheTVDB, AniDB, TVmaze };
}
public static MovieIdentificationService[] getMovieIdentificationServices() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

View File

@ -0,0 +1,57 @@
package net.filebot.web;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import com.cedarsoftware.util.io.JsonObject;
import com.cedarsoftware.util.io.JsonReader;
public class CachedJsonResource extends AbstractCachedResource<String, String> {
public CachedJsonResource(String resource) {
super(resource, String.class, ONE_DAY, 2, 1000);
}
public CachedJsonResource(String resource, long expirationTime, int retryCountLimit, long retryWaitTime) {
super(resource, String.class, expirationTime, retryCountLimit, retryWaitTime);
}
@Override
protected Cache getCache() {
return CacheManager.getInstance().getCache("web-datasource-lv3");
}
public JsonObject<?, ?> getJSON() throws IOException {
try {
return (JsonObject<?, ?>) JsonReader.jsonToMaps(get());
} catch (Exception e) {
throw new IOException(String.format("Error while loading JSON resource: %s (%s)", getResourceLocation(resource), e.getMessage()));
}
}
@Override
public String process(String data) throws IOException {
try {
JsonReader.jsonToMaps(data); // make sure JSON is valid
} catch (Exception e) {
throw new IOException(String.format("Malformed JSON: %s (%s)", getResourceLocation(resource), e.getMessage()));
}
return data;
}
@Override
protected String fetchData(URL url, long lastModified) throws IOException {
ByteBuffer data = WebRequest.fetchIfModified(url, lastModified);
if (data == null)
return null; // not modified
return StandardCharsets.UTF_8.decode(data).toString();
}
}

View File

@ -20,7 +20,7 @@ import org.xml.sax.helpers.DefaultHandler;
public class CachedXmlResource extends AbstractCachedResource<String, String> {
public CachedXmlResource(String resource) {
super(resource, String.class, ONE_WEEK, 2, 1000);
super(resource, String.class, ONE_DAY, 2, 1000);
}
public CachedXmlResource(String resource, long expirationTime, int retryCountLimit, long retryWaitTime) {

View File

@ -0,0 +1,148 @@
package net.filebot.web;
import static net.filebot.web.WebRequest.*;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.swing.Icon;
import net.filebot.Cache;
import net.filebot.ResourceManager;
import com.cedarsoftware.util.io.JsonObject;
public class TVMazeClient extends AbstractEpisodeListProvider {
@Override
public String getName() {
return "TVmaze";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.tvmaze");
}
@Override
public boolean hasSeasonSupport() {
return true;
}
@Override
protected SortOrder vetoRequestParameter(SortOrder order) {
return SortOrder.Airdate;
}
@Override
protected Locale vetoRequestParameter(Locale language) {
return Locale.ENGLISH;
}
@Override
protected SearchResult createSearchResult(int id) {
return new TVMazeSearchResult(id, null);
}
@Override
public ResultCache getCache() {
return new ResultCache(getName(), Cache.getCache("web-datasource"));
}
@Override
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws IOException {
// e.g. http://api.tvmaze.com/search/shows?q=girls
JsonObject<?, ?> response = request("search/shows?q=" + encode(query, true));
List<SearchResult> results = new ArrayList<SearchResult>();
if (response.isArray()) {
for (Object result : response.getArray()) {
Map<?, ?> show = (Map<?, ?>) ((Map<?, ?>) result).get("show");
int id = Integer.parseInt(show.get("id").toString());
String name = show.get("name").toString();
// FUTURE WORK: consider adding TVmaze aka titles for each result, e.g. http://api.tvmaze.com/shows/1/akas
results.add(new TVMazeSearchResult(id, name));
}
}
return results;
}
protected SeriesInfo fetchSeriesInfo(TVMazeSearchResult show, SortOrder sortOrder, Locale locale) throws IOException {
// e.g. http://api.tvmaze.com/shows/1
JsonObject<?, ?> response = request("shows/" + show.getId());
String status = (String) response.get("status");
String runtime = response.get("runtime").toString();
String premiered = (String) response.get("premiered");
JsonObject<?, ?> genres = (JsonObject<?, ?>) response.get("genres");
JsonObject<?, ?> rating = (JsonObject<?, ?>) response.get("rating");
SeriesInfo seriesInfo = new SeriesInfo(getName(), sortOrder, locale, show.getId());
seriesInfo.setName(show.getName());
seriesInfo.setAliasNames(show.getEffectiveNames());
seriesInfo.setStatus(status);
seriesInfo.setRuntime(new Integer(runtime));
seriesInfo.setStartDate(SimpleDate.parse(premiered, "yyyy-MM-dd"));
if (genres != null && genres.isArray()) {
seriesInfo.setGenres(Arrays.stream(genres.getArray()).map(Objects::toString).collect(Collectors.toList()));
}
if (rating != null && rating.isMap() && !rating.isEmpty()) {
seriesInfo.setRating(new Double((String) rating.get("average")));
}
return seriesInfo;
}
@Override
protected SeriesData fetchSeriesData(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
TVMazeSearchResult show = (TVMazeSearchResult) searchResult;
SeriesInfo seriesInfo = fetchSeriesInfo(show, sortOrder, locale);
// e.g. http://api.tvmaze.com/shows/1/episodes
JsonObject<?, ?> response = request("shows/" + show.getId() + "/episodes");
List<Episode> episodes = new ArrayList<Episode>(25);
if (response.isArray()) {
for (Object element : response.getArray()) {
JsonObject<?, ?> episode = (JsonObject<?, ?>) element;
try {
String episodeTitle = episode.get("name").toString();
Integer seasonNumber = Integer.parseInt(episode.get("season").toString());
Integer episodeNumber = Integer.parseInt(episode.get("number").toString());
SimpleDate airdate = SimpleDate.parse(episode.get("airdate").toString(), "yyyy-MM-dd");
episodes.add(new Episode(seriesInfo.getName(), seasonNumber, episodeNumber, episodeTitle, null, null, airdate, new SeriesInfo(seriesInfo)));
} catch (Exception e) {
Logger.getLogger(TVMazeClient.class.getName()).log(Level.WARNING, "Illegal episode data: " + e + ": " + episode);
}
}
}
return new SeriesData(seriesInfo, episodes);
}
public JsonObject<?, ?> request(String resource) throws IOException {
return new CachedJsonResource("http://api.tvmaze.com/" + resource).getJSON();
}
@Override
public URI getEpisodeListLink(SearchResult searchResult) {
return URI.create("http://www.tvmaze.com/shows/" + ((TVMazeSearchResult) searchResult).getId());
}
}

View File

@ -0,0 +1,40 @@
package net.filebot.web;
public class TVMazeSearchResult extends SearchResult {
protected int id;
protected TVMazeSearchResult() {
// used by serializer
}
public TVMazeSearchResult(int id, String name) {
super(name, new String[0]);
this.id = id;
}
public int getId() {
return id;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object object) {
if (object instanceof TVMazeSearchResult) {
TVMazeSearchResult other = (TVMazeSearchResult) object;
return this.id == other.id;
}
return false;
}
@Override
public TVMazeSearchResult clone() {
return new TVMazeSearchResult(id, name);
}
}

View File

@ -1,155 +0,0 @@
package net.filebot.web;
import static java.util.Collections.*;
import static net.filebot.util.XPathUtilities.*;
import static net.filebot.web.EpisodeUtilities.*;
import static net.filebot.web.WebRequest.*;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.swing.Icon;
import net.filebot.Cache;
import net.filebot.ResourceManager;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
public class TVRageClient extends AbstractEpisodeListProvider {
private String host = "services.tvrage.com";
private String apikey;
public TVRageClient(String apikey) {
this.apikey = apikey;
}
@Override
public String getName() {
return "TVRage";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.tvrage");
}
@Override
public boolean hasSeasonSupport() {
return true;
}
@Override
protected SortOrder vetoRequestParameter(SortOrder order) {
return SortOrder.Airdate;
}
@Override
protected Locale vetoRequestParameter(Locale language) {
return Locale.ENGLISH;
}
@Override
public ResultCache getCache() {
return new ResultCache(getName(), Cache.getCache("web-datasource"));
}
@Override
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws IOException, SAXException {
Document dom = request("/feeds/full_search.php", singletonMap("show", query));
List<Node> nodes = selectNodes("Results/show", dom);
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
for (Node node : nodes) {
int showid = Integer.parseInt(getTextContent("showid", node));
String name = getTextContent("name", node);
String link = getTextContent("link", node);
searchResults.add(new TVRageSearchResult(name, showid, link));
}
return searchResults;
}
@Override
protected SeriesData fetchSeriesData(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
TVRageSearchResult series = (TVRageSearchResult) searchResult;
Document dom = request("/feeds/full_show_info.php", singletonMap("sid", series.getId()));
// parse series data
Node seriesNode = selectNode("Show", dom);
SeriesInfo seriesInfo = new SeriesInfo(getName(), sortOrder, locale, series.getId());
seriesInfo.setAliasNames(searchResult.getEffectiveNames());
seriesInfo.setName(getTextContent("name", seriesNode));
seriesInfo.setNetwork(getTextContent("network", seriesNode));
seriesInfo.setRuntime(getInteger(getTextContent("runtime", seriesNode)));
seriesInfo.setStatus(getTextContent("status", seriesNode));
seriesInfo.setGenres(getListContent("genre", null, getChild("genres", seriesNode)));
seriesInfo.setStartDate(SimpleDate.parse(selectString("started", seriesNode), "MMM/dd/yyyy"));
// parse episode data
List<Episode> episodes = new ArrayList<Episode>(25);
List<Episode> specials = new ArrayList<Episode>(5);
// episodes and specials
for (Node node : selectNodes("//episode", dom)) {
String title = getTextContent("title", node);
Integer episodeNumber = getInteger(getTextContent("seasonnum", node));
String seasonIdentifier = getAttribute("no", node.getParentNode());
Integer seasonNumber = seasonIdentifier == null ? null : new Integer(seasonIdentifier);
SimpleDate airdate = SimpleDate.parse(getTextContent("airdate", node), "yyyy-MM-dd");
// check if we have season and episode number, if not it must be a special episode
if (episodeNumber == null || seasonNumber == null) {
// handle as special episode
seasonNumber = getInteger(getTextContent("season", node));
int specialNumber = filterBySeason(specials, seasonNumber).size() + 1;
specials.add(new Episode(seriesInfo.getName(), seasonNumber, null, title, null, specialNumber, airdate, new SeriesInfo(seriesInfo)));
} else {
// handle as normal episode
if (sortOrder == SortOrder.Absolute) {
episodeNumber = getInteger(getTextContent("epnum", node));
seasonNumber = null;
}
episodes.add(new Episode(seriesInfo.getName(), seasonNumber, episodeNumber, title, null, null, airdate, new SeriesInfo(seriesInfo)));
}
}
// add specials at the end
episodes.addAll(specials);
return new SeriesData(seriesInfo, episodes);
}
public Document request(String resource, Map<String, Object> parameters) throws IOException, SAXException {
Map<String, Object> param = new LinkedHashMap<String, Object>(parameters);
if (apikey != null) {
param.put("key", apikey);
}
URL url = new URL("http", host, resource + "?" + encodeParameters(param, true));
return getDocument(url);
}
@Override
protected SearchResult createSearchResult(int id) {
return new TVRageSearchResult(null, id, null);
}
@Override
public URI getEpisodeListLink(SearchResult searchResult) {
return URI.create(((TVRageSearchResult) searchResult).getLink() + "/episode_list/all");
}
}

View File

@ -1,50 +0,0 @@
package net.filebot.web;
public class TVRageSearchResult extends SearchResult {
protected int showId;
protected String link;
protected TVRageSearchResult() {
// used by serializer
}
public TVRageSearchResult(String name, int showId, String link) {
super(name, new String[0]);
this.showId = showId;
this.link = link;
}
public int getId() {
return showId;
}
public int getSeriesId() {
return showId;
}
public String getLink() {
return link;
}
@Override
public int hashCode() {
return showId;
}
@Override
public boolean equals(Object object) {
if (object instanceof TVRageSearchResult) {
TVRageSearchResult other = (TVRageSearchResult) object;
return this.showId == other.showId;
}
return false;
}
@Override
public TVRageSearchResult clone() {
return new TVRageSearchResult(name, showId, link);
}
}

View File

@ -7,29 +7,28 @@ import java.util.Locale;
import org.junit.Test;
public class TVRageClientTest {
public class TVMazeClientTest {
/**
* 145 episodes / 7 seasons
*/
private static TVRageSearchResult buffySearchResult = new TVRageSearchResult("Buffy the Vampire Slayer", 2930, "http://www.tvrage.com/Buffy_The_Vampire_Slayer");
private static TVMazeSearchResult buffySearchResult = new TVMazeSearchResult(427, "Buffy the Vampire Slayer");
@Test
public void search() throws Exception {
List<SearchResult> results = tvrage.search("Buffy", Locale.ENGLISH);
List<SearchResult> results = client.search("Buffy", Locale.ENGLISH);
TVRageSearchResult result = (TVRageSearchResult) results.get(0);
TVMazeSearchResult result = (TVMazeSearchResult) results.get(0);
assertEquals(buffySearchResult.getName(), result.getName());
assertEquals(buffySearchResult.getSeriesId(), result.getSeriesId());
assertEquals(buffySearchResult.getLink(), result.getLink());
assertEquals(buffySearchResult.getId(), result.getId());
}
private TVRageClient tvrage = new TVRageClient("5AhRvLfKAP10unE9Vnfr");
private TVMazeClient client = new TVMazeClient();
@Test
public void getEpisodeList() throws Exception {
List<Episode> list = EpisodeUtilities.filterBySeason(tvrage.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH), 7);
List<Episode> list = EpisodeUtilities.filterBySeason(client.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH), 7);
assertEquals(22, list.size());
@ -46,7 +45,7 @@ public class TVRageClientTest {
@Test
public void getEpisodeListAll() throws Exception {
List<Episode> list = tvrage.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH);
List<Episode> list = client.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH);
assertEquals(143, list.size());
@ -62,7 +61,7 @@ public class TVRageClientTest {
@Test
public void getEpisodeListLinkAll() throws Exception {
assertEquals(tvrage.getEpisodeListLink(buffySearchResult).toString(), "http://www.tvrage.com/Buffy_The_Vampire_Slayer/episode_list/all");
assertEquals("http://www.tvmaze.com/shows/427", client.getEpisodeListLink(buffySearchResult).toString());
}
}