* ignore non-subtitles files when extracting archives

* download subtitle package on keystroke ENTER
* renamed OpenSubtitlesClient to OpenSubtitlesXmlRpc
* renamed OpenSubtitlesSubtitleClient to OpenSubtitlesClient
* refactoring
This commit is contained in:
Reinhard Pointner 2009-07-03 10:06:33 +00:00
parent 19d5b576db
commit f897837811
13 changed files with 421 additions and 354 deletions

View File

@ -3,7 +3,6 @@ package net.sourceforge.filebot.subtitle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -40,8 +39,10 @@ public class MicroDVDReader extends SubtitleReader {
from = to + 1; from = to + 1;
} }
if (properties.size() < 2) if (properties.size() < 2) {
// ignore illegal lines
return null; return null;
}
long startFrame = Long.parseLong(properties.get(0)); long startFrame = Long.parseLong(properties.get(0));
long endFrame = Long.parseLong(properties.get(1)); long endFrame = Long.parseLong(properties.get(1));
@ -56,7 +57,7 @@ public class MicroDVDReader extends SubtitleReader {
} }
// translate '|' to new lines // translate '|' to new lines
List<String> lines = Arrays.asList(text.split(Pattern.quote("|"))); String[] lines = text.split(Pattern.quote("|"));
// convert frame interval to time interval // convert frame interval to time interval
return new SubtitleElement(Math.round(startFrame * fps), Math.round(endFrame * fps), join(lines, "\n")); return new SubtitleElement(Math.round(startFrame * fps), Math.round(endFrame * fps), join(lines, "\n"));

View File

@ -44,7 +44,7 @@ public class SubRipReader extends SubtitleReader {
lines.add(line); lines.add(line);
} }
return new SubtitleElement(t1, t2, join(lines, "\n")); return new SubtitleElement(t1, t2, join(lines.toArray(), "\n"));
} }
} }

View File

@ -74,9 +74,10 @@ public class SubStationAlphaReader extends SubtitleReader {
long end = timeFormat.parse(row[format.get("End")]).getTime(); long end = timeFormat.parse(row[format.get("End")]).getTime();
String text = row[format.get("Text")].trim(); String text = row[format.get("Text")].trim();
// translate "\\n" to new lines
String[] lines = Pattern.compile(Pattern.quote("\\N"), Pattern.CASE_INSENSITIVE).split(text); String[] lines = Pattern.compile(Pattern.quote("\\N"), Pattern.CASE_INSENSITIVE).split(text);
return new SubtitleElement(start, end, join(Arrays.asList(lines), "\n")); return new SubtitleElement(start, end, join(lines, "\n"));
} }
} }

View File

@ -3,8 +3,7 @@ package net.sourceforge.filebot.subtitle;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner; import java.util.Scanner;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -24,20 +23,22 @@ public class SubViewerReader extends SubtitleReader {
// element starts with interval (e.g. 00:42:16.33,00:42:19.39) // element starts with interval (e.g. 00:42:16.33,00:42:19.39)
String[] interval = scanner.nextLine().split(",", 2); String[] interval = scanner.nextLine().split(",", 2);
if (interval.length < 2 || interval[0].startsWith("[")) if (interval.length < 2 || interval[0].startsWith("[")) {
// ignore property lines
return null; return null;
}
try {
long t1 = timeFormat.parse(interval[0]).getTime(); long t1 = timeFormat.parse(interval[0]).getTime();
long t2 = timeFormat.parse(interval[1]).getTime(); long t2 = timeFormat.parse(interval[1]).getTime();
// append subtitle line // translate [br] to new lines
List<String> lines = new ArrayList<String>(2); String[] lines = scanner.nextLine().split(Pattern.quote("[br]"));
for (String text : scanner.nextLine().split(Pattern.quote("[br]"))) {
lines.add(text);
}
return new SubtitleElement(t1, t2, join(lines, "\n")); return new SubtitleElement(t1, t2, join(lines, "\n"));
} catch (InputMismatchException e) {
// can't parse interval, ignore line
return null;
}
} }
} }

View File

@ -40,14 +40,6 @@ public enum SubtitleFormat {
public SubtitleReader newReader(Readable readable) { public SubtitleReader newReader(Readable readable) {
return new SubStationAlphaReader(new Scanner(readable)); return new SubStationAlphaReader(new Scanner(readable));
} }
},
SAMI {
@Override
public SubtitleReader newReader(Readable readable) {
throw new UnsupportedOperationException("SAMI reader not implemented");
}
}; };
public abstract SubtitleReader newReader(Readable readable); public abstract SubtitleReader newReader(Readable readable);

View File

@ -65,13 +65,13 @@ public abstract class SubtitleReader implements Iterator<SubtitleElement>, Close
} }
protected String join(Iterable<?> values, String delimiter) { protected String join(Object[] values, String delimiter) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) { for (int i = 0; i < values.length; i++) {
sb.append(iterator.next()); sb.append(values[i]);
if (iterator.hasNext()) { if (i < values.length - 1) {
sb.append(delimiter); sb.append(delimiter);
} }
} }

View File

@ -18,6 +18,7 @@ import java.io.InputStreamReader;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
@ -46,6 +47,7 @@ import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ListSelection; import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.ObservableElementList; import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.TextFilterator; import ca.odell.glazedlists.TextFilterator;
import ca.odell.glazedlists.matchers.MatcherEditor; import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.swing.EventListModel; import ca.odell.glazedlists.swing.EventListModel;
@ -77,7 +79,7 @@ public class SubtitleDownloadComponent extends JComponent {
public SubtitleDownloadComponent() { public SubtitleDownloadComponent() {
JList packageList = new JList(createPackageListModel()); final JList packageList = new JList(createPackageListModel());
packageList.setFixedCellHeight(32); packageList.setFixedCellHeight(32);
packageList.setCellRenderer(renderer); packageList.setCellRenderer(renderer);
@ -122,6 +124,15 @@ public class SubtitleDownloadComponent extends JComponent {
scrollPane.setViewportBorder(new LineBorder(fileList.getBackground())); scrollPane.setViewportBorder(new LineBorder(fileList.getBackground()));
add(scrollPane, "newline, hmin max(80px, 30%)"); add(scrollPane, "newline, hmin max(80px, 30%)");
// install fetch action
TunedUtilities.installAction(packageList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Fetch") {
@Override
public void actionPerformed(ActionEvent e) {
fetchAll(packageList.getSelectedValues());
}
});
// install open action // install open action
TunedUtilities.installAction(fileList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Open") { TunedUtilities.installAction(fileList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Open") {
@ -159,8 +170,20 @@ public class SubtitleDownloadComponent extends JComponent {
protected ListModel createFileListModel() { protected ListModel createFileListModel() {
// source list
EventList<MemoryFile> source = getFileModel();
// sort by name
source = new SortedList<MemoryFile>(source, new Comparator<MemoryFile>() {
@Override
public int compare(MemoryFile m1, MemoryFile m2) {
return m1.getName().compareToIgnoreCase(m2.getName());
}
});
// as list model // as list model
return new EventListModel<MemoryFile>(getFileModel()); return new EventListModel<MemoryFile>(source);
} }
@ -233,7 +256,12 @@ public class SubtitleDownloadComponent extends JComponent {
private void open(Object[] selection) { private void open(Object[] selection) {
try { try {
for (Object object : selection) { for (Object object : selection) {
open((MemoryFile) object); MemoryFile file = (MemoryFile) object;
// only open subtitle files
if (SUBTITLE_FILES.accept(file.getName())) {
open(file);
}
} }
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e); Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
@ -260,7 +288,7 @@ public class SubtitleDownloadComponent extends JComponent {
try { try {
if (reader.hasNext()) { if (reader.hasNext()) {
// correct format // correct format found
List<SubtitleElement> list = new ArrayList<SubtitleElement>(500); List<SubtitleElement> list = new ArrayList<SubtitleElement>(500);
// read subtitle file // read subtitle file
@ -275,15 +303,13 @@ public class SubtitleDownloadComponent extends JComponent {
viewer.setData(list); viewer.setData(list);
viewer.setVisible(true); viewer.setVisible(true);
// done // we're done
return; return;
} }
} finally { } finally {
reader.close(); reader.close();
} }
} }
throw new IOException("Cannot read subtitle format");
} }
@ -447,6 +473,7 @@ public class SubtitleDownloadComponent extends JComponent {
final Object[] selection = list.getSelectedValues(); final Object[] selection = list.getSelectedValues();
if (selection.length > 0) {
JPopupMenu contextMenu = new JPopupMenu(); JPopupMenu contextMenu = new JPopupMenu();
// Open // Open
@ -470,6 +497,7 @@ public class SubtitleDownloadComponent extends JComponent {
contextMenu.show(e.getComponent(), e.getX(), e.getY()); contextMenu.show(e.getComponent(), e.getX(), e.getY());
} }
} }
}
}; };

View File

@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import static java.util.Collections.*; import static java.util.Collections.*;
import static net.sourceforge.filebot.FileBotUtilities.*;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@ -181,17 +182,19 @@ public class SubtitlePackage {
List<MemoryFile> vfs = new ArrayList<MemoryFile>(); List<MemoryFile> vfs = new ArrayList<MemoryFile>();
for (MemoryFile file : archiveType.fromData(data)) { for (MemoryFile file : archiveType.fromData(data)) {
if (SUBTITLE_FILES.accept(file.getName())) {
// add subtitle files, ignore non-subtitle files
vfs.add(file);
} else {
// check if file is a supported archive // check if file is a supported archive
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName())); ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
if (type == ArchiveType.UNDEFINED) { if (type != ArchiveType.UNDEFINED) {
// add subtitle file
vfs.add(file);
} else {
// extract nested archives recursively // extract nested archives recursively
vfs.addAll(extract(type, file.getData())); vfs.addAll(extract(type, file.getData()));
} }
} }
}
return vfs; return vfs;
} }

View File

@ -19,7 +19,7 @@ import javax.swing.JComboBox;
import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.ui.AbstractSearchPanel; import net.sourceforge.filebot.ui.AbstractSearchPanel;
import net.sourceforge.filebot.ui.SelectDialog; import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleClient; import net.sourceforge.filebot.web.OpenSubtitlesClient;
import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SublightSubtitleClient; import net.sourceforge.filebot.web.SublightSubtitleClient;
import net.sourceforge.filebot.web.SubsceneSubtitleClient; import net.sourceforge.filebot.web.SubsceneSubtitleClient;
@ -79,7 +79,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
@Override @Override
protected SubtitleProvider[] createSearchEngines() { protected SubtitleProvider[] createSearchEngines() {
return new SubtitleProvider[] { return new SubtitleProvider[] {
new OpenSubtitlesSubtitleClient(String.format("%s v%s", getApplicationName(), getApplicationVersion())), new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion())),
new SubsceneSubtitleClient(), new SubsceneSubtitleClient(),
new SublightSubtitleClient(getApplicationName(), Settings.userRoot().get("sublight.apikey")), new SublightSubtitleClient(getApplicationName(), Settings.userRoot().get("sublight.apikey")),
new SubtitleSourceClient() new SubtitleSourceClient()

View File

@ -2,212 +2,101 @@
package net.sourceforge.filebot.web; package net.sourceforge.filebot.web;
import java.net.MalformedURLException; import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.logging.Level;
import java.util.regex.Pattern; import java.util.logging.Logger;
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property; import javax.swing.Icon;
import redstone.xmlrpc.XmlRpcClient;
import redstone.xmlrpc.XmlRpcException; import net.sourceforge.filebot.ResourceManager;
import redstone.xmlrpc.XmlRpcFault; import net.sourceforge.tuned.Timer;
/** /**
* Client for the OpenSubtitles XML-RPC API. * SubtitleClient for OpenSubtitles.
*/ */
public class OpenSubtitlesClient { public class OpenSubtitlesClient implements SubtitleProvider {
private static final String url = "http://www.opensubtitles.org/xml-rpc"; private final OpenSubtitlesXmlRpc xmlrpc;
private final String clientInfo;
private String token = null;
public OpenSubtitlesClient(String clientInfo) { public OpenSubtitlesClient(String useragent) {
this.clientInfo = clientInfo; this.xmlrpc = new OpenSubtitlesXmlRpc(useragent);
} }
/** @Override
* Login as anonymous user public String getName() {
* return "OpenSubtitles";
* @throws XmlRpcFault
*/
public void loginAnonymous() throws XmlRpcFault {
login("", "");
} }
/** @Override
* Login as user and use English as language public Icon getIcon() {
* return ResourceManager.getIcon("search.opensubtitles");
* @param username
* @param password
* @throws XmlRpcFault
*/
public void login(String username, String password) throws XmlRpcFault {
login(username, password, "en");
} }
/** @Override
* This will login user. This method should be called always when starting talking with public List<SearchResult> search(String query) throws Exception {
* server. // require login
* login();
* @param username username (blank for anonymous user)
* @param password password (blank for anonymous user)
* @param language <a
* href="http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes">ISO639</a>
* 2-letter codes as language and later communication will be done in this
* language if applicable (error codes and so on).
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public synchronized void login(String username, String password, String language) throws XmlRpcFault { List<SearchResult> results = (List) xmlrpc.searchMoviesOnIMDB(query);
Map<String, String> response = (Map<String, String>) invoke("LogIn", username, password, language, clientInfo); return results;
checkStatus(response.get("status"));
token = response.get("token");
} }
/** @Override
* This will logout user (ends session id). Good call this function is before ending public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
* (closing) clients program. // require login
* login();
* @throws XmlRpcFault
*/ @SuppressWarnings("unchecked")
public synchronized void logout() throws XmlRpcFault { List<SubtitleDescriptor> subtitles = (List) xmlrpc.searchSubtitles(((MovieDescriptor) searchResult).getImdbId(), languageName);
return subtitles;
}
@Override
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
//TODO provide link
return null;
}
protected synchronized void login() throws Exception {
if (!xmlrpc.isLoggedOn()) {
xmlrpc.loginAnonymous();
}
logoutTimer.set(10, TimeUnit.MINUTES, true);
}
protected synchronized void logout() {
if (xmlrpc.isLoggedOn()) {
try { try {
invoke("LogOut", token); xmlrpc.logout();
// anonymous users will always get a 401 Unauthorized when trying to logout
// do not check status for logout response
// checkStatus(response.get("status"));
} finally {
token = null;
}
}
public synchronized boolean isLoggedOn() {
return token != null;
}
/**
* Check whether status is OK or not
*
* @param status status code and message (e.g. 200 OK, 401 Unauthorized, ...)
* @throws XmlRpcFault thrown if status code is not OK
*/
private void checkStatus(String status) throws XmlRpcFault {
if (status.equals("200 OK"))
return;
Matcher m = Pattern.compile("(\\d+).*").matcher(status);
if (!m.matches())
throw new XmlRpcException("Illegal status code: " + status);
throw new XmlRpcFault(Integer.parseInt(m.group(1)), status);
}
private Object invoke(String method, Object... arguments) throws XmlRpcFault {
try {
XmlRpcClient rpc = new XmlRpcClient(url, false);
return rpc.invoke(method, arguments);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
/**
* This simple function returns basic server info.
*/
@SuppressWarnings("unchecked")
public Map<String, String> getServerInfo() throws XmlRpcFault {
return (Map<String, String>) invoke("ServerInfo", token);
}
@SuppressWarnings("unchecked")
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, String languageName) throws XmlRpcFault {
Map<String, String> searchListEntry = new HashMap<String, String>(2);
// pad imdbId with zeros
//TODO needed???
searchListEntry.put("imdbid", String.format("%07d", imdbid));
searchListEntry.put("sublanguageid", getSubLanguageID(languageName));
List<Map<String, String>> searchList = Collections.singletonList(searchListEntry);
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, searchList);
List<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
try {
for (Map<String, String> subtitleData : response.get("data")) {
subs.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
}
} catch (ClassCastException e) {
// if the response is an error message, generic types won't match
throw new XmlRpcException("Illegal response: " + response.toString(), e);
}
return subs;
}
private String getSubLanguageID(String languageName) {
//TODO test if sublanguageid is really ISO3 language code
throw new UnsupportedOperationException("Not implemented");
}
@SuppressWarnings("unchecked")
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
ArrayList<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
for (Map<String, String> movie : response.get("data")) {
String title = movie.get("title");
// get end index of first non-aka title (aka titles are separated by Â)
int endIndex = title.indexOf('\u00C2');
if (endIndex > 0) {
title = title.substring(0, endIndex);
}
movies.add(new MovieDescriptor(title, Integer.parseInt(movie.get("year")), Integer.parseInt(movie.get("id"))));
}
return movies;
}
@SuppressWarnings("unchecked")
public boolean noOperation() {
try {
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
checkStatus(response.get("status"));
return true;
} catch (Exception e) { } catch (Exception e) {
return false; Logger.getLogger(getClass().getName()).log(Level.WARNING, "Logout failed", e);
} }
} }
logoutTimer.cancel();
}
protected final Timer logoutTimer = new Timer() {
@Override
public void run() {
logout();
}
};
} }

View File

@ -1,102 +0,0 @@
package net.sourceforge.filebot.web;
import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.Timer;
/**
* SubtitleClient for OpenSubtitles.
*/
public class OpenSubtitlesSubtitleClient implements SubtitleProvider {
private final OpenSubtitlesClient client;
public OpenSubtitlesSubtitleClient(String clientInfo) {
this.client = new OpenSubtitlesClient(clientInfo);
}
@Override
public String getName() {
return "OpenSubtitles";
}
@Override
public Icon getIcon() {
return ResourceManager.getIcon("search.opensubtitles");
}
@Override
public List<SearchResult> search(String query) throws Exception {
// require login
login();
@SuppressWarnings("unchecked")
List<SearchResult> results = (List) client.searchMoviesOnIMDB(query);
return results;
}
@Override
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
// require login
login();
@SuppressWarnings("unchecked")
List<SubtitleDescriptor> subtitles = (List) client.searchSubtitles(((MovieDescriptor) searchResult).getImdbId(), languageName);
return subtitles;
}
@Override
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
//TODO provide link
return null;
}
protected synchronized void login() throws Exception {
if (!client.isLoggedOn()) {
client.loginAnonymous();
}
logoutTimer.set(10, TimeUnit.MINUTES, true);
}
protected synchronized void logout() {
if (client.isLoggedOn()) {
try {
client.logout();
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Logout failed", e);
}
}
logoutTimer.cancel();
}
protected final Timer logoutTimer = new Timer() {
@Override
public void run() {
logout();
}
};
}

View File

@ -2,18 +2,21 @@
package net.sourceforge.filebot.web; package net.sourceforge.filebot.web;
import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.zip.GZIPInputStream;
import net.sourceforge.tuned.ByteBufferOutputStream;
/** /**
* Describes a subtitle on OpenSubtitles. * Describes a subtitle on OpenSubtitles.
* *
* @see OpenSubtitlesClient * @see OpenSubtitlesXmlRpc
*/ */
public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor { public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
@ -75,32 +78,49 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
} }
public Map<Property, String> getProperties() { public String getProperty(Property key) {
return Collections.unmodifiableMap(properties); return properties.get(key);
} }
@Override @Override
public String getName() { public String getName() {
return properties.get(Property.SubFileName); return getProperty(Property.SubFileName);
} }
@Override @Override
public String getLanguageName() { public String getLanguageName() {
return properties.get(Property.LanguageName); return getProperty(Property.LanguageName);
}
@Override
public ByteBuffer fetch() throws Exception {
return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
} }
@Override @Override
public String getType() { public String getType() {
return "zip"; return getProperty(Property.SubFormat);
}
public int getSize() {
return Integer.parseInt(getProperty(Property.SubSize));
}
@Override
public ByteBuffer fetch() throws Exception {
URL resource = new URL(getProperty(Property.SubDownloadLink));
InputStream stream = new GZIPInputStream(resource.openStream());
try {
ByteBufferOutputStream buffer = new ByteBufferOutputStream(getSize());
// read all
buffer.transferFully(stream);
return buffer.getByteBuffer();
} finally {
stream.close();
}
} }

View File

@ -0,0 +1,234 @@
package net.sourceforge.filebot.web;
import static java.util.Collections.*;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import redstone.xmlrpc.XmlRpcClient;
import redstone.xmlrpc.XmlRpcException;
import redstone.xmlrpc.XmlRpcFault;
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
/**
* Client for the OpenSubtitles XML-RPC API.
*/
public class OpenSubtitlesXmlRpc {
private static final String url = "http://www.opensubtitles.org/xml-rpc";
private final String useragent;
private String token;
public OpenSubtitlesXmlRpc(String useragent) {
this.useragent = useragent;
}
/**
* Login as anonymous user
*
* @throws XmlRpcFault
*/
public void loginAnonymous() throws XmlRpcFault {
login("", "");
}
/**
* Login as user and use English as language
*
* @param username
* @param password
* @throws XmlRpcFault
*/
public void login(String username, String password) throws XmlRpcFault {
login(username, password, "en");
}
/**
* This will login user. This method should be called always when starting talking with
* server.
*
* @param username username (blank for anonymous user)
* @param password password (blank for anonymous user)
* @param language <a
* href="http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes">ISO639</a>
* 2-letter codes as language and later communication will be done in this
* language if applicable (error codes and so on).
*/
@SuppressWarnings("unchecked")
public synchronized void login(String username, String password, String language) throws XmlRpcFault {
Map<String, String> response = (Map<String, String>) invoke("LogIn", username, password, language, useragent);
checkStatus(response.get("status"));
token = response.get("token");
}
/**
* This will logout user (ends session id). Good call this function is before ending
* (closing) clients program.
*
* @throws XmlRpcFault
*/
public synchronized void logout() throws XmlRpcFault {
try {
invoke("LogOut", token);
// anonymous users will always get a 401 Unauthorized when trying to logout
// do not check status for logout response
// checkStatus(response.get("status"));
} finally {
token = null;
}
}
public synchronized boolean isLoggedOn() {
return token != null;
}
/**
* Check whether status is OK or not
*
* @param status status code and message (e.g. 200 OK, 401 Unauthorized, ...)
* @throws XmlRpcFault thrown if status code is not OK
*/
private void checkStatus(String status) throws XmlRpcFault {
if (status.equals("200 OK"))
return;
Matcher m = Pattern.compile("(\\d+).*").matcher(status);
if (!m.matches())
throw new XmlRpcException("Illegal status code: " + status);
throw new XmlRpcFault(Integer.parseInt(m.group(1)), status);
}
private Object invoke(String method, Object... arguments) throws XmlRpcFault {
try {
XmlRpcClient rpc = new XmlRpcClient(url, false);
return rpc.invoke(method, arguments);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
/**
* This simple function returns basic server info.
*/
@SuppressWarnings("unchecked")
public Map<String, String> getServerInfo() throws XmlRpcFault {
return (Map<String, String>) invoke("ServerInfo", token);
}
@SuppressWarnings("unchecked")
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, String languageName) throws XmlRpcFault {
Map<String, String> query = new HashMap<String, String>(2);
query.put("imdbid", String.format("%07d", imdbid));
query.put("sublanguageid", getSubLanguageID(languageName));
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, singletonList(query));
List<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
try {
for (Map<String, String> subtitleData : response.get("data")) {
subs.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
}
} catch (ClassCastException e) {
// if the response is an error message, generic types won't match
throw new XmlRpcException("Illegal response: " + response.toString(), e);
}
return subs;
}
private final Map<String, String> languageMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
@SuppressWarnings("unchecked")
public String getSubLanguageID(String languageName) throws XmlRpcFault {
synchronized (languageMap) {
if (languageMap.isEmpty()) {
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("GetSubLanguages", "en");
for (Map<String, String> language : response.get("data")) {
languageMap.put(language.get("LanguageName"), language.get("SubLanguageID"));
}
}
}
return languageMap.get(languageName);
}
@SuppressWarnings("unchecked")
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
List<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
Pattern moviePattern = Pattern.compile("(.+) \\((\\d{4})\\).*");
for (Map<String, String> movie : response.get("data")) {
try {
// get non-aka title (aka titles are separated by Â)
Scanner titleScanner = new Scanner(movie.get("title")).useDelimiter("\u00C2");
Matcher matcher = moviePattern.matcher(titleScanner.next().trim());
if (!matcher.matches())
throw new InputMismatchException("Cannot parse movie: " + movie);
String title = matcher.group(1);
String year = matcher.group(2);
movies.add(new MovieDescriptor(title, Integer.parseInt(year), Integer.parseInt(movie.get("id"))));
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
}
}
return movies;
}
@SuppressWarnings("unchecked")
public boolean noOperation() {
try {
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
checkStatus(response.get("status"));
return true;
} catch (Exception e) {
return false;
}
}
}