* 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:
parent
19d5b576db
commit
f897837811
|
@ -3,7 +3,6 @@ package net.sourceforge.filebot.subtitle;
|
|||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -40,8 +39,10 @@ public class MicroDVDReader extends SubtitleReader {
|
|||
from = to + 1;
|
||||
}
|
||||
|
||||
if (properties.size() < 2)
|
||||
if (properties.size() < 2) {
|
||||
// ignore illegal lines
|
||||
return null;
|
||||
}
|
||||
|
||||
long startFrame = Long.parseLong(properties.get(0));
|
||||
long endFrame = Long.parseLong(properties.get(1));
|
||||
|
@ -56,7 +57,7 @@ public class MicroDVDReader extends SubtitleReader {
|
|||
}
|
||||
|
||||
// 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
|
||||
return new SubtitleElement(Math.round(startFrame * fps), Math.round(endFrame * fps), join(lines, "\n"));
|
||||
|
|
|
@ -44,7 +44,7 @@ public class SubRipReader extends SubtitleReader {
|
|||
lines.add(line);
|
||||
}
|
||||
|
||||
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
||||
return new SubtitleElement(t1, t2, join(lines.toArray(), "\n"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,9 +74,10 @@ public class SubStationAlphaReader extends SubtitleReader {
|
|||
long end = timeFormat.parse(row[format.get("End")]).getTime();
|
||||
String text = row[format.get("Text")].trim();
|
||||
|
||||
// translate "\\n" to new lines
|
||||
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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,8 +3,7 @@ package net.sourceforge.filebot.subtitle;
|
|||
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.InputMismatchException;
|
||||
import java.util.Scanner;
|
||||
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)
|
||||
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;
|
||||
|
||||
long t1 = timeFormat.parse(interval[0]).getTime();
|
||||
long t2 = timeFormat.parse(interval[1]).getTime();
|
||||
|
||||
// append subtitle line
|
||||
List<String> lines = new ArrayList<String>(2);
|
||||
|
||||
for (String text : scanner.nextLine().split(Pattern.quote("[br]"))) {
|
||||
lines.add(text);
|
||||
}
|
||||
|
||||
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
||||
try {
|
||||
long t1 = timeFormat.parse(interval[0]).getTime();
|
||||
long t2 = timeFormat.parse(interval[1]).getTime();
|
||||
|
||||
// translate [br] to new lines
|
||||
String[] lines = scanner.nextLine().split(Pattern.quote("[br]"));
|
||||
|
||||
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
||||
} catch (InputMismatchException e) {
|
||||
// can't parse interval, ignore line
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,14 +40,6 @@ public enum SubtitleFormat {
|
|||
public SubtitleReader newReader(Readable 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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
|
||||
sb.append(iterator.next());
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
sb.append(values[i]);
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
if (i < values.length - 1) {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.io.InputStreamReader;
|
|||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
@ -46,6 +47,7 @@ import ca.odell.glazedlists.FilterList;
|
|||
import ca.odell.glazedlists.GlazedLists;
|
||||
import ca.odell.glazedlists.ListSelection;
|
||||
import ca.odell.glazedlists.ObservableElementList;
|
||||
import ca.odell.glazedlists.SortedList;
|
||||
import ca.odell.glazedlists.TextFilterator;
|
||||
import ca.odell.glazedlists.matchers.MatcherEditor;
|
||||
import ca.odell.glazedlists.swing.EventListModel;
|
||||
|
@ -77,7 +79,7 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
|
||||
|
||||
public SubtitleDownloadComponent() {
|
||||
JList packageList = new JList(createPackageListModel());
|
||||
final JList packageList = new JList(createPackageListModel());
|
||||
packageList.setFixedCellHeight(32);
|
||||
packageList.setCellRenderer(renderer);
|
||||
|
||||
|
@ -122,6 +124,15 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
scrollPane.setViewportBorder(new LineBorder(fileList.getBackground()));
|
||||
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
|
||||
TunedUtilities.installAction(fileList, KeyStroke.getKeyStroke("ENTER"), new AbstractAction("Open") {
|
||||
|
||||
|
@ -159,8 +170,20 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
|
||||
|
||||
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
|
||||
return new EventListModel<MemoryFile>(getFileModel());
|
||||
return new EventListModel<MemoryFile>(source);
|
||||
}
|
||||
|
||||
|
||||
|
@ -233,7 +256,12 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
private void open(Object[] selection) {
|
||||
try {
|
||||
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) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
||||
|
@ -260,7 +288,7 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
|
||||
try {
|
||||
if (reader.hasNext()) {
|
||||
// correct format
|
||||
// correct format found
|
||||
List<SubtitleElement> list = new ArrayList<SubtitleElement>(500);
|
||||
|
||||
// read subtitle file
|
||||
|
@ -275,15 +303,13 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
viewer.setData(list);
|
||||
viewer.setVisible(true);
|
||||
|
||||
// done
|
||||
// we're done
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("Cannot read subtitle format");
|
||||
}
|
||||
|
||||
|
||||
|
@ -447,27 +473,29 @@ public class SubtitleDownloadComponent extends JComponent {
|
|||
|
||||
final Object[] selection = list.getSelectedValues();
|
||||
|
||||
JPopupMenu contextMenu = new JPopupMenu();
|
||||
|
||||
// Open
|
||||
contextMenu.add(new AbstractAction("Open") {
|
||||
if (selection.length > 0) {
|
||||
JPopupMenu contextMenu = new JPopupMenu();
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
open(selection);
|
||||
}
|
||||
});
|
||||
|
||||
// Save as ...
|
||||
contextMenu.add(new AbstractAction("Save as ...") {
|
||||
// Open
|
||||
contextMenu.add(new AbstractAction("Open") {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
open(selection);
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
save(selection);
|
||||
}
|
||||
});
|
||||
|
||||
contextMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
// Save as ...
|
||||
contextMenu.add(new AbstractAction("Save as ...") {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
save(selection);
|
||||
}
|
||||
});
|
||||
|
||||
contextMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.panel.subtitle;
|
|||
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static net.sourceforge.filebot.FileBotUtilities.*;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
@ -181,15 +182,17 @@ public class SubtitlePackage {
|
|||
List<MemoryFile> vfs = new ArrayList<MemoryFile>();
|
||||
|
||||
for (MemoryFile file : archiveType.fromData(data)) {
|
||||
// check if file is a supported archive
|
||||
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
|
||||
|
||||
if (type == ArchiveType.UNDEFINED) {
|
||||
// add subtitle file
|
||||
if (SUBTITLE_FILES.accept(file.getName())) {
|
||||
// add subtitle files, ignore non-subtitle files
|
||||
vfs.add(file);
|
||||
} else {
|
||||
// extract nested archives recursively
|
||||
vfs.addAll(extract(type, file.getData()));
|
||||
// check if file is a supported archive
|
||||
ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName()));
|
||||
|
||||
if (type != ArchiveType.UNDEFINED) {
|
||||
// extract nested archives recursively
|
||||
vfs.addAll(extract(type, file.getData()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import javax.swing.JComboBox;
|
|||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.filebot.ui.AbstractSearchPanel;
|
||||
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.SublightSubtitleClient;
|
||||
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
|
||||
|
@ -79,7 +79,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
|||
@Override
|
||||
protected SubtitleProvider[] createSearchEngines() {
|
||||
return new SubtitleProvider[] {
|
||||
new OpenSubtitlesSubtitleClient(String.format("%s v%s", getApplicationName(), getApplicationVersion())),
|
||||
new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion())),
|
||||
new SubsceneSubtitleClient(),
|
||||
new SublightSubtitleClient(getApplicationName(), Settings.userRoot().get("sublight.apikey")),
|
||||
new SubtitleSourceClient()
|
||||
|
|
|
@ -2,212 +2,101 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
|
||||
import redstone.xmlrpc.XmlRpcClient;
|
||||
import redstone.xmlrpc.XmlRpcException;
|
||||
import redstone.xmlrpc.XmlRpcFault;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
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 String clientInfo;
|
||||
|
||||
private String token = null;
|
||||
private final OpenSubtitlesXmlRpc xmlrpc;
|
||||
|
||||
|
||||
public OpenSubtitlesClient(String clientInfo) {
|
||||
this.clientInfo = clientInfo;
|
||||
public OpenSubtitlesClient(String useragent) {
|
||||
this.xmlrpc = new OpenSubtitlesXmlRpc(useragent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Login as anonymous user
|
||||
*
|
||||
* @throws XmlRpcFault
|
||||
*/
|
||||
public void loginAnonymous() throws XmlRpcFault {
|
||||
login("", "");
|
||||
@Override
|
||||
public String getName() {
|
||||
return "OpenSubtitles";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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");
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.opensubtitles");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@Override
|
||||
public List<SearchResult> search(String query) throws Exception {
|
||||
// require login
|
||||
login();
|
||||
|
||||
Map<String, String> response = (Map<String, String>) invoke("LogIn", username, password, language, clientInfo);
|
||||
checkStatus(response.get("status"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<SearchResult> results = (List) xmlrpc.searchMoviesOnIMDB(query);
|
||||
|
||||
token = response.get("token");
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@Override
|
||||
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
|
||||
// require login
|
||||
login();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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)));
|
||||
protected synchronized void logout() {
|
||||
if (xmlrpc.isLoggedOn()) {
|
||||
try {
|
||||
xmlrpc.logout();
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Logout failed", e);
|
||||
}
|
||||
} 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;
|
||||
logoutTimer.cancel();
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
protected final Timer logoutTimer = new Timer() {
|
||||
|
||||
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"))));
|
||||
@Override
|
||||
public void run() {
|
||||
logout();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -2,18 +2,21 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import net.sourceforge.tuned.ByteBufferOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* Describes a subtitle on OpenSubtitles.
|
||||
*
|
||||
* @see OpenSubtitlesClient
|
||||
* @see OpenSubtitlesXmlRpc
|
||||
*/
|
||||
public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
||||
|
||||
|
@ -75,32 +78,49 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
|||
}
|
||||
|
||||
|
||||
public Map<Property, String> getProperties() {
|
||||
return Collections.unmodifiableMap(properties);
|
||||
public String getProperty(Property key) {
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return properties.get(Property.SubFileName);
|
||||
return getProperty(Property.SubFileName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getLanguageName() {
|
||||
return properties.get(Property.LanguageName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
return WebRequest.fetch(new URL(properties.get(Property.ZipDownloadLink)));
|
||||
return getProperty(Property.LanguageName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue