* added initial support for serienjunkies as episode list provider

This commit is contained in:
Reinhard Pointner 2011-08-05 04:38:11 +00:00
parent e163824bd0
commit d499bb01d6
12 changed files with 418 additions and 11 deletions

View File

@ -2,7 +2,7 @@
<project name="FileBot" default="fatjar">
<property name="title" value="${ant.project.name}" />
<property name="version" value="1.96.471" />
<property name="version" value="1.97" />
<tstamp>
<format property="today" pattern="yyyy-MM-dd" />
@ -65,6 +65,10 @@
<include name="org/cyberneko/html/**" />
</zipfileset>
<zipfileset src="${dir.lib}/json-simple.jar">
<include name="org/json/simple/**" />
</zipfileset>
<zipfileset src="${dir.lib}/simmetrics.jar">
<include name="uk/ac/shef/wit/simmetrics/**" />
</zipfileset>

BIN
fw/search.serienjunkies.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
lib/json-simple.jar Normal file

Binary file not shown.

View File

@ -133,11 +133,11 @@
/>
<!--
Very long-lived cache (one month!) for AniDB anime list and episode information.
Very long-lived cache (one month!) for AniDB and Serienjunkies anime list and episode information.
-->
<cache name="anidb"
<cache name="web-persistent-datasource"
maxElementsInMemory="20"
maxElementsOnDisk="120"
maxElementsOnDisk="240"
eternal="false"
timeToIdleSeconds="2628000"
timeToLiveSeconds="2628000"

View File

@ -1,7 +1,8 @@
# application settings
application.name: FileBot
application.version: 1.96
application.version: 1.97
thetvdb.apikey: 58B4AA94C59AD656
themoviedb.apikey: 66308fb6e3fd850dde4c7d21df2e8306
sublight.apikey: afa9ecb2-a3ee-42b1-9225-000b4038bc85
serienjunkies.apikey: 9fbhw9uebfiwvbefzuwv

View File

@ -8,6 +8,7 @@ import net.sourceforge.filebot.web.AnidbClient;
import net.sourceforge.filebot.web.EpisodeListProvider;
import net.sourceforge.filebot.web.IMDbClient;
import net.sourceforge.filebot.web.OpenSubtitlesClient;
import net.sourceforge.filebot.web.SerienjunkiesClient;
import net.sourceforge.filebot.web.SublightSubtitleClient;
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
import net.sourceforge.filebot.web.SubtitleProvider;
@ -28,6 +29,7 @@ public final class WebServices {
public static final TVDotComClient TVDotCom = new TVDotComClient();
public static final IMDbClient IMDb = new IMDbClient();
public static final TheTVDBClient TheTVDB = new TheTVDBClient(getApplicationProperty("thetvdb.apikey"));
public static final SerienjunkiesClient Serienjunkies = new SerienjunkiesClient(getApplicationProperty("serienjunkies.apikey"));
// subtitle dbs
public static final OpenSubtitlesClient OpenSubtitles = new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion()));
@ -36,7 +38,7 @@ public final class WebServices {
public static EpisodeListProvider[] getEpisodeListProviders() {
return new EpisodeListProvider[] { TVRage, AniDB, TVDotCom, IMDb, TheTVDB };
return new EpisodeListProvider[] { TVRage, AniDB, TVDotCom, IMDb, TheTVDB, Serienjunkies };
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

View File

@ -45,8 +45,7 @@ import net.sourceforge.filebot.ResourceManager;
public class AnidbClient implements EpisodeListProvider {
private static final String host = "anidb.net";
private static final AnidbCache cache = new AnidbCache(CacheManager.getInstance().getCache("anidb"));
private static final AnidbCache cache = new AnidbCache(CacheManager.getInstance().getCache("web-persistent-datasource"));
private final String client;
private final int clientver;
@ -205,7 +204,7 @@ public class AnidbClient implements EpisodeListProvider {
}
private List<AnidbSearchResult> getAnimeTitles() throws Exception {
protected List<AnidbSearchResult> getAnimeTitles() throws Exception {
URL url = new URL("http", host, "/api/animetitles.dat.gz");
// try cache first

View File

@ -0,0 +1,309 @@
package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.EpisodeListUtilities.*;
import static net.sourceforge.filebot.web.WebRequest.*;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javax.net.ssl.HttpsURLConnection;
import javax.swing.Icon;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import uk.ac.shef.wit.simmetrics.similaritymetrics.AbstractStringMetric;
import uk.ac.shef.wit.simmetrics.similaritymetrics.QGramsDistance;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sourceforge.filebot.ResourceManager;
public class SerienjunkiesClient implements EpisodeListProvider {
private static final String host = "api.serienjunkies.de";
private static final SerienjunkiesCache cache = new SerienjunkiesCache(CacheManager.getInstance().getCache("web-persistent-datasource"));
private final String apikey;
public SerienjunkiesClient(String apikey) {
this.apikey = apikey;
}
@Override
public List<SearchResult> search(String query) throws Exception {
// normalize
query = query.toLowerCase();
AbstractStringMetric metric = new QGramsDistance();
final List<Entry<SearchResult, Float>> resultSet = new ArrayList<Entry<SearchResult, Float>>();
for (SerienjunkiesSearchResult anime : getSeriesTitles()) {
for (String name : new String[] { anime.getMainTitle(), anime.getGermanTitle() }) {
if (name != null) {
// normalize
name = name.toLowerCase();
float similarity = metric.getSimilarity(name, query);
if (similarity > 0.5 || name.contains(query)) {
resultSet.add(new SimpleEntry<SearchResult, Float>(anime, similarity));
// add only once
break;
}
}
}
}
// sort by similarity descending (best matches first)
Collections.sort(resultSet, new Comparator<Entry<SearchResult, Float>>() {
@Override
public int compare(Entry<SearchResult, Float> o1, Entry<SearchResult, Float> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
// view for the first 20 search results
return new AbstractList<SearchResult>() {
@Override
public SearchResult get(int index) {
return resultSet.get(index).getKey();
}
@Override
public int size() {
return Math.min(20, resultSet.size());
}
};
}
protected List<SerienjunkiesSearchResult> getSeriesTitles() throws Exception {
// try cache first
List<SerienjunkiesSearchResult> seriesList = cache.getSeriesList();
if (seriesList != null)
return seriesList;
// fetch series data
Reader reader = getReader(createConnection("allseries.php?d=" + apikey));
seriesList = new ArrayList<SerienjunkiesSearchResult>();
try {
JSONObject data = (JSONObject) JSONValue.parse(reader);
JSONArray list = (JSONArray) data.get("allseries");
for (Object element : list) {
JSONObject obj = (JSONObject) element;
String sid = (String) obj.get("id");
String mainTitle = (String) obj.get("short");
String germanTitle = (String) obj.get("short_german");
seriesList.add(new SerienjunkiesSearchResult(Integer.parseInt(sid), mainTitle, germanTitle != null && germanTitle.length() > 0 ? germanTitle : null));
}
// populate cache
cache.putSeriesList(seriesList);
} finally {
reader.close();
}
return seriesList;
}
@Override
public List<Episode> getEpisodeList(SearchResult searchResult) throws Exception {
SerienjunkiesSearchResult series = (SerienjunkiesSearchResult) searchResult;
// try cache first
List<Episode> episodes = cache.getEpisodeList(series.getSeriesId());
if (episodes != null)
return episodes;
// fetch series data
Reader reader = getReader(createConnection("allepisodes.php?d=" + apikey + "&q=" + series.getSeriesId()));
episodes = new ArrayList<Episode>(25);
try {
JSONObject data = (JSONObject) JSONValue.parse(reader);
JSONArray list = (JSONArray) data.get("allepisodes");
for (int i = 0; i < list.size(); i++) {
JSONObject obj = (JSONObject) list.get(i);
String season = (String) obj.get("season");
String episode = (String) obj.get("episode");
String title = (String) obj.get("german");
episodes.add(new Episode(series.getName(), new Integer(season), new Integer(episode), title, i + 1, null, null));
}
// populate cache
cache.putEpisodeList(episodes, series.getSeriesId());
} finally {
reader.close();
}
// make sure episodes are in ordered correctly
sortEpisodes(episodes);
return episodes;
}
private HttpsURLConnection createConnection(String resource) throws IOException, GeneralSecurityException {
URL url = new URL("https", host, resource);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
// disable SSL certificate validation
connection.setSSLSocketFactory(createIgnoreCertificateSocketFactory());
return connection;
}
@Override
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
return null;
}
@Override
public URI getEpisodeListLink(SearchResult searchResult) {
return null;
}
@Override
public URI getEpisodeListLink(SearchResult searchResult, int season) {
return null;
}
@Override
public String getName() {
return "Serienjunkies";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.serienjunkies");
}
@Override
public boolean hasSingleSeasonSupport() {
return false;
}
public static class SerienjunkiesSearchResult extends SearchResult implements Serializable {
protected int sid;
protected String mainTitle;
protected String germanTitle;
protected SerienjunkiesSearchResult() {
// used by serializer
}
public SerienjunkiesSearchResult(int sid, String mainTitle, String germanTitle) {
this.sid = sid;
this.mainTitle = mainTitle;
this.germanTitle = germanTitle;
}
public int getSeriesId() {
return sid;
}
@Override
public String getName() {
return germanTitle != null ? germanTitle : mainTitle; // prefer german title
}
public String getMainTitle() {
return mainTitle;
}
public String getGermanTitle() {
return germanTitle;
}
}
private static class SerienjunkiesCache {
private final Cache cache;
public SerienjunkiesCache(Cache cache) {
this.cache = cache;
}
public void putSeriesList(Collection<SerienjunkiesSearchResult> anime) {
cache.put(new Element(host + "SeriesList", anime.toArray(new SerienjunkiesSearchResult[0])));
}
public List<SerienjunkiesSearchResult> getSeriesList() {
Element element = cache.get(host + "SeriesList");
if (element != null)
return Arrays.asList((SerienjunkiesSearchResult[]) element.getValue());
return null;
}
public void putEpisodeList(Collection<Episode> episodes, int sid) {
cache.put(new Element(host + "EpisodeList" + sid, episodes.toArray(new Episode[0])));
}
public List<Episode> getEpisodeList(int sid) {
Element element = cache.get(host + "EpisodeList" + sid);
if (element != null)
return Arrays.asList((Episode[]) element.getValue());
return null;
}
}
}

View File

@ -14,6 +14,9 @@ import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
@ -24,6 +27,10 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -219,6 +226,29 @@ public final class WebRequest {
}
public static SSLSocketFactory createIgnoreCertificateSocketFactory() throws GeneralSecurityException {
// create a trust manager that does not validate certificate chains
TrustManager trustAnyCertificate = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { trustAnyCertificate }, new SecureRandom());
return sc.getSocketFactory();
}
/**
* Dummy constructor to prevent instantiation.
*/

View File

@ -0,0 +1,62 @@
package net.sourceforge.filebot.web;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import net.sf.ehcache.CacheManager;
import net.sourceforge.filebot.web.SerienjunkiesClient.SerienjunkiesSearchResult;
public class SerienjunkiesClientTest {
private SerienjunkiesClient serienjunkies = new SerienjunkiesClient("9fbhw9uebfiwvbefzuwv");
@Test
public void search() throws Exception {
List<SearchResult> results = serienjunkies.search("alias die agentin");
assertEquals(1, results.size());
SerienjunkiesSearchResult first = (SerienjunkiesSearchResult) results.get(0);
assertEquals(34, first.getSeriesId());
assertEquals("Alias - Die Agentin", first.getName());
assertEquals("Alias", first.getMainTitle());
assertEquals("Alias - Die Agentin", first.getGermanTitle());
}
@Test
public void getEpisodeListAll() throws Exception {
List<Episode> list = serienjunkies.getEpisodeList(new SerienjunkiesSearchResult(260, "Grey's Anatomy", null));
// check ordinary episode
Episode eps = list.get(0);
assertEquals("Grey's Anatomy", eps.getSeriesName());
assertEquals("Nur 48 Stunden", eps.getTitle());
assertEquals("1", eps.getEpisode().toString());
assertEquals("1", eps.getSeason().toString());
assertEquals("1", eps.getAbsolute().toString());
// check umlaut in title
eps = list.get(2);
assertEquals("Überleben ist alles", eps.getTitle());
assertEquals("1", eps.getSeason().toString());
assertEquals("3", eps.getEpisode().toString());
assertEquals("3", eps.getAbsolute().toString());
}
@BeforeClass
@AfterClass
public static void clearCache() {
CacheManager.getInstance().clearAll();
}
}

View File

@ -8,8 +8,8 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, TMDbClientTest.class, IMDbClientTest.class, SubsceneSubtitleClientTest.class, SublightSubtitleClientTest.class,
OpenSubtitlesXmlRpcTest.class })
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SerienjunkiesClientTest.class, TMDbClientTest.class, IMDbClientTest.class, SubsceneSubtitleClientTest.class,
SublightSubtitleClientTest.class, OpenSubtitlesXmlRpcTest.class })
public class WebTestSuite {
}