* support for SubtitleSource (www.subtitlesource.org)

refactoring:
* renamed XPathUtil
* use ConcurrentMap in LanguageResolver
This commit is contained in:
Reinhard Pointner 2009-02-06 17:57:18 +00:00
parent 90f58b06ff
commit 684a7512bc
20 changed files with 377 additions and 65 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

View File

@ -2,9 +2,9 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class LanguageResolver {
@ -16,7 +16,7 @@ public class LanguageResolver {
return defaultInstance;
}
private final Map<String, Locale> cache = new HashMap<String, Locale>();
private final ConcurrentMap<String, Locale> cache = new ConcurrentHashMap<String, Locale>();
/**
@ -25,13 +25,15 @@ public class LanguageResolver {
* @param languageName english name of the language
* @return the locale for this language or null if no locale for this language exists
*/
public synchronized Locale getLocale(String languageName) {
public Locale getLocale(String languageName) {
// case insensitive
String key = languageName.toLowerCase();
Locale locale = cache.get(languageName.toLowerCase());
Locale locale = cache.get(key);
if (locale == null) {
locale = findLocale(languageName);
cache.put(languageName.toLowerCase(), locale);
cache.put(key, locale);
}
return locale;
@ -62,7 +64,7 @@ public class LanguageResolver {
* @return {@link Locale} for the given language, or null if no matching {@link Locale} is
* available
*/
private Locale findLocale(String languageName) {
protected Locale findLocale(String languageName) {
for (Locale locale : Locale.getAvailableLocales()) {
if (locale.getDisplayLanguage(Locale.ENGLISH).equalsIgnoreCase(languageName))
return locale;

View File

@ -20,6 +20,7 @@ import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
import net.sourceforge.filebot.web.SubtitleClient;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.filebot.web.SubtitleSourceClient;
import net.sourceforge.tuned.ListChangeSynchronizer;
import net.sourceforge.tuned.ui.LabelProvider;
import net.sourceforge.tuned.ui.SimpleLabelProvider;
@ -51,6 +52,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
engines.add(new OpenSubtitlesSubtitleClient(String.format("%s v%s", getApplicationName(), getApplicationVersion())));
engines.add(new SubsceneSubtitleClient());
engines.add(new SubtitleSourceClient());
return engines;
}
@ -131,9 +133,14 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
@Override
public void process(Collection<SubtitlePackage> episodes) {
//TODO subtitle tab ui
System.out.println(episodes);
public void process(Collection<SubtitlePackage> subtitles) {
getComponent().getPackagePanel().getModel().addAll(subtitles);
}
@Override
public SubtitleDownloadPanel getComponent() {
return (SubtitleDownloadPanel) super.getComponent();
}

View File

@ -3,10 +3,10 @@ package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
import static net.sourceforge.tuned.XPathUtil.exists;
import static net.sourceforge.tuned.XPathUtil.selectNode;
import static net.sourceforge.tuned.XPathUtil.selectNodes;
import static net.sourceforge.tuned.XPathUtil.selectString;
import static net.sourceforge.tuned.XPathUtilities.exists;
import static net.sourceforge.tuned.XPathUtilities.selectNode;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import static net.sourceforge.tuned.XPathUtilities.selectString;
import java.io.IOException;
import java.net.MalformedURLException;

View File

@ -17,4 +17,15 @@ public class MovieDescriptor extends SearchResult {
return imdbId;
}
@Override
public boolean equals(Object object) {
if (object instanceof MovieDescriptor) {
MovieDescriptor other = (MovieDescriptor) object;
return this.getImdbId() == other.getImdbId() && this.getName() == other.getName();
}
return super.equals(object);
}
}

View File

@ -8,9 +8,6 @@ import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.tuned.DownloadTask;
@ -96,32 +93,13 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
}
@Override
public String getAuthor() {
return properties.get(Property.UserNickName);
}
public long getSize() {
return Long.parseLong(properties.get(Property.SubSize));
}
public URL getDownloadLink() {
String link = properties.get(Property.ZipDownloadLink);
try {
return new URL(link);
} catch (MalformedURLException e) {
Logger.getLogger("global").log(Level.WARNING, "Invalid download link: " + link);
return null;
}
}
@Override
public DownloadTask createDownloadTask() {
return new DownloadTask(getDownloadLink());
try {
return new DownloadTask(new URL(properties.get(Property.ZipDownloadLink)));
} catch (MalformedURLException e) {
throw new UnsupportedOperationException(e);
}
}

View File

@ -24,4 +24,5 @@ public abstract class SearchResult implements Serializable {
public String toString() {
return name;
}
}

View File

@ -3,9 +3,9 @@ package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
import static net.sourceforge.tuned.XPathUtil.selectNode;
import static net.sourceforge.tuned.XPathUtil.selectNodes;
import static net.sourceforge.tuned.XPathUtil.selectString;
import static net.sourceforge.tuned.XPathUtilities.selectNode;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import static net.sourceforge.tuned.XPathUtilities.selectString;
import java.io.IOException;
import java.net.MalformedURLException;

View File

@ -65,7 +65,7 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
@Override
public String toString() {
return String.format("%s [%s]", title, language);
return String.format("%s [%s]", getName(), getLanguageName());
}
}

View File

@ -13,9 +13,6 @@ public interface SubtitleDescriptor {
public String getLanguageName();
public String getAuthor();
public String getArchiveType();

View File

@ -0,0 +1,151 @@
package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.WebRequest.getDocument;
import static net.sourceforge.tuned.XPathUtilities.getTextContent;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.Icon;
import net.sourceforge.filebot.ResourceManager;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class SubtitleSourceClient implements SubtitleClient {
protected static final String HOST = "www.subtitlesource.org";
private static final int PAGE_SIZE = 20;
@Override
public String getName() {
return "SubtitleSource";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.subtitlesource");
}
@Override
public List<SearchResult> search(String query) throws Exception {
return search(query, "all");
}
public List<SearchResult> search(String query, String language) throws Exception {
// e.g. http://www.subtitlesource.org/api/xmlsearch/firefly/all/0
URL url = new URL("http", HOST, "/api/xmlsearch/" + URLEncoder.encode(query, "utf-8") + "/" + language + "/0");
Document dom = getDocument(url);
Map<Integer, String> movieMap = new LinkedHashMap<Integer, String>();
for (Node node : selectNodes("//sub", dom)) {
Integer imdb = Integer.valueOf(getTextContent("imdb", node));
if (!movieMap.containsKey(imdb)) {
String title = getTextContent("title", node);
movieMap.put(imdb, title);
}
}
// create SearchResult collection
List<SearchResult> result = new ArrayList<SearchResult>();
for (Entry<Integer, String> movie : movieMap.entrySet()) {
result.add(new MovieDescriptor(movie.getValue(), movie.getKey()));
}
return result;
}
@Override
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception {
// english language name or null
String languageFilter = (language == null || language == Locale.ROOT) ? null : language.getDisplayLanguage(Locale.ENGLISH);
List<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>();
for (SubtitleDescriptor subtitle : getSubtitleList(searchResult)) {
if (languageFilter == null || languageFilter.equalsIgnoreCase(subtitle.getLanguageName())) {
subtitles.add(subtitle);
}
}
return subtitles;
}
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
List<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>();
for (int offset = 0; true; offset += PAGE_SIZE) {
List<SubtitleDescriptor> page = getSubtitleList(searchResult, offset);
// add new subtitles
subtitles.addAll(page);
if (page.size() < PAGE_SIZE) {
// last page reached
return subtitles;
}
}
}
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, int offset) throws Exception {
int imdb = ((MovieDescriptor) searchResult).getImdbId();
// e.g. http://www.subtitlesource.org/api/xmlsearch/0303461/imdb/0
URL url = new URL("http", HOST, "/api/xmlsearch/" + imdb + "/imdb/" + offset);
Document dom = getDocument(url);
List<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>();
for (Node node : selectNodes("//sub", dom)) {
int id = Integer.parseInt(getTextContent("id", node));
String releaseName = getTextContent("releasename", node);
String language = getTextContent("language", node);
String title = getTextContent("title", node);
int season = Integer.parseInt(getTextContent("season", node));
int episode = Integer.parseInt(getTextContent("episode", node));
subtitles.add(new SubtitleSourceSubtitleDescriptor(id, releaseName, language, title, season, episode));
}
return subtitles;
}
@Override
public URI getSubtitleListLink(SearchResult searchResult, Locale language) {
int imdb = ((MovieDescriptor) searchResult).getImdbId();
try {
return new URI("http://" + HOST + "/title/" + String.format("tt%07d", imdb));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,90 @@
package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.SubtitleSourceClient.HOST;
import java.net.MalformedURLException;
import java.net.URL;
import net.sourceforge.tuned.DownloadTask;
public class SubtitleSourceSubtitleDescriptor implements SubtitleDescriptor {
private final int id;
private final String releaseName;
private final String language;
private final String title;
private final int season;
private final int episode;
public SubtitleSourceSubtitleDescriptor(int id, String releaseName, String language, String title, int season, int episode) {
this.id = id;
this.releaseName = releaseName;
this.language = language;
this.title = title;
this.season = season;
this.episode = episode;
}
@Override
public String getName() {
if (releaseName == null || releaseName.isEmpty()) {
if (season == 0 && episode == 0) {
return title;
}
StringBuilder sb = new StringBuilder(title).append(" - ");
if (season != 0) {
sb.append(season);
if (episode != 0) {
sb.append("x").append(episode);
}
} else {
// episode cannot be 0 at this point
sb.append(episode);
}
return sb.toString();
}
return releaseName;
}
@Override
public String getLanguageName() {
return language;
}
@Override
public DownloadTask createDownloadTask() {
try {
// e.g. http://www.subtitlesource.org/download/zip/760
return new DownloadTask(new URL("http", HOST, "/download/zip/" + id));
} catch (MalformedURLException e) {
throw new UnsupportedOperationException(e);
}
}
@Override
public String getArchiveType() {
return "zip";
}
@Override
public String toString() {
return String.format("%s [%s]", getName(), getLanguageName());
}
}

View File

@ -3,8 +3,8 @@ package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
import static net.sourceforge.tuned.XPathUtil.selectNodes;
import static net.sourceforge.tuned.XPathUtil.selectString;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import static net.sourceforge.tuned.XPathUtilities.selectString;
import java.io.IOException;
import java.net.URI;

View File

@ -3,10 +3,10 @@ package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.WebRequest.getDocument;
import static net.sourceforge.tuned.XPathUtil.getTextContent;
import static net.sourceforge.tuned.XPathUtil.selectInteger;
import static net.sourceforge.tuned.XPathUtil.selectNodes;
import static net.sourceforge.tuned.XPathUtil.selectString;
import static net.sourceforge.tuned.XPathUtilities.getTextContent;
import static net.sourceforge.tuned.XPathUtilities.selectInteger;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import static net.sourceforge.tuned.XPathUtilities.selectString;
import java.io.IOException;
import java.net.URI;

View File

@ -3,14 +3,15 @@ package net.sourceforge.filebot.web;
import static net.sourceforge.filebot.web.WebRequest.getDocument;
import static net.sourceforge.tuned.XPathUtil.getTextContent;
import static net.sourceforge.tuned.XPathUtil.selectInteger;
import static net.sourceforge.tuned.XPathUtil.selectNodes;
import static net.sourceforge.tuned.XPathUtil.selectString;
import static net.sourceforge.tuned.XPathUtilities.getTextContent;
import static net.sourceforge.tuned.XPathUtilities.selectInteger;
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
import static net.sourceforge.tuned.XPathUtilities.selectString;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
@ -205,7 +206,11 @@ public class TheTVDBClient implements EpisodeListClient {
public URI getEpisodeListLink(SearchResult searchResult) {
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
return URI.create("http://www.thetvdb.com/?tab=seasonall&id=" + seriesId);
try {
return new URI("http://" + host + "/?tab=seasonall&id=" + seriesId);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@ -225,9 +230,12 @@ public class TheTVDBClient implements EpisodeListClient {
cache.putSeasonId(seriesId, season, seasonId);
}
return new URI("http://www.thetvdb.com/?tab=season&seriesid=" + seriesId + "&seasonid=" + seasonId);
} catch (Exception e) {
return new URI("http://" + host + "/?tab=season&seriesid=" + seriesId + "&seasonid=" + seasonId);
} catch (IOException e) {
// log and ignore any IOException
Logger.getLogger("global").log(Level.WARNING, "Failed to retrieve season id", e);
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;

View File

@ -14,7 +14,7 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public final class XPathUtil {
public final class XPathUtilities {
public static Node selectNode(String xpath, Object node) {
try {
@ -95,7 +95,7 @@ public final class XPathUtil {
/**
* Dummy constructor to prevent instantiation.
*/
private XPathUtil() {
private XPathUtilities() {
throw new UnsupportedOperationException();
}

View File

@ -30,7 +30,7 @@ public class SubsceneSubtitleClientTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
twinpeaksSearchResult = new SubsceneSearchResult("Twin Peaks - First Season (1990)", new URL("http://subscene.com/twin-peaks--first-season/subtitles-32482.aspx"), 17);
twinpeaksSearchResult = new SubsceneSearchResult("Twin Peaks - First Season (1990)", new URL("http://subscene.com/twin-peaks--first-season/subtitles-32482.aspx"), 18);
lostSearchResult = new SubsceneSearchResult("Lost - Fourth Season (2008)", new URL("http://subscene.com/Lost-Fourth-Season/subtitles-70963.aspx"), 420);
}

View File

@ -0,0 +1,67 @@
package net.sourceforge.filebot.web;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Locale;
import org.junit.Test;
public class SubtitleSourceClientTest {
private static final SubtitleSourceClient client = new SubtitleSourceClient();
@Test
public void search() throws Exception {
List<SearchResult> list = client.search("babylon 5");
MovieDescriptor sample = (MovieDescriptor) list.get(0);
// check sample entry
assertEquals("Babylon 5", sample.getName());
assertEquals(105946, sample.getImdbId());
// check page size
assertEquals(1, list.size());
}
@Test
public void getSubtitleListAll() throws Exception {
List<SubtitleDescriptor> list = client.getSubtitleList(new MovieDescriptor("Buffy", 118276), Locale.ENGLISH);
SubtitleDescriptor sample = list.get(0);
// check sample entry (order is unpredictable)
assertTrue(sample.getName().startsWith("Buffy"));
assertEquals("English", sample.getLanguageName());
// check size
assertTrue(list.size() > 100);
}
@Test
public void getSubtitleListSinglePage() throws Exception {
List<SubtitleDescriptor> list = client.getSubtitleList(new MovieDescriptor("Firefly", 303461), 0);
SubtitleDescriptor sample = list.get(0);
// check sample entry (order is unpredictable)
assertTrue(sample.getName().startsWith("Firefly"));
// check page size
assertEquals(20, list.size());
}
@Test
public void getSubtitleListLink() {
assertEquals("http://www.subtitlesource.org/title/tt0303461", client.getSubtitleListLink(new MovieDescriptor("Firefly", 303461), null).toString());
}
}

View File

@ -8,7 +8,7 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SubsceneSubtitleClientTest.class, OpenSubtitlesHasherTest.class })
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SubsceneSubtitleClientTest.class, SubtitleSourceClientTest.class, OpenSubtitlesHasherTest.class })
public class WebTestSuite {
}