+ added support for subtitle download via video/movie hash
* added video/subtitle file drop target in SubtitlePanel * added VideoHashSubtitleDownloadDialog
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 180 B |
After Width: | Height: | Size: 682 B |
After Width: | Height: | Size: 698 B |
After Width: | Height: | Size: 689 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -75,8 +75,8 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
|
|||
|
||||
searchTextField.getEditor().setAction(searchAction);
|
||||
|
||||
searchTextField.getSelectButton().setModel(Arrays.asList(createSearchEngines()));
|
||||
searchTextField.getSelectButton().setLabelProvider(createSearchEngineLabelProvider());
|
||||
searchTextField.getSelectButton().setModel(Arrays.asList(getSearchEngines()));
|
||||
searchTextField.getSelectButton().setLabelProvider(getSearchEngineLabelProvider());
|
||||
|
||||
try {
|
||||
// restore selected subtitle client
|
||||
|
@ -101,10 +101,10 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
|
|||
}
|
||||
|
||||
|
||||
protected abstract S[] createSearchEngines();
|
||||
protected abstract S[] getSearchEngines();
|
||||
|
||||
|
||||
protected abstract LabelProvider<S> createSearchEngineLabelProvider();
|
||||
protected abstract LabelProvider<S> getSearchEngineLabelProvider();
|
||||
|
||||
|
||||
protected abstract Settings getSettings();
|
||||
|
|
|
@ -79,7 +79,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListProvider, E
|
|||
|
||||
|
||||
@Override
|
||||
protected EpisodeListProvider[] createSearchEngines() {
|
||||
protected EpisodeListProvider[] getSearchEngines() {
|
||||
return new EpisodeListProvider[] {
|
||||
new TVRageClient(),
|
||||
new AnidbClient(),
|
||||
|
@ -91,7 +91,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListProvider, E
|
|||
|
||||
|
||||
@Override
|
||||
protected LabelProvider<EpisodeListProvider> createSearchEngineLabelProvider() {
|
||||
protected LabelProvider<EpisodeListProvider> getSearchEngineLabelProvider() {
|
||||
return SimpleLabelProvider.forClass(EpisodeListProvider.class);
|
||||
}
|
||||
|
||||
|
|
|
@ -449,7 +449,7 @@ class HistoryDialog extends JDialog {
|
|||
type = QUESTION_MESSAGE;
|
||||
options = EnumSet.of(Option.Rename, Option.ChangeDirectory, Option.Cancel);
|
||||
} else {
|
||||
String text = String.format("Some files are missing. Please select a different directory.");
|
||||
String text = "Some files are missing. Please select a different directory.";
|
||||
JList missingFilesComponent = new JList(missingFiles.toArray()) {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static javax.swing.BorderFactory.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.plaf.basic.BasicComboBoxUI;
|
||||
import javax.swing.plaf.basic.BasicComboPopup;
|
||||
import javax.swing.plaf.basic.ComboPopup;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
|
||||
public class SimpleComboBox extends JComboBox {
|
||||
|
||||
public SimpleComboBox() {
|
||||
setUI(new SimpleComboBoxUI());
|
||||
setBorder(createEmptyBorder());
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleComboBoxUI extends BasicComboBoxUI {
|
||||
|
||||
@Override
|
||||
protected JButton createArrowButton() {
|
||||
JButton button = new JButton(ResourceManager.getIcon("arrow.down"));
|
||||
button.setContentAreaFilled(false);
|
||||
button.setBorderPainted(false);
|
||||
button.setFocusPainted(false);
|
||||
button.setOpaque(false);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ComboPopup createPopup() {
|
||||
return new BasicComboPopup(comboBox) {
|
||||
|
||||
@Override
|
||||
protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
|
||||
Rectangle bounds = super.computePopupBounds(px, py, pw, ph);
|
||||
|
||||
// allow combobox popup to be wider than the combobox itself
|
||||
bounds.width = Math.max(bounds.width, list.getPreferredSize().width);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void configurePopup() {
|
||||
super.configurePopup();
|
||||
|
||||
setOpaque(true);
|
||||
setBackground(list.getBackground());
|
||||
|
||||
// use gray instead of black border for combobox popup
|
||||
setBorder(createCompoundBorder(createLineBorder(Color.gray, 1), createEmptyBorder(1, 1, 1, 1)));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.MediaTypes.*;
|
||||
import static net.sourceforge.filebot.ui.transfer.FileTransferable.*;
|
||||
import static net.sourceforge.tuned.FileUtilities.*;
|
||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.awt.dnd.DropTargetAdapter;
|
||||
import java.awt.dnd.DropTargetDragEvent;
|
||||
import java.awt.dnd.DropTargetDropEvent;
|
||||
import java.awt.dnd.DropTargetEvent;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.web.VideoHashSubtitleService;
|
||||
|
||||
|
||||
abstract class SubtitleDropTarget extends JButton {
|
||||
|
||||
private enum DropAction {
|
||||
Download,
|
||||
Upload,
|
||||
Cancel
|
||||
}
|
||||
|
||||
|
||||
public SubtitleDropTarget() {
|
||||
setHorizontalAlignment(CENTER);
|
||||
|
||||
setHideActionText(true);
|
||||
setBorderPainted(false);
|
||||
setContentAreaFilled(false);
|
||||
setFocusPainted(false);
|
||||
|
||||
setBackground(Color.white);
|
||||
setOpaque(false);
|
||||
|
||||
// initialize with default mode
|
||||
setDropAction(DropAction.Download);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
|
||||
// install mouse listener
|
||||
addActionListener(clickHandler);
|
||||
|
||||
// install drop target
|
||||
new DropTarget(this, dropHandler);
|
||||
}
|
||||
|
||||
|
||||
private void setDropAction(DropAction dropAction) {
|
||||
setIcon(getIcon(dropAction));
|
||||
}
|
||||
|
||||
|
||||
private Icon getIcon(DropAction dropAction) {
|
||||
switch (dropAction) {
|
||||
case Download:
|
||||
return ResourceManager.getIcon("subtitle.exact.download");
|
||||
case Upload:
|
||||
return ResourceManager.getIcon("subtitle.exact.upload");
|
||||
default:
|
||||
return ResourceManager.getIcon("message.error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public abstract VideoHashSubtitleService[] getServices();
|
||||
|
||||
|
||||
public abstract String getQueryLanguage();
|
||||
|
||||
|
||||
private boolean handleDownload(List<File> videoFiles) {
|
||||
VideoHashSubtitleDownloadDialog dialog = new VideoHashSubtitleDownloadDialog(getWindow(this));
|
||||
|
||||
// initialize download parameters
|
||||
dialog.setVideoFiles(videoFiles.toArray(new File[0]));
|
||||
|
||||
for (VideoHashSubtitleService service : getServices()) {
|
||||
dialog.addSubtitleService(service);
|
||||
}
|
||||
|
||||
// start looking for subtitles
|
||||
dialog.startQuery(getQueryLanguage());
|
||||
|
||||
// initialize window properties
|
||||
dialog.setIconImage(getImage(getIcon(DropAction.Download)));
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
||||
dialog.pack();
|
||||
|
||||
// show dialog
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
|
||||
// now it's up to the user
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private boolean handleUpload(Map<File, File> videosMappedBySubtitle) {
|
||||
// TODO implement upload
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
|
||||
private boolean handleDrop(List<File> files) {
|
||||
// perform a drop action depending on the given files
|
||||
if (containsOnly(files, VIDEO_FILES)) {
|
||||
return handleDownload(files);
|
||||
}
|
||||
|
||||
if (containsOnly(files, FOLDERS)) {
|
||||
// collect all video files from the dropped folders
|
||||
return handleDownload(filter(listFiles(files, 0), VIDEO_FILES));
|
||||
}
|
||||
|
||||
if (containsOnly(files, SUBTITLE_FILES)) {
|
||||
// TODO implement upload
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
if (filter(files, VIDEO_FILES).size() == filter(files, SUBTITLE_FILES).size()) {
|
||||
// TODO implement upload
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private DropAction getDropAction(List<File> files) {
|
||||
// video files only, or any folder, containing video files
|
||||
if (containsOnly(files, VIDEO_FILES) || (containsOnly(files, FOLDERS) && filter(listFiles(files, 0), VIDEO_FILES).size() > 0)) {
|
||||
return DropAction.Download;
|
||||
}
|
||||
|
||||
// subtitle files only, or video/subtitle pairs
|
||||
if (containsOnly(files, SUBTITLE_FILES) || filter(files, VIDEO_FILES).size() == filter(files, SUBTITLE_FILES).size()) {
|
||||
return DropAction.Upload;
|
||||
}
|
||||
|
||||
// unknown input
|
||||
return DropAction.Cancel;
|
||||
}
|
||||
|
||||
|
||||
private final DropTargetAdapter dropHandler = new DropTargetAdapter() {
|
||||
|
||||
@Override
|
||||
public void dragEnter(DropTargetDragEvent dtde) {
|
||||
DropAction dropAction = DropAction.Cancel;
|
||||
|
||||
try {
|
||||
dropAction = getDropAction(getFilesFromTransferable(dtde.getTransferable()));
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// update visual representation
|
||||
setDropAction(dropAction);
|
||||
|
||||
// accept or reject
|
||||
if (dropAction != DropAction.Cancel) {
|
||||
dtde.acceptDrag(DnDConstants.ACTION_COPY);
|
||||
} else {
|
||||
dtde.rejectDrag();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void dragExit(DropTargetEvent dte) {
|
||||
// reset to default state
|
||||
setDropAction(DropAction.Download);
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public void drop(DropTargetDropEvent dtde) {
|
||||
dtde.acceptDrop(DnDConstants.ACTION_REFERENCE);
|
||||
|
||||
try {
|
||||
dtde.dropComplete(handleDrop(getFilesFromTransferable(dtde.getTransferable())));
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
// reset to default state
|
||||
dragExit(dtde);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final ActionListener clickHandler = new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setMultiSelectionEnabled(true);
|
||||
|
||||
// collect media file extensions (video and subtitle files)
|
||||
List<String> extensions = new ArrayList<String>();
|
||||
Collections.addAll(extensions, VIDEO_FILES.extensions());
|
||||
Collections.addAll(extensions, SUBTITLE_FILES.extensions());
|
||||
|
||||
chooser.setFileFilter(new FileNameExtensionFilter("Media files", extensions.toArray(new String[0])));
|
||||
|
||||
if (chooser.showOpenDialog(getWindow(evt.getSource())) == JFileChooser.APPROVE_OPTION) {
|
||||
List<File> files = Arrays.asList(chooser.getSelectedFiles());
|
||||
|
||||
if (getDropAction(files) != DropAction.Cancel) {
|
||||
handleDrop(Arrays.asList(chooser.getSelectedFiles()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -2,10 +2,14 @@
|
|||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.Settings.*;
|
||||
import static net.sourceforge.filebot.ui.panel.subtitle.LanguageComboBoxModel.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.net.URI;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
|
@ -19,13 +23,10 @@ import javax.swing.JComboBox;
|
|||
import net.sourceforge.filebot.Settings;
|
||||
import net.sourceforge.filebot.ui.AbstractSearchPanel;
|
||||
import net.sourceforge.filebot.ui.SelectDialog;
|
||||
import net.sourceforge.filebot.web.OpenSubtitlesClient;
|
||||
import net.sourceforge.filebot.web.SearchResult;
|
||||
import net.sourceforge.filebot.web.SublightSubtitleClient;
|
||||
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
|
||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
||||
import net.sourceforge.filebot.web.SubtitleProvider;
|
||||
import net.sourceforge.filebot.web.SubtitleSourceClient;
|
||||
import net.sourceforge.filebot.web.VideoHashSubtitleService;
|
||||
import net.sourceforge.tuned.PreferencesList;
|
||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
||||
import net.sourceforge.tuned.ui.LabelProvider;
|
||||
|
@ -92,22 +93,60 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
|
|||
|
||||
// add after text field
|
||||
add(languageComboBox, 1);
|
||||
|
||||
// add at the top right corner
|
||||
add(dropTarget, "width 1.6cm, height 1.2cm, pos n 0% 100% n", 0);
|
||||
}
|
||||
|
||||
|
||||
private final SubtitleDropTarget dropTarget = new SubtitleDropTarget() {
|
||||
|
||||
@Override
|
||||
public VideoHashSubtitleService[] getServices() {
|
||||
return SubtitleServices.getVideoHashSubtitleServices();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getQueryLanguage() {
|
||||
// use currently selected language for drop target
|
||||
return languageModel.getSelectedItem() == ALL_LANGUAGES ? null : languageModel.getSelectedItem().getName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
Path2D path = new Path2D.Float();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(0, getHeight() - 1 - 12);
|
||||
path.quadTo(0, getHeight() - 1, 12, getHeight() - 1);
|
||||
path.lineTo(getWidth(), getHeight() - 1);
|
||||
path.lineTo(getWidth(), 0);
|
||||
|
||||
g2d.setPaint(getBackground());
|
||||
g2d.fill(path);
|
||||
|
||||
g2d.setPaint(Color.gray);
|
||||
g2d.draw(path);
|
||||
|
||||
g2d.translate(2, 0);
|
||||
super.paintComponent(g2d);
|
||||
g2d.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
protected SubtitleProvider[] getSearchEngines() {
|
||||
return SubtitleServices.getSubtitleProviders();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected SubtitleProvider[] createSearchEngines() {
|
||||
return new SubtitleProvider[] {
|
||||
new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion())),
|
||||
new SubsceneSubtitleClient(),
|
||||
new SublightSubtitleClient(getApplicationName(), getApplicationProperty("sublight.apikey")),
|
||||
new SubtitleSourceClient()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected LabelProvider<SubtitleProvider> createSearchEngineLabelProvider() {
|
||||
protected LabelProvider<SubtitleProvider> getSearchEngineLabelProvider() {
|
||||
return SimpleLabelProvider.forClass(SubtitleProvider.class);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.Settings.*;
|
||||
|
||||
import net.sourceforge.filebot.web.OpenSubtitlesClient;
|
||||
import net.sourceforge.filebot.web.SublightSubtitleClient;
|
||||
import net.sourceforge.filebot.web.SubsceneSubtitleClient;
|
||||
import net.sourceforge.filebot.web.SubtitleProvider;
|
||||
import net.sourceforge.filebot.web.SubtitleSourceClient;
|
||||
import net.sourceforge.filebot.web.VideoHashSubtitleService;
|
||||
|
||||
|
||||
final class SubtitleServices {
|
||||
|
||||
public static final OpenSubtitlesClient openSubtitlesClient = new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion()));
|
||||
public static final SublightSubtitleClient sublightSubtitleClient = new SublightSubtitleClient(getApplicationName(), getApplicationProperty("sublight.apikey"));
|
||||
|
||||
public static final SubsceneSubtitleClient subsceneSubtitleClient = new SubsceneSubtitleClient();
|
||||
public static final SubtitleSourceClient subtitleSourceClient = new SubtitleSourceClient();
|
||||
|
||||
|
||||
public static SubtitleProvider[] getSubtitleProviders() {
|
||||
return new SubtitleProvider[] { openSubtitlesClient, subsceneSubtitleClient, sublightSubtitleClient, subtitleSourceClient };
|
||||
}
|
||||
|
||||
|
||||
public static VideoHashSubtitleService[] getVideoHashSubtitleServices() {
|
||||
return new VideoHashSubtitleService[] { openSubtitlesClient, sublightSubtitleClient };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dummy constructor to prevent instantiation.
|
||||
*/
|
||||
private SubtitleServices() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,804 @@
|
|||
|
||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static javax.swing.BorderFactory.*;
|
||||
import static javax.swing.JOptionPane.*;
|
||||
import static net.sourceforge.filebot.ui.panel.subtitle.SubtitleUtilities.*;
|
||||
|
||||
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.nio.ByteBuffer;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.DefaultCellEditor;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.DefaultListSelectionModel;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.SwingWorker.StateValue;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
||||
import net.sourceforge.filebot.web.VideoHashSubtitleService;
|
||||
import net.sourceforge.tuned.FileUtilities;
|
||||
import net.sourceforge.tuned.ui.AbstractBean;
|
||||
import net.sourceforge.tuned.ui.LinkButton;
|
||||
import net.sourceforge.tuned.ui.RoundBorder;
|
||||
|
||||
|
||||
class VideoHashSubtitleDownloadDialog extends JDialog {
|
||||
|
||||
private final JPanel servicePanel = new JPanel(new MigLayout());
|
||||
private final List<VideoHashSubtitleServiceBean> services = new ArrayList<VideoHashSubtitleServiceBean>();
|
||||
|
||||
private final JTable subtitleMappingTable = createTable();
|
||||
|
||||
private ExecutorService downloadService;
|
||||
|
||||
|
||||
public VideoHashSubtitleDownloadDialog(Window owner) {
|
||||
super(owner, "Download Subtitles", ModalityType.MODELESS);
|
||||
|
||||
JComponent content = (JComponent) getContentPane();
|
||||
content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]"));
|
||||
|
||||
servicePanel.setBorder(new RoundBorder());
|
||||
servicePanel.setOpaque(false);
|
||||
servicePanel.setBackground(new Color(0xFAFAD2)); // LightGoldenRodYellow
|
||||
|
||||
content.add(new JScrollPane(subtitleMappingTable), "grow, wrap");
|
||||
content.add(servicePanel, "gap after indent*2");
|
||||
|
||||
content.add(new JButton(downloadAction), "tag ok");
|
||||
content.add(new JButton(finishAction), "tag cancel");
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
editor.setRenderer(new SubtitleOptionRenderer());
|
||||
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;
|
||||
editor.setModel(new DefaultComboBoxModel(mapping.getOptions()));
|
||||
editor.setSelectedItem(mapping.getSelectedOption());
|
||||
|
||||
return editor;
|
||||
}
|
||||
});
|
||||
|
||||
// disable selection
|
||||
table.setSelectionModel(new DefaultListSelectionModel() {
|
||||
|
||||
@Override
|
||||
public void addSelectionInterval(int from, int to) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSelectionInterval(int from, int to) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setAnchorSelectionIndex(int anchorIndex) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setLeadSelectionIndex(int leadIndex) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
public void setVideoFiles(File[] videoFiles) {
|
||||
subtitleMappingTable.setModel(new SubtitleMappingTableModel(videoFiles));
|
||||
}
|
||||
|
||||
|
||||
public void addSubtitleService(final VideoHashSubtitleService service) {
|
||||
final VideoHashSubtitleServiceBean serviceBean = new VideoHashSubtitleServiceBean(service);
|
||||
final LinkButton component = new LinkButton(serviceBean.getName(), ResourceManager.getIcon("database.go"), serviceBean.getLink());
|
||||
|
||||
serviceBean.addPropertyChangeListener(new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (serviceBean.getState() == StateValue.STARTED) {
|
||||
component.setIcon(ResourceManager.getIcon("database.go"));
|
||||
} else {
|
||||
component.setIcon(ResourceManager.getIcon(serviceBean.getError() == null ? "database.ok" : "database.error"));
|
||||
}
|
||||
|
||||
component.setToolTipText(serviceBean.getError() == null ? null : serviceBean.getError().getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
services.add(serviceBean);
|
||||
servicePanel.add(component);
|
||||
}
|
||||
|
||||
|
||||
public void startQuery(String languageName) {
|
||||
final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel();
|
||||
|
||||
// query services concurrently
|
||||
for (VideoHashSubtitleServiceBean service : services) {
|
||||
QueryTask task = new QueryTask(service, mappingModel.getVideoFiles(), languageName) {
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
Map<File, List<SubtitleDescriptorBean>> subtitles = get();
|
||||
|
||||
// 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
|
||||
mappingModel.setOptionColumnVisible(true);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// start background worker
|
||||
task.execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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(VideoHashSubtitleDownloadDialog.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) {
|
||||
// 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();
|
||||
|
||||
// collect the subtitles that will be fetched
|
||||
List<DownloadTask> downloadQueue = new ArrayList<DownloadTask>();
|
||||
|
||||
for (SubtitleMapping mapping : mappingModel) {
|
||||
SubtitleDescriptorBean subtitleBean = mapping.getSelectedOption();
|
||||
|
||||
if (subtitleBean != null && subtitleBean.getState() == null) {
|
||||
downloadQueue.add(new DownloadTask(subtitleBean, mapping.getSubtitleFile()));
|
||||
}
|
||||
}
|
||||
|
||||
// collect downloads that will override a file
|
||||
List<DownloadTask> confirmReplaceDownloadQueue = new ArrayList<DownloadTask>();
|
||||
List<String> existingFiles = new ArrayList<String>();
|
||||
|
||||
for (DownloadTask download : downloadQueue) {
|
||||
if (download.getDestination().exists()) {
|
||||
confirmReplaceDownloadQueue.add(download);
|
||||
existingFiles.add(download.getDestination().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.newSingleThreadExecutor();
|
||||
|
||||
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 (downloadService != null) {
|
||||
downloadService.shutdownNow();
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static class SubtitleMappingOptionRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private final JComboBox optionComboBox = new SimpleComboBox();
|
||||
|
||||
|
||||
public SubtitleMappingOptionRenderer() {
|
||||
optionComboBox.setRenderer(new SubtitleOptionRenderer());
|
||||
}
|
||||
|
||||
|
||||
@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()) {
|
||||
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) {
|
||||
// no subtitles found
|
||||
setText("No subtitles found");
|
||||
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 {
|
||||
// download complete
|
||||
setText(mapping.getSubtitleFile().getName());
|
||||
setIcon(ResourceManager.getIcon("status.ok"));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SubtitleOptionRenderer extends DefaultListCellRenderer {
|
||||
|
||||
private final Border padding = createEmptyBorder(3, 3, 3, 3);
|
||||
|
||||
|
||||
@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;
|
||||
setText(subtitleBean.getText());
|
||||
setIcon(subtitleBean.getError() == null ? subtitleBean.getIcon() : ResourceManager.getIcon("status.warning"));
|
||||
|
||||
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) {
|
||||
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
|
||||
if (optionColumnVisible) {
|
||||
fireTableCellUpdated(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SubtitleMapping extends AbstractBean {
|
||||
|
||||
private final File videoFile;
|
||||
|
||||
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 new File(videoFile.getParentFile(), FileUtilities.getName(videoFile) + '.' + selectedOption.getType());
|
||||
}
|
||||
|
||||
|
||||
public boolean isEditable() {
|
||||
return 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;
|
||||
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 SubtitleDescriptor subtitle;
|
||||
private final VideoHashSubtitleServiceBean source;
|
||||
|
||||
private StateValue state;
|
||||
private Exception error;
|
||||
|
||||
|
||||
public SubtitleDescriptorBean(SubtitleDescriptor subtitle, VideoHashSubtitleServiceBean source) {
|
||||
this.subtitle = subtitle;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
|
||||
public String getText() {
|
||||
return subtitle.getName() + '.' + subtitle.getType();
|
||||
}
|
||||
|
||||
|
||||
public Icon getIcon() {
|
||||
return source.getIcon();
|
||||
}
|
||||
|
||||
|
||||
public String getType() {
|
||||
return subtitle.getType();
|
||||
}
|
||||
|
||||
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
setState(StateValue.STARTED);
|
||||
|
||||
try {
|
||||
return subtitle.fetch();
|
||||
} catch (Exception e) {
|
||||
// remember exception
|
||||
error = e;
|
||||
|
||||
// rethrow exception
|
||||
throw 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<Map<File, List<SubtitleDescriptorBean>>, Void> {
|
||||
|
||||
private final VideoHashSubtitleServiceBean service;
|
||||
|
||||
private final File[] videoFiles;
|
||||
private final String languageName;
|
||||
|
||||
|
||||
public QueryTask(VideoHashSubtitleServiceBean service, Collection<File> videoFiles, String languageName) {
|
||||
this.service = service;
|
||||
this.videoFiles = videoFiles.toArray(new File[0]);
|
||||
this.languageName = languageName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Map<File, List<SubtitleDescriptorBean>> doInBackground() throws Exception {
|
||||
Map<File, List<SubtitleDescriptorBean>> subtitleSet = new HashMap<File, List<SubtitleDescriptorBean>>();
|
||||
|
||||
for (final Entry<File, List<SubtitleDescriptor>> result : service.getSubtitleList(videoFiles, languageName).entrySet()) {
|
||||
List<SubtitleDescriptorBean> subtitles = new ArrayList<SubtitleDescriptorBean>();
|
||||
|
||||
// associate subtitles with services
|
||||
for (SubtitleDescriptor subtitleDescriptor : result.getValue()) {
|
||||
subtitles.add(new SubtitleDescriptorBean(subtitleDescriptor, service));
|
||||
}
|
||||
|
||||
subtitleSet.put(result.getKey(), subtitles);
|
||||
}
|
||||
|
||||
return subtitleSet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class DownloadTask extends SwingWorker<File, Void> {
|
||||
|
||||
private final SubtitleDescriptorBean subtitle;
|
||||
private final File destination;
|
||||
|
||||
|
||||
public DownloadTask(SubtitleDescriptorBean subtitle, File destination) {
|
||||
this.subtitle = subtitle;
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
|
||||
public SubtitleDescriptorBean getSubtitleBean() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
|
||||
public File getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected File doInBackground() {
|
||||
try {
|
||||
// fetch subtitle
|
||||
ByteBuffer data = subtitle.fetch();
|
||||
|
||||
if (isCancelled())
|
||||
return null;
|
||||
|
||||
// save to file
|
||||
write(data, destination);
|
||||
|
||||
return destination;
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class VideoHashSubtitleServiceBean extends AbstractBean {
|
||||
|
||||
private final VideoHashSubtitleService service;
|
||||
|
||||
private StateValue state;
|
||||
private Throwable error;
|
||||
|
||||
|
||||
public VideoHashSubtitleServiceBean(VideoHashSubtitleService service) {
|
||||
this.service = service;
|
||||
this.state = StateValue.PENDING;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return service.getName();
|
||||
}
|
||||
|
||||
|
||||
public Icon getIcon() {
|
||||
return service.getIcon();
|
||||
}
|
||||
|
||||
|
||||
public URI getLink() {
|
||||
return service.getLink();
|
||||
}
|
||||
|
||||
|
||||
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] files, String languageName) throws Exception {
|
||||
setState(StateValue.STARTED);
|
||||
|
||||
try {
|
||||
return service.getSubtitleList(files, languageName);
|
||||
} catch (Exception e) {
|
||||
// remember error
|
||||
error = e;
|
||||
|
||||
// rethrow error
|
||||
throw 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -44,6 +44,12 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
return URI.create("http://www.opensubtitles.org");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.opensubtitles");
|
||||
|
|
|
@ -58,6 +58,12 @@ public class SublightSubtitleClient implements SubtitleProvider, VideoHashSubtit
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
return URI.create("http://www.sublight.si");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.sublight");
|
||||
|
@ -131,7 +137,7 @@ public class SublightSubtitleClient implements SubtitleProvider, VideoHashSubtit
|
|||
}
|
||||
} catch (LinkageError e) {
|
||||
// MediaInfo native lib not available
|
||||
throw new UnsupportedOperationException(e);
|
||||
throw new UnsupportedOperationException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
|
@ -292,7 +298,8 @@ public class SublightSubtitleClient implements SubtitleProvider, VideoHashSubtit
|
|||
|
||||
@Override
|
||||
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
|
||||
return null;
|
||||
// note that sublight can only be accessed via the soap API
|
||||
return URI.create("http://www.sublight.si/SearchSubtitles.aspx");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,12 @@ public class SubsceneSubtitleClient implements SubtitleProvider {
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
return URI.create("http://subscene.com");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.subscene");
|
||||
|
|
|
@ -22,6 +22,9 @@ public interface SubtitleProvider {
|
|||
public String getName();
|
||||
|
||||
|
||||
public URI getLink();
|
||||
|
||||
|
||||
public Icon getIcon();
|
||||
|
||||
}
|
||||
|
|
|
@ -15,11 +15,11 @@ import java.util.Map;
|
|||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
|
||||
public class SubtitleSourceClient implements SubtitleProvider {
|
||||
|
||||
|
@ -34,6 +34,12 @@ public class SubtitleSourceClient implements SubtitleProvider {
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
return URI.create("http://www.subtitlesource.org");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.subtitlesource");
|
||||
|
|
|
@ -3,9 +3,12 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
|
||||
public interface VideoHashSubtitleService {
|
||||
|
||||
|
@ -14,4 +17,13 @@ public interface VideoHashSubtitleService {
|
|||
|
||||
public boolean publishSubtitle(int imdbid, String languageName, File videoFile, File subtitleFile) throws Exception;
|
||||
|
||||
|
||||
public String getName();
|
||||
|
||||
|
||||
public URI getLink();
|
||||
|
||||
|
||||
public Icon getIcon();
|
||||
|
||||
}
|
||||
|
|
|
@ -140,6 +140,32 @@ public final class FileUtilities {
|
|||
}
|
||||
|
||||
|
||||
public static List<File> listFiles(Iterable<File> folders, int maxDepth) {
|
||||
List<File> files = new ArrayList<File>();
|
||||
|
||||
// collect files from directory tree
|
||||
for (File folder : folders) {
|
||||
listFiles(folder, 0, files, maxDepth);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
private static void listFiles(File folder, int depth, List<File> files, int maxDepth) {
|
||||
if (depth > maxDepth)
|
||||
return;
|
||||
|
||||
for (File file : folder.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
listFiles(file, depth + 1, files, maxDepth);
|
||||
} else {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invalid filename characters: \, /, :, *, ?, ", <, >, |, \r and \n
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
package net.sourceforge.tuned.ui;
|
||||
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
|
||||
import javax.swing.event.SwingPropertyChangeSupport;
|
||||
|
||||
|
||||
public abstract class AbstractBean {
|
||||
|
||||
private final PropertyChangeSupport pcs;
|
||||
|
||||
|
||||
public AbstractBean() {
|
||||
pcs = new SwingPropertyChangeSupport(this, true);
|
||||
}
|
||||
|
||||
|
||||
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
|
||||
pcs.firePropertyChange(propertyName, oldValue, newValue);
|
||||
}
|
||||
|
||||
|
||||
protected void firePropertyChange(PropertyChangeEvent e) {
|
||||
pcs.firePropertyChange(e);
|
||||
}
|
||||
|
||||
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
public PropertyChangeListener[] getPropertyChangeListeners() {
|
||||
return pcs.getPropertyChangeListeners();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
package net.sourceforge.tuned.ui;
|
||||
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.RenderingHints;
|
||||
|
||||
import javax.swing.border.AbstractBorder;
|
||||
|
||||
|
||||
public class RoundBorder extends AbstractBorder {
|
||||
|
||||
private final Color color;
|
||||
private final Insets insets;
|
||||
private final int arc;
|
||||
|
||||
|
||||
public RoundBorder() {
|
||||
this.color = new Color(0xACACAC);
|
||||
this.arc = 12;
|
||||
this.insets = new Insets(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
|
||||
public RoundBorder(Color color, int arc, Insets insets) {
|
||||
this.color = color;
|
||||
this.arc = arc;
|
||||
this.insets = insets;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isBorderOpaque() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2d.setPaint(c.getBackground());
|
||||
g2d.fillRoundRect(x, y, width - 1, height - 1, arc, arc);
|
||||
|
||||
g2d.setPaint(color);
|
||||
g2d.drawRoundRect(x, y, width - 1, height - 1, arc, arc);
|
||||
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Insets getBorderInsets(Component c) {
|
||||
return new Insets(insets.top, insets.left, insets.bottom, insets.right);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Insets getBorderInsets(Component c, Insets insets) {
|
||||
insets.top = this.insets.top;
|
||||
insets.left = this.insets.left;
|
||||
insets.bottom = this.insets.bottom;
|
||||
insets.right = this.insets.right;
|
||||
|
||||
return insets;
|
||||
}
|
||||
|
||||
}
|