* 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:
parent
c6bfd7755f
commit
373b0c2662
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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) {
|
||||||
try {
|
if (isCancelled() || Thread.interrupted()) {
|
||||||
if (isCancelled())
|
|
||||||
throw new CancellationException();
|
throw new CancellationException();
|
||||||
|
}
|
||||||
|
|
||||||
if (remainingVideos.isEmpty())
|
if (remainingVideos.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
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
|
|
@ -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]));
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
try {
|
||||||
// search for movies / series
|
// search for movies / series
|
||||||
SearchResult[] result = xmlrpc.searchMoviesOnIMDB(query).toArray(new SearchResult[0]);
|
List<Movie> resultSet = xmlrpc.searchMoviesOnIMDB(query);
|
||||||
|
return Arrays.asList(resultSet.toArray(new SearchResult[0]));
|
||||||
return Arrays.asList(result);
|
} catch (ClassCastException e) {
|
||||||
|
// unexpected xmlrpc responses (e.g. error messages instead of results) will trigger this
|
||||||
|
throw new XmlRpcException("Illegal XMLRPC response on searchMoviesOnIMDB");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,11 +23,14 @@ 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 (iterator.hasNext()) {
|
if (sb.length() > 0) {
|
||||||
sb.append(delimiter);
|
sb.append(delimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.append(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue