* allow manual input if no subtitles have been found via auto-matching

* allow multi-value input in input dialog
* make sure background workers get cancelled / terminated as expected
This commit is contained in:
Reinhard Pointner 2011-11-28 10:24:46 +00:00
parent c6bfd7755f
commit 373b0c2662
11 changed files with 153 additions and 93 deletions

View File

@ -51,7 +51,7 @@ public final class WebServices {
public static SubtitleProvider[] getSubtitleProviders() { public static SubtitleProvider[] getSubtitleProviders() {
return new SubtitleProvider[] { OpenSubtitles, Subscene, Sublight }; return new SubtitleProvider[] { OpenSubtitles, Sublight, Subscene };
} }

View File

@ -290,6 +290,13 @@ public class SeriesNameMatcher {
@Override @Override
public boolean add(String value) { public boolean add(String value) {
value = value.trim();
// require series name to have at least two characters
if (value.length() < 2) {
return false;
}
String current = data.get(key(value)); String current = data.get(key(value));
// prefer strings with similar upper/lower case ratio (e.g. prefer Roswell over roswell) // prefer strings with similar upper/lower case ratio (e.g. prefer Roswell over roswell)

View File

@ -2,9 +2,10 @@
package net.sourceforge.filebot.ui; package net.sourceforge.filebot.ui;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Component; import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@ -37,8 +38,8 @@ public class SelectDialog<T> extends JDialog {
private boolean valueSelected = false; private boolean valueSelected = false;
public SelectDialog(Window owner, Collection<? extends T> options) { public SelectDialog(Component parent, Collection<? extends T> options) {
super(owner, "Select", ModalityType.DOCUMENT_MODAL); super(getWindow(parent), "Select", ModalityType.DOCUMENT_MODAL);
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);

View File

@ -2,7 +2,7 @@
package net.sourceforge.filebot.ui.rename; package net.sourceforge.filebot.ui.rename;
import java.awt.Window; import java.awt.Component;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -12,5 +12,5 @@ import net.sourceforge.filebot.similarity.Match;
interface AutoCompleteMatcher { interface AutoCompleteMatcher {
List<Match<File, ?>> match(List<File> files, Locale locale, boolean autodetection, Window parent) throws Exception; List<Match<File, ?>> match(List<File> files, Locale locale, boolean autodetection, Component parent) throws Exception;
} }

View File

@ -4,12 +4,13 @@ package net.sourceforge.filebot.ui.rename;
import static java.util.Collections.*; import static java.util.Collections.*;
import static net.sourceforge.filebot.MediaTypes.*; import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.similarity.SeriesNameMatcher.*;
import static net.sourceforge.filebot.web.EpisodeUtilities.*; import static net.sourceforge.filebot.web.EpisodeUtilities.*;
import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*; import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Window;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -56,7 +57,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, final Window window) throws Exception { protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, final Component parent) throws Exception {
if (searchResults.size() == 1) { if (searchResults.size() == 1) {
return searchResults.get(0); return searchResults.get(0);
} }
@ -67,9 +68,9 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// use name similarity metric // use name similarity metric
SimilarityMetric metric = new NameSimilarityMetric(); SimilarityMetric metric = new NameSimilarityMetric();
// find probable matches using name similarity > 0.9 // find probable matches using name similarity >= 0.9
for (SearchResult result : searchResults) { for (SearchResult result : searchResults) {
if (metric.getSimilarity(normalizeName(query), normalizeName(result.getName())) > 0.9) { if (metric.getSimilarity(normalizeName(query), normalizeName(result.getName())) >= 0.9) {
probableMatches.add(result); probableMatches.add(result);
} }
} }
@ -85,7 +86,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
@Override @Override
public SearchResult call() throws Exception { public SearchResult call() throws Exception {
// multiple results have been found, user must select one // multiple results have been found, user must select one
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(window, searchResults); SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(parent, searchResults);
selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query)); selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query));
selectDialog.getCancelAction().putValue(Action.NAME, "Ignore"); selectDialog.getCancelAction().putValue(Action.NAME, "Ignore");
@ -117,12 +118,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
protected Collection<String> detectSeriesNames(Collection<File> files) { protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final Locale locale, final Component parent) throws Exception {
return new SeriesNameMatcher().matchAll(files.toArray(new File[0]));
}
protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final Locale locale, final Window window) throws Exception {
List<Callable<List<Episode>>> tasks = new ArrayList<Callable<List<Episode>>>(); List<Callable<List<Episode>>> tasks = new ArrayList<Callable<List<Episode>>>();
// detect series names and create episode list fetch tasks // detect series names and create episode list fetch tasks
@ -135,7 +131,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// select search result // select search result
if (results.size() > 0) { if (results.size() > 0) {
SearchResult selectedSearchResult = selectSearchResult(query, results, window); SearchResult selectedSearchResult = selectSearchResult(query, results, parent);
if (selectedSearchResult != null) { if (selectedSearchResult != null) {
List<Episode> episodes = provider.getEpisodeList(selectedSearchResult, locale); List<Episode> episodes = provider.getEpisodeList(selectedSearchResult, locale);
@ -171,14 +167,14 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
@Override @Override
public List<Match<File, ?>> match(final List<File> files, final Locale locale, final boolean autodetection, final Window window) throws Exception { public List<Match<File, ?>> match(final List<File> files, final Locale locale, final boolean autodetection, final Component parent) throws Exception {
// focus on movie and subtitle files // focus on movie and subtitle files
final List<File> mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES); final List<File> mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES);
final Map<File, List<File>> filesByFolder = mapByFolder(mediaFiles); final Map<File, List<File>> filesByFolder = mapByFolder(mediaFiles);
// do matching all at once // do matching all at once
if (filesByFolder.keySet().size() <= 5 || detectSeriesNames(mediaFiles).size() <= 5) { if (filesByFolder.keySet().size() <= 5 || detectSeriesName(mediaFiles).size() <= 5) {
return matchEpisodeSet(mediaFiles, locale, autodetection, window); return matchEpisodeSet(mediaFiles, locale, autodetection, parent);
} }
// assume that many shows will be matched, do it folder by folder // assume that many shows will be matched, do it folder by folder
@ -190,7 +186,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
@Override @Override
public List<Match<File, ?>> call() throws Exception { public List<Match<File, ?>> call() throws Exception {
return matchEpisodeSet(folder, locale, autodetection, window); return matchEpisodeSet(folder, locale, autodetection, parent);
} }
}); });
} }
@ -214,16 +210,16 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
public List<Match<File, ?>> matchEpisodeSet(final List<File> files, Locale locale, boolean autodetection, Window window) throws Exception { public List<Match<File, ?>> matchEpisodeSet(final List<File> files, Locale locale, boolean autodetection, Component parent) throws Exception {
Set<Episode> episodes = emptySet(); Set<Episode> episodes = emptySet();
// detect series name and fetch episode list // detect series name and fetch episode list
if (autodetection) { if (autodetection) {
Collection<String> names = detectSeriesNames(files); Collection<String> names = detectSeriesName(files);
if (names.size() > 0) { if (names.size() > 0) {
// only allow one fetch session at a time so later requests can make use of cached results // only allow one fetch session at a time so later requests can make use of cached results
synchronized (provider) { synchronized (provider) {
episodes = fetchEpisodeSet(names, locale, window); episodes = fetchEpisodeSet(names, locale, parent);
} }
} }
} }
@ -239,15 +235,15 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
suggestion = files.get(0).getParentFile().getName(); suggestion = files.get(0).getParentFile().getName();
} }
String input = null; List<String> input = emptyList();
synchronized (this) { synchronized (this) {
input = showInputDialog("Enter series name:", suggestion, files.get(0).getParentFile().getName(), window); input = showMultiValueInputDialog("Enter series name:", suggestion, files.get(0).getParentFile().getName(), parent);
} }
if (input != null) { if (input.size() > 0) {
// only allow one fetch session at a time so later requests can make use of cached results // only allow one fetch session at a time so later requests can make use of cached results
synchronized (provider) { synchronized (provider) {
episodes = fetchEpisodeSet(singleton(input), locale, window); episodes = fetchEpisodeSet(input, locale, parent);
} }
} }
} }
@ -272,5 +268,4 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
return matches; return matches;
} }
} }

View File

@ -8,7 +8,7 @@ import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*; import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Window; import java.awt.Component;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@ -53,7 +53,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
@Override @Override
public List<Match<File, ?>> match(final List<File> files, Locale locale, boolean autodetect, Window window) throws Exception { public List<Match<File, ?>> match(final List<File> files, Locale locale, boolean autodetect, Component parent) throws Exception {
// handle movie files // handle movie files
File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]); File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]);
@ -70,7 +70,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
// unknown hash, try via imdb id from nfo file // unknown hash, try via imdb id from nfo file
if (movie == null || !autodetect) { if (movie == null || !autodetect) {
movie = grabMovieName(movieFiles[i], locale, autodetect, window, movie); movie = grabMovieName(movieFiles[i], locale, autodetect, parent, movie);
if (movie != null) { if (movie != null) {
Analytics.trackEvent(service.getName(), "SearchMovie", movie.toString(), 1); Analytics.trackEvent(service.getName(), "SearchMovie", movie.toString(), 1);
@ -161,7 +161,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
} }
protected Movie grabMovieName(File movieFile, Locale locale, boolean autodetect, Window window, Movie... suggestions) throws Exception { protected Movie grabMovieName(File movieFile, Locale locale, boolean autodetect, Component parent, Movie... suggestions) throws Exception {
List<Movie> options = new ArrayList<Movie>(); List<Movie> options = new ArrayList<Movie>();
// add default value if any // add default value if any
@ -200,7 +200,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
String input = null; String input = null;
synchronized (this) { synchronized (this) {
input = showInputDialog("Enter movie name:", suggestion, options.get(0).getName(), window); input = showInputDialog("Enter movie name:", suggestion, options.get(0).getName(), parent);
} }
if (input != null) { if (input != null) {
@ -210,11 +210,11 @@ class MovieHashMatcher implements AutoCompleteMatcher {
} }
} }
return options.isEmpty() ? null : selectMovie(options, window); return options.isEmpty() ? null : selectMovie(options, parent);
} }
protected Movie selectMovie(final List<Movie> options, final Window window) throws Exception { protected Movie selectMovie(final List<Movie> options, final Component parent) throws Exception {
if (options.size() == 1) { if (options.size() == 1) {
return options.get(0); return options.get(0);
} }
@ -225,7 +225,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
@Override @Override
public Movie call() throws Exception { public Movie call() throws Exception {
// multiple results have been found, user must select one // multiple results have been found, user must select one
SelectDialog<Movie> selectDialog = new SelectDialog<Movie>(window, options); SelectDialog<Movie> selectDialog = new SelectDialog<Movie>(parent, options);
selectDialog.getHeaderLabel().setText("Select Movie:"); selectDialog.getHeaderLabel().setText("Select Movie:");
selectDialog.getCancelAction().putValue(Action.NAME, "Ignore"); selectDialog.getCancelAction().putValue(Action.NAME, "Ignore");

View File

@ -4,8 +4,11 @@ package net.sourceforge.filebot.ui.subtitle;
import static javax.swing.BorderFactory.*; import static javax.swing.BorderFactory.*;
import static javax.swing.JOptionPane.*; import static javax.swing.JOptionPane.*;
import static net.sourceforge.filebot.similarity.SeriesNameMatcher.*;
import static net.sourceforge.filebot.subtitle.SubtitleUtilities.*; import static net.sourceforge.filebot.subtitle.SubtitleUtilities.*;
import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.StringUtilities.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
@ -59,9 +62,7 @@ import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.similarity.EpisodeMetrics; import net.sourceforge.filebot.similarity.EpisodeMetrics;
import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher; import net.sourceforge.filebot.similarity.Matcher;
import net.sourceforge.filebot.similarity.SeriesNameMatcher;
import net.sourceforge.filebot.similarity.SimilarityMetric; import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.ui.Language;
import net.sourceforge.filebot.vfs.MemoryFile; import net.sourceforge.filebot.vfs.MemoryFile;
import net.sourceforge.filebot.web.SubtitleDescriptor; import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.filebot.web.SubtitleProvider; import net.sourceforge.filebot.web.SubtitleProvider;
@ -73,7 +74,7 @@ import net.sourceforge.tuned.ui.LinkButton;
import net.sourceforge.tuned.ui.RoundBorder; import net.sourceforge.tuned.ui.RoundBorder;
class VideoHashSubtitleDownloadDialog extends JDialog { class SubtitleAutoMatchDialog extends JDialog {
private final JPanel hashMatcherServicePanel = createServicePanel(0xFAFAD2); // LightGoldenRodYellow private final JPanel hashMatcherServicePanel = createServicePanel(0xFAFAD2); // LightGoldenRodYellow
private final JPanel nameMatcherServicePanel = createServicePanel(0xFFEBCD); // BlanchedAlmond private final JPanel nameMatcherServicePanel = createServicePanel(0xFFEBCD); // BlanchedAlmond
@ -85,8 +86,8 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
private ExecutorService downloadService; private ExecutorService downloadService;
public VideoHashSubtitleDownloadDialog(Window owner) { public SubtitleAutoMatchDialog(Window owner) {
super(owner, "Download Subtitles", ModalityType.MODELESS); super(owner, "Download Subtitles", ModalityType.DOCUMENT_MODAL);
JComponent content = (JComponent) getContentPane(); JComponent content = (JComponent) getContentPane();
content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]")); content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]"));
@ -189,8 +190,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
public void startQuery(String languageName) { public void startQuery(String languageName) {
final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel(); final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel();
QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), languageName, SubtitleAutoMatchDialog.this) {
QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), languageName) {
@Override @Override
protected void process(List<Map<File, List<SubtitleDescriptorBean>>> sequence) { protected void process(List<Map<File, List<SubtitleDescriptorBean>>> sequence) {
@ -212,8 +212,8 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
} }
}; };
ExecutorService executor = Executors.newFixedThreadPool(1); queryService = Executors.newFixedThreadPool(1);
executor.submit(queryTask); queryService.submit(queryTask);
} }
@ -232,7 +232,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
JOptionPane optionPane = new JOptionPane(message, WARNING_MESSAGE, YES_NO_CANCEL_OPTION, null, options); JOptionPane optionPane = new JOptionPane(message, WARNING_MESSAGE, YES_NO_CANCEL_OPTION, null, options);
// display option dialog // display option dialog
optionPane.createDialog(VideoHashSubtitleDownloadDialog.this, "Replace").setVisible(true); optionPane.createDialog(SubtitleAutoMatchDialog.this, "Replace").setVisible(true);
// replace all // replace all
if (options[0] == optionPane.getValue()) if (options[0] == optionPane.getValue())
@ -277,7 +277,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
try { try {
mapping.setSubtitleFile(get()); mapping.setSubtitleFile(get());
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage()); Logger.getLogger(SubtitleAutoMatchDialog.class.getName()).log(Level.WARNING, e.getMessage(), e);
} }
} }
}); });
@ -642,7 +642,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
public String getText() { public String getText() {
return formatSubtitle(descriptor.getName(), getLanguage(), getType()); return formatSubtitle(descriptor.getName(), getLanguageName(), getType());
} }
@ -651,8 +651,8 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
} }
public String getLanguage() { public String getLanguageName() {
return Language.getISO3LanguageCodeByName(descriptor.getLanguageName()); return descriptor.getLanguageName();
} }
@ -706,13 +706,15 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
private static class QueryTask extends SwingWorker<Collection<File>, Map<File, List<SubtitleDescriptorBean>>> { private static class QueryTask extends SwingWorker<Collection<File>, Map<File, List<SubtitleDescriptorBean>>> {
private final Component parent;
private final Collection<SubtitleServiceBean> services; private final Collection<SubtitleServiceBean> services;
private final Collection<File> remainingVideos; private final Collection<File> remainingVideos;
private final String languageName; private final String languageName;
public QueryTask(Collection<SubtitleServiceBean> services, Collection<File> videoFiles, String languageName) { public QueryTask(Collection<SubtitleServiceBean> services, Collection<File> videoFiles, String languageName, Component parent) {
this.parent = parent;
this.services = services; this.services = services;
this.remainingVideos = new TreeSet<File>(videoFiles); this.remainingVideos = new TreeSet<File>(videoFiles);
this.languageName = languageName; this.languageName = languageName;
@ -722,16 +724,17 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
@Override @Override
protected Collection<File> doInBackground() throws Exception { protected Collection<File> doInBackground() throws Exception {
for (SubtitleServiceBean service : services) { for (SubtitleServiceBean service : services) {
if (isCancelled() || Thread.interrupted()) {
throw new CancellationException();
}
if (remainingVideos.isEmpty()) {
break;
}
try { try {
if (isCancelled())
throw new CancellationException();
if (remainingVideos.isEmpty())
break;
Map<File, List<SubtitleDescriptorBean>> subtitleSet = new HashMap<File, List<SubtitleDescriptorBean>>(); Map<File, List<SubtitleDescriptorBean>> subtitleSet = new HashMap<File, List<SubtitleDescriptorBean>>();
for (final Entry<File, List<SubtitleDescriptor>> result : service.lookupSubtitles(remainingVideos, languageName, parent).entrySet()) {
for (final Entry<File, List<SubtitleDescriptor>> result : service.lookupSubtitles(remainingVideos, languageName).entrySet()) {
List<SubtitleDescriptorBean> subtitles = new ArrayList<SubtitleDescriptorBean>(); List<SubtitleDescriptorBean> subtitles = new ArrayList<SubtitleDescriptorBean>();
// associate subtitles with services // associate subtitles with services
@ -750,8 +753,15 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
} }
publish(subtitleSet); publish(subtitleSet);
} catch (CancellationException e) {
// don't ignore cancellation
throw e;
} catch (InterruptedException e) {
// don't ignore cancellation
throw e;
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage()); // log and ignore
Logger.getLogger(SubtitleAutoMatchDialog.class.getName()).log(Level.WARNING, e.getMessage(), e);
} }
} }
@ -781,9 +791,10 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
if (descriptor.getType() == null && subtitle == null) if (descriptor.getType() == null && subtitle == null)
return null; return null;
// prefer type from descriptor because we need to know before we download the actual subtitle file
String base = FileUtilities.getName(video); String base = FileUtilities.getName(video);
String ext = descriptor.getType() != null ? descriptor.getType() : getExtension(subtitle.getName()); String ext = (descriptor.getType() != null) ? descriptor.getType() : getExtension(subtitle.getName());
return new File(video.getParentFile(), formatSubtitle(base, descriptor.getLanguage(), ext)); return new File(video.getParentFile(), formatSubtitle(base, descriptor.getLanguageName(), ext));
} }
@ -802,7 +813,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
return destination; return destination;
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage()); Logger.getLogger(SubtitleAutoMatchDialog.class.getName()).log(Level.WARNING, e.getMessage(), e);
} }
return null; return null;
@ -842,14 +853,14 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
} }
protected abstract Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName) throws Exception; protected abstract Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName, Component parent) throws Exception;
public final Map<File, List<SubtitleDescriptor>> lookupSubtitles(Collection<File> files, String languageName) throws Exception { public final Map<File, List<SubtitleDescriptor>> lookupSubtitles(Collection<File> files, String languageName, Component parent) throws Exception {
setState(StateValue.STARTED); setState(StateValue.STARTED);
try { try {
return getSubtitleList(files, languageName); return getSubtitleList(files, languageName, parent);
} catch (Exception e) { } catch (Exception e) {
// remember error // remember error
error = e; error = e;
@ -891,7 +902,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
@Override @Override
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName) throws Exception { protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName, Component parent) throws Exception {
return service.getSubtitleList(files.toArray(new File[0]), languageName); return service.getSubtitleList(files.toArray(new File[0]), languageName);
} }
} }
@ -909,17 +920,29 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
@Override @Override
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName) throws Exception { protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName, Component parent) throws Exception {
Map<File, List<SubtitleDescriptor>> subtitlesByFile = new HashMap<File, List<SubtitleDescriptor>>(); Map<File, List<SubtitleDescriptor>> subtitlesByFile = new HashMap<File, List<SubtitleDescriptor>>();
for (File file : files) { for (File file : files) {
subtitlesByFile.put(file, new ArrayList<SubtitleDescriptor>()); subtitlesByFile.put(file, new ArrayList<SubtitleDescriptor>());
} }
// auto-detect query and search for subtitles // auto-detect query and search for subtitles
Collection<String> querySet = new SeriesNameMatcher().matchAll(files.toArray(new File[0])); Collection<String> querySet = detectSeriesName(files);
List<SubtitleDescriptor> subtitles = findSubtitles(service, querySet, languageName); List<SubtitleDescriptor> subtitles = findSubtitles(service, querySet, languageName);
// if auto-detection fails, ask user for input
if (subtitles.isEmpty()) { if (subtitles.isEmpty()) {
throw new IllegalArgumentException("Unable to lookup subtitles:" + querySet); // dialog may have been cancelled by now
if (Thread.interrupted())
throw new CancellationException();
querySet = showMultiValueInputDialog("Enter series / movie names:", join(querySet, ","), service.getName(), parent);
subtitles = findSubtitles(service, querySet, languageName);
// still no luck... na women ye mei banfa
if (subtitles.isEmpty()) {
throw new Exception("Unable to lookup subtitles:" + querySet);
}
} }
// first match everything as best as possible, then filter possibly bad matches // first match everything as best as possible, then filter possibly bad matches

View File

@ -96,7 +96,7 @@ abstract class SubtitleDropTarget extends JButton {
private boolean handleDownload(List<File> videoFiles) { private boolean handleDownload(List<File> videoFiles) {
VideoHashSubtitleDownloadDialog dialog = new VideoHashSubtitleDownloadDialog(getWindow(this)); SubtitleAutoMatchDialog dialog = new SubtitleAutoMatchDialog(getWindow(this));
// initialize download parameters // initialize download parameters
dialog.setVideoFiles(videoFiles.toArray(new File[0])); dialog.setVideoFiles(videoFiles.toArray(new File[0]));

View File

@ -24,6 +24,8 @@ import java.util.logging.Logger;
import javax.swing.Icon; import javax.swing.Icon;
import redstone.xmlrpc.XmlRpcException;
import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.Query; import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.Query;
import net.sourceforge.tuned.Timer; import net.sourceforge.tuned.Timer;
@ -65,10 +67,14 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
// require login // require login
login(); login();
// search for movies / series try {
SearchResult[] result = xmlrpc.searchMoviesOnIMDB(query).toArray(new SearchResult[0]); // search for movies / series
List<Movie> resultSet = xmlrpc.searchMoviesOnIMDB(query);
return Arrays.asList(result); return Arrays.asList(resultSet.toArray(new SearchResult[0]));
} catch (ClassCastException e) {
// unexpected xmlrpc responses (e.g. error messages instead of results) will trigger this
throw new XmlRpcException("Illegal XMLRPC response on searchMoviesOnIMDB");
}
} }

View File

@ -2,23 +2,20 @@
package net.sourceforge.tuned; package net.sourceforge.tuned;
import static java.util.Arrays.*;
import java.util.Iterator; import java.util.Iterator;
public final class StringUtilities { public final class StringUtilities {
public static boolean isEmptyValue(Object object) {
return object != null && object.toString().length() > 0;
}
public static String join(Object[] values, CharSequence delimiter) { public static String join(Object[] values, CharSequence delimiter) {
StringBuilder sb = new StringBuilder(); return join(asList(values), delimiter);
for (int i = 0; i < values.length; i++) {
sb.append(values[i]);
if (i < values.length - 1) {
sb.append(delimiter);
}
}
return sb.toString();
} }
@ -26,10 +23,13 @@ public final class StringUtilities {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) { for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
sb.append(iterator.next()); Object value = iterator.next();
if (!isEmptyValue(value)) {
if (sb.length() > 0) {
sb.append(delimiter);
}
if (iterator.hasNext()) { sb.append(value);
sb.append(delimiter);
} }
} }

View File

@ -2,6 +2,7 @@
package net.sourceforge.tuned.ui; package net.sourceforge.tuned.ui;
import static java.util.Collections.*;
import static javax.swing.JOptionPane.*; import static javax.swing.JOptionPane.*;
import java.awt.Color; import java.awt.Color;
@ -18,6 +19,8 @@ import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
@ -118,7 +121,32 @@ public final class TunedUtilities {
} }
public static String showInputDialog(final String text, final String initialValue, final String title, final Window parent) throws InvocationTargetException, InterruptedException { public static List<String> showMultiValueInputDialog(final String text, final String initialValue, final String title, final Component parent) throws InvocationTargetException, InterruptedException {
String input = showInputDialog(text, initialValue, title, parent);
if (input == null || input.isEmpty()) {
return emptyList();
}
for (char separator : new char[] { '|', ';', ',' }) {
if (input.indexOf(separator) >= 0) {
List<String> values = new ArrayList<String>();
for (String field : input.split(Character.toString(separator))) {
if (field.length() > 0) {
values.add(field);
}
}
if (values.size() > 0) {
return values;
}
}
}
return singletonList(input);
}
public static String showInputDialog(final String text, final String initialValue, final String title, final Component parent) throws InvocationTargetException, InterruptedException {
final StringBuilder buffer = new StringBuilder(); final StringBuilder buffer = new StringBuilder();
SwingUtilities.invokeAndWait(new Runnable() { SwingUtilities.invokeAndWait(new Runnable() {