filebot/source/net/filebot/ui/subtitle/SubtitleAutoMatchDialog.java

953 lines
28 KiB
Java

package net.filebot.ui.subtitle;
import static javax.swing.BorderFactory.*;
import static javax.swing.JOptionPane.*;
import static net.filebot.Logging.*;
import static net.filebot.Settings.*;
import static net.filebot.subtitle.SubtitleUtilities.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.URI;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import net.filebot.ResourceManager;
import net.filebot.WebServices;
import net.filebot.mac.MacAppUtilities;
import net.filebot.subtitle.SubtitleMetrics;
import net.filebot.subtitle.SubtitleNaming;
import net.filebot.util.ui.AbstractBean;
import net.filebot.util.ui.DashedSeparator;
import net.filebot.util.ui.EmptySelectionModel;
import net.filebot.util.ui.LinkButton;
import net.filebot.util.ui.RoundBorder;
import net.filebot.vfs.MemoryFile;
import net.filebot.web.SubtitleDescriptor;
import net.filebot.web.SubtitleProvider;
import net.filebot.web.VideoHashSubtitleService;
import net.miginfocom.swing.MigLayout;
class SubtitleAutoMatchDialog extends JDialog {
private static final Color hashMatchColor = new Color(0xFAFAD2); // LightGoldenRodYellow
private static final Color nameMatchColor = new Color(0xFFEBCD); // BlanchedAlmond
private final JPanel hashMatcherServicePanel = createServicePanel(hashMatchColor);
private final JPanel nameMatcherServicePanel = createServicePanel(nameMatchColor);
private final List<SubtitleServiceBean> services = new ArrayList<SubtitleServiceBean>();
private final JTable subtitleMappingTable = createTable();
private final JComboBox<SubtitleNaming> preferredSubtitleNaming = new JComboBox<SubtitleNaming>(SubtitleNaming.values());
private ExecutorService queryService;
private ExecutorService downloadService;
public SubtitleAutoMatchDialog(Window owner) {
super(owner, "Download Subtitles", ModalityType.DOCUMENT_MODAL);
preferredSubtitleNaming.setSelectedItem(SubtitleNaming.MATCH_VIDEO_ADD_LANGUAGE_TAG);
JComponent content = (JComponent) getContentPane();
content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]"));
content.add(new JScrollPane(subtitleMappingTable), "grow, wrap");
content.add(hashMatcherServicePanel, "gap after rel");
content.add(nameMatcherServicePanel, "gap after indent*2");
JLabel namingLabel = new JLabel("Subtitle Naming:");
namingLabel.setHorizontalAlignment(JLabel.RIGHT);
content.add(namingLabel, "gap after rel, grow");
content.add(preferredSubtitleNaming, "gap after indent*2");
content.add(new JButton(downloadAction), "tag ok");
content.add(new JButton(finishAction), "tag cancel");
}
protected JPanel createServicePanel(Color color) {
JPanel panel = new JPanel(new MigLayout("hidemode 3"));
panel.setBorder(new RoundBorder());
panel.setOpaque(false);
panel.setBackground(color);
panel.setVisible(false);
return panel;
}
protected JTable createTable() {
JTable table = new JTable(new SubtitleMappingTableModel());
table.setDefaultRenderer(SubtitleMapping.class, new SubtitleMappingOptionRenderer());
table.setRowHeight(24);
table.setIntercellSpacing(new Dimension(5, 5));
table.setBackground(Color.white);
table.setAutoCreateRowSorter(true);
table.setFillsViewportHeight(true);
JComboBox editor = new SimpleComboBox(ResourceManager.getIcon("action.select"));
editor.setRenderer(new SubtitleOptionRenderer(true));
// disable selection
table.setSelectionModel(new EmptySelectionModel());
editor.setFocusable(false);
table.setDefaultEditor(SubtitleMapping.class, new DefaultCellEditor(editor) {
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JComboBox editor = (JComboBox) super.getTableCellEditorComponent(table, value, isSelected, row, column);
SubtitleMapping mapping = (SubtitleMapping) value;
DefaultComboBoxModel model = new DefaultComboBoxModel(mapping.getOptions());
model.addElement(null); // option to cancel selection
editor.setModel(model);
editor.setSelectedItem(mapping.getSelectedOption());
return editor;
}
});
return table;
}
public void setVideoFiles(File[] videoFiles) {
subtitleMappingTable.setModel(new SubtitleMappingTableModel(videoFiles));
}
public void addSubtitleService(VideoHashSubtitleService service) {
addSubtitleService(new VideoHashSubtitleServiceBean(service), hashMatcherServicePanel);
}
public void addSubtitleService(SubtitleProvider service) {
addSubtitleService(new SubtitleProviderBean(service, this), nameMatcherServicePanel);
}
protected void addSubtitleService(final SubtitleServiceBean service, final JPanel servicePanel) {
final LinkButton component = new LinkButton(service.getDescription(), null, ResourceManager.getIcon("database"), service.getLink());
component.setBorder(BorderFactory.createEmptyBorder());
component.setVisible(false);
service.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (service.getState() == StateValue.STARTED) {
component.setIcon(ResourceManager.getIcon("database.go"));
} else {
component.setIcon(ResourceManager.getIcon(service.getError() == null ? "database.ok" : "database.error"));
}
component.setVisible(true);
component.setToolTipText(String.format("%s: %s", service.getName(), service.getError() == null ? service.getState().toString().toLowerCase() : service.getError().getMessage()));
servicePanel.setVisible(true);
servicePanel.getParent().revalidate();
}
});
services.add(service);
servicePanel.add(component);
}
public void startQuery(String languageName) {
final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel();
QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), languageName, SubtitleAutoMatchDialog.this) {
@Override
protected void process(List<Map<File, List<SubtitleDescriptorBean>>> sequence) {
for (Map<File, List<SubtitleDescriptorBean>> subtitles : sequence) {
// update subtitle options
for (SubtitleMapping subtitleMapping : mappingModel) {
List<SubtitleDescriptorBean> options = subtitles.get(subtitleMapping.getVideoFile());
if (options != null && options.size() > 0) {
subtitleMapping.addOptions(options);
}
}
// make subtitle column visible
if (subtitles.size() > 0) {
mappingModel.setOptionColumnVisible(true);
}
}
}
@Override
protected void done() {
SwingUtilities.invokeLater(() -> mappingModel.fireTableStructureChanged()); // make sure UI is refershed after completion
}
};
queryService = Executors.newFixedThreadPool(1);
queryService.submit(queryTask);
}
private Boolean showConfirmReplaceDialog(List<?> files) {
JList existingFilesComponent = new JList(files.toArray()) {
@Override
public Dimension getPreferredScrollableViewportSize() {
// adjust component size
return new Dimension(80, 50);
}
};
Object[] message = new Object[] { "Replace existing subtitle files?", new JScrollPane(existingFilesComponent) };
Object[] options = new Object[] { "Replace All", "Skip All", "Cancel" };
JOptionPane optionPane = new JOptionPane(message, WARNING_MESSAGE, YES_NO_CANCEL_OPTION, null, options);
// display option dialog
optionPane.createDialog(SubtitleAutoMatchDialog.this, "Replace").setVisible(true);
// replace all
if (options[0] == optionPane.getValue())
return true;
// skip all
if (options[1] == optionPane.getValue())
return false;
// cancel
return null;
}
private final Action downloadAction = new AbstractAction("Download", ResourceManager.getIcon("dialog.continue")) {
@Override
public void actionPerformed(ActionEvent evt) {
// disable any active cell editor
if (subtitleMappingTable.getCellEditor() != null) {
subtitleMappingTable.getCellEditor().stopCellEditing();
}
// don't allow restart of download as long as there are still unfinished download tasks
if (downloadService != null && !downloadService.isTerminated()) {
return;
}
final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel();
// make sure we have access to the parent folder structure, not just the dropped file
if (isMacSandbox()) {
MacAppUtilities.askUnlockFolders(getWindow(evt.getSource()), mappingModel.getVideoFiles());
}
// collect the subtitles that will be fetched
List<DownloadTask> downloadQueue = new ArrayList<DownloadTask>();
for (final SubtitleMapping mapping : mappingModel) {
SubtitleDescriptorBean subtitleBean = mapping.getSelectedOption();
if (subtitleBean != null && subtitleBean.getState() == null) {
downloadQueue.add(new DownloadTask(mapping.getVideoFile(), subtitleBean, (SubtitleNaming) preferredSubtitleNaming.getSelectedItem()) {
@Override
protected void done() {
try {
mapping.setSubtitleFile(get());
} catch (Exception e) {
debug.log(Level.WARNING, e.getMessage(), e);
}
}
});
}
}
// collect downloads that will override a file
List<DownloadTask> confirmReplaceDownloadQueue = new ArrayList<DownloadTask>();
List<String> existingFiles = new ArrayList<String>();
for (DownloadTask download : downloadQueue) {
// target destination may not be known until files are extracted from archives
File target = download.getDestination(null);
if (target != null && target.exists()) {
confirmReplaceDownloadQueue.add(download);
existingFiles.add(target.getName());
}
}
// confirm replace
if (confirmReplaceDownloadQueue.size() > 0) {
Boolean option = showConfirmReplaceDialog(existingFiles);
// abort the operation altogether
if (option == null) {
return;
}
// don't replace any files
if (option == false) {
downloadQueue.removeAll(confirmReplaceDownloadQueue);
}
}
// start download
if (downloadQueue.size() > 0) {
downloadService = Executors.newFixedThreadPool(2);
for (DownloadTask downloadTask : downloadQueue) {
downloadTask.getSubtitleBean().setState(StateValue.PENDING);
downloadService.execute(downloadTask);
}
// terminate after all downloads have been completed
downloadService.shutdown();
}
}
};
private final Action finishAction = new AbstractAction("Close", ResourceManager.getIcon("dialog.cancel")) {
@Override
public void actionPerformed(ActionEvent evt) {
if (queryService != null) {
queryService.shutdownNow();
}
if (downloadService != null) {
downloadService.shutdownNow();
}
setVisible(false);
dispose();
}
};
private static class SubtitleMappingOptionRenderer extends DefaultTableCellRenderer {
private final JComboBox optionComboBox = new SimpleComboBox(ResourceManager.getIcon("action.select"));
public SubtitleMappingOptionRenderer() {
optionComboBox.setBackground(Color.white);
optionComboBox.setRenderer(new SubtitleOptionRenderer(false));
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
SubtitleMapping mapping = (SubtitleMapping) value;
SubtitleDescriptorBean subtitleBean = mapping.getSelectedOption();
// render combobox for subtitle options
if (mapping.isEditable() && subtitleBean != null) {
optionComboBox.setModel(new DefaultComboBoxModel(new Object[] { subtitleBean }));
return optionComboBox;
}
// render status label
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setForeground(table.getForeground());
if (subtitleBean == null) {
if (mapping.getOptions().length == 0) {
// no subtitles found
setText("No subtitles found");
setIcon(null);
setForeground(Color.gray);
} else {
// no subtitles found
setText("No subtitles selected");
setIcon(null);
setForeground(Color.gray);
}
} else if (subtitleBean.getState() == StateValue.PENDING) {
// download in the queue
setText(subtitleBean.getText());
setIcon(ResourceManager.getIcon("worker.pending"));
} else if (subtitleBean.getState() == StateValue.STARTED) {
// download in progress
setText(subtitleBean.getText());
setIcon(ResourceManager.getIcon("action.fetch"));
} else if (mapping.getSubtitleFile() != null) {
// download complete
setText(mapping.getSubtitleFile().getName());
setIcon(ResourceManager.getIcon("status.ok"));
} else {
setText(null);
setIcon(null);
}
return this;
}
}
private static class SubtitleOptionRenderer extends DefaultListCellRenderer {
private final Border padding = createEmptyBorder(3, 3, 3, 3);
private final boolean isEditor;
public SubtitleOptionRenderer(boolean isEditor) {
this.isEditor = isEditor;
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus);
setBorder(padding);
SubtitleDescriptorBean subtitleBean = (SubtitleDescriptorBean) value;
if (isEditor && index == list.getModel().getSize() - 2) {
setBorder(new CompoundBorder(new DashedSeparator(10, 4, Color.lightGray, list.getBackground()), getBorder())); // this element is always the last one
}
if (value == null) {
setText("Cancel selection");
setIcon(ResourceManager.getIcon("dialog.cancel"));
setToolTipText(null);
} else {
if (subtitleBean.getError() == null) {
setText(subtitleBean.getText());
setIcon(subtitleBean.getIcon());
setToolTipText(null);
} else {
setText(String.format("%s (%s)", subtitleBean.getText(), subtitleBean.getError().getMessage()));
setIcon(ResourceManager.getIcon("status.warning"));
setToolTipText(subtitleBean.getError().toString());
}
if (!isSelected) {
float f = subtitleBean.getMatchProbability();
if (f < 1) {
setOpaque(true);
setBackground(nameMatchColor);
}
if (f < 0.9f) {
setOpaque(true);
setBackground(derive(Color.RED, (1 - f) * 0.5f));
}
}
}
return this;
}
}
private static class SubtitleMappingTableModel extends AbstractTableModel implements Iterable<SubtitleMapping> {
private final SubtitleMapping[] data;
private boolean optionColumnVisible = false;
public SubtitleMappingTableModel(File... videoFiles) {
data = new SubtitleMapping[videoFiles.length];
for (int i = 0; i < videoFiles.length; i++) {
data[i] = new SubtitleMapping(videoFiles[i]);
data[i].addPropertyChangeListener(new SubtitleMappingListener(i));
}
}
public List<File> getVideoFiles() {
return new AbstractList<File>() {
@Override
public File get(int index) {
return data[index].getVideoFile();
}
@Override
public int size() {
return data.length;
}
};
}
@Override
public Iterator<SubtitleMapping> iterator() {
return Arrays.asList(data).iterator();
}
public void setOptionColumnVisible(boolean optionColumnVisible) {
if (this.optionColumnVisible == optionColumnVisible)
return;
this.optionColumnVisible = optionColumnVisible;
// update columns
fireTableStructureChanged();
}
@Override
public int getColumnCount() {
return optionColumnVisible ? 2 : 1;
}
@Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "Video";
case 1:
return "Subtitle";
}
return null;
}
@Override
public int getRowCount() {
return data.length;
}
@Override
public Object getValueAt(int row, int column) {
switch (column) {
case 0:
return data[row].getVideoFile().getName();
case 1:
return data[row];
}
return null;
}
@Override
public void setValueAt(Object value, int row, int column) {
data[row].setSelectedOption((SubtitleDescriptorBean) value);
}
@Override
public boolean isCellEditable(int row, int column) {
return column == 1 && data[row].isEditable();
}
@Override
public Class<?> getColumnClass(int column) {
switch (column) {
case 0:
return String.class;
case 1:
return SubtitleMapping.class;
}
return null;
}
private class SubtitleMappingListener implements PropertyChangeListener {
private final int index;
public SubtitleMappingListener(int index) {
this.index = index;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
// update state and subtitle options
fireTableRowsUpdated(index, index);
}
}
}
private static class SubtitleMapping extends AbstractBean {
private File videoFile;
private File subtitleFile;
private SubtitleDescriptorBean selectedOption;
private List<SubtitleDescriptorBean> options = new ArrayList<SubtitleDescriptorBean>();
public SubtitleMapping(File videoFile) {
this.videoFile = videoFile;
}
public File getVideoFile() {
return videoFile;
}
public File getSubtitleFile() {
return subtitleFile;
}
public void setSubtitleFile(File subtitleFile) {
this.subtitleFile = subtitleFile;
firePropertyChange("subtitleFile", null, this.subtitleFile);
}
public boolean isEditable() {
return subtitleFile == null && options.size() > 0 && (selectedOption == null || (selectedOption.getState() == null || selectedOption.getError() != null));
}
public SubtitleDescriptorBean getSelectedOption() {
return selectedOption;
}
public void setSelectedOption(SubtitleDescriptorBean selectedOption) {
if (this.selectedOption != null) {
this.selectedOption.removePropertyChangeListener(selectedOptionListener);
}
this.selectedOption = selectedOption;
if (this.selectedOption != null) {
this.selectedOption.addPropertyChangeListener(selectedOptionListener);
}
firePropertyChange("selectedOption", null, this.selectedOption);
}
public SubtitleDescriptorBean[] getOptions() {
return options.toArray(new SubtitleDescriptorBean[0]);
}
public void addOptions(List<SubtitleDescriptorBean> options) {
this.options.addAll(options);
if (selectedOption == null && options.size() > 0) {
setSelectedOption(options.get(0));
}
}
private final PropertyChangeListener selectedOptionListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
firePropertyChange("selectedOption", null, selectedOption);
}
};
}
private static class SubtitleDescriptorBean extends AbstractBean {
private final File videoFile;
private final SubtitleDescriptor descriptor;
private final SubtitleServiceBean service;
private StateValue state;
private Exception error;
public SubtitleDescriptorBean(File videoFile, SubtitleDescriptor descriptor, SubtitleServiceBean service) {
this.videoFile = videoFile;
this.descriptor = descriptor;
this.service = service;
}
public SubtitleDescriptor getDescriptor() {
return descriptor;
}
public float getMatchProbability() {
return service.getMatchProbabilty(videoFile, descriptor);
}
public String getText() {
return formatSubtitle(descriptor.getName(), getLanguageName(), getType());
}
public Icon getIcon() {
return service.getIcon();
}
public String getLanguageName() {
return descriptor.getLanguageName();
}
public String getType() {
return descriptor.getType();
}
public MemoryFile fetch() throws Exception {
setState(StateValue.STARTED);
try {
return fetchSubtitle(descriptor);
} catch (Exception e) {
// remember and rethrow exception
throw (error = e);
} finally {
setState(StateValue.DONE);
}
}
public Exception getError() {
return error;
}
public StateValue getState() {
return state;
}
public void setState(StateValue state) {
this.state = state;
firePropertyChange("state", null, this.state);
}
@Override
public String toString() {
return getText();
}
}
private static class QueryTask extends SwingWorker<Collection<File>, Map<File, List<SubtitleDescriptorBean>>> {
private final Component parent;
private final Collection<SubtitleServiceBean> services;
private final Collection<File> remainingVideos;
private final String languageName;
public QueryTask(Collection<SubtitleServiceBean> services, Collection<File> videoFiles, String languageName, Component parent) {
this.parent = parent;
this.services = services;
this.remainingVideos = new TreeSet<File>(videoFiles);
this.languageName = languageName;
}
@Override
protected Collection<File> doInBackground() throws Exception {
for (SubtitleServiceBean service : services) {
if (isCancelled() || Thread.interrupted()) {
throw new CancellationException();
}
if (remainingVideos.isEmpty()) {
break;
}
try {
Map<File, List<SubtitleDescriptorBean>> subtitleSet = new HashMap<File, List<SubtitleDescriptorBean>>();
for (final Entry<File, List<SubtitleDescriptor>> result : service.lookupSubtitles(remainingVideos, languageName, parent).entrySet()) {
Set<SubtitleDescriptor> subtitlesByRelevance = new LinkedHashSet<SubtitleDescriptor>();
// guess best hash match (default order is open bad due to invalid hash links)
SubtitleDescriptor bestMatch = getBestMatch(result.getKey(), result.getValue(), false);
if (bestMatch != null) {
subtitlesByRelevance.add(bestMatch);
}
subtitlesByRelevance.addAll(result.getValue());
// associate subtitles with services
List<SubtitleDescriptorBean> subtitles = new ArrayList<SubtitleDescriptorBean>();
for (SubtitleDescriptor it : subtitlesByRelevance) {
subtitles.add(new SubtitleDescriptorBean(result.getKey(), it, service));
}
subtitleSet.put(result.getKey(), subtitles);
}
// only lookup subtitles for remaining videos
for (Entry<File, List<SubtitleDescriptorBean>> it : subtitleSet.entrySet()) {
if (it.getValue() != null && it.getValue().size() > 0) {
remainingVideos.remove(it.getKey());
}
}
publish(subtitleSet);
} catch (CancellationException e) {
// don't ignore cancellation
throw e;
} catch (InterruptedException e) {
// don't ignore cancellation
throw e;
} catch (Exception e) {
// log and ignore
debug.log(Level.WARNING, e.getMessage());
}
}
return remainingVideos;
}
}
private static class DownloadTask extends SwingWorker<File, Void> {
private final File video;
private final SubtitleDescriptorBean descriptor;
private final SubtitleNaming naming;
public DownloadTask(File video, SubtitleDescriptorBean descriptor, SubtitleNaming naming) {
this.video = video;
this.descriptor = descriptor;
this.naming = naming;
}
public SubtitleDescriptorBean getSubtitleBean() {
return descriptor;
}
public File getDestination(MemoryFile subtitle) {
if (descriptor.getType() == null && subtitle == null)
return null;
// prefer type from descriptor because we need to know before we download the actual subtitle file
String name = naming.format(video, descriptor.getDescriptor(), descriptor.getType());
return new File(video.getParentFile(), name);
}
@Override
protected File doInBackground() {
try {
// fetch subtitle
MemoryFile subtitle = descriptor.fetch();
if (isCancelled())
return null;
// save to file
File destination = getDestination(subtitle);
writeFile(subtitle.getData(), destination);
return destination;
} catch (Exception e) {
debug.log(Level.WARNING, e.getMessage(), e);
}
return null;
}
}
protected static abstract class SubtitleServiceBean extends AbstractBean {
private final String name;
private final Icon icon;
private final URI link;
private StateValue state = StateValue.PENDING;
private Exception error = null;
public SubtitleServiceBean(String name, Icon icon, URI link) {
this.name = name;
this.icon = icon;
this.link = link;
}
public String getName() {
return name;
}
public Icon getIcon() {
return icon;
}
public URI getLink() {
return link;
}
public abstract String getDescription();
public abstract float getMatchProbabilty(File videoFile, SubtitleDescriptor descriptor);
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, Component parent) throws Exception {
setState(StateValue.STARTED);
try {
return getSubtitleList(files, languageName, parent);
} catch (Exception e) {
throw (error = e);
} finally {
setState(StateValue.DONE);
}
}
private void setState(StateValue state) {
this.state = state;
firePropertyChange("state", null, this.state);
}
public StateValue getState() {
return state;
}
public Throwable getError() {
return error;
}
}
protected static class VideoHashSubtitleServiceBean extends SubtitleServiceBean {
private VideoHashSubtitleService service;
public VideoHashSubtitleServiceBean(VideoHashSubtitleService service) {
super(service.getName(), service.getIcon(), service.getLink());
this.service = service;
}
@Override
public String getDescription() {
return service == WebServices.OpenSubtitles ? "Exact Search" : service.getName();
}
@Override
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> files, String languageName, Component parent) throws Exception {
return lookupSubtitlesByHash(service, files, languageName, true, false);
}
@Override
public float getMatchProbabilty(File videoFile, SubtitleDescriptor descriptor) {
return 1;
}
}
protected static class SubtitleProviderBean extends SubtitleServiceBean {
private SubtitleProvider service;
public SubtitleProviderBean(SubtitleProvider service, SubtitleAutoMatchDialog inputProvider) {
super(service.getName(), service.getIcon(), service.getLink());
this.service = service;
}
@Override
public String getDescription() {
return "Fuzzy Search";
}
@Override
protected Map<File, List<SubtitleDescriptor>> getSubtitleList(Collection<File> fileSet, String languageName, Component parent) throws Exception {
return findSubtitlesByName(service, fileSet, languageName, null, true, false);
}
@Override
public float getMatchProbabilty(File videoFile, SubtitleDescriptor descriptor) {
return SubtitleMetrics.verificationMetric().getSimilarity(videoFile, descriptor);
}
}
}