* refactor subtitle upload (and improve CD1/CD2 upload support)
This commit is contained in:
parent
e431a48120
commit
fde21946dc
|
@ -32,5 +32,6 @@
|
|||
<classpathentry kind="lib" path="lib/ivy/jar/jna-platform.jar"/>
|
||||
<classpathentry kind="lib" path="lib/ivy/jar/language-detector.jar"/>
|
||||
<classpathentry kind="lib" path="lib/ivy/bundle/guava.jar"/>
|
||||
<classpathentry kind="lib" path="lib/ivy/jar/streamex.jar"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
|
|
@ -194,6 +194,10 @@
|
|||
<include name="com/google/**" />
|
||||
</zipfileset>
|
||||
|
||||
<zipfileset src="${dir.lib}/ivy/jar/streamex.jar">
|
||||
<include name="one/util/streamex/**" />
|
||||
</zipfileset>
|
||||
|
||||
<!-- include classes and native libraries -->
|
||||
<zipfileset src="${dir.lib}/ivy/jar/jna.jar">
|
||||
<include name="com/sun/jna/**" />
|
||||
|
|
1
ivy.xml
1
ivy.xml
|
@ -24,6 +24,7 @@
|
|||
<dependency org="net.sf.sevenzipjbinding" name="sevenzipjbinding" rev="9.20-2.00beta" />
|
||||
<dependency org="net.sf.sevenzipjbinding" name="sevenzipjbinding-all-platforms" rev="9.20-2.00beta" />
|
||||
<dependency org="com.optimaize.languagedetector" name="language-detector" rev="0.5" />
|
||||
<dependency org="one.util" name="streamex" rev="0.5.4" />
|
||||
|
||||
<!-- FileBot Scripting -->
|
||||
<dependency org="org.apache.ant" name="ant" rev="1.9.6" />
|
||||
|
|
|
@ -36,6 +36,7 @@ import javax.swing.SwingUtilities;
|
|||
import net.filebot.ResourceManager;
|
||||
import net.filebot.Settings;
|
||||
import net.filebot.mac.MacAppUtilities;
|
||||
import net.filebot.ui.subtitle.upload.SubtitleUploadDialog;
|
||||
import net.filebot.util.FileUtilities;
|
||||
import net.filebot.util.FileUtilities.ParentFilter;
|
||||
import net.filebot.web.OpenSubtitlesClient;
|
||||
|
@ -299,8 +300,8 @@ abstract class SubtitleDropTarget extends JButton {
|
|||
// 1. try to find exact match in drop data
|
||||
return findMatch(subtitle, videos, FileUtilities::getName).orElseGet(() -> {
|
||||
// 2. guess movie file from the parent folder if only a subtitle file was dropped in
|
||||
return findMatch(subtitle, getChildren(subtitle.getParentFile(), VIDEO_FILES), FileUtilities::getName).orElse(null);
|
||||
});
|
||||
return findMatch(subtitle, getChildren(subtitle.getParentFile(), VIDEO_FILES), FileUtilities::getName).orElse(null);
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<File> findMatch(File file, List<File> options, Function<File, String> comparator) {
|
||||
|
|
|
@ -1,788 +0,0 @@
|
|||
package net.filebot.ui.subtitle;
|
||||
|
||||
import static net.filebot.ui.NotificationLogging.*;
|
||||
import static net.filebot.MediaTypes.*;
|
||||
import static net.filebot.UserFiles.*;
|
||||
import static net.filebot.media.MediaDetection.*;
|
||||
import static net.filebot.util.ui.SwingUI.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
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.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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.DefaultListCellRenderer;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.CellEditorListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellEditor;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
|
||||
import net.filebot.Language;
|
||||
import net.filebot.ResourceManager;
|
||||
import net.filebot.WebServices;
|
||||
import net.filebot.media.MediaDetection;
|
||||
import net.filebot.similarity.SeriesNameMatcher;
|
||||
import net.filebot.ui.LanguageComboBox;
|
||||
import net.filebot.ui.SelectDialog;
|
||||
import net.filebot.util.FileUtilities;
|
||||
import net.filebot.util.ui.AbstractBean;
|
||||
import net.filebot.util.ui.EmptySelectionModel;
|
||||
import net.filebot.web.Movie;
|
||||
import net.filebot.web.OpenSubtitlesClient;
|
||||
import net.filebot.web.SearchResult;
|
||||
import net.filebot.web.SubtitleSearchResult;
|
||||
import net.filebot.web.TheTVDBSeriesInfo;
|
||||
import net.filebot.web.VideoHashSubtitleService.CheckResult;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
public class SubtitleUploadDialog extends JDialog {
|
||||
|
||||
private final JTable subtitleMappingTable = createTable();
|
||||
|
||||
private final OpenSubtitlesClient database;
|
||||
|
||||
private ExecutorService checkExecutorService = Executors.newSingleThreadExecutor();
|
||||
private ExecutorService uploadExecutorService;
|
||||
|
||||
public SubtitleUploadDialog(OpenSubtitlesClient database, Window owner) {
|
||||
super(owner, "Upload Subtitles", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
this.database = database;
|
||||
|
||||
JComponent content = (JComponent) getContentPane();
|
||||
content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]"));
|
||||
|
||||
content.add(new JScrollPane(subtitleMappingTable), "grow, wrap");
|
||||
|
||||
content.add(new JButton(uploadAction), "tag ok");
|
||||
content.add(new JButton(finishAction), "tag cancel");
|
||||
}
|
||||
|
||||
protected JTable createTable() {
|
||||
JTable table = new JTable(new SubtitleMappingTableModel());
|
||||
table.setDefaultRenderer(Movie.class, new MovieRenderer());
|
||||
table.setDefaultRenderer(File.class, new FileRenderer());
|
||||
table.setDefaultRenderer(Language.class, new LanguageRenderer());
|
||||
table.setDefaultRenderer(SubtitleMapping.Status.class, new StatusRenderer());
|
||||
|
||||
table.setRowHeight(28);
|
||||
table.setIntercellSpacing(new Dimension(5, 5));
|
||||
|
||||
table.setBackground(Color.white);
|
||||
table.setAutoCreateRowSorter(true);
|
||||
table.setFillsViewportHeight(true);
|
||||
|
||||
LanguageComboBox languageEditor = new LanguageComboBox(Language.getLanguage("en"), null);
|
||||
|
||||
// disable selection
|
||||
table.setSelectionModel(new EmptySelectionModel());
|
||||
languageEditor.setFocusable(false);
|
||||
|
||||
table.setDefaultEditor(Language.class, new DefaultCellEditor(languageEditor) {
|
||||
|
||||
@Override
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
LanguageComboBox editor = (LanguageComboBox) super.getTableCellEditorComponent(table, value, isSelected, row, column);
|
||||
editor.getModel().setSelectedItem(value);
|
||||
return editor;
|
||||
}
|
||||
});
|
||||
|
||||
table.setDefaultEditor(Movie.class, new TableCellEditor() {
|
||||
|
||||
@Override
|
||||
public boolean stopCellEditing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSelectCell(EventObject evt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellEditorListener(CellEditorListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(EventObject evt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCellEditorValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelCellEditing() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellEditorListener(CellEditorListener evt) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
try {
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
SubtitleMappingTableModel model = (SubtitleMappingTableModel) table.getModel();
|
||||
SubtitleMapping mapping = model.getData()[table.convertRowIndexToModel(row)];
|
||||
|
||||
File video = mapping.getVideo() != null ? mapping.getVideo() : mapping.getSubtitle();
|
||||
String query = FileUtilities.getName(video);
|
||||
|
||||
// check if query contain an episode identifier
|
||||
SeriesNameMatcher snm = new SeriesNameMatcher();
|
||||
String sn = snm.matchByEpisodeIdentifier(query);
|
||||
if (sn != null) {
|
||||
query = sn;
|
||||
}
|
||||
|
||||
final String input = showInputDialog("Enter movie / series name:", stripReleaseInfo(query), getStructurePathTail(video).getPath(), SubtitleUploadDialog.this);
|
||||
if (input != null && input.length() > 0) {
|
||||
SwingWorker<List<SubtitleSearchResult>, Void> worker = new SwingWorker<List<SubtitleSearchResult>, Void>() {
|
||||
|
||||
@Override
|
||||
protected List<SubtitleSearchResult> doInBackground() throws Exception {
|
||||
return database.searchIMDB(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
List<SubtitleSearchResult> options = get();
|
||||
if (options.size() > 0) {
|
||||
SelectDialog<Movie> dialog = new SelectDialog<Movie>(SubtitleUploadDialog.this, options);
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
Movie selectedValue = dialog.getSelectedValue();
|
||||
if (selectedValue != null) {
|
||||
mapping.setIdentity(selectedValue);
|
||||
if (mapping.getIdentity() != null && mapping.getLanguage() != null && mapping.getVideo() != null) {
|
||||
mapping.setForceIdentity(true);
|
||||
mapping.setState(SubtitleMapping.Status.CheckPending);
|
||||
startChecking();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UILogger.warning(String.format("%s: \"%s\" has not been found", database.getName(), input));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(SubtitleUploadDialog.class.getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
};
|
||||
};
|
||||
worker.execute();
|
||||
} else {
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(SubtitleUploadDialog.class.getClass().getName()).log(Level.WARNING, e.toString());
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
table.setDefaultEditor(File.class, new TableCellEditor() {
|
||||
|
||||
@Override
|
||||
public boolean stopCellEditing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSelectCell(EventObject evt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellEditorListener(CellEditorListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(EventObject evt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCellEditorValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelCellEditing() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellEditorListener(CellEditorListener evt) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
SubtitleMappingTableModel model = (SubtitleMappingTableModel) table.getModel();
|
||||
SubtitleMapping mapping = model.getData()[table.convertRowIndexToModel(row)];
|
||||
|
||||
List<File> files = showLoadDialogSelectFiles(false, false, mapping.getSubtitle().getParentFile(), VIDEO_FILES, "Select Video File", new ActionEvent(table, ActionEvent.ACTION_PERFORMED, "Select"));
|
||||
if (files.size() > 0) {
|
||||
mapping.setVideo(files.get(0));
|
||||
mapping.setState(SubtitleMapping.Status.CheckPending);
|
||||
startChecking();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setUploadPlan(Map<File, File> uploadPlan) {
|
||||
List<SubtitleMapping> mappings = new ArrayList<SubtitleMapping>(uploadPlan.size());
|
||||
for (Entry<File, File> entry : uploadPlan.entrySet()) {
|
||||
File subtitle = entry.getKey();
|
||||
File video = entry.getValue();
|
||||
|
||||
Locale locale = MediaDetection.guessLanguageFromSuffix(subtitle);
|
||||
Language language = Language.getLanguage(locale);
|
||||
|
||||
mappings.add(new SubtitleMapping(subtitle, video, language));
|
||||
}
|
||||
|
||||
subtitleMappingTable.setModel(new SubtitleMappingTableModel(mappings.toArray(new SubtitleMapping[0])));
|
||||
}
|
||||
|
||||
public void startChecking() {
|
||||
SubtitleMapping[] data = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData();
|
||||
for (SubtitleMapping it : data) {
|
||||
if (it.getSubtitle() != null) {
|
||||
if (it.getStatus() == SubtitleMapping.Status.CheckPending) {
|
||||
checkExecutorService.submit(new CheckTask(it));
|
||||
}
|
||||
} else {
|
||||
it.setState(SubtitleMapping.Status.IllegalInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Action uploadAction = new AbstractAction("Upload", 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 upload as long as there are still unfinished download tasks
|
||||
if (uploadExecutorService != null && !uploadExecutorService.isTerminated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uploadExecutorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
SubtitleMapping[] data = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData();
|
||||
for (final SubtitleMapping it : data) {
|
||||
if (it.getStatus() == SubtitleMapping.Status.UploadReady) {
|
||||
uploadExecutorService.submit(new UploadTask(it));
|
||||
}
|
||||
}
|
||||
|
||||
// terminate after all uploads have been completed
|
||||
uploadExecutorService.shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
private final Action finishAction = new AbstractAction("Close", ResourceManager.getIcon("dialog.cancel")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
if (checkExecutorService != null) {
|
||||
checkExecutorService.shutdownNow();
|
||||
}
|
||||
if (uploadExecutorService != null) {
|
||||
uploadExecutorService.shutdownNow();
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
|
||||
private class MovieRenderer extends DefaultTableCellRenderer {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if (value != null) {
|
||||
Movie movie = (Movie) value;
|
||||
setText(movie.toString());
|
||||
setToolTipText(String.format("%s [tt%07d]", movie.toString(), movie.getImdbId()));
|
||||
setIcon(database.getIcon());
|
||||
setForeground(table.getForeground());
|
||||
} else {
|
||||
setText("<Click to select movie / series>");
|
||||
setToolTipText(null);
|
||||
setIcon(null);
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private class FileRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if (value != null) {
|
||||
File file = (File) value;
|
||||
setText(file.getName());
|
||||
setToolTipText(file.getPath());
|
||||
if (SUBTITLE_FILES.accept(file)) {
|
||||
setIcon(ResourceManager.getIcon("file.subtitle"));
|
||||
} else if (VIDEO_FILES.accept(file)) {
|
||||
setIcon(ResourceManager.getIcon("file.video"));
|
||||
}
|
||||
setForeground(table.getForeground());
|
||||
} else {
|
||||
setText("<Click to select video file>");
|
||||
setToolTipText(null);
|
||||
setIcon(null);
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private class LanguageRenderer implements TableCellRenderer, ListCellRenderer {
|
||||
|
||||
private DefaultTableCellRenderer tableCell = new DefaultTableCellRenderer();
|
||||
private DefaultListCellRenderer listCell = new DefaultListCellRenderer();
|
||||
|
||||
private Component configure(JLabel c, JComponent parent, Object value, boolean isSelected, boolean hasFocus) {
|
||||
if (value != null) {
|
||||
Language language = (Language) value;
|
||||
c.setText(language.getName());
|
||||
c.setIcon(ResourceManager.getFlagIcon(language.getCode()));
|
||||
c.setForeground(parent.getForeground());
|
||||
} else {
|
||||
c.setText("<Click to select language>");
|
||||
c.setIcon(null);
|
||||
c.setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
return configure((DefaultTableCellRenderer) tableCell.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column), table, value, isSelected, hasFocus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
return configure((DefaultListCellRenderer) listCell.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus), list, value, isSelected, cellHasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
private class StatusRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
String text = null;
|
||||
Icon icon = null;
|
||||
|
||||
// CheckPending, Checking, CheckFailed, AlreadyExists, Identifying, IdentificationRequired, UploadPending, Uploading, UploadComplete, UploadFailed;
|
||||
switch ((SubtitleMapping.Status) value) {
|
||||
case IllegalInput:
|
||||
text = "Please select video file";
|
||||
icon = ResourceManager.getIcon("status.error");
|
||||
break;
|
||||
case CheckPending:
|
||||
text = "Pending...";
|
||||
icon = ResourceManager.getIcon("worker.pending");
|
||||
break;
|
||||
case Checking:
|
||||
text = "Checking database...";
|
||||
icon = ResourceManager.getIcon("database.go");
|
||||
break;
|
||||
case CheckFailed:
|
||||
text = "Failed to check database";
|
||||
icon = ResourceManager.getIcon("database.error");
|
||||
break;
|
||||
case AlreadyExists:
|
||||
text = "Subtitle already exists in database";
|
||||
icon = ResourceManager.getIcon("database.ok");
|
||||
break;
|
||||
case Identifying:
|
||||
text = "Auto-detect missing information";
|
||||
icon = ResourceManager.getIcon("action.export");
|
||||
break;
|
||||
case IdentificationRequired:
|
||||
text = "Please select Movie / Series and Language";
|
||||
icon = ResourceManager.getIcon("dialog.continue.invalid");
|
||||
break;
|
||||
case UploadReady:
|
||||
text = "Ready for upload";
|
||||
icon = ResourceManager.getIcon("dialog.continue");
|
||||
break;
|
||||
case Uploading:
|
||||
text = "Uploading...";
|
||||
icon = ResourceManager.getIcon("database.go");
|
||||
break;
|
||||
case UploadComplete:
|
||||
text = "Upload successful";
|
||||
icon = ResourceManager.getIcon("database.ok");
|
||||
break;
|
||||
case UploadFailed:
|
||||
text = "Upload failed";
|
||||
icon = ResourceManager.getIcon("database.error");
|
||||
break;
|
||||
}
|
||||
|
||||
setText(text);
|
||||
setIcon(icon);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private class SubtitleMappingTableModel extends AbstractTableModel {
|
||||
|
||||
private final SubtitleMapping[] data;
|
||||
|
||||
public SubtitleMappingTableModel(SubtitleMapping... mappings) {
|
||||
this.data = mappings.clone();
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i].addPropertyChangeListener(new SubtitleMappingListener(i));
|
||||
}
|
||||
}
|
||||
|
||||
public SubtitleMapping[] getData() {
|
||||
return data.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return "Movie / Series";
|
||||
case 1:
|
||||
return "Video File";
|
||||
case 2:
|
||||
return "Subtitle File";
|
||||
case 3:
|
||||
return "Language";
|
||||
case 4:
|
||||
return "Status";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return data[row].getIdentity();
|
||||
case 1:
|
||||
return data[row].getVideo();
|
||||
case 2:
|
||||
return data[row].getSubtitle();
|
||||
case 3:
|
||||
return data[row].getLanguage();
|
||||
case 4:
|
||||
return data[row].getStatus();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object value, int row, int column) {
|
||||
if (getColumnClass(column) == Language.class && value instanceof Language) {
|
||||
data[row].setLanguage((Language) value);
|
||||
|
||||
if (data[row].getStatus() == SubtitleMapping.Status.IdentificationRequired) {
|
||||
data[row].setState(SubtitleMapping.Status.CheckPending);
|
||||
startChecking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return (column == 0 || column == 1 || column == 3) && EnumSet.of(SubtitleMapping.Status.IdentificationRequired, SubtitleMapping.Status.UploadReady, SubtitleMapping.Status.IllegalInput).contains(data[row].getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return Movie.class;
|
||||
case 1:
|
||||
return File.class;
|
||||
case 2:
|
||||
return File.class;
|
||||
case 3:
|
||||
return Language.class;
|
||||
case 4:
|
||||
return SubtitleMapping.Status.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 {
|
||||
|
||||
enum Status {
|
||||
IllegalInput, CheckPending, Checking, CheckFailed, AlreadyExists, Identifying, IdentificationRequired, UploadReady, Uploading, UploadComplete, UploadFailed;
|
||||
}
|
||||
|
||||
private Object identity;
|
||||
private Object remoteIdentity;
|
||||
private File subtitle;
|
||||
private File video;
|
||||
private Language language;
|
||||
|
||||
private Status status = Status.CheckPending;
|
||||
private String message = null;
|
||||
private boolean forceIdentity = false;
|
||||
|
||||
public SubtitleMapping(File subtitle, File video, Language language) {
|
||||
this.subtitle = subtitle;
|
||||
this.video = video;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public Object getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public Object getRemoteIdentity() {
|
||||
return remoteIdentity;
|
||||
}
|
||||
|
||||
public File getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public File getVideo() {
|
||||
return video;
|
||||
}
|
||||
|
||||
public Language getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setVideo(File video) {
|
||||
this.video = video;
|
||||
firePropertyChange("video", null, this.video);
|
||||
}
|
||||
|
||||
public void setIdentity(Object identity) {
|
||||
this.identity = identity;
|
||||
firePropertyChange("identity", null, this.identity);
|
||||
}
|
||||
|
||||
public void setRemoteIdentity(Object identity) {
|
||||
this.remoteIdentity = identity;
|
||||
firePropertyChange("remoteIdentity", null, this.remoteIdentity);
|
||||
}
|
||||
|
||||
public void setLanguage(Language language) {
|
||||
this.language = language;
|
||||
firePropertyChange("language", null, this.language);
|
||||
}
|
||||
|
||||
public void setState(Status status) {
|
||||
this.status = status;
|
||||
firePropertyChange("status", null, this.status);
|
||||
}
|
||||
|
||||
public boolean getForceIdentity() {
|
||||
return this.forceIdentity;
|
||||
}
|
||||
|
||||
public void setForceIdentity(boolean forceIdentity) {
|
||||
this.forceIdentity = forceIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
private class CheckTask extends SwingWorker<Object, Void> {
|
||||
|
||||
private final SubtitleMapping mapping;
|
||||
|
||||
public CheckTask(SubtitleMapping mapping) {
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
try {
|
||||
CheckResult checkResult = null;
|
||||
|
||||
if (!mapping.getForceIdentity() && mapping.getVideo() != null) {
|
||||
mapping.setState(SubtitleMapping.Status.Checking);
|
||||
|
||||
checkResult = database.checkSubtitle(mapping.getVideo(), mapping.getSubtitle());
|
||||
|
||||
if (checkResult.exists) {
|
||||
mapping.setRemoteIdentity(checkResult.identity);
|
||||
mapping.setLanguage(Language.getLanguage(checkResult.language)); // trust language hint only if upload not required
|
||||
|
||||
// force upload all subtitles regardless of what TryUploadSubtitles returns (because other programs often submit crap)
|
||||
// mapping.setState(SubtitleMapping.Status.AlreadyExists);
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getLanguage() == null) {
|
||||
mapping.setState(SubtitleMapping.Status.Identifying);
|
||||
try {
|
||||
Locale locale = database.detectLanguage(FileUtilities.readFile(mapping.getSubtitle()));
|
||||
mapping.setLanguage(Language.getLanguage(locale));
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.WARNING, "Failed to auto-detect language: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getIdentity() == null && mapping.getVideo() != null) {
|
||||
mapping.setState(SubtitleMapping.Status.Identifying);
|
||||
try {
|
||||
if (MediaDetection.isEpisode(mapping.getVideo().getPath(), true)) {
|
||||
List<String> seriesNames = MediaDetection.detectSeriesNames(Collections.singleton(mapping.getVideo()), true, false, Locale.ENGLISH);
|
||||
NAMES: for (String name : seriesNames) {
|
||||
List<SearchResult> options = WebServices.TheTVDB.search(name, Locale.ENGLISH);
|
||||
for (SearchResult entry : options) {
|
||||
TheTVDBSeriesInfo seriesInfo = (TheTVDBSeriesInfo) WebServices.TheTVDB.getSeriesInfo(entry, Locale.ENGLISH);
|
||||
if (seriesInfo.getImdbId() != null) {
|
||||
int imdbId = grepImdbId(seriesInfo.getImdbId()).iterator().next();
|
||||
mapping.setIdentity(WebServices.OpenSubtitles.getMovieDescriptor(new Movie(null, 0, imdbId, -1), Locale.ENGLISH));
|
||||
break NAMES;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Collection<Movie> identity = MediaDetection.detectMovie(mapping.getVideo(), database, Locale.ENGLISH, true);
|
||||
for (Movie it : identity) {
|
||||
if (it.getImdbId() <= 0 && it.getTmdbId() > 0) {
|
||||
it = WebServices.TheMovieDB.getMovieDescriptor(it, Locale.ENGLISH);
|
||||
}
|
||||
if (it != null && it.getImdbId() > 0) {
|
||||
mapping.setIdentity(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.WARNING, "Failed to auto-detect movie: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getVideo() == null) {
|
||||
mapping.setState(SubtitleMapping.Status.IllegalInput);
|
||||
} else if (mapping.getIdentity() == null || mapping.getLanguage() == null) {
|
||||
mapping.setState(SubtitleMapping.Status.IdentificationRequired);
|
||||
} else {
|
||||
mapping.setState(SubtitleMapping.Status.UploadReady);
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
|
||||
mapping.setState(SubtitleMapping.Status.CheckFailed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class UploadTask extends SwingWorker<Object, Void> {
|
||||
|
||||
private final SubtitleMapping mapping;
|
||||
|
||||
public UploadTask(SubtitleMapping mapping) {
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() {
|
||||
try {
|
||||
mapping.setState(SubtitleMapping.Status.Uploading);
|
||||
|
||||
database.uploadSubtitle(mapping.getIdentity(), mapping.getLanguage().getLocale(), mapping.getVideo(), mapping.getSubtitle());
|
||||
mapping.setState(SubtitleMapping.Status.UploadComplete);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(UploadTask.class.getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
|
||||
mapping.setState(SubtitleMapping.Status.UploadFailed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static net.filebot.MediaTypes.*;
|
||||
import static net.filebot.UserFiles.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.event.CellEditorListener;
|
||||
import javax.swing.table.TableCellEditor;
|
||||
|
||||
class FileEditor implements TableCellEditor {
|
||||
|
||||
@Override
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
SubtitleMappingTableModel model = (SubtitleMappingTableModel) table.getModel();
|
||||
SubtitleMapping mapping = model.getData()[table.convertRowIndexToModel(row)];
|
||||
|
||||
List<File> files = showLoadDialogSelectFiles(false, false, mapping.getSubtitle().getParentFile(), VIDEO_FILES, "Select Video File", new ActionEvent(table, ActionEvent.ACTION_PERFORMED, "Select"));
|
||||
if (files.size() > 0) {
|
||||
mapping.setVideo(files.get(0));
|
||||
mapping.setState(Status.CheckPending);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopCellEditing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSelectCell(EventObject evt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellEditorListener(CellEditorListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(EventObject evt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCellEditorValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelCellEditing() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellEditorListener(CellEditorListener evt) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static net.filebot.MediaTypes.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import net.filebot.ResourceManager;
|
||||
|
||||
class FileRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if (value != null) {
|
||||
File file = (File) value;
|
||||
setText(file.getName());
|
||||
setToolTipText(file.getPath());
|
||||
if (SUBTITLE_FILES.accept(file)) {
|
||||
setIcon(ResourceManager.getIcon("file.subtitle"));
|
||||
} else if (VIDEO_FILES.accept(file)) {
|
||||
setIcon(ResourceManager.getIcon("file.video"));
|
||||
}
|
||||
setForeground(table.getForeground());
|
||||
} else {
|
||||
setText("<Click to select video file>");
|
||||
setToolTipText(null);
|
||||
setIcon(null);
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.DefaultCellEditor;
|
||||
import javax.swing.JTable;
|
||||
|
||||
import net.filebot.Language;
|
||||
import net.filebot.ui.LanguageComboBox;
|
||||
|
||||
class LanguageEditor extends DefaultCellEditor {
|
||||
|
||||
public LanguageEditor() {
|
||||
super(createLanguageComboBox());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
LanguageComboBox editor = (LanguageComboBox) super.getTableCellEditorComponent(table, value, isSelected, row, column);
|
||||
editor.getModel().setSelectedItem(value);
|
||||
return editor;
|
||||
}
|
||||
|
||||
public static LanguageComboBox createLanguageComboBox() {
|
||||
LanguageComboBox languageEditor = new LanguageComboBox(Language.getLanguage("en"), null);
|
||||
languageEditor.setFocusable(false);
|
||||
return languageEditor;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
|
||||
import net.filebot.Language;
|
||||
import net.filebot.ResourceManager;
|
||||
|
||||
class LanguageRenderer implements TableCellRenderer, ListCellRenderer {
|
||||
|
||||
private DefaultTableCellRenderer tableCell = new DefaultTableCellRenderer();
|
||||
private DefaultListCellRenderer listCell = new DefaultListCellRenderer();
|
||||
|
||||
private Component configure(JLabel c, JComponent parent, Object value, boolean isSelected, boolean hasFocus) {
|
||||
if (value != null) {
|
||||
Language language = (Language) value;
|
||||
c.setText(language.getName());
|
||||
c.setIcon(ResourceManager.getFlagIcon(language.getCode()));
|
||||
c.setForeground(parent.getForeground());
|
||||
} else {
|
||||
c.setText("<Click to select language>");
|
||||
c.setIcon(null);
|
||||
c.setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
return configure((DefaultTableCellRenderer) tableCell.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column), table, value, isSelected, hasFocus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
return configure((DefaultListCellRenderer) listCell.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus), list, value, isSelected, cellHasFocus);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static net.filebot.media.MediaDetection.*;
|
||||
import static net.filebot.ui.NotificationLogging.*;
|
||||
import static net.filebot.util.ui.SwingUI.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.io.File;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.CellEditorListener;
|
||||
import javax.swing.table.TableCellEditor;
|
||||
|
||||
import net.filebot.similarity.SeriesNameMatcher;
|
||||
import net.filebot.ui.SelectDialog;
|
||||
import net.filebot.util.FileUtilities;
|
||||
import net.filebot.web.Movie;
|
||||
import net.filebot.web.OpenSubtitlesClient;
|
||||
import net.filebot.web.SubtitleSearchResult;
|
||||
|
||||
class MovieEditor implements TableCellEditor {
|
||||
|
||||
private OpenSubtitlesClient database;
|
||||
|
||||
public MovieEditor(OpenSubtitlesClient database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
|
||||
try {
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
SubtitleMappingTableModel model = (SubtitleMappingTableModel) table.getModel();
|
||||
SubtitleMapping mapping = model.getData()[table.convertRowIndexToModel(row)];
|
||||
|
||||
File video = mapping.getVideo() != null ? mapping.getVideo() : mapping.getSubtitle();
|
||||
String query = FileUtilities.getName(video);
|
||||
|
||||
// check if query contain an episode identifier
|
||||
SeriesNameMatcher snm = new SeriesNameMatcher();
|
||||
String sn = snm.matchByEpisodeIdentifier(query);
|
||||
if (sn != null) {
|
||||
query = sn;
|
||||
}
|
||||
|
||||
final String input = showInputDialog("Enter movie / series name:", stripReleaseInfo(query), getStructurePathTail(video).getPath(), table);
|
||||
if (input != null && input.length() > 0) {
|
||||
SwingWorker<List<SubtitleSearchResult>, Void> worker = new SwingWorker<List<SubtitleSearchResult>, Void>() {
|
||||
|
||||
@Override
|
||||
protected List<SubtitleSearchResult> doInBackground() throws Exception {
|
||||
return database.searchIMDB(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
List<SubtitleSearchResult> options = get();
|
||||
if (options.size() > 0) {
|
||||
SelectDialog<Movie> dialog = new SelectDialog<Movie>(table, options);
|
||||
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
|
||||
dialog.setVisible(true);
|
||||
Movie selectedValue = dialog.getSelectedValue();
|
||||
if (selectedValue != null) {
|
||||
mapping.setIdentity(selectedValue);
|
||||
if (mapping.getIdentity() != null && mapping.getLanguage() != null && mapping.getVideo() != null) {
|
||||
mapping.setState(Status.CheckPending);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UILogger.warning(String.format("%s: \"%s\" has not been found", database.getName(), input));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(SubtitleUploadDialog.class.getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
};
|
||||
};
|
||||
worker.execute();
|
||||
} else {
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(SubtitleUploadDialog.class.getClass().getName()).log(Level.WARNING, e.toString());
|
||||
getWindow(table).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopCellEditing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSelectCell(EventObject evt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellEditorListener(CellEditorListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(EventObject evt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCellEditorValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelCellEditing() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellEditorListener(CellEditorListener evt) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import net.filebot.web.Movie;
|
||||
|
||||
class MovieRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private Icon icon;
|
||||
|
||||
public MovieRenderer(Icon icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if (value != null) {
|
||||
Movie movie = (Movie) value;
|
||||
setText(movie.toString());
|
||||
setToolTipText(String.format("%s [tt%07d]", movie.toString(), movie.getImdbId()));
|
||||
setIcon(icon);
|
||||
setForeground(table.getForeground());
|
||||
} else {
|
||||
setText("<Click to select movie / series>");
|
||||
setToolTipText(null);
|
||||
setIcon(null);
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
enum Status {
|
||||
IllegalInput, CheckPending, Checking, CheckFailed, AlreadyExists, Identifying, IdentificationRequired, UploadReady, Uploading, UploadComplete, UploadFailed;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import net.filebot.ResourceManager;
|
||||
|
||||
class StatusRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
String text = null;
|
||||
Icon icon = null;
|
||||
|
||||
switch ((Status) value) {
|
||||
case IllegalInput:
|
||||
text = "Please select video matching video file";
|
||||
icon = ResourceManager.getIcon("status.error");
|
||||
break;
|
||||
case CheckPending:
|
||||
text = "Pending...";
|
||||
icon = ResourceManager.getIcon("worker.pending");
|
||||
break;
|
||||
case Checking:
|
||||
text = "Checking database...";
|
||||
icon = ResourceManager.getIcon("database.go");
|
||||
break;
|
||||
case CheckFailed:
|
||||
text = "Failed to check database";
|
||||
icon = ResourceManager.getIcon("database.error");
|
||||
break;
|
||||
case AlreadyExists:
|
||||
text = "Subtitle already exists in database";
|
||||
icon = ResourceManager.getIcon("database.ok");
|
||||
break;
|
||||
case Identifying:
|
||||
text = "Auto-detect missing information";
|
||||
icon = ResourceManager.getIcon("action.export");
|
||||
break;
|
||||
case IdentificationRequired:
|
||||
text = "Please select Movie / Series and Language";
|
||||
icon = ResourceManager.getIcon("dialog.continue.invalid");
|
||||
break;
|
||||
case UploadReady:
|
||||
text = "Ready for upload";
|
||||
icon = ResourceManager.getIcon("dialog.continue");
|
||||
break;
|
||||
case Uploading:
|
||||
text = "Uploading...";
|
||||
icon = ResourceManager.getIcon("database.go");
|
||||
break;
|
||||
case UploadComplete:
|
||||
text = "Upload successful";
|
||||
icon = ResourceManager.getIcon("database.ok");
|
||||
break;
|
||||
case UploadFailed:
|
||||
text = "Upload failed";
|
||||
icon = ResourceManager.getIcon("database.error");
|
||||
break;
|
||||
}
|
||||
|
||||
setText(text);
|
||||
setIcon(icon);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import net.filebot.Language;
|
||||
|
||||
class SubtitleGroup {
|
||||
|
||||
private final SubtitleMapping[] mapping;
|
||||
|
||||
public SubtitleGroup(List<SubtitleMapping> mapping) {
|
||||
this.mapping = mapping.toArray(new SubtitleMapping[mapping.size()]);
|
||||
}
|
||||
|
||||
public void setState(Status status) {
|
||||
for (SubtitleMapping it : mapping) {
|
||||
it.setState(status);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUploadReady() {
|
||||
return stream(mapping).allMatch(SubtitleMapping::isUploadReady);
|
||||
}
|
||||
|
||||
public Object getIdentity() {
|
||||
return mapping[0].getIdentity();
|
||||
}
|
||||
|
||||
public Language getLanguage() {
|
||||
return mapping[0].getLanguage();
|
||||
}
|
||||
|
||||
public File[] getVideoFiles() {
|
||||
return stream(mapping).map(SubtitleMapping::getVideo).toArray(File[]::new);
|
||||
}
|
||||
|
||||
public File[] getSubtitleFiles() {
|
||||
return stream(mapping).map(SubtitleMapping::getSubtitle).toArray(File[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asList(getIdentity(), getLanguage(), asList(getVideoFiles()), asList(getSubtitleFiles())).toString();
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.filebot.Language;
|
||||
import net.filebot.util.ui.AbstractBean;
|
||||
import net.filebot.web.Movie;
|
||||
|
||||
class SubtitleMapping extends AbstractBean {
|
||||
|
||||
private Movie identity;
|
||||
private File video;
|
||||
private File subtitle;
|
||||
private Language language;
|
||||
|
||||
private Status status;
|
||||
|
||||
public SubtitleMapping(File subtitle, File video, Language language) {
|
||||
this.subtitle = subtitle;
|
||||
this.video = video;
|
||||
this.language = language;
|
||||
|
||||
this.status = (video == null || subtitle == null) ? Status.IllegalInput : Status.CheckPending;
|
||||
}
|
||||
|
||||
public boolean isCheckReady() {
|
||||
return subtitle != null && status == Status.CheckPending;
|
||||
}
|
||||
|
||||
public boolean isUploadReady() {
|
||||
return identity != null && subtitle != null && video != null && language != null && status == Status.UploadReady;
|
||||
}
|
||||
|
||||
public Object getGroup() {
|
||||
return asList(identity.getImdbId(), language.getCode());
|
||||
}
|
||||
|
||||
public Object getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public File getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public File getVideo() {
|
||||
return video;
|
||||
}
|
||||
|
||||
public Language getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setVideo(File video) {
|
||||
this.video = video;
|
||||
firePropertyChange("video", null, this.video);
|
||||
}
|
||||
|
||||
public void setIdentity(Movie identity) {
|
||||
this.identity = identity;
|
||||
firePropertyChange("identity", null, this.identity);
|
||||
}
|
||||
|
||||
public void setLanguage(Language language) {
|
||||
this.language = language;
|
||||
firePropertyChange("language", null, this.language);
|
||||
}
|
||||
|
||||
public void setState(Status status) {
|
||||
this.status = status;
|
||||
firePropertyChange("status", null, this.status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asList(identity, video, subtitle, language, status).toString();
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static javax.swing.SwingUtilities.*;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import net.filebot.Language;
|
||||
import net.filebot.web.Movie;
|
||||
|
||||
class SubtitleMappingTableModel extends AbstractTableModel {
|
||||
|
||||
private SubtitleMapping[] data;
|
||||
private Runnable onCheckPending;
|
||||
|
||||
public SubtitleMappingTableModel() {
|
||||
this.data = new SubtitleMapping[0];
|
||||
}
|
||||
|
||||
public SubtitleMappingTableModel(Collection<SubtitleMapping> rows) {
|
||||
this.data = rows.toArray(new SubtitleMapping[rows.size()]);
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i].addPropertyChangeListener(new UpdateRowListener(i));
|
||||
}
|
||||
}
|
||||
|
||||
public SubtitleMappingTableModel onCheckPending(Runnable onCheckPending) {
|
||||
this.onCheckPending = onCheckPending;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SubtitleMapping[] getData() {
|
||||
return data.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return "Movie / Series";
|
||||
case 1:
|
||||
return "Video File";
|
||||
case 2:
|
||||
return "Subtitle File";
|
||||
case 3:
|
||||
return "Language";
|
||||
case 4:
|
||||
return "Status";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return data[row].getIdentity();
|
||||
case 1:
|
||||
return data[row].getVideo();
|
||||
case 2:
|
||||
return data[row].getSubtitle();
|
||||
case 3:
|
||||
return data[row].getLanguage();
|
||||
case 4:
|
||||
return data[row].getStatus();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object value, int row, int column) {
|
||||
if (getColumnClass(column) == Language.class && value instanceof Language) {
|
||||
data[row].setLanguage((Language) value);
|
||||
|
||||
if (data[row].getStatus() == Status.IdentificationRequired) {
|
||||
data[row].setState(Status.CheckPending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return (column == 0 || column == 1 || column == 3) && EnumSet.of(Status.IdentificationRequired, Status.UploadReady, Status.IllegalInput).contains(data[row].getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
return Movie.class;
|
||||
case 1:
|
||||
return File.class;
|
||||
case 2:
|
||||
return File.class;
|
||||
case 3:
|
||||
return Language.class;
|
||||
case 4:
|
||||
return Status.class;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private class UpdateRowListener implements PropertyChangeListener {
|
||||
|
||||
private final int index;
|
||||
|
||||
public UpdateRowListener(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
// update state and subtitle options
|
||||
fireTableRowsUpdated(index, index);
|
||||
|
||||
if (evt.getNewValue().equals(Status.CheckPending)) {
|
||||
invokeLater(onCheckPending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
package net.filebot.ui.subtitle.upload;
|
||||
|
||||
import static net.filebot.media.MediaDetection.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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 java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import net.filebot.Language;
|
||||
import net.filebot.ResourceManager;
|
||||
import net.filebot.WebServices;
|
||||
import net.filebot.media.MediaDetection;
|
||||
import net.filebot.util.FileUtilities;
|
||||
import net.filebot.util.ui.EmptySelectionModel;
|
||||
import net.filebot.web.Movie;
|
||||
import net.filebot.web.OpenSubtitlesClient;
|
||||
import net.filebot.web.SearchResult;
|
||||
import net.filebot.web.TheTVDBSeriesInfo;
|
||||
import net.filebot.web.VideoHashSubtitleService.CheckResult;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import one.util.streamex.StreamEx;
|
||||
|
||||
public class SubtitleUploadDialog extends JDialog {
|
||||
|
||||
private final JTable subtitleMappingTable;
|
||||
|
||||
private final OpenSubtitlesClient database;
|
||||
|
||||
private ExecutorService checkExecutorService = Executors.newSingleThreadExecutor();
|
||||
private ExecutorService uploadExecutorService;
|
||||
|
||||
public SubtitleUploadDialog(OpenSubtitlesClient database, Window owner) {
|
||||
super(owner, "Upload Subtitles", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
this.database = database;
|
||||
subtitleMappingTable = createTable();
|
||||
|
||||
JComponent content = (JComponent) getContentPane();
|
||||
content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]"));
|
||||
|
||||
content.add(new JScrollPane(subtitleMappingTable), "grow, wrap");
|
||||
|
||||
content.add(new JButton(uploadAction), "tag ok");
|
||||
content.add(new JButton(finishAction), "tag cancel");
|
||||
}
|
||||
|
||||
protected JTable createTable() {
|
||||
JTable table = new JTable(new SubtitleMappingTableModel());
|
||||
table.setDefaultRenderer(Movie.class, new MovieRenderer(database.getIcon()));
|
||||
table.setDefaultRenderer(File.class, new FileRenderer());
|
||||
table.setDefaultRenderer(Language.class, new LanguageRenderer());
|
||||
table.setDefaultRenderer(Status.class, new StatusRenderer());
|
||||
|
||||
table.setRowHeight(28);
|
||||
table.setIntercellSpacing(new Dimension(5, 5));
|
||||
|
||||
table.setBackground(Color.white);
|
||||
table.setAutoCreateRowSorter(true);
|
||||
table.setFillsViewportHeight(true);
|
||||
|
||||
// disable selection
|
||||
table.setSelectionModel(new EmptySelectionModel());
|
||||
|
||||
table.setDefaultEditor(Movie.class, new MovieEditor(database));
|
||||
table.setDefaultEditor(File.class, new FileEditor());
|
||||
table.setDefaultEditor(Language.class, new LanguageEditor());
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setUploadPlan(Map<File, File> uploadPlan) {
|
||||
List<SubtitleMapping> mappings = new ArrayList<SubtitleMapping>(uploadPlan.size());
|
||||
for (Entry<File, File> entry : uploadPlan.entrySet()) {
|
||||
File subtitle = entry.getKey();
|
||||
File video = entry.getValue();
|
||||
|
||||
Locale locale = MediaDetection.guessLanguageFromSuffix(subtitle);
|
||||
Language language = Language.getLanguage(locale);
|
||||
|
||||
mappings.add(new SubtitleMapping(subtitle, video, language));
|
||||
}
|
||||
|
||||
subtitleMappingTable.setModel(new SubtitleMappingTableModel(mappings).onCheckPending(this::startChecking));
|
||||
}
|
||||
|
||||
public void startChecking() {
|
||||
SubtitleMapping[] data = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData();
|
||||
for (SubtitleMapping it : data) {
|
||||
if (it.isCheckReady()) {
|
||||
checkExecutorService.submit(new CheckTask(it));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Pattern CDI_PATTERN = Pattern.compile("(?<!\\p{Alnum})CD\\D?(?<i>[1-9])(?!\\p{Digit})", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
|
||||
|
||||
private int getCD(SubtitleMapping mapping) {
|
||||
int i = Integer.MIN_VALUE;
|
||||
for (File f : new File[] { mapping.getSubtitle(), mapping.getVideo() }) {
|
||||
Matcher m = CDI_PATTERN.matcher(f.getName());
|
||||
while (m.find()) {
|
||||
i = Integer.parseInt(m.group("i"));
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private List<SubtitleGroup> groupRunsByCD(SubtitleMapping[] table) {
|
||||
return StreamEx.ofValues(StreamEx.of(table).sortedBy(SubtitleMapping::getVideo).groupingBy(SubtitleMapping::getGroup)).flatMap((group) -> {
|
||||
return StreamEx.of(group).groupRuns((m1, m2) -> getCD(m1) + 1 == getCD(m2)).map(SubtitleGroup::new);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private final Action uploadAction = new AbstractAction("Upload", 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 upload as long as there are still unfinished download tasks
|
||||
if (uploadExecutorService != null && !uploadExecutorService.isTerminated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uploadExecutorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
SubtitleMapping[] table = ((SubtitleMappingTableModel) subtitleMappingTable.getModel()).getData();
|
||||
for (SubtitleGroup it : groupRunsByCD(table)) {
|
||||
if (it.isUploadReady()) {
|
||||
uploadExecutorService.submit(new UploadTask(it));
|
||||
}
|
||||
}
|
||||
|
||||
// terminate after all uploads have been completed
|
||||
uploadExecutorService.shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
private final Action finishAction = new AbstractAction("Close", ResourceManager.getIcon("dialog.cancel")) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
if (checkExecutorService != null) {
|
||||
checkExecutorService.shutdownNow();
|
||||
}
|
||||
if (uploadExecutorService != null) {
|
||||
uploadExecutorService.shutdownNow();
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
|
||||
private class CheckTask extends SwingWorker<Object, Void> {
|
||||
|
||||
private final SubtitleMapping mapping;
|
||||
|
||||
public CheckTask(SubtitleMapping mapping) {
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
try {
|
||||
|
||||
if (mapping.getIdentity() == null && mapping.getVideo() != null) {
|
||||
mapping.setState(Status.Checking);
|
||||
|
||||
CheckResult checkResult = database.checkSubtitle(mapping.getVideo(), mapping.getSubtitle());
|
||||
|
||||
// force upload all subtitles regardless of what TryUploadSubtitles returns (because other programs often submit crap)
|
||||
if (checkResult.exists) {
|
||||
mapping.setLanguage(Language.getLanguage(checkResult.language)); // trust language hint only if upload not required
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getLanguage() == null) {
|
||||
mapping.setState(Status.Identifying);
|
||||
try {
|
||||
Locale locale = database.detectLanguage(FileUtilities.readFile(mapping.getSubtitle()));
|
||||
mapping.setLanguage(Language.getLanguage(locale));
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.WARNING, "Failed to auto-detect language: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getIdentity() == null && mapping.getVideo() != null) {
|
||||
mapping.setState(Status.Identifying);
|
||||
try {
|
||||
if (MediaDetection.isEpisode(mapping.getVideo().getPath(), true)) {
|
||||
List<String> seriesNames = MediaDetection.detectSeriesNames(Collections.singleton(mapping.getVideo()), true, false, Locale.ENGLISH);
|
||||
NAMES: for (String name : seriesNames) {
|
||||
List<SearchResult> options = WebServices.TheTVDB.search(name, Locale.ENGLISH);
|
||||
for (SearchResult entry : options) {
|
||||
TheTVDBSeriesInfo seriesInfo = (TheTVDBSeriesInfo) WebServices.TheTVDB.getSeriesInfo(entry, Locale.ENGLISH);
|
||||
if (seriesInfo.getImdbId() != null) {
|
||||
int imdbId = grepImdbId(seriesInfo.getImdbId()).iterator().next();
|
||||
mapping.setIdentity(WebServices.OpenSubtitles.getMovieDescriptor(new Movie(null, 0, imdbId, -1), Locale.ENGLISH));
|
||||
break NAMES;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Collection<Movie> identity = MediaDetection.detectMovie(mapping.getVideo(), database, Locale.ENGLISH, true);
|
||||
for (Movie it : identity) {
|
||||
if (it.getImdbId() <= 0 && it.getTmdbId() > 0) {
|
||||
it = WebServices.TheMovieDB.getMovieDescriptor(it, Locale.ENGLISH);
|
||||
}
|
||||
if (it != null && it.getImdbId() > 0) {
|
||||
mapping.setIdentity(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.WARNING, "Failed to auto-detect movie: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.getVideo() == null) {
|
||||
mapping.setState(Status.IllegalInput);
|
||||
} else if (mapping.getIdentity() == null || mapping.getLanguage() == null) {
|
||||
mapping.setState(Status.IdentificationRequired);
|
||||
} else {
|
||||
mapping.setState(Status.UploadReady);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CheckTask.class.getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
|
||||
mapping.setState(Status.CheckFailed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class UploadTask extends SwingWorker<Object, Void> {
|
||||
|
||||
private final SubtitleGroup uploadGroup;
|
||||
|
||||
public UploadTask(SubtitleGroup uploadGroup) {
|
||||
this.uploadGroup = uploadGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground() {
|
||||
try {
|
||||
uploadGroup.setState(Status.Uploading);
|
||||
|
||||
database.uploadSubtitle(uploadGroup.getIdentity(), uploadGroup.getLanguage().getLocale(), uploadGroup.getVideoFiles(), uploadGroup.getSubtitleFiles());
|
||||
|
||||
uploadGroup.setState(Status.UploadComplete);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(UploadTask.class.getClass().getName()).log(Level.SEVERE, e.getMessage(), e);
|
||||
uploadGroup.setState(Status.UploadFailed);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -322,14 +322,30 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
|
|||
}
|
||||
|
||||
@Override
|
||||
public void uploadSubtitle(Object identity, Locale language, File videoFile, File subtitleFile) throws Exception {
|
||||
public void uploadSubtitle(Object identity, Locale language, File[] videoFile, File[] subtitleFile) throws Exception {
|
||||
if (!(identity instanceof Movie && ((Movie) identity).getImdbId() > 0)) {
|
||||
throw new IllegalArgumentException("Illegal Movie ID: " + identity);
|
||||
}
|
||||
|
||||
int imdbid = ((Movie) identity).getImdbId();
|
||||
String languageName = getSubLanguageID(language.getDisplayName(Locale.ENGLISH), false);
|
||||
String subLanguageID = getSubLanguageID(language.getDisplayName(Locale.ENGLISH), false);
|
||||
|
||||
BaseInfo info = new BaseInfo();
|
||||
info.setIDMovieImdb(imdbid);
|
||||
info.setSubLanguageID(subLanguageID);
|
||||
|
||||
SubFile[] subFiles = new SubFile[videoFile.length];
|
||||
for (int i = 0; i < subFiles.length; i++) {
|
||||
subFiles[i] = getSubFile(info, videoFile[i], subtitleFile[i]);
|
||||
}
|
||||
|
||||
// require login
|
||||
login();
|
||||
|
||||
xmlrpc.uploadSubtitles(info, subFiles);
|
||||
}
|
||||
|
||||
private SubFile getSubFile(BaseInfo info, File videoFile, File subtitleFile) throws IOException {
|
||||
// subhash (md5 of subtitles), subfilename, moviehash, moviebytesize, moviefilename
|
||||
SubFile sub = new SubFile();
|
||||
sub.setSubHash(md5(readFile(subtitleFile)));
|
||||
|
@ -338,10 +354,6 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
|
|||
sub.setMovieByteSize(videoFile.length());
|
||||
sub.setMovieFileName(videoFile.getName());
|
||||
|
||||
BaseInfo info = new BaseInfo();
|
||||
info.setIDMovieImdb(imdbid);
|
||||
info.setSubLanguageID(languageName);
|
||||
|
||||
// encode subtitle contents
|
||||
sub.setSubContent(readFile(subtitleFile));
|
||||
|
||||
|
@ -355,10 +367,7 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
|
|||
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||
}
|
||||
|
||||
// require login
|
||||
login();
|
||||
|
||||
xmlrpc.uploadSubtitles(info, sub);
|
||||
return sub;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package net.filebot.web;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.Collections.*;
|
||||
import static net.filebot.util.StringUtilities.*;
|
||||
|
||||
|
@ -165,8 +164,6 @@ public class OpenSubtitlesXmlRpc {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static final Pattern CDI_PATTERN = Pattern.compile("(?<!\\p{Alnum})CD\\D?(?<i>[1-9])(?!\\p{Digit})", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
|
||||
|
||||
private Map<String, Object> getUploadStruct(BaseInfo baseInfo, SubFile... subtitles) {
|
||||
Map<String, Object> struct = new LinkedHashMap<String, Object>();
|
||||
|
||||
|
@ -175,19 +172,8 @@ public class OpenSubtitlesXmlRpc {
|
|||
struct.put("baseinfo", baseInfo);
|
||||
}
|
||||
|
||||
// put cd1, cd2, ...
|
||||
for (SubFile it : subtitles) {
|
||||
int i = 1;
|
||||
Matcher m = CDI_PATTERN.matcher(it.toString());
|
||||
while (m.find()) {
|
||||
i = Integer.parseInt(m.group("i"));
|
||||
}
|
||||
String key = "cd" + i;
|
||||
if (!struct.containsKey(key)) {
|
||||
struct.put(key, it);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Duplicate key: %s: %s", key, asList(subtitles)));
|
||||
}
|
||||
for (int i = 0; i < subtitles.length; i++) {
|
||||
struct.put("cd" + (i + 1), subtitles[i]);
|
||||
}
|
||||
|
||||
return struct;
|
||||
|
|
|
@ -128,7 +128,7 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void uploadSubtitle(Object identity, Locale locale, File videoFile, File subtitleFile) throws Exception {
|
||||
public void uploadSubtitle(Object identity, Locale locale, File[] videoFile, File[] subtitleFile) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public interface VideoHashSubtitleService {
|
|||
|
||||
public CheckResult checkSubtitle(File videoFile, File subtitleFile) throws Exception;
|
||||
|
||||
public void uploadSubtitle(Object identity, Locale locale, File videoFile, File subtitleFile) throws Exception;
|
||||
public void uploadSubtitle(Object identity, Locale locale, File[] videoFiles, File[] subtitleFiles) throws Exception;
|
||||
|
||||
public static class CheckResult {
|
||||
public final boolean exists;
|
||||
|
|
Loading…
Reference in New Issue