package net.filebot.ui.subtitle; import static net.filebot.Logging.*; import static net.filebot.MediaTypes.*; import static net.filebot.Settings.*; import static net.filebot.UserFiles.*; import static net.filebot.ui.transfer.FileTransferable.*; import static net.filebot.util.FileUtilities.*; import static net.filebot.util.ui.SwingUI.*; 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.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.logging.Level; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.SwingUtilities; import net.filebot.ResourceManager; import net.filebot.platform.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; import net.filebot.web.SubtitleProvider; import net.filebot.web.VideoHashSubtitleService; abstract class SubtitleDropTarget extends JButton { public enum DropAction { Accept, Cancel } public SubtitleDropTarget() { setHorizontalAlignment(CENTER); setHideActionText(true); setBorderPainted(false); setContentAreaFilled(false); setFocusPainted(false); setBackground(Color.white); setOpaque(false); // initialize with default mode setDropAction(DropAction.Accept); setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); // install mouse listener addActionListener(clickHandler); // install drop target new DropTarget(this, dropHandler); } protected void setDropAction(DropAction dropAction) { setIcon(getIcon(dropAction)); } protected abstract OpenSubtitlesClient getSubtitleService(); protected abstract boolean handleDrop(List<File> files); protected abstract DropAction getDropAction(List<File> files); protected abstract Icon getIcon(DropAction dropAction); private final DropTargetAdapter dropHandler = new DropTargetAdapter() { @Override public void dragEnter(DropTargetDragEvent dtde) { DropAction dropAction = DropAction.Accept; try { dropAction = getDropAction(getFilesFromTransferable(dtde.getTransferable())); } catch (Exception e) { // just accept the drag if we can't access the transferable, // because on some implementations we can't access transferable data before we accept the drag, // but accepting or rejecting the drag depends on the files dragged } // update visual representation setDropAction(dropAction); // accept or reject if (dropAction != DropAction.Cancel) { dtde.acceptDrag(DnDConstants.ACTION_REFERENCE); } else { dtde.rejectDrag(); } } @Override public void dragExit(DropTargetEvent dte) { // reset to default state setDropAction(DropAction.Accept); }; @Override public void drop(DropTargetDropEvent dtde) { dtde.acceptDrop(DnDConstants.ACTION_REFERENCE); boolean accept = false; try { List<File> files = getFilesFromTransferable(dtde.getTransferable()); accept = getDropAction(files) != DropAction.Cancel; if (accept) { // invoke later so we don't block the DnD operation with the download dialog SwingUtilities.invokeLater(() -> handleDrop(files)); } } catch (Exception e) { log.log(Level.WARNING, e.getMessage(), e); } dtde.dropComplete(accept); dragExit(dtde); } }; private final ActionListener clickHandler = new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { // collect media file extensions (video and subtitle files) List<File> files = showLoadDialogSelectFiles(true, true, null, ExtensionFileFilter.union(VIDEO_FILES, SUBTITLE_FILES), "Select Video Folder", evt); if (files.size() > 0 && getDropAction(files) != DropAction.Cancel) { handleDrop(files); } } }; public static abstract class Download extends SubtitleDropTarget { public abstract VideoHashSubtitleService[] getVideoHashSubtitleServices(); public abstract SubtitleProvider[] getSubtitleProviders(); public abstract Locale getQueryLanguage(); @Override protected DropAction getDropAction(List<File> selection) { // accept video files and folders return filter(selection, VIDEO_FILES, FOLDERS).size() > 0 ? DropAction.Accept : DropAction.Cancel; } @Override protected boolean handleDrop(List<File> selection) { if (getQueryLanguage() == null) { log.info("Please select your preferred subtitle language."); return false; } if (getSubtitleService().isAnonymous() && !isAppStore()) { log.info(String.format("%s: Please enter your login details.", getSubtitleService().getName())); return false; } List<File> files = listFiles(selection, VIDEO_FILES, HUMAN_NAME_ORDER); if (files.size() > 0) { handleDownload(files); return true; } return false; } protected boolean handleDownload(Collection<File> videoFiles) { SubtitleAutoMatchDialog dialog = new SubtitleAutoMatchDialog(getWindow(this)); // initialize download parameters dialog.setVideoFiles(videoFiles.toArray(new File[0])); for (VideoHashSubtitleService service : getVideoHashSubtitleServices()) { dialog.addSubtitleService(service); } for (SubtitleProvider service : getSubtitleProviders()) { dialog.addSubtitleService(service); } // start looking for subtitles dialog.startQuery(getQueryLanguage()); // initialize window properties dialog.setIconImage(getImage(getIcon(DropAction.Accept))); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setSize(1050, 600); // show dialog dialog.setLocationRelativeTo(dialog.getOwner()); dialog.setVisible(true); return true; } @Override protected Icon getIcon(DropAction dropAction) { switch (dropAction) { case Accept: return ResourceManager.getIcon("subtitle.exact.download"); default: return ResourceManager.getIcon("message.error"); } } } public static abstract class Upload extends SubtitleDropTarget { @Override protected DropAction getDropAction(List<File> selection) { // accept video files and folders return filter(selection, SUBTITLE_FILES).size() > 0 || filter(selection, FOLDERS).size() > 0 ? DropAction.Accept : DropAction.Cancel; } @Override protected boolean handleDrop(List<File> selection) { if (getSubtitleService().isAnonymous()) { log.info(String.format("%s: You must be logged in to upload subtitles.", getSubtitleService().getName())); return false; } setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // make sure we have access to the parent folder structure, not just the dropped file if (isMacSandbox()) { MacAppUtilities.askUnlockFolders(getWindow(this), selection); } // perform a drop action depending on the given files List<File> files = listFiles(selection, FILES, HUMAN_NAME_ORDER); List<File> videos = filter(files, VIDEO_FILES); List<File> subtitles = filter(files, SUBTITLE_FILES); Map<File, File> uploadPlan = new LinkedHashMap<File, File>(); for (File subtitle : subtitles) { File video = getVideoForSubtitle(subtitle, filter(videos, new ParentFilter(subtitle.getParentFile()))); uploadPlan.put(subtitle, video); } setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if (uploadPlan.size() > 0) { handleUpload(uploadPlan); return true; } return false; } protected void handleUpload(Map<File, File> uploadPlan) { SubtitleUploadDialog dialog = new SubtitleUploadDialog(getSubtitleService(), getWindow(this)); // initialize window properties dialog.setIconImage(getImage(getIcon(DropAction.Accept))); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setSize(950, 575); // show dialog dialog.setLocation(getOffsetLocation(dialog.getOwner())); // start processing dialog.setUploadPlan(uploadPlan); dialog.startChecking(); // show dialog dialog.setVisible(true); } protected File getVideoForSubtitle(File subtitle, List<File> videos) { // 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); }); } private Optional<File> findMatch(File file, List<File> options, Function<File, String> comparator) { String subtitleFileName = comparator.apply(file).toLowerCase(); for (File it : options) { if (subtitleFileName.length() > 0 && subtitleFileName.startsWith(comparator.apply(it).toLowerCase())) { return Optional.of(it); } } return Optional.empty(); } @Override protected Icon getIcon(DropAction dropAction) { if (dropAction == DropAction.Accept) return ResourceManager.getIcon("subtitle.exact.upload"); return ResourceManager.getIcon("message.error"); } } }